Test Mode
Use test mode to build and test your integration without processing real payments or moving real money.
Test API Keys
Test keys start with sk_test_ and pk_test_. All API requests made with test keys operate in test mode.
// Test mode - No real money
const salam = new Salam('sk_test_xxxxx');
// Live mode - Real money
const salam = new Salam('sk_live_xxxxx');Dashboard Toggle
You can toggle between test and live modes in the Dashboard. Test mode data is completely separate from live mode.
Test Cards
Use these card numbers for testing different scenarios:
Successful Payments
| Card Number | Description | 3D Secure |
|---|---|---|
4242 4242 4242 4242 | Succeeds immediately | No |
4000 0027 6000 3184 | Requires 3D Secure authentication | Yes |
4000 0000 0000 0077 | Succeeds after processing delay | No |
5555 5555 5555 4444 | Mastercard - Succeeds | No |
3782 822463 10005 | American Express - Succeeds | No |
Declined Cards
| Card Number | Decline Reason | Error Code |
|---|---|---|
4000 0000 0000 0002 | Generic decline | card_declined |
4000 0000 0000 9995 | Insufficient funds | insufficient_funds |
4000 0000 0000 9987 | Lost card | lost_card |
4000 0000 0000 9979 | Stolen card | stolen_card |
4000 0000 0000 0069 | Expired card | expired_card |
4000 0000 0000 0127 | Incorrect CVC | incorrect_cvc |
4000 0000 0000 0119 | Processing error | processing_error |
Card Details
For all test cards:
- Expiry Date: Any future date (e.g.,
12/25,06/2026) - CVC: Any 3 digits for Visa/Mastercard (e.g.,
123) - CVC: Any 4 digits for American Express (e.g.,
1234) - Cardholder Name: Any name
Test FPX Banks
Use these bank codes for FPX testing:
| Bank Code | Bank Name | Behavior |
|---|---|---|
TEST0001 | Test Bank Success | Payment succeeds |
TEST0002 | Test Bank Failure | Payment fails |
TEST0003 | Test Bank Pending | Pending, then succeeds after 30s |
TEST0004 | Test Bank Timeout | Connection timeout |
TEST0005 | Test Bank Canceled | User cancels at bank |
Example:
const payment = await salam.payments.create({
amount: 10000,
currency: 'MYR',
payment_method_data: {
type: 'fpx',
fpx: {
bank_code: 'TEST0001', // Will succeed
},
},
return_url: 'https://yoursite.com/payment/complete',
});Production FPX Banks (Test Mode)
You can also use real FPX bank codes in test mode. They will simulate the bank flow without real transactions:
const payment = await salam.payments.create({
amount: 10000,
currency: 'MYR',
payment_method_data: {
type: 'fpx',
fpx: {
bank_code: 'MBBEMYKL', // Maybank - simulated in test mode
},
},
return_url: 'https://yoursite.com/payment/complete',
});Test Webhooks
Using the CLI
Install and use the Salam CLI to forward webhooks to your local development server:
# Install the CLI
npm install -g @salam/cli
# Forward webhooks to your local server
salam listen --forward-to localhost:3000/webhook
# With custom headers
salam listen --forward-to localhost:3000/webhook \
--header "X-Custom-Header: value"Example output:
✓ Connected to Salam webhook relay
✓ Forwarding webhooks to http://localhost:3000/webhook
→ payment.succeeded [evt_xxxxx]
POST http://localhost:3000/webhook [200 OK]
→ refund.created [evt_yyyyy]
POST http://localhost:3000/webhook [200 OK]Manual Testing
Send test events from the Dashboard:
- Go to Dashboard → Developers → Webhooks
- Select your webhook endpoint
- Click "Send test webhook"
- Choose an event type (e.g.,
payment.succeeded) - View the request and response
Test Event Types
Available test webhook events:
payment.succeededpayment.failedpayment.canceledrefund.createdrefund.failedcustomer.createdcustomer.updatedpayment_method.attachedpayment_method.detached
Testing Scenarios
Successful Payment Flow
// 1. Create payment
const payment = await salam.payments.create({
amount: 10000,
currency: 'MYR',
payment_method_data: {
type: 'card',
card: {
number: '4242424242424242',
exp_month: 12,
exp_year: 2025,
cvc: '123',
},
},
});
console.log(payment.status); // 'succeeded'
// 2. Webhook received (in your server)
// payment.succeeded event with payment objectFailed Payment Flow
// Payment with declined card
const payment = await salam.payments.create({
amount: 10000,
currency: 'MYR',
payment_method_data: {
type: 'card',
card: {
number: '4000000000000002', // Generic decline
exp_month: 12,
exp_year: 2025,
cvc: '123',
},
},
});
console.log(payment.status); // 'failed'
console.log(payment.last_payment_error.code); // 'card_declined'
// Webhook: payment.failed event3D Secure Flow
// 1. Create payment with 3DS card
const payment = await salam.payments.create({
amount: 10000,
currency: 'MYR',
payment_method_data: {
type: 'card',
card: {
number: '4000002760003184',
exp_month: 12,
exp_year: 2025,
cvc: '123',
},
},
return_url: 'https://yoursite.com/payment/complete',
});
console.log(payment.status); // 'requires_action'
// 2. Redirect customer to authenticate
const redirectUrl = payment.next_action.redirect_to_url;
// 3. After authentication, payment status changes to 'succeeded'
// Webhook: payment.succeeded eventRefund Flow
// 1. Create successful payment first
const payment = await salam.payments.create({
amount: 10000,
currency: 'MYR',
payment_method_data: {
type: 'card',
card: {
number: '4242424242424242',
exp_month: 12,
exp_year: 2025,
cvc: '123',
},
},
});
// 2. Create refund
const refund = await salam.refunds.create({
payment: payment.id,
amount: 5000, // Partial refund: RM 50
});
console.log(refund.status); // 'succeeded'
console.log(refund.amount); // 5000
// Webhook: refund.created eventSandbox Limitations
Test mode has some limitations compared to live mode:
| Feature | Test Mode | Live Mode |
|---|---|---|
| Real money | ❌ No | ✅ Yes |
| Bank authorization | ❌ Simulated | ✅ Real |
| 3D Secure | ⚠️ Simplified | ✅ Full |
| FPX flow | ⚠️ Simulated | ✅ Real |
| Rate limits | ⚠️ More lenient | ✅ Standard |
| Webhook delivery | ✅ Same | ✅ Same |
| Error scenarios | ✅ All testable | ⚠️ Real only |
Testing Best Practices
Test All Scenarios
Make sure to test:
- [ ] Successful card payments
- [ ] Declined card payments (multiple decline reasons)
- [ ] 3D Secure authentication flow
- [ ] FPX bank selection and payment
- [ ] Partial refunds
- [ ] Full refunds
- [ ] Webhook handling
- [ ] Idempotency (duplicate requests)
- [ ] Error handling
- [ ] Timeout scenarios
Automated Testing
Example test suite:
const Salam = require('@salam/salam-node');
const salam = new Salam('sk_test_xxxxx');
describe('Payment Flow', () => {
test('successful payment', async () => {
const payment = await salam.payments.create({
amount: 10000,
currency: 'MYR',
payment_method_data: {
type: 'card',
card: {
number: '4242424242424242',
exp_month: 12,
exp_year: 2025,
cvc: '123',
},
},
});
expect(payment.status).toBe('succeeded');
expect(payment.amount).toBe(10000);
});
test('declined payment', async () => {
const payment = await salam.payments.create({
amount: 10000,
currency: 'MYR',
payment_method_data: {
type: 'card',
card: {
number: '4000000000000002',
exp_month: 12,
exp_year: 2025,
cvc: '123',
},
},
});
expect(payment.status).toBe('failed');
expect(payment.last_payment_error.code).toBe('card_declined');
});
});Going Live
Before switching to live mode:
Pre-Launch Checklist
- [ ] ✅ All payment flows tested
- [ ] ✅ All error scenarios handled
- [ ] ✅ Webhook signature verification working
- [ ] ✅ Refund flows tested
- [ ] ✅ 3D Secure flow tested
- [ ] ✅ FPX payments tested
- [ ] ✅ Idempotency implemented
- [ ] ✅ Logging and monitoring set up
- [ ] ✅ Security review completed
- [ ] ✅ Obtained live API keys
Switch to Live Mode
- Replace test API keys with live keys:
// Before (test mode)
const salam = new Salam('sk_test_xxxxx');
// After (live mode)
const salam = new Salam(process.env.SALAM_LIVE_SECRET_KEY);- Update webhook endpoints in Dashboard
- Verify live mode in Dashboard
- Monitor first few live transactions closely
Important
Never use test cards in live mode. They will be declined. Only real payment methods work in live mode.
Troubleshooting
"Invalid API key" error
Make sure you're using a test key (sk_test_) and not a live key in test mode.
Webhook not received locally
- Check if CLI is running:
salam listen - Verify your server is accessible
- Check firewall settings
- Review webhook logs in Dashboard
Payment stuck in "processing"
This is normal for test mode. Some test cards simulate delays. Wait up to 30 seconds or use a different test card.
Next Steps
- Quick Start - Create your first payment
- Accept a Payment - Complete integration guide
- Handle Webhooks - Production webhook setup
- API Reference - Full API documentation