@sikka/aps
v0.0.24
Published
A Stripe-like developer-friendly SDK for Amazon Payment Services integration. Supports payment links, hosted checkout, tokenization, webhooks, and payment management.
Maintainers
Readme
Amazon Payment Services SDK
A Stripe-like developer-friendly SDK for Amazon Payment Services integration
Features
- 🚀 Stripe-like DX - Intuitive API design inspired by Stripe
- ⚛️ React Hooks - useAPS, useCheckout, usePayment hooks for seamless React integration
- 🧩 React Components - Pre-built components: HostedCheckoutButton, ErrorDisplay, PaymentStatus, TokenizationForm
- 🔗 Payment Links - Generate shareable payment URLs via official APS API with status tracking
- 🛒 Hosted Checkout - Full-featured payment pages with 3DS support and recurring payments
- 💳 Tokenization - Secure card storage for recurring payments with token management
- 🔄 Recurring Payments - Complete subscription support (UNSCHEDULED, VARIABLE, FIXED modes)
- 🔔 Webhooks - Real-time payment event notifications with signature verification
- 💰 Payment Management - Capture, refund, void, and query transactions
- 🔐 3D Secure - Standard, Standalone, and External 3DS authentication
- 💱 Multi-Currency - Full support for Middle East currencies + currency conversion
- 📊 Installments - Buy Now Pay Later with installment payment plans
- 📦 Batch Processing - Process multiple payments/refunds in batches
- 🔒 Secure - Proper SHA-256 signature calculation per APS documentation
- 📦 TypeScript - Full TypeScript support with comprehensive types
- 🧪 Testing Utilities - Mock client, test cards, and webhook helpers
- 🛡️ Error Handling - Detailed error messages with suggested actions
- 🌐 Network Tokenization - Support for card scheme network tokens
Installation
npm install @sikka/apsQuick Start
1. Initialize the Client
import APS from '@sikka/aps';
const aps = new APS({
merchantId: process.env.APS_MERCHANT_ID,
accessCode: process.env.APS_ACCESS_CODE,
requestSecret: process.env.APS_REQUEST_SECRET,
responseSecret: process.env.APS_RESPONSE_SECRET,
environment: 'sandbox' // or 'production'
});TypeScript Types
All types are exported from the package:
import type {
APSConfig,
PaymentResponse,
PaymentLinkResponse,
TokenizedCard,
RefundResponse,
CaptureResponse,
VoidResponse,
WebhookEvent,
Order,
Customer,
TransactionStatus,
PaymentMethod
} from '@sikka/aps';2. Create a Payment Link
const link = await aps.paymentLinks.create({
order: {
id: 'order_123',
amount: 10000, // 100.00 SAR (in fils/cents)
currency: 'SAR',
description: 'Premium Plan Subscription',
customer: {
email: '[email protected]',
name: 'Ahmed Al-Saud',
phone: '+966501234567'
}
},
tokenValidityHours: 24 // Link expires in 24 hours
});
console.log('Payment URL:', link.url);
// Send this link to your customer via email, SMS, etc.3. Create Hosted Checkout
const checkout = await aps.hostedCheckout.create({
order: {
id: 'order_456',
amount: 25000, // 250.00 SAR
currency: 'SAR',
description: 'E-commerce Purchase',
customer: {
email: '[email protected]', // Required for hosted checkout
name: 'Ahmed Al-Saud'
}
},
returnUrl: 'https://yoursite.com/api/payment/return', // Use API route to handle POST data
allowedPaymentMethods: ['card', 'apple_pay', 'mada']
});
// Redirect user to the hosted checkout page
if (checkout.redirectForm) {
// Create a form and submit to redirectForm.url
// The customer will be redirected back to returnUrl after payment
}Important: Hosted Checkout uses return_url for all redirects (success, failure, or cancel).
⚠️ POST Data: APS sends the transaction result as a POST request (form data), not GET query parameters. You need an API route to receive the POST data:
// app/api/payment/return/route.ts
export async function POST(request: NextRequest) {
const formData = await request.formData();
// Convert to query parameters
const params = new URLSearchParams();
formData.forEach((value, key) => {
if (typeof value === 'string') params.append(key, value);
});
// Redirect to result page
return NextResponse.redirect(
new URL(`/payment/result?${params.toString()}`, request.url)
);
}Then use the API route as your returnUrl:
returnUrl: 'https://yoursite.com/api/payment/return'4. Custom Payment Page (Non-PCI)
Create a custom payment form while APS handles PCI compliance through tokenization.
// Step 1: Get tokenization form (backend)
const tokenizationForm = aps.customPaymentPage.getTokenizationForm({
returnUrl: 'https://yoursite.com/api/token-result'
});
// Step 2: Render form in your frontend
// <form method="POST" action={tokenizationForm.url}>
// {Object.entries(tokenizationForm.params).map(([key, value]) => (
// <input type="hidden" name={key} value={value} />
// ))}
// <input name="card_number" placeholder="Card Number" required />
// <input name="expiry_date" placeholder="MM/YY" required />
// <input name="card_security_code" placeholder="CVV" required />
// <input name="card_holder_name" placeholder="Card Holder Name" required />
// <button type="submit">Pay</button>
// </form>
// Step 3: Handle response at returnUrl (backend)
// Response contains: token_name (and sometimes agreement_id)
// Step 4: Charge with token (backend)
// IMPORTANT: If you do NOT have an agreement_id yet, this is treated as a
// customer-present ECOMMERCE transaction. APS may require 3D Secure.
// Provide returnUrl so the SDK can return a redirectForm when 3DS is needed.
const payment = await aps.customPaymentPage.chargeWithToken({
order: {
id: 'order_123',
amount: 10000, // 100.00 SAR
currency: 'SAR',
description: 'Product Purchase'
},
tokenName: 'token_from_step_3',
customerEmail: '[email protected]',
returnUrl: 'https://yoursite.com/api/payment-return'
});
// Step 5: Save the agreement_id from the successful charge response
// agreement_id is required for future unattended server-to-server charges.How it works:
- Get tokenization form from APS
- Render custom form with your branding
- Customer enters card details
- Form submits to APS (Non-PCI compliant)
- APS returns token to your returnUrl
- Use token to charge payments server-to-server
Important Notes:
- Tokenization form collects card details securely (Non-PCI compliant)
- Card data submits directly to APS, never touches your server
- Token received at returnUrl can be used for immediate or future charges
- Use
chargeWithToken()for server-to-server payment processing remember_meis a Hosted Checkout parameter — it must NOT be sent to the Payment API PURCHASE endpoint. Doing so can cause APS error00044(Token name does not exist).- First charge flow: If you only have a
token_name(noagreement_id),chargeWithToken()performs a customer-present ECOMMERCE charge. You MUST providereturnUrlto handle 3D Secure redirects. On success, save theagreement_idreturned by APS. - Recurring flow: For unattended cron-job billing, use
aps.recurring.process()with BOTHtokenNameANDagreementId. This sendseci: 'RECURRING'and avoids 3DS.
Payments Module (Management)
Manage existing transactions.
// Capture authorized payment
await aps.payments.capture({
transactionId: string,
amount?: number // Optional, full capture if not specified
});
// Refund payment
await aps.payments.refund({
transactionId: string,
amount?: number, // Optional, full refund if not specified
reason?: string
});
// Void authorization
await aps.payments.void({
transactionId: string,
reason?: string
});
// Query transaction
await aps.payments.query({
transactionId?: string,
orderId?: string
});Webhooks Module
Verify and parse webhook events.
// In your API route handler
const signature = request.headers.get('x-aps-signature') || '';
const payload = request.body;
// Verify signature
const isValid = aps.webhooks.verifySignature(
JSON.stringify(payload),
signature
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Construct event
const event = aps.webhooks.constructEvent(payload);
// Handle by type
switch (event.type) {
case 'payment.success':
case 'payment.failed':
case 'payment.pending':
case 'refund.success':
case 'refund.failed':
case 'chargeback.created':
case 'subscription.renewed':
case 'subscription.cancelled':
}Webhook Event Structure:
{
id: string;
type: WebhookEventType;
timestamp: Date;
data: {
transactionId: string;
orderId: string;
amount: number;
currency: string;
status: TransactionStatus;
paymentMethod?: string;
metadata?: Record<string, any>;
};
rawPayload: object;
}Supported Payment Methods
| Method | Regions | Description |
|--------|---------|-------------|
| card | All | Visa, Mastercard, American Express |
| apple_pay | All | Apple Pay |
| mada | Saudi Arabia | MADA debit cards |
| stc_pay | Saudi Arabia | STC Pay wallet |
| knet | Kuwait | KNET payment |
| naps | Qatar | NAPS payment |
| fawry | Egypt | Fawry cash payment |
| meeza | Egypt | Meeza cards |
| sadad | Saudi Arabia | Sadad payment |
| aman | Egypt | Aman installment |
Environment Variables
Create a .env.local file:
# APS Merchant Credentials (from your APS dashboard)
APS_MERCHANT_ID=your_merchant_id
APS_ACCESS_CODE=your_access_code
APS_REQUEST_SECRET=your_request_secret
APS_RESPONSE_SECRET=your_response_secret
# APS Configuration
APS_ENVIRONMENT=sandbox # Use 'production' for live
APS_CURRENCY=SAR # Default currency
APS_LANGUAGE=en # Default language
# Your App URL
NEXT_PUBLIC_APP_URL=http://localhost:3000Supported Currencies
- SAR - Saudi Riyal
- AED - UAE Dirham
- KWD - Kuwaiti Dinar
- QAR - Qatari Riyal
- BHD - Bahraini Dinar
- OMR - Omani Rial
- JOD - Jordanian Dinar
- EGP - Egyptian Pound
- USD - US Dollar
- EUR - Euro
Error Handling
import { APSException, APSError } from '@sikka/aps';
try {
const link = await aps.paymentLinks.create({ ... });
} catch (error) {
if (error instanceof APSException) {
console.error('Error Code:', error.code);
console.error('Message:', error.message);
console.error('Status:', error.statusCode);
console.error('Details:', error.rawResponse);
// Handle specific errors
switch (error.code) {
case 'PAYMENT_LINK_ERROR':
// Handle payment link creation failure
break;
case 'SIGNATURE_ERROR':
// Handle signature mismatch
break;
case 'INVALID_CREDENTIALS':
// Handle authentication failure
break;
}
} else {
console.error('Unexpected error:', error);
}
}Common Response Codes
| Code | Message | Description |
|------|---------|-------------|
| 00000 | Success | Transaction successful |
| 48000 | Success | Payment link created successfully |
| 10030 | Authentication failed | 3DS authentication failed |
| 00008 | Signature mismatch | Invalid signature |
| 00002 | Invalid parameter | Parameter format error |
| 14000 | Declined | Card declined by issuer |
Security Best Practices
- Never expose credentials - Always use environment variables
- Server-side only - All APS API calls must be server-side
- Verify webhooks - Always verify webhook signatures
- Use HTTPS - Required for production
- PCI Compliance - Use hosted checkout or payment links to avoid PCI scope
- Validate amounts - Always validate amounts server-side before creating payments
Testing
Sandbox Environment
const aps = new APS({
// ... credentials
environment: 'sandbox'
});Test Cards (use in sandbox):
| Card Number | Type | CVV | Expiry | |-------------|------|-----|--------| | 4111 1111 1111 1111 | Visa | 123 | 12/25 | | 5297 4100 0000 0002 | MADA | 123 | 12/25 | | 5100 0000 0000 0008 | Mastercard | 123 | 12/25 |
Production
const aps = new APS({
// ... production credentials
environment: 'production'
});Next.js Integration Example
See the included test app for a complete working example:
# Run the test app
pnpm devVisit http://localhost:3000 to test payment creation and http://localhost:3000/docs for documentation.
License
MIT License - see LICENSE for details.
Support
For APS-specific issues, contact Amazon Payment Services merchant support:
- Email: [email protected]
- Documentation: https://paymentservices.amazon.com/docs
