SeloraXDEVELOPERS

One-Time Charges

One-Time Charges

One-time charges let you collect a single payment from a merchant. Use them for setup fees, content packs, credit bundles, or any non-recurring purchase.

Charge Flow

Step 1: Create the Charge

Send a POST request from your app's backend:

POST /api/apps/v1/billing/charges
Authorization: Bearer sx_at_...
Content-Type: application/json
 
{
  "name": "SMS Pack 100",
  "description": "100 SMS credits",
  "amount": 150.00,
  "currency": "BDT",
  "return_url": "https://app.example.com/billing/success",
  "metadata": { "sku": "sms-100" },
  "idempotency_key": "unique-key-123"
}

Parameters:

FieldTypeRequiredDescription
namestringYesDisplay name shown to the merchant
descriptionstringNoAdditional details about the charge
amountnumberYesAmount in BDT (min 10.00, max 50,000.00)
currencystringYesMust be "BDT"
return_urlstringNoURL to redirect the merchant after payment
metadataobjectNoArbitrary key-value data stored with the charge
idempotency_keystringNoUnique key to prevent duplicate charges

Response:

{
  "charge_id": 5,
  "name": "SMS Pack 100",
  "amount": 150.00,
  "base_amount": 150.00,
  "currency": "BDT",
  "fee_payer": "developer",
  "commission_rate": 0.1,
  "platform_amount": 15.00,
  "gateway_fee_rate": 0.025,
  "gateway_fee_amount": 3.75,
  "developer_amount": 131.25,
  "status": "pending",
  "confirmation_url": "/22/settings/apps/billing/5",
  "created_at": "2026-02-28T10:00:00.000Z"
}

The amount field is what the merchant pays. When fee_payer is developer, amount equals base_amount. When fee_payer is merchant, amount = base_amount + platform_amount + gateway_fee_amount.

Step 2: Redirect the Merchant to Approval

Since your app runs inside an iframe in the SeloraX dashboard, you cannot navigate directly. Instead, use postMessage to tell the parent dashboard to navigate the merchant to the charge approval page:

window.parent.postMessage({
  type: 'selorax:billing-redirect',
  url: confirmation_url
}, '*');

The dashboard will navigate the merchant to the charge approval page, which displays the charge name, description, amount, and your app's information.

Step 3: Merchant Approves

When the merchant clicks "Approve & Pay" on the approval page:

  1. The dashboard calls POST /api/apps/billing/charges/:id/approve.
  2. The platform creates an EPS payment session with a unique merchant_transaction_id.
  3. The merchant is redirected to the EPS payment gateway to complete payment.

Step 4: Payment Completes

After the merchant completes payment on the EPS gateway:

  1. EPS redirects to GET /api/apps/billing/callback?session_txn=...&status=success.
  2. The platform verifies the transaction directly with EPS to confirm payment.
  3. The charge status is updated to active.
  4. A charge.activated webhook is fired to your app.
  5. The merchant is redirected to one of:
    • Your return_url (if provided): {return_url}?payment=success&charge_id=5
    • The default billing complete page: /{store_id}/settings/apps/billing/complete?payment=success

Decline Flow

If the merchant clicks "Decline" on the approval page:

  • The charge status is set to declined.
  • A charge.declined webhook is fired to your app.
  • The merchant is redirected back to your app or the apps list.

Expiry

If the merchant takes no action within 48 hours of charge creation:

  • The charge status is automatically set to expired.
  • A charge.expired webhook is fired to your app.

Idempotency

To prevent duplicate charges (for example, if a network error causes a retry), include an idempotency_key in the request body or an Idempotency-Key header:

POST /api/apps/v1/billing/charges
Idempotency-Key: order-123-sms-pack

If a charge with the same idempotency key already exists for your app and store, the existing charge is returned instead of creating a new one.

Webhooks

EventFired When
charge.activatedMerchant approved and payment succeeded
charge.declinedMerchant explicitly declined
charge.expiredNo action within 48 hours

See the Webhooks documentation for details on receiving and verifying webhook payloads.

Example: Complete Flow

// 1. Create charge from your backend
const response = await fetch('https://api.selorax.io/api/apps/v1/billing/charges', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'SMS Pack 100',
    description: '100 SMS credits',
    amount: 150.00,
    currency: 'BDT',
    return_url: 'https://app.example.com/billing/callback',
    idempotency_key: `sms-pack-${storeId}-${Date.now()}`
  })
});
 
const charge = await response.json();
 
// 2. Send confirmation_url to your frontend iframe
// Your frontend then does:
window.parent.postMessage({
  type: 'selorax:billing-redirect',
  url: charge.confirmation_url
}, '*');
 
// 3. Handle the return_url callback
// When merchant is redirected back to your return_url:
// https://app.example.com/billing/callback?payment=success&charge_id=5
const params = new URLSearchParams(window.location.search);
if (params.get('payment') === 'success') {
  // Charge was successful — verify via API or wait for webhook
}