Skip to content

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.

javascript
// 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 NumberDescription3D Secure
4242 4242 4242 4242Succeeds immediatelyNo
4000 0027 6000 3184Requires 3D Secure authenticationYes
4000 0000 0000 0077Succeeds after processing delayNo
5555 5555 5555 4444Mastercard - SucceedsNo
3782 822463 10005American Express - SucceedsNo

Declined Cards

Card NumberDecline ReasonError Code
4000 0000 0000 0002Generic declinecard_declined
4000 0000 0000 9995Insufficient fundsinsufficient_funds
4000 0000 0000 9987Lost cardlost_card
4000 0000 0000 9979Stolen cardstolen_card
4000 0000 0000 0069Expired cardexpired_card
4000 0000 0000 0127Incorrect CVCincorrect_cvc
4000 0000 0000 0119Processing errorprocessing_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 CodeBank NameBehavior
TEST0001Test Bank SuccessPayment succeeds
TEST0002Test Bank FailurePayment fails
TEST0003Test Bank PendingPending, then succeeds after 30s
TEST0004Test Bank TimeoutConnection timeout
TEST0005Test Bank CanceledUser cancels at bank

Example:

javascript
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:

javascript
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:

bash
# 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:

  1. Go to Dashboard → Developers → Webhooks
  2. Select your webhook endpoint
  3. Click "Send test webhook"
  4. Choose an event type (e.g., payment.succeeded)
  5. View the request and response

Test Event Types

Available test webhook events:

  • payment.succeeded
  • payment.failed
  • payment.canceled
  • refund.created
  • refund.failed
  • customer.created
  • customer.updated
  • payment_method.attached
  • payment_method.detached

Testing Scenarios

Successful Payment Flow

javascript
// 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 object

Failed Payment Flow

javascript
// 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 event

3D Secure Flow

javascript
// 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 event

Refund Flow

javascript
// 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 event

Sandbox Limitations

Test mode has some limitations compared to live mode:

FeatureTest ModeLive 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:

javascript
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

  1. Replace test API keys with live keys:
javascript
// Before (test mode)
const salam = new Salam('sk_test_xxxxx');

// After (live mode)
const salam = new Salam(process.env.SALAM_LIVE_SECRET_KEY);
  1. Update webhook endpoints in Dashboard
  2. Verify live mode in Dashboard
  3. 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

  1. Check if CLI is running: salam listen
  2. Verify your server is accessible
  3. Check firewall settings
  4. 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

Released under the MIT License.