GloSwitch Docs

Payments

Initiate payments and collect money through the API

Payments

The Payments API allows you to collect payments through various payment methods including mobile money (MTN, Moov, Orange) and card payments (Stripe).

Authentication

All API requests require a Bearer token. Include your API token in the Authorization header:

Authorization: Bearer YOUR_API_TOKEN

Note: The payment verification endpoint (/api/payments/verify/{reference}) is publicly accessible and does not require authentication.

Endpoints

MethodEndpointDescription
POST/api/payments/initiateInitiate a new payment
GET/api/payments/verify/{reference}Verify payment status (public)
GET/api/payments/by-order/{order_id}Get payment by order ID

Base URLs

EnvironmentURL
Demohttps://gsp-api.demo.tindorah.com
Productionhttps://gsp-api.tindorah.com

Initiate Payment

curl --request POST \
  --url https://gsp-api.demo.tindorah.com/api/payments/initiate \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "order_id": "ORDER-1001",
    "currency": "XAF",
    "country_code": "cm",
    "payment_method": "mtn_cm",
    "return_url": "https://example.com/payments/return",
    "items": [
      {
        "title": "Starter plan",
        "price": 25000,
        "quantity": 1
      }
    ],
    "user": {
      "email": "customer@example.com",
      "name": "John Doe"
    }
  }'

Request Body

FieldTypeRequiredDescription
order_idstringYesYour unique order identifier (max 255 chars)
itemsarrayYes*Payment items (required if no service_code)
items[].titlestringYesItem title (max 255 chars)
items[].pricenumberYesUnit price (must be > 0)
items[].quantityintegerYesQuantity (min 1)
items[].descriptionstringNoItem description (max 255 chars)
currencystringYes*Currency code (required if no service_code)
country_codestringNoISO country code (e.g., bj, cm, bf)
payment_methodstringYesPayment method code (see below)
return_urlstringYesURL to redirect after payment (max 500 chars)
invoice_emailstringNoEmail for invoice delivery
payment_phone_numberstringNoMobile money phone number
user_idintegerNo**Existing user ID
userobjectNo**New user data
user.emailstringYesUser email
user.namestringNoUser name
metadataobjectNoCustom data (stored with payment)
service_codestringNoPre-configured service code

*Not required if service_code is provided

**Either user_id or user object is required

Response (201 Created)

{
  "status": 201,
  "message": "Payment initiated successfully",
  "data": {
    "id": "9c8f7e6d-5a4b-3c2d-1e0f-9a8b7c6d5e4f",
    "reference": "01234567-89ab-cdef-0123-456789abcdef",
    "order_id": "order-12345",
    "amount": 5000,
    "currency": "XOF",
    "payment_method": "mtn_open",
    "status": "pending",
    "invoice_email": "customer@example.com",
    "invoice_url": "https://api.example.com/invoices/download/signed-url",
    "payment_url": null,
    "return_url": "https://yourapp.com/checkout/complete",
    "metadata": {
      "subscription_id": "sub-456",
      "plan": "premium"
    },
    "initiated_at": "2024-06-17T10:30:00.000000Z",
    "items": [
      {
        "title": "Premium Subscription",
        "unit_price": 5000,
        "quantity": 1
      }
    ]
  }
}

Verify Payment Status

This endpoint is publicly accessible and does not require authentication.

curl https://gsp-api.demo.tindorah.com/api/payments/verify/{reference}

Response (200 OK)

{
  "status": 200,
  "message": "Payment verified successfully",
  "data": {
    "id": "9c8f7e6d-5a4b-3c2d-1e0f-9a8b7c6d5e4f",
    "reference": "01234567-89ab-cdef-0123-456789abcdef",
    "order_id": "order-12345",
    "amount": 5000,
    "currency": "XOF",
    "payment_method": "mtn_open",
    "status": "success",
    "initiated_at": "2024-06-17T10:30:00.000000Z"
  }
}

Polling Example

async function checkPaymentStatus(reference) {
  const response = await fetch(
    `https://gsp-api.demo.tindorah.com/api/payments/verify/${reference}`
  );
  const data = await response.json();

  if (data.data.status === "success") {
    showSuccessPage();
  } else if (data.data.status === "failed" || data.data.status === "canceled") {
    showErrorPage();
  } else {
    // Still pending, check again later
    setTimeout(() => checkPaymentStatus(reference), 5000);
  }
}

