TypeScript SDK for AddPay Cloud API - CNP payment processing
npm install addpay-js



A comprehensive TypeScript SDK for the AddPay Cloud API, designed for Card Not Present (CNP) payment processing in South Africa.
- ๐ Resource-based architecture for clean, intuitive API interaction
- ๐ง Fluent builders for easy request construction
- ๐ RSA signature validation for secure communications
- ๐ฑ CNP-focused payment processing
- ๐งช Comprehensive test coverage with Vitest
- ๐ Full TypeScript support with detailed type definitions
- ๐ Built-in fetch - no external HTTP dependencies
- Hosted Checkout - Redirect customers to AddPay's secure payment page
- DebiCheck - South African debit order system with mandate management
- Tokenization - Secure card tokenization for recurring payments
``bash`
pnpm add @addpay/sdkor
npm install @addpay/sdkor
yarn add @addpay/sdk
`typescript
import { AddPayClient } from '@addpay/sdk';
// Production configuration
const client = AddPayClient.create({
appId: 'your-app-id',
merchantNo: 'your-merchant-number',
storeNo: 'your-store-number',
privateKey: 'your-rsa-private-key',
publicKey: 'your-rsa-public-key',
gatewayPublicKey: 'gateway-public-key',
sandbox: false
});
// For testing, use your test credentials
`
`typescript
// Standard API approach
const checkout = await client.checkout.create({
merchant_order_no: 'ORDER-12345',
order_amount: '100.00',
price_currency: 'ZAR',
notify_url: 'https://yoursite.com/webhook',
return_url: 'https://yoursite.com/success',
description: 'Product purchase'
});
console.log('Checkout URL:', checkout.pay_url);
// Fluent builder approach
const checkout2 = await client.checkout
.createCheckout()
.merchantOrderNo('ORDER-12346')
.amount('250.00')
.currency('ZAR')
.notifyUrl('https://yoursite.com/webhook')
.returnUrl('https://yoursite.com/success')
.description('Subscription payment')
.customerInfo({
customer_email: 'customer@example.com',
customer_phone: '+27123456789'
})
.execute();
`
`typescript
import { AddPayClient, CheckoutRequest } from '@addpay/sdk';
const client = AddPayClient.create({ / your config / });
async function processCheckout() {
try {
// Create checkout session
const checkout = await client.checkout
.createCheckout()
.merchantOrderNo(ORDER-${Date.now()})
.amount('199.99')
.currency('ZAR')
.notifyUrl('https://api.yoursite.com/addpay/webhook')
.returnUrl('https://yoursite.com/payment/success')
.description('Premium subscription')
.goods([{
goods_name: 'Premium Plan',
goods_id: 'PLAN_PREMIUM',
goods_quantity: 1,
goods_price: '199.99'
}])
.customerInfo({
customer_id: 'CUST001',
customer_email: 'john@example.com',
customer_phone: '+27823456789',
customer_name: 'John Doe'
})
.sceneInfo({
device_ip: '192.168.1.1'
})
.execute();
console.log('Redirect user to:', checkout.pay_url);
// Later, check payment status
const status = await client.checkout.getStatus({
merchant_order_no: checkout.merchant_order_no
});
if (status.trans_status === 2) { // Completed
console.log('Payment successful!');
}
} catch (error) {
console.error('Checkout failed:', error);
}
}
`
`typescriptMANDATE-${Date.now()}
async function setupDebiCheckMandate() {
try {
// Create mandate
const mandate = await client.debicheck
.createMandateBuilder()
.merchantOrderNo()
.customer(customer => customer
.id('CUST001')
.name('John Doe')
.email('john@example.com')
.idNumber('8001015009087')
.accountNumber('1234567890')
.accountType('CURRENT')
.bankName('Standard Bank')
.branchCode('051001')
)
.mandate(mandate => mandate
.type('RECURRING')
.maxAmount('500.00')
.currency('ZAR')
.startDate('2024-01-01')
.endDate('2024-12-31')
.frequency('MONTHLY')
)
.notifyUrl('https://api.yoursite.com/debicheck/webhook')
.returnUrl('https://yoursite.com/mandate/success')
.execute();
console.log('Mandate reference:', mandate.mandate_reference);
console.log('Auth URL:', mandate.auth_url);
// Once approved, collect payment
if (mandate.mandate_status === 'APPROVED') {
const collection = await client.debicheck.collect({
mandate_reference: mandate.mandate_reference,
merchant_order_no: COLLECT-${Date.now()},
collection_amount: '299.99',
currency: 'ZAR',
notify_url: 'https://api.yoursite.com/debicheck/collection-webhook'
});
console.log('Collection initiated:', collection);
}
} catch (error) {
console.error('DebiCheck failed:', error);
}
}
`
`typescriptTOKEN-${Date.now()}
async function tokenizeAndPay() {
try {
// Tokenize card (typically done during initial setup)
const tokenResponse = await client.token
.createTokenization()
.merchantOrderNo()
.customer(customer => customer
.id('CUST001')
.email('john@example.com')
.name('John Doe')
.address(addr => addr
.line1('123 Main Street')
.city('Cape Town')
.state('Western Cape')
.postalCode('8001')
.country('ZA')
)
)
.verificationAmount('1.00')
.currency('ZAR')
.notifyUrl('https://api.yoursite.com/token/webhook')
.execute();
const token = tokenResponse.token;
console.log('Card tokenized:', token);
console.log('Masked card:', tokenResponse.card_info.masked_card_number);
// Use token for payment
const payment = await client.token
.createPayment()
.token(token)
.merchantOrderNo(PAY-${Date.now()})
.amount('99.99')
.currency('ZAR')
.description('Recurring subscription payment')
.notifyUrl('https://api.yoursite.com/token/payment-webhook')
.execute();
console.log('Payment processed:', payment);
// List customer's tokens
const tokens = await client.token.list({
customer_id: 'CUST001',
page: 1,
limit: 10
});
console.log(Customer has ${tokens.total} tokens);
} catch (error) {
console.error('Tokenization failed:', error);
}
}
`
`typescript`
const client = AddPayClient.create({
appId: 'wz715fc0d10ee9d156',
merchantNo: '302100085224',
storeNo: '4021000637',
privateKey: process.env.ADDPAY_PRIVATE_KEY!,
publicKey: process.env.ADDPAY_PUBLIC_KEY!,
gatewayPublicKey: process.env.ADDPAY_GATEWAY_PUBLIC_KEY!,
baseUrl: 'https://api.paycloud.africa',
timeout: 30000,
sandbox: process.env.NODE_ENV !== 'production'
});
`typescript
import { AddPayError } from '@addpay/sdk';
try {
const checkout = await client.checkout.create({
merchant_order_no: 'ORDER001',
order_amount: '100.00',
price_currency: 'ZAR',
notify_url: 'https://example.com/notify',
return_url: 'https://example.com/return'
});
} catch (error) {
if (error instanceof AddPayError) {
console.error('AddPay Error:', error.code, error.message);
console.error('Details:', error.details);
} else {
console.error('Unexpected error:', error);
}
}
`
`typescript
import { CryptoUtils } from '@addpay/sdk';
function validateWebhook(body: any, signature: string, publicKey: string): boolean {
const signString = CryptoUtils.buildSignatureString(body);
return CryptoUtils.verifyWithRSA(signString, signature, publicKey);
}
// Express.js webhook endpoint
app.post('/webhook/addpay', (req, res) => {
const signature = req.headers['x-signature'] as string;
if (!validateWebhook(req.body, signature, gatewayPublicKey)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { merchant_order_no, trans_status, order_no } = req.body;
switch (trans_status) {
case 2: // Completed
console.log(Payment ${order_no} completed for order ${merchant_order_no});Payment ${order_no} cancelled for order ${merchant_order_no}
// Update your database
break;
case 3: // Cancelled
console.log();Payment ${order_no} status: ${trans_status}
break;
default:
console.log();
}
res.json({ status: 'ok' });
});
`
The SDK includes comprehensive test coverage:
`bash`
pnpm test # Run tests once
pnpm test:watch # Run tests in watch mode
pnpm test:coverage # Run tests with coverage report
The SDK is written in TypeScript and provides full type definitions:
`typescript`
import type {
CheckoutRequest,
CheckoutResponse,
DebiCheckMandateRequest,
TokenizationRequest,
ApiConfig
} from '@addpay/sdk';
`bash`
ADDPAY_APP_ID=your-app-id
ADDPAY_MERCHANT_NO=your-merchant-number
ADDPAY_STORE_NO=your-store-number
ADDPAY_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
ADDPAY_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
ADDPAY_GATEWAY_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
For sandbox testing, use:
- App ID: wz715fc0d10ee9d156302100085224
- Merchant No: 4021000637
- Store No: http://gw.wisepaycloud.com
- Endpoint:
1. Always validate webhooks using the provided signature verification
2. Use unique merchant order numbers to avoid conflicts
3. Implement proper error handling for all API calls
4. Store tokens securely and never log sensitive card data
5. Use HTTPS for all webhook and return URLs
6. Implement idempotency for payment operations
This project uses automated releases with semantic-release and follows Conventional Commits.
1. Fork the repository
2. Create a feature branch: git checkout -b feat/your-featurefeat: add new feature
3. Make your changes with conventional commit messages:
- (minor version bump)fix: resolve bug
- (patch version bump)feat!: breaking change
- (major version bump)pnpm test
4. Add tests for new functionality
5. Run the test suite:
6. Submit a pull request
`bash``
git commit -m "feat: add webhook validation helper"
git commit -m "fix: handle network timeout errors"
git commit -m "docs: update API examples"
See CONTRIBUTING.md for detailed guidelines.
MIT
- ๐ Official AddPay Documentation
- ๐ Report Issues
- ๐ฌ Discussions
Created by CheckoutJoy - Advanced, Localized Checkouts for Course Creators
---
Built with โค๏ธ for the South African payment ecosystem.