Card Payments API
Accept Visa and Mastercard payments with AES-256-GCM client-side encryption. Sensitive card data (number, expiry, CVV) is encrypted in the browser or mobile app before it ever reaches your server. Your server then forwards the encrypted payload to DGS-Pay, which decrypts and processes it securely.
Card Data Encryption
Before sending a charge request you must encrypt all four card fields using your merchant encryption key (from Settings → API Settings). Test and live environments each have a different key.
- Encrypt card data only in the browser or mobile app — never on your backend server.
- Never log, store, inspect, or transmit raw card numbers, CVV values, or PINs anywhere.
- Serve your checkout page exclusively over HTTPS.
- Do not pass raw card fields to your server even temporarily.
Encryption Flow
1. Customer fills card form in browser/app encryptCard() ← runs in browser using DGS Encryption Key ↓ 2. Browser sends encrypted payload to YOUR server ↓ 3. Your server forwards encrypted payload to DGS-Pay /charges ↓ 4. DGS-Pay decrypts with private key and processes payment
JavaScript Encryption Implementation
// Load your encryption key from the merchant dashboard const ENC_KEY = "YOUR_ENCRYPTION_KEY_FROM_DASHBOARD"; async function importKey(b64) { const raw = Uint8Array.from(atob(b64), c => c.charCodeAt(0)); return crypto.subtle.importKey( 'raw', raw, { name: 'AES-GCM' }, false, ['encrypt'] ); } function hexNonce() { // Produces a 12-byte (24 hex char) nonce — required by DGS-Pay const bytes = crypto.getRandomValues(new Uint8Array(6)); return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(''); } async function encryptField(plaintext, key, nonce) { const iv = new TextEncoder().encode(nonce); const enc = await crypto.subtle.encrypt( { name: 'AES-GCM', iv, tagLength: 128 }, key, new TextEncoder().encode(plaintext) ); return btoa(String.fromCharCode(...new Uint8Array(enc))); } async function encryptCard(cardNumber, month, year, cvv) { const key = await importKey(ENC_KEY); const nonce = hexNonce(); // all four fields share one nonce const [encCard, encMon, encYr, encCvv] = await Promise.all([ encryptField(cardNumber, key, nonce), encryptField(month, key, nonce), encryptField(year, key, nonce), encryptField(cvv, key, nonce), ]); return { card_encrypted_number: encCard, card_encrypted_expiry_month: encMon, card_encrypted_expiry_year: encYr, card_encrypted_cvv: encCvv, card_nonce: nonce, }; } // Usage example const payload = await encryptCard("4532123456789010", "12", "2028", "123"); // payload now contains card_encrypted_* fields + card_nonce
- All four card fields must be encrypted using the same nonce.
- The nonce must be exactly 12 bytes — 24 hex characters.
- Generate a new fresh nonce for every transaction; never reuse.
Initiate a Card Payment
After encrypting the card fields on the client, send them along with the transaction details to /charges.
curl -X POST \ https://pay.digitalservicescenter.rw/generation/v2/charges \ -H "X-DGS-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 5000, "currency": "RWF", "payment_type": "card", "customer_email": "john.doe@example.com", "customer_first_name": "John", "customer_last_name": "Doe", "customer_phone": "788123456", "customer_phone_country_code": "250", "redirect_url": "https://yourdomain.com/payment-success", "narration": "Payment for Order #1234", "card_encrypted_number": "<encrypted_number>", "card_encrypted_expiry_month": "<encrypted_month>", "card_encrypted_expiry_year": "<encrypted_year>", "card_encrypted_cvv": "<encrypted_cvv>", "card_nonce": "<24_hex_nonce>" }'
{
"amount": 5000,
"currency": "RWF",
"payment_type": "card",
"customer_email": "john.doe@example.com",
"customer_first_name": "John",
"customer_last_name": "Doe",
"customer_phone": "788123456",
"customer_phone_country_code": "250",
"redirect_url": "https://yourdomain.com/payment-success",
"narration": "Payment for Order #1234",
"card_encrypted_number": "<encrypted_number>",
"card_encrypted_expiry_month": "<encrypted_month>",
"card_encrypted_expiry_year": "<encrypted_year>",
"card_encrypted_cvv": "<encrypted_cvv>",
"card_nonce": "<24_hex_nonce>"
}
Payment Authorization
Some cards require additional verification after the initial charge. The charge response will include a next_action_type field telling you what the customer must do. You then submit their input to /authorize.
transaction_status equals "successful" AND the response contains no next_action_type field.
{
"dgs_reference": "dgs_123456789",
"auth_type": "pin",
"auth_pin_encrypted": "<base64_encrypted_pin>",
"auth_pin_nonce": "abc123def456"
}
{
"dgs_reference": "dgs_123456789",
"auth_type": "otp",
"auth_otp": "123456"
}
{
"dgs_reference": "dgs_123456789",
"auth_type": "avs",
"avs_line1": "123 Main Street",
"avs_line2": "Apt 4B",
"avs_city": "Kigali",
"avs_state": "Kigali City",
"avs_country": "RW",
"avs_postal_code": "00000"
}
| auth_type | Required Fields | When triggered |
|---|---|---|
pin | auth_pin_encrypted, auth_pin_nonce | Card issuer requires PIN verification. |
otp | auth_otp | 3DS OTP sent to customer's registered number. |
avs | avs_line1, avs_country (minimum) | Issuer requires billing address match. |