Payment Methods

Mobile Money

CodeDescriptionCurrencyCountry
mtn_openMTN Mobile MoneyXOFBJ
moovMoov MoneyXOFBJ
mtn_momo_bjMTN MoMo BeninXOFBJ
mtn_cmMTN CameroonXAFCM
orange_cmOrange CameroonXAFCM
orange_bfOrange Burkina FasoXOFBF
moov_bfMoov Burkina FasoXOFBF

Card Payments

CodeDescriptionCurrency
stripeCard Payment (Stripe)Any

Currencies

CodeDescription
XOFWest African CFA Franc
XAFCentral African CFA Franc
USDUS Dollar
EUREuro

Payment Status Values

StatusDescription
createdPayment record created, awaiting initiation
pendingPayment initiated, awaiting customer action
successPayment completed successfully
failedPayment attempt failed
canceledPayment canceled by customer
declinedPayment declined by provider
expiredPayment window expired
refundedPayment was refunded

Payment Flow

Mobile Money (MTN, Moov, Orange)

  1. Call POST /api/payments/initiate with payment_phone_number
  2. Customer receives USSD prompt on their phone
  3. Customer approves payment on their phone
  4. Poll GET /api/payments/verify/{reference} to check status

Card Payments (Stripe)

  1. Call POST /api/payments/initiate
  2. Redirect customer to payment_url
  3. Customer completes payment on Stripe checkout page
  4. Customer is redirected to your return_url with status

Callback Handling

After payment completion, customers are redirected to your return_url with query parameters:

https://yourapp.com/checkout/complete?status=success&reference=01234567-89ab...
ParameterDescription
statusFinal payment status
referencePayment reference (use to verify)

Request Examples

Mobile Money Payment (New User)

{
  "order_id": "order-12345",
  "items": [
    {
      "title": "Premium Subscription",
      "price": 5000,
      "quantity": 1,
      "description": "Monthly plan"
    }
  ],
  "currency": "XOF",
  "country_code": "bj",
  "payment_method": "mtn_open",
  "return_url": "https://yourapp.com/checkout/complete",
  "invoice_email": "customer@example.com",
  "payment_phone_number": "+22997000000",
  "user": {
    "email": "customer@example.com",
    "name": "John Doe"
  },
  "metadata": {
    "subscription_id": "sub-456",
    "plan": "premium"
  }
}

Card Payment (Existing User)

{
  "order_id": "order-67890",
  "items": [
    {
      "title": "Event Ticket",
      "price": 25.00,
      "quantity": 2
    },
    {
      "title": "Processing Fee",
      "price": 2.50,
      "quantity": 1
    }
  ],
  "currency": "USD",
  "payment_method": "stripe",
  "return_url": "https://yourapp.com/tickets/confirm",
  "user_id": 42,
  "invoice_email": "customer@example.com"
}

Service-Based Payment

{
  "order_id": "order-99999",
  "service_code": "premium-monthly",
  "payment_method": "mtn_cm",
  "return_url": "https://yourapp.com/subscription/complete",
  "user_id": 42
}

Error Responses

Validation Error (422)

{
  "message": "The payment method field is required.",
  "errors": {
    "payment_method": ["The payment method field is required."]
  }
}

Duplicate Order ID (422)

{
  "message": "The order id has already been taken.",
  "errors": {
    "order_id": ["The order id has already been taken."]
  }
}

Payment Not Found (404)

{
  "message": "Payment not found"
}

Phone Number Formats

CountryFormatExample
Benin+229XXXXXXXX+22997000000
Cameroon+237XXXXXXXXX+237650000000
Burkina Faso+226XXXXXXXX+22670000000

Best Practices

  1. Always verify payments - Do not trust client-side status alone. Call the /verify endpoint.
  2. Store the reference - Save the reference from initiation response for later verification.
  3. Handle all statuses - Implement handlers for success, failure, cancellation, and expiry.
  4. Use idempotent order IDs - Each order_id can only be used once per tenant.
  5. Poll responsibly - When polling the verify endpoint, use reasonable intervals (e.g., every 5 seconds).