@sikka/aps 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,892 @@
1
+ # Amazon Payment Services SDK
2
+
3
+ <div align="center">
4
+
5
+ [![npm version](https://img.shields.io/npm/v/amazon-payment-services.svg)](https://www.npmjs.com/package/amazon-payment-services)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ **A Stripe-like developer-friendly SDK for Amazon Payment Services integration**
9
+
10
+ </div>
11
+
12
+ ---
13
+
14
+ ## Features
15
+
16
+ - ๐Ÿš€ **Stripe-like DX** - Intuitive API design inspired by Stripe
17
+ - โš›๏ธ **React Hooks** - useAPS, useCheckout, usePayment hooks for seamless React integration
18
+ - ๐Ÿงฉ **React Components** - Pre-built components: HostedCheckoutButton, ErrorDisplay, PaymentStatus
19
+ - ๐Ÿ”— **Payment Links** - Generate shareable payment URLs via official APS API
20
+ - ๐Ÿ›’ **Hosted Checkout** - Full-featured payment pages with 3DS support
21
+ - ๐Ÿ’ณ **Tokenization** - Secure card storage for recurring payments
22
+ - ๐Ÿ”” **Webhooks** - Real-time payment event notifications with signature verification
23
+ - ๐Ÿ’ฐ **Payment Management** - Capture, refund, void, and query transactions
24
+ - ๐ŸŒ **Multi-Currency** - Full support for Middle East currencies (SAR, AED, KWD, etc.)
25
+ - ๐Ÿ”’ **Secure** - Proper SHA-256 signature calculation per APS documentation
26
+ - ๐Ÿ“ฆ **TypeScript** - Full TypeScript support with comprehensive types
27
+ - ๐Ÿงช **Testing Utilities** - Mock client, test cards, and webhook helpers
28
+ - ๐Ÿ›ก๏ธ **Error Handling** - Detailed error messages with suggested actions
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ npm install amazon-payment-services
34
+ ```
35
+
36
+ ## Quick Start
37
+
38
+ ### 1. Initialize the Client
39
+
40
+ ```typescript
41
+ import APS from 'amazon-payment-services';
42
+
43
+ const aps = new APS({
44
+ merchantId: process.env.APS_MERCHANT_ID,
45
+ accessCode: process.env.APS_ACCESS_CODE,
46
+ requestSecret: process.env.APS_REQUEST_SECRET,
47
+ responseSecret: process.env.APS_RESPONSE_SECRET,
48
+ environment: 'sandbox' // or 'production'
49
+ });
50
+ ```
51
+
52
+ ### 2. Create a Payment Link
53
+
54
+ ```typescript
55
+ const link = await aps.paymentLinks.create({
56
+ order: {
57
+ id: 'order_123',
58
+ amount: 10000, // 100.00 SAR (in fils/cents)
59
+ currency: 'SAR',
60
+ description: 'Premium Plan Subscription',
61
+ customer: {
62
+ email: 'customer@example.com',
63
+ name: 'Ahmed Al-Saud',
64
+ phone: '+966501234567'
65
+ }
66
+ },
67
+ tokenValidityHours: 24 // Link expires in 24 hours
68
+ });
69
+
70
+ console.log('Payment URL:', link.url);
71
+ // Send this link to your customer via email, SMS, etc.
72
+ ```
73
+
74
+ ### 3. Create Hosted Checkout
75
+
76
+ ```typescript
77
+ const checkout = await aps.hostedCheckout.create({
78
+ order: {
79
+ id: 'order_456',
80
+ amount: 25000, // 250.00 SAR
81
+ currency: 'SAR',
82
+ description: 'E-commerce Purchase',
83
+ customer: {
84
+ email: 'customer@example.com', // Required for hosted checkout
85
+ name: 'Ahmed Al-Saud'
86
+ }
87
+ },
88
+ returnUrl: 'https://yoursite.com/api/payment/return', // Use API route to handle POST data
89
+ allowedPaymentMethods: ['card', 'apple_pay', 'mada']
90
+ });
91
+
92
+ // Redirect user to the hosted checkout page
93
+ if (checkout.redirectForm) {
94
+ // Create a form and submit to redirectForm.url
95
+ // The customer will be redirected back to returnUrl after payment
96
+ }
97
+ ```
98
+
99
+ **Important:** Hosted Checkout uses `return_url` for all redirects (success, failure, or cancel).
100
+
101
+ **โš ๏ธ 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:
102
+
103
+ ```typescript
104
+ // app/api/payment/return/route.ts
105
+ export async function POST(request: NextRequest) {
106
+ const formData = await request.formData();
107
+
108
+ // Convert to query parameters
109
+ const params = new URLSearchParams();
110
+ formData.forEach((value, key) => {
111
+ if (typeof value === 'string') params.append(key, value);
112
+ });
113
+
114
+ // Redirect to result page
115
+ return NextResponse.redirect(
116
+ new URL(`/payment/result?${params.toString()}`, request.url)
117
+ );
118
+ }
119
+ ```
120
+
121
+ Then use the API route as your `returnUrl`:
122
+ ```typescript
123
+ returnUrl: 'https://yoursite.com/api/payment/return'
124
+ ```
125
+
126
+ ### 4. Custom Payment Page (Non-PCI)
127
+
128
+ Create a custom payment form while APS handles PCI compliance through tokenization.
129
+
130
+ ```typescript
131
+ // Step 1: Get tokenization form (backend)
132
+ const tokenizationForm = aps.customPaymentPage.getTokenizationForm({
133
+ returnUrl: 'https://yoursite.com/api/token-result'
134
+ });
135
+
136
+ // Step 2: Render form in your frontend
137
+ // <form method="POST" action={tokenizationForm.url}>
138
+ // {Object.entries(tokenizationForm.params).map(([key, value]) => (
139
+ // <input type="hidden" name={key} value={value} />
140
+ // ))}
141
+ // <input name="card_number" placeholder="Card Number" required />
142
+ // <input name="expiry_date" placeholder="MM/YY" required />
143
+ // <input name="card_security_code" placeholder="CVV" required />
144
+ // <input name="card_holder_name" placeholder="Card Holder Name" required />
145
+ // <button type="submit">Pay</button>
146
+ // </form>
147
+
148
+ // Step 3: Handle response at returnUrl (backend)
149
+ // Response contains: token_name
150
+
151
+ // Step 4: Charge with token (backend)
152
+ const payment = await aps.customPaymentPage.chargeWithToken({
153
+ order: {
154
+ id: 'order_123',
155
+ amount: 10000, // 100.00 SAR
156
+ currency: 'SAR',
157
+ description: 'Product Purchase'
158
+ },
159
+ tokenName: 'token_from_step_3',
160
+ customerEmail: 'customer@example.com'
161
+ });
162
+ ```
163
+
164
+ **How it works:**
165
+ 1. Get tokenization form from APS
166
+ 2. Render custom form with your branding
167
+ 3. Customer enters card details
168
+ 4. Form submits to APS (Non-PCI compliant)
169
+ 5. APS returns token to your returnUrl
170
+ 6. Use token to charge payments server-to-server
171
+
172
+ ```typescript
173
+ import { NextRequest, NextResponse } from 'next/server';
174
+ import { getAPSClient } from './aps-client';
175
+
176
+ export async function POST(request: NextRequest) {
177
+ const body = await request.json();
178
+ const signature = request.headers.get('x-aps-signature') || '';
179
+
180
+ const aps = getAPSClient();
181
+
182
+ // Verify webhook signature
183
+ const event = aps.webhooks.constructEvent(body, signature);
184
+
185
+ // Handle different event types
186
+ switch (event.type) {
187
+ case 'payment.success':
188
+ // Update order status, send confirmation email, etc.
189
+ console.log('Payment successful:', event.data);
190
+ break;
191
+ case 'payment.failed':
192
+ // Notify customer, retry logic, etc.
193
+ console.log('Payment failed:', event.data);
194
+ break;
195
+ case 'refund.success':
196
+ // Process refund confirmation
197
+ console.log('Refund processed:', event.data);
198
+ break;
199
+ }
200
+
201
+ return NextResponse.json({ received: true });
202
+ }
203
+ ```
204
+
205
+ ### 7. Payment Management
206
+
207
+ ```typescript
208
+ // Capture an authorized payment (for two-step checkout)
209
+ const capture = await aps.payments.capture({
210
+ transactionId: 'txn_123',
211
+ amount: 10000 // Optional, defaults to full amount
212
+ });
213
+
214
+ // Refund a payment (full or partial)
215
+ const refund = await aps.payments.refund({
216
+ transactionId: 'txn_123',
217
+ amount: 5000, // Partial refund (50.00 SAR)
218
+ reason: 'Customer request'
219
+ });
220
+
221
+ // Void an authorization (before capture)
222
+ const voided = await aps.payments.void({
223
+ transactionId: 'txn_123',
224
+ reason: 'Order cancelled'
225
+ });
226
+
227
+ // Query transaction status
228
+ const transaction = await aps.payments.query({
229
+ transactionId: 'txn_123'
230
+ });
231
+
232
+ console.log('Transaction status:', transaction.status);
233
+ ```
234
+
235
+ ## React Hooks & Components
236
+
237
+ ### Setup Provider
238
+
239
+ ```tsx
240
+ import { APSProvider } from '@sikka/aps/react';
241
+ import APS from '@sikka/aps';
242
+
243
+ const aps = new APS({
244
+ merchantId: process.env.APS_MERCHANT_ID!,
245
+ accessCode: process.env.APS_ACCESS_CODE!,
246
+ requestSecret: process.env.APS_REQUEST_SECRET!,
247
+ responseSecret: process.env.APS_RESPONSE_SECRET!,
248
+ environment: 'sandbox'
249
+ });
250
+
251
+ function App() {
252
+ return (
253
+ <APSProvider client={aps}>
254
+ <YourApp />
255
+ </APSProvider>
256
+ );
257
+ }
258
+ ```
259
+
260
+ ### useAPS Hook
261
+
262
+ Access the APS client from any component:
263
+
264
+ ```tsx
265
+ import { useAPS } from '@sikka/aps/react';
266
+
267
+ function MyComponent() {
268
+ const aps = useAPS();
269
+
270
+ const handleClick = async () => {
271
+ const link = await aps.paymentLinks.create({...});
272
+ console.log(link.url);
273
+ };
274
+ }
275
+ ```
276
+
277
+ ### useCheckout Hook
278
+
279
+ Manage hosted checkout state:
280
+
281
+ ```tsx
282
+ import { useCheckout } from '@sikka/aps/react';
283
+
284
+ function CheckoutPage() {
285
+ const { createCheckout, redirectToCheckout, isLoading, error } = useCheckout();
286
+
287
+ const handleCheckout = async () => {
288
+ await createCheckout({
289
+ order: { id: 'order_123', amount: 10000, currency: 'SAR', customer: { email: 'test@example.com' } },
290
+ returnUrl: '/payment-result'
291
+ });
292
+ redirectToCheckout();
293
+ };
294
+ }
295
+ ```
296
+
297
+ ### usePayment Hook
298
+
299
+ Manage payment lifecycle:
300
+
301
+ ```tsx
302
+ import { usePayment } from '@sikka/aps/react';
303
+
304
+ function PaymentPage() {
305
+ const { createPaymentLink, confirmPayment, status } = usePayment();
306
+
307
+ const handlePayment = async () => {
308
+ const link = await createPaymentLink({ order: {...} });
309
+ window.open(link.url, '_blank');
310
+ };
311
+
312
+ // After customer returns
313
+ const finalStatus = await confirmPayment({ transactionId: 'txn_123' });
314
+ }
315
+ ```
316
+
317
+ ### HostedCheckoutButton Component
318
+
319
+ Complete checkout button with built-in state management:
320
+
321
+ ```tsx
322
+ import { HostedCheckoutButton } from '@sikka/aps/react';
323
+
324
+ <HostedCheckoutButton
325
+ order={{
326
+ id: 'order_123',
327
+ amount: 10000,
328
+ currency: 'SAR',
329
+ customer: { email: 'customer@example.com' }
330
+ }}
331
+ returnUrl="https://yoursite.com/result"
332
+ >
333
+ Pay Now
334
+ </HostedCheckoutButton>
335
+ ```
336
+
337
+ ### ErrorDisplay Component
338
+
339
+ Display APS errors with helpful messages:
340
+
341
+ ```tsx
342
+ import { ErrorDisplay } from '@sikka/aps/react';
343
+
344
+ <ErrorDisplay
345
+ error={error}
346
+ onRetry={() => handleRetry()}
347
+ onDismiss={() => setError(null)}
348
+ />
349
+ ```
350
+
351
+ ### PaymentStatus Component
352
+
353
+ Display payment status with icons:
354
+
355
+ ```tsx
356
+ import { PaymentStatus } from '@sikka/aps/react';
357
+
358
+ <PaymentStatus
359
+ status="captured"
360
+ amount={10000}
361
+ currency="SAR"
362
+ transactionId="txn_123"
363
+ />
364
+ ```
365
+
366
+ ## Error Handling
367
+
368
+ ### Get Error Details
369
+
370
+ ```typescript
371
+ import { getErrorDetails, isRetryableError } from '@sikka/aps';
372
+
373
+ try {
374
+ await aps.paymentLinks.create({...});
375
+ } catch (error) {
376
+ if (error.code) {
377
+ const details = getErrorDetails(error.code);
378
+ console.log(details.message); // Human-readable message
379
+ console.log(details.action); // Suggested action
380
+ console.log(details.category); // Error category
381
+ console.log(details.documentation); // Link to docs
382
+
383
+ if (isRetryableError(error.code)) {
384
+ await retryPayment();
385
+ }
386
+ }
387
+ }
388
+ ```
389
+
390
+ ### Response Codes
391
+
392
+ ```typescript
393
+ import { ResponseCodes, ErrorCategories, categorizeError } from '@sikka/aps';
394
+
395
+ if (response.response_code === ResponseCodes.SUCCESS) {
396
+ // Payment successful
397
+ } else if (response.response_code === ResponseCodes.INSUFFICIENT_FUNDS) {
398
+ // Handle insufficient funds
399
+ }
400
+
401
+ const category = categorizeError(response.response_code);
402
+ ```
403
+
404
+ ### Validation
405
+
406
+ ```typescript
407
+ import { validators } from '@sikka/aps';
408
+
409
+ // Validate individual fields
410
+ validators.isValidMerchantReference('order_123');
411
+ validators.isValidAmount(10000, 'SAR');
412
+ validators.isValidCardNumber('4111111111111111');
413
+
414
+ // Validate all at once
415
+ const result = validators.validatePaymentParams({
416
+ merchant_reference: 'order_123',
417
+ amount: 10000,
418
+ currency: 'SAR',
419
+ customer_email: 'customer@example.com'
420
+ });
421
+
422
+ if (!result.valid) {
423
+ result.errors.forEach(err => console.log(err.field, err.error));
424
+ }
425
+ ```
426
+
427
+ ## Testing
428
+
429
+ ### Mock Client
430
+
431
+ ```typescript
432
+ import { mockAPS } from '@sikka/aps/test';
433
+
434
+ const aps = mockAPS({ simulate: 'success', delay: 100 });
435
+ const link = await aps.paymentLinks.create({...});
436
+
437
+ // Simulate errors
438
+ const declinedAps = mockAPS({ simulate: 'declined' });
439
+ await expect(declinedAps.paymentLinks.create({...}))
440
+ .rejects.toThrow('Transaction declined');
441
+ ```
442
+
443
+ ### Test Cards
444
+
445
+ ```typescript
446
+ import { TestCards, testCards } from '@sikka/aps/test';
447
+
448
+ TestCards.VISA.SUCCESS // '4111111111111111'
449
+ TestCards.VISA.DECLINED // '4000000000000002'
450
+ TestCards.MADA.SUCCESS // '5297410000000002'
451
+
452
+ testCards.visa.success;
453
+ testCards.visa.declined;
454
+ ```
455
+
456
+ ### Mock Webhooks
457
+
458
+ ```typescript
459
+ import { createMockWebhookPayload, createMockSignature } from '@sikka/aps/test';
460
+
461
+ const payload = createMockWebhookPayload('payment.success', {
462
+ merchant_reference: 'order_123',
463
+ amount: '10000',
464
+ });
465
+
466
+ const signature = createMockSignature(payload, 'your-secret');
467
+ ```
468
+
469
+ ## Complete API Reference
470
+
471
+ ### Configuration
472
+
473
+ | Parameter | Type | Required | Default | Description |
474
+ |-----------|------|----------|---------|-------------|
475
+ | `merchantId` | string | โœ“ | - | Your APS merchant ID from dashboard |
476
+ | `accessCode` | string | โœ“ | - | API access code from Integration Settings |
477
+ | `requestSecret` | string | โœ“ | - | SHA Request Phrase for signing |
478
+ | `responseSecret` | string | โœ“ | - | SHA Response Phrase for verification |
479
+ | `environment` | string | | `'sandbox'` | `'sandbox'` or `'production'` |
480
+ | `currency` | string | | `'USD'` | Default currency code (e.g., `'SAR'`, `'AED'`) |
481
+ | `language` | string | | `'en'` | Interface language: `'en'` or `'ar'` |
482
+
483
+ ### Payment Links Module
484
+
485
+ Create payment links that can be shared via email, SMS, WhatsApp, etc.
486
+
487
+ ```typescript
488
+ await aps.paymentLinks.create(options: PaymentLinkOptions)
489
+ ```
490
+
491
+ **Options:**
492
+
493
+ | Field | Type | Required | Description |
494
+ |-------|------|----------|-------------|
495
+ | `order.id` | string | | Unique order reference (auto-generated if not provided) |
496
+ | `order.amount` | number | โœ“ | Amount in **fils/cents** (e.g., 10000 = 100.00 SAR) |
497
+ | `order.currency` | string | โœ“ | Three-letter ISO currency code |
498
+ | `order.description` | string | | Order description shown to customer |
499
+ | `order.customer.email` | string | โœ“ | Customer email for notifications |
500
+ | `order.customer.name` | string | | Customer full name |
501
+ | `order.customer.phone` | string | | Customer phone (international format) |
502
+ | `tokenValidityHours` | number | | Link expiry in hours (default: 24) |
503
+ | `recurring` | boolean | | Enable for recurring/subscription payments |
504
+ | `allowedPaymentMethods` | array | | Restrict to specific methods: `['card', 'mada', 'apple_pay']` |
505
+ | `metadata` | object | | Custom key-value pairs to attach to order |
506
+
507
+ **Returns:**
508
+
509
+ ```typescript
510
+ {
511
+ url: string; // Payment link URL to share
512
+ linkId: string; // APS payment link ID
513
+ orderId: string; // Your order reference
514
+ expiresAt?: Date; // Expiry timestamp
515
+ rawResponse: object; // Full APS API response
516
+ }
517
+ ```
518
+
519
+ ### Hosted Checkout Module
520
+
521
+ Redirect customers to a secure APS-hosted payment page.
522
+
523
+ ```typescript
524
+ await aps.hostedCheckout.create(options: HostedCheckoutOptions)
525
+ ```
526
+
527
+ **Options:**
528
+
529
+ | Field | Type | Required | Description |
530
+ |-------|------|----------|-------------|
531
+ | `order.id` | string | | Unique order reference (auto-generated if not provided) |
532
+ | `order.amount` | number | โœ“ | Amount in **fils/cents** (e.g., 10000 = 100.00 SAR) |
533
+ | `order.currency` | string | โœ“ | Three-letter ISO currency code |
534
+ | `order.description` | string | | Order description shown to customer |
535
+ | `order.customer.email` | string | โœ“ | **Required** - Customer email for payment page |
536
+ | `order.customer.name` | string | | Customer full name |
537
+ | `order.customer.phone` | string | | Customer phone (international format) |
538
+ | `returnUrl` | string | โœ“ | Redirect URL after payment (success, failure, or cancel) |
539
+ | `tokenize` | boolean | | Allow card saving for future payments (`remember_me`) |
540
+ | `allowedPaymentMethods` | array | | Limit payment methods: `['card', 'mada', 'apple_pay']` |
541
+ | `hideShipping` | boolean | | Hide shipping information fields |
542
+
543
+ **Returns:**
544
+
545
+ ```typescript
546
+ {
547
+ transactionId: string;
548
+ orderId: string;
549
+ status: 'pending' | 'authorized' | 'captured' | 'failed';
550
+ amount: number;
551
+ currency: string;
552
+ redirectForm: {
553
+ url: string; // APS payment page URL
554
+ method: 'POST';
555
+ params: Record<string, string>; // All form parameters including signature
556
+ };
557
+ rawResponse: object;
558
+ }
559
+ ```
560
+
561
+ **Important Notes:**
562
+ - `customer_email` is **required** by APS
563
+ - Use `returnUrl` (not `successUrl`/`failureUrl`) - APS redirects to this URL for all outcomes
564
+ - The response contains a `redirectForm` that should be submitted via POST to redirect the customer
565
+
566
+ ### Tokenization Module
567
+
568
+ Securely tokenize card details for recurring payments.
569
+
570
+ ```typescript
571
+ await aps.tokens.create(options: TokenizeCardOptions)
572
+ ```
573
+
574
+ **Options:**
575
+
576
+ | Field | Type | Required | Description |
577
+ |-------|------|----------|-------------|
578
+ | `cardNumber` | string | โœ“ | Full card number |
579
+ | `expiryMonth` | string | โœ“ | 2-digit month (MM) |
580
+ | `expiryYear` | string | โœ“ | 2-digit year (YY) |
581
+ | `cvv` | string | โœ“ | Card security code |
582
+ | `cardholderName` | string | | Name as shown on card |
583
+
584
+ **Returns:**
585
+
586
+ ```typescript
587
+ {
588
+ token: string; // Card token for future use
589
+ last4: string; // Last 4 digits
590
+ brand: string; // Card brand (visa, mastercard, mada, etc.)
591
+ expiryMonth: string;
592
+ expiryYear: string;
593
+ }
594
+ ```
595
+
596
+ **Additional Methods:**
597
+
598
+ ```typescript
599
+ // Verify if token is still valid
600
+ const isValid = await aps.tokens.verify('tok_xxxxx');
601
+
602
+ // Delete/invalidate a token
603
+ await aps.tokens.delete('tok_xxxxx');
604
+ ```
605
+
606
+ ### Custom Payment Page Module (Non-PCI)
607
+
608
+ Create custom payment forms while maintaining PCI compliance through tokenization.
609
+
610
+ ```typescript
611
+ // Get tokenization form
612
+ const form = aps.customPaymentPage.getTokenizationForm({
613
+ returnUrl: 'https://yoursite.com/api/token-result',
614
+ merchantReference: 'order_123'
615
+ });
616
+
617
+ // Render form in your frontend with custom styling
618
+ // Form submits to APS endpoint for secure tokenization
619
+
620
+ // After receiving token, charge with:
621
+ await aps.customPaymentPage.chargeWithToken({
622
+ order: {
623
+ id: string,
624
+ amount: number,
625
+ currency: string,
626
+ description?: string
627
+ },
628
+ tokenName: string, // Token from tokenization response
629
+ customerEmail: string, // Required
630
+ customerName?: string,
631
+ customerPhone?: string,
632
+ returnUrl?: string
633
+ });
634
+ ```
635
+
636
+ **Returns (chargeWithToken):**
637
+
638
+ ```typescript
639
+ {
640
+ transactionId: string;
641
+ orderId: string;
642
+ status: 'captured' | 'authorized' | 'pending' | 'failed';
643
+ amount: number;
644
+ currency: string;
645
+ paymentMethod?: string;
646
+ authenticationUrl?: string; // 3DS URL if required
647
+ redirectForm?: { ... }; // For 3DS redirect
648
+ message?: string;
649
+ rawResponse: object;
650
+ }
651
+ ```
652
+
653
+ **Important Notes:**
654
+ - Tokenization form collects card details securely (Non-PCI compliant)
655
+ - Card data submits directly to APS, never touches your server
656
+ - Token received at returnUrl can be used for immediate or future charges
657
+ - Use `chargeWithToken()` for server-to-server payment processing
658
+
659
+ ### Payments Module (Management)
660
+
661
+ Manage existing transactions.
662
+
663
+ ```typescript
664
+ // Capture authorized payment
665
+ await aps.payments.capture({
666
+ transactionId: string,
667
+ amount?: number // Optional, full capture if not specified
668
+ });
669
+
670
+ // Refund payment
671
+ await aps.payments.refund({
672
+ transactionId: string,
673
+ amount?: number, // Optional, full refund if not specified
674
+ reason?: string
675
+ });
676
+
677
+ // Void authorization
678
+ await aps.payments.void({
679
+ transactionId: string,
680
+ reason?: string
681
+ });
682
+
683
+ // Query transaction
684
+ await aps.payments.query({
685
+ transactionId?: string,
686
+ orderId?: string
687
+ });
688
+ ```
689
+
690
+ ### Webhooks Module
691
+
692
+ Verify and parse webhook events.
693
+
694
+ ```typescript
695
+ // In your API route handler
696
+ const signature = request.headers.get('x-aps-signature') || '';
697
+ const payload = request.body;
698
+
699
+ // Verify signature
700
+ const isValid = aps.webhooks.verifySignature(
701
+ JSON.stringify(payload),
702
+ signature
703
+ );
704
+
705
+ if (!isValid) {
706
+ return res.status(401).send('Invalid signature');
707
+ }
708
+
709
+ // Construct event
710
+ const event = aps.webhooks.constructEvent(payload);
711
+
712
+ // Handle by type
713
+ switch (event.type) {
714
+ case 'payment.success':
715
+ case 'payment.failed':
716
+ case 'payment.pending':
717
+ case 'refund.success':
718
+ case 'refund.failed':
719
+ case 'chargeback.created':
720
+ case 'subscription.renewed':
721
+ case 'subscription.cancelled':
722
+ }
723
+ ```
724
+
725
+ **Webhook Event Structure:**
726
+
727
+ ```typescript
728
+ {
729
+ id: string;
730
+ type: WebhookEventType;
731
+ timestamp: Date;
732
+ data: {
733
+ transactionId: string;
734
+ orderId: string;
735
+ amount: number;
736
+ currency: string;
737
+ status: TransactionStatus;
738
+ paymentMethod?: string;
739
+ metadata?: Record<string, any>;
740
+ };
741
+ rawPayload: object;
742
+ }
743
+ ```
744
+
745
+ ## Supported Payment Methods
746
+
747
+ | Method | Regions | Description |
748
+ |--------|---------|-------------|
749
+ | `card` | All | Visa, Mastercard, American Express |
750
+ | `apple_pay` | All | Apple Pay |
751
+ | `mada` | Saudi Arabia | MADA debit cards |
752
+ | `stc_pay` | Saudi Arabia | STC Pay wallet |
753
+ | `knet` | Kuwait | KNET payment |
754
+ | `naps` | Qatar | NAPS payment |
755
+ | `fawry` | Egypt | Fawry cash payment |
756
+ | `meeza` | Egypt | Meeza cards |
757
+ | `sadad` | Saudi Arabia | Sadad payment |
758
+ | `aman` | Egypt | Aman installment |
759
+
760
+ ## Environment Variables
761
+
762
+ Create a `.env.local` file:
763
+
764
+ ```bash
765
+ # APS Merchant Credentials (from your APS dashboard)
766
+ APS_MERCHANT_ID=your_merchant_id
767
+ APS_ACCESS_CODE=your_access_code
768
+ APS_REQUEST_SECRET=your_request_secret
769
+ APS_RESPONSE_SECRET=your_response_secret
770
+
771
+ # APS Configuration
772
+ APS_ENVIRONMENT=sandbox # Use 'production' for live
773
+ APS_CURRENCY=SAR # Default currency
774
+ APS_LANGUAGE=en # Default language
775
+
776
+ # Your App URL
777
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
778
+ ```
779
+
780
+ ## Supported Currencies
781
+
782
+ - **SAR** - Saudi Riyal
783
+ - **AED** - UAE Dirham
784
+ - **KWD** - Kuwaiti Dinar
785
+ - **QAR** - Qatari Riyal
786
+ - **BHD** - Bahraini Dinar
787
+ - **OMR** - Omani Rial
788
+ - **JOD** - Jordanian Dinar
789
+ - **EGP** - Egyptian Pound
790
+ - **USD** - US Dollar
791
+ - **EUR** - Euro
792
+
793
+ ## Error Handling
794
+
795
+ ```typescript
796
+ import { APSException, APSError } from 'amazon-payment-services';
797
+
798
+ try {
799
+ const link = await aps.paymentLinks.create({ ... });
800
+ } catch (error) {
801
+ if (error instanceof APSException) {
802
+ console.error('Error Code:', error.code);
803
+ console.error('Message:', error.message);
804
+ console.error('Status:', error.statusCode);
805
+ console.error('Details:', error.rawResponse);
806
+
807
+ // Handle specific errors
808
+ switch (error.code) {
809
+ case 'PAYMENT_LINK_ERROR':
810
+ // Handle payment link creation failure
811
+ break;
812
+ case 'SIGNATURE_ERROR':
813
+ // Handle signature mismatch
814
+ break;
815
+ case 'INVALID_CREDENTIALS':
816
+ // Handle authentication failure
817
+ break;
818
+ }
819
+ } else {
820
+ console.error('Unexpected error:', error);
821
+ }
822
+ }
823
+ ```
824
+
825
+ ## Common Response Codes
826
+
827
+ | Code | Message | Description |
828
+ |------|---------|-------------|
829
+ | `00000` | Success | Transaction successful |
830
+ | `48000` | Success | Payment link created successfully |
831
+ | `10030` | Authentication failed | 3DS authentication failed |
832
+ | `00008` | Signature mismatch | Invalid signature |
833
+ | `00002` | Invalid parameter | Parameter format error |
834
+ | `14000` | Declined | Card declined by issuer |
835
+
836
+ ## Security Best Practices
837
+
838
+ 1. **Never expose credentials** - Always use environment variables
839
+ 2. **Server-side only** - All APS API calls must be server-side
840
+ 3. **Verify webhooks** - Always verify webhook signatures
841
+ 4. **Use HTTPS** - Required for production
842
+ 5. **PCI Compliance** - Use hosted checkout or payment links to avoid PCI scope
843
+ 6. **Validate amounts** - Always validate amounts server-side before creating payments
844
+
845
+ ## Testing
846
+
847
+ ### Sandbox Environment
848
+
849
+ ```typescript
850
+ const aps = new APS({
851
+ // ... credentials
852
+ environment: 'sandbox'
853
+ });
854
+ ```
855
+
856
+ **Test Cards** (use in sandbox):
857
+
858
+ | Card Number | Type | CVV | Expiry |
859
+ |-------------|------|-----|--------|
860
+ | 4111 1111 1111 1111 | Visa | 123 | 12/25 |
861
+ | 5297 4100 0000 0002 | MADA | 123 | 12/25 |
862
+ | 5100 0000 0000 0008 | Mastercard | 123 | 12/25 |
863
+
864
+ ### Production
865
+
866
+ ```typescript
867
+ const aps = new APS({
868
+ // ... production credentials
869
+ environment: 'production'
870
+ });
871
+ ```
872
+
873
+ ## Next.js Integration Example
874
+
875
+ See the included test app for a complete working example:
876
+
877
+ ```bash
878
+ # Run the test app
879
+ pnpm dev
880
+ ```
881
+
882
+ Visit `http://localhost:3000` to test payment creation and `http://localhost:3000/docs` for documentation.
883
+
884
+ ## License
885
+
886
+ MIT License - see [LICENSE](LICENSE) for details.
887
+
888
+ ## Support
889
+
890
+ For APS-specific issues, contact Amazon Payment Services merchant support:
891
+ - Email: merchantsupport-ps@amazon.com
892
+ - Documentation: https://paymentservices.amazon.com/docs