Amazon Payment Services (PayFort): MENA Integration Guide
Production-ready Amazon Payment Services integration for MENA e-commerce. Covers merchant page integration, tokenization, installments, KNET, and multi-currency processing.
Table of Contents
- Amazon Payment Services Overview
- Integration Options
- Merchant Page Integration
- Signature Generation
- Tokenization for Recurring Payments
- Installment Payments
- KNET Integration for Kuwait
- Response Handling and Webhooks
Amazon Payment Services Overview
Amazon Payment Services (formerly PayFort) is the go-to payment gateway for MENA enterprises. Having integrated it for multiple UAE and Saudi platforms:
- Multi-GCC support - UAE, Saudi Arabia, Egypt, Kuwait, Bahrain, Qatar
- Amazon backing - Enterprise-grade reliability
- Local acquiring - Direct bank connections in each country
- Installments - Built-in BNPL for regional banks
Integration Options
APS offers three integration methods:
- Redirection - Simplest, but worst UX
- Merchant Page Integration - Best balance of control and compliance
- Custom Integration - Full control, highest PCI requirements
I recommend Merchant Page Integration for most use cases.
Merchant Page Integration
Configuration
// config/aps.ts export const APS_CONFIG = { merchantIdentifier: process.env.APS_MERCHANT_IDENTIFIER!, accessCode: process.env.APS_ACCESS_CODE!, shaRequestPhrase: process.env.APS_SHA_REQUEST_PHRASE!, shaResponsePhrase: process.env.APS_SHA_RESPONSE_PHRASE!, shaType: 'SHA-256', baseUrl: process.env.NODE_ENV === 'production' ? 'https://checkout.payfort.com' : 'https://sbcheckout.payfort.com', language: 'en', // or 'ar' };
Payment Service
// services/aps/PaymentService.ts import crypto from 'crypto'; import { APS_CONFIG } from '@/config/aps'; interface CreatePaymentParams { orderId: string; amount: number; currency: string; customerEmail: string; customerName: string; returnUrl: string; } export class APSPaymentService { generateSignature(params: Record<string, string>, phrase: string): string { // Sort parameters alphabetically const sortedKeys = Object.keys(params).sort(); // Concatenate: PHRASE + key=value pairs + PHRASE let signatureString = phrase; for (const key of sortedKeys) { signatureString += `${key}=${params[key]}`; } signatureString += phrase; // Hash with SHA-256 return crypto .createHash('sha256') .update(signatureString) .digest('hex'); } async createPaymentRequest(params: CreatePaymentParams): Promise<{ paymentUrl: string; formData: Record<string, string>; }> { const { orderId, amount, currency, customerEmail, customerName, returnUrl, } = params; // Validate order const order = await this.orderRepo.findById(orderId); if (!order) { throw new PaymentError('INVALID_ORDER', 'Order not found'); } // APS expects amount in minor units (fils/cents) const amountInMinorUnits = Math.round(amount * 100).toString(); const paymentParams: Record<string, string> = { command: 'PURCHASE', access_code: APS_CONFIG.accessCode, merchant_identifier: APS_CONFIG.merchantIdentifier, merchant_reference: orderId, amount: amountInMinorUnits, currency: currency.toUpperCase(), language: APS_CONFIG.language, customer_email: customerEmail, customer_name: customerName, return_url: returnUrl, }; // Generate signature paymentParams.signature = this.generateSignature( paymentParams, APS_CONFIG.shaRequestPhrase ); // Store payment record await this.paymentRepo.create({ orderId, merchantReference: orderId, amount, currency, status: 'pending', }); return { paymentUrl: `${APS_CONFIG.baseUrl}/FortAPI/paymentPage`, formData: paymentParams, }; } verifyResponseSignature(params: Record<string, string>): boolean { const { signature, ...dataParams } = params; const calculatedSignature = this.generateSignature( dataParams, APS_CONFIG.shaResponsePhrase ); return signature === calculatedSignature; } async processCallback(params: Record<string, string>): Promise<{ success: boolean; orderId: string; transactionId?: string; errorMessage?: string; }> { // Verify signature first if (!this.verifyResponseSignature(params)) { throw new PaymentError('INVALID_SIGNATURE', 'Response signature mismatch'); } const responseCode = params.response_code; const orderId = params.merchant_reference; const fortId = params.fort_id; // Success codes: 14000 (success), 02000 (success with 3DS) const isSuccess = responseCode === '14000' || responseCode === '02000'; await this.paymentRepo.updateByOrderId(orderId, { status: isSuccess ? 'paid' : 'failed', apsTransactionId: fortId, responseCode, responseMessage: params.response_message, }); if (isSuccess) { await this.orderService.fulfillOrder(orderId); } return { success: isSuccess, orderId, transactionId: fortId, errorMessage: isSuccess ? undefined : params.response_message, }; } }
React Integration
// components/payment/APSPayment.tsx import { useState, useEffect } from 'react'; interface APSPaymentProps { orderId: string; amount: number; currency: string; customerEmail: string; customerName: string; } export function APSPayment({ orderId, amount, currency, customerEmail, customerName, }: APSPaymentProps) { const [formData, setFormData] = useState<Record<string, string> | null>(null); const [paymentUrl, setPaymentUrl] = useState<string>(''); const [isLoading, setIsLoading] = useState(false); const initializePayment = async () => { setIsLoading(true); try { const response = await fetch('/api/aps/create-payment', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ orderId, amount, currency, customerEmail, customerName, returnUrl: `${window.location.origin}/checkout/callback`, }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.message); } setPaymentUrl(data.paymentUrl); setFormData(data.formData); } catch (error) { console.error('Payment initialization failed:', error); } finally { setIsLoading(false); } }; useEffect(() => { initializePayment(); }, [orderId]); // Auto-submit form when ready useEffect(() => { if (formData && paymentUrl) { const form = document.getElementById('aps-form') as HTMLFormElement; form?.submit(); } }, [formData, paymentUrl]); if (isLoading) { return ( <div className="flex items-center justify-center py-12"> <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" /> <span className="ml-3">Preparing payment...</span> </div> ); } if (!formData) { return <div>Failed to initialize payment</div>; } return ( <form id="aps-form" action={paymentUrl} method="POST" className="hidden" > {Object.entries(formData).map(([key, value]) => ( <input key={key} type="hidden" name={key} value={value} /> ))} </form> ); }
Tokenization for Recurring Payments
// Create tokenization request async createTokenizationRequest(params) { const tokenParams = { service_command: 'TOKENIZATION', access_code: APS_CONFIG.accessCode, merchant_identifier: APS_CONFIG.merchantIdentifier, merchant_reference: params.merchantReference, language: APS_CONFIG.language, return_url: params.returnUrl, }; tokenParams.signature = this.generateSignature( tokenParams, APS_CONFIG.shaRequestPhrase ); return tokenParams; } // Use token for subsequent payments async chargeToken(tokenName: string, amount: number, currency: string) { const chargeParams = { command: 'PURCHASE', access_code: APS_CONFIG.accessCode, merchant_identifier: APS_CONFIG.merchantIdentifier, merchant_reference: generateOrderId(), amount: Math.round(amount * 100).toString(), currency, language: 'en', token_name: tokenName, }; chargeParams.signature = this.generateSignature( chargeParams, APS_CONFIG.shaRequestPhrase ); const response = await fetch(`${APS_CONFIG.baseUrl}/FortAPI/paymentApi`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(chargeParams), }); return response.json(); }
Installment Payments
// Add installments to payment request const installmentParams = { ...baseParams, installments: 'STANDALONE', plan_code: 'PLAN_CODE_FROM_APS', // Get from APS dashboard issuer_code: 'BANK_CODE', // e.g., 'ADCB' for Abu Dhabi Commercial Bank number_of_installments: '6', // 3, 6, 9, 12 months };
KNET Integration for Kuwait
// KNET-specific parameters const knetParams = { ...baseParams, payment_option: 'KNET', // KNET requires KWD currency currency: 'KWD', // Amount in fils (1 KWD = 1000 fils) amount: Math.round(amount * 1000).toString(), };
Conclusion
Amazon Payment Services integration requires:
- Signature generation - SHA-256 with proper ordering
- Response verification - Always verify callback signatures
- Currency handling - Different minor units per currency
- Regional methods - KNET for Kuwait, mada routing for Saudi
APS is the enterprise choice for MENA. Get the signatures right, and you have a reliable payment foundation.
Related Articles
Payment Integrations24 min read
HyperPay Payment Integration for Saudi Arabia and GCC
Production-ready HyperPay integration for Saudi and GCC e-commerce. Covers mada card processing, STC Pay, Apple Pay, Copy and Pay forms, and SAMA compliance requirements.
Payment Integrations26 min read
Paymob Payment Integration for Egypt and MENA Markets
Production-ready Paymob Accept API integration for Egypt and MENA markets. Covers iframe tokenization, mobile wallets (Vodafone Cash, Orange Money), webhook security, and Arabic localization patterns.
Security Engineering18 min read
API Security Hardening: A Practitioner's Guide
Secure your APIs with rate limiting, input validation, and CORS configuration. Production-tested checklist covering authentication, encryption, and error handling.
Security Engineering21 min read
Authentication and Authorization in Production Systems
Implement secure JWT authentication with refresh token rotation, RBAC, and OAuth 2.0 flows. Production patterns from healthcare and government systems.