Skip to content

Checkout Flow

Learn how to build a complete checkout experience with Salam Gateway.

Overview

The checkout flow has three main stages:

  1. Payment Creation - Create payment on your server
  2. Customer Checkout - Redirect customer to complete payment
  3. Payment Confirmation - Receive webhook and show confirmation

Payment Creation

Create a payment when the customer is ready to checkout:

typescript
// On your server
app.post('/api/checkout', async (req, res) => {
  try {
    const payment = await salam.payments.create({
      amount: req.body.amount,
      description: req.body.description,
      success_url: `${process.env.APP_URL}/checkout/success`,
      cancel_url: `${process.env.APP_URL}/checkout/cancel`,
      metadata: {
        order_id: req.body.order_id,
        customer_email: req.body.email,
      },
    });
    
    res.json({ redirect_url: payment.redirect_url });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

Customer Checkout

Redirect the customer to the checkout page:

javascript
// On your frontend
async function checkout() {
  const response = await fetch('/api/checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      amount: 10000,
      description: 'Order #1234',
      order_id: '1234',
      email: 'customer@example.com',
    }),
  });
  
  const { redirect_url } = await response.json();
  
  // Redirect to checkout
  window.location.href = redirect_url;
}\n```

## Success Page

Handle the redirect after successful payment:

```typescript
// pages/checkout/success.tsx
export default function CheckoutSuccess() {
  const searchParams = useSearchParams();
  const paymentId = searchParams.get('payment_id');
  
  useEffect(() => {
    // Verify payment status
    fetch(`/api/payments/${paymentId}/verify`)
      .then(res => res.json())
      .then(data => {
        if (data.status === 'captured') {
          // Payment confirmed
          showSuccessMessage();
        }
      });
  }, [paymentId]);
  
  return (
    <div>
      <h1>Payment Successful!</h1>
      <p>Your order has been confirmed.</p>
    </div>
  );
}

Cancel Page

Handle cancelled payments:

typescript
// pages/checkout/cancel.tsx
export default function CheckoutCancel() {
  return (
    <div>
      <h1>Payment Cancelled</h1>
      <p>Your payment was cancelled. No charges were made.</p>
      <button onClick={() => router.push('/cart')}>
        Return to Cart
      </button>
    </div>
  );
}

Webhook Handler

Process the payment confirmation:

typescript
app.post('/webhooks/salam',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const signature = req.headers['salam-signature'];
    
    try {
      const event = salam.webhooks.constructEvent(req.body, signature);
      
      if (event.type === 'payment.captured') {
        const payment = event.data;
        
        // Update order status
        await db.orders.update(
          { id: payment.metadata.order_id },
          { status: 'paid', payment_id: payment.id }
        );
        
        // Send confirmation email
        await sendOrderConfirmation(payment.metadata.customer_email);
        
        // Trigger fulfillment
        await fulfillOrder(payment.metadata.order_id);
      }
      
      res.json({ received: true });
    } catch (error) {
      res.status(400).send('Webhook Error');
    }
  }
);

Complete Flow Diagram

┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│  Cart    │────▶│  Create  │────▶│ Checkout │────▶│ Success  │
│  Page    │     │  Payment │     │   Page   │     │   Page   │
└──────────┘     └──────────┘     └──────────┘     └──────────┘
                      │                                  ▲
                      │                                  │
                      ▼                                  │
                 ┌──────────┐                      ┌──────────┐
                 │ Webhook  │─────────────────────▶│  Update  │
                 │ Received │                      │  Order   │
                 └──────────┘                      └──────────┘

Best Practices

1. Store Payment ID

Always store the payment ID with your order:

typescript
await db.orders.create({
  id: order_id,
  payment_id: payment.id,
  status: 'pending',
  amount: payment.amount,
});

2. Use Metadata

Pass relevant data in metadata:

typescript
const payment = await salam.payments.create({
  amount: 10000,
  metadata: {
    order_id: '1234',
    customer_id: 'cus_abc',
    items: JSON.stringify([...]),
  },
});

3. Handle Timeouts

Payments expire after 30 minutes:

typescript
if (payment.status === 'expired') {
  // Show message: \"Payment expired. Please try again.\"
}

4. Verify Payment Status

Always verify payment status on success page:

typescript
const payment = await salam.payments.retrieve(paymentId);

if (payment.status !== 'captured') {
  // Don't show success message
}

Next Steps

Released under the MIT License.