Payments by API

Take a payment from your own server, without sending the customer to Biftpay's checkout: your app shows the bank details or collects the card, and your server talks to Biftpay. The payment is the same payment intent as with checkout, with the same statuses and webhooks (see Payments).

  1. Create a payment intent.
  2. Pay it by bank transfer or card from your server.
  3. Wait for payment_intent.successful (a webhook), then fulfil the order.

Every POST needs an Idempotency-Key. Retrying with the same key never charges twice.

Bank transfer#

Ask Biftpay for an account for this payment, and show it to your customer:

bash
curl -X POST https://sandbox.api.biftpay.com/v1/payment_intents/pi_.../bank_transfer \
  -H "Authorization: Bearer bp_test_..." \
  -H "Idempotency-Key: order-1042-transfer"
json
{
  "id": "…",
  "object": "payment_intent",
  "status": "processing",
  "bank_transfer": {
    "account_number": "8023114907",
    "bank_name": "…",
    "account_name": "Kemi Stores Checkout",
    "amount": 1016130,
    "expires_at": "2026-10-15T10:42:00.000Z"
  },
  "next_action": null
}

Tell the customer to send exactly bank_transfer.amount (it includes the fee when the customer pays it) to that account before expires_at. Asking again returns the same account while it's open. When the money lands the payment becomes successful and you get the webhook, even if it arrives after the account expired. Bank transfer is for naira payments.

In test mode, the account is a Biftpay test account: send the transfer yourself with POST /v1/test_helpers/payment_intents/{id}/simulate_transfer and the payment completes, with its webhook.

Cards#

Your server receives card numbers, so your systems must be PCI DSS compliant. Test mode works for everyone. For live mode, request Card payments by API in the dashboard (Developers or Settings → Compliance) with your PCI DSS attestation of compliance; we turn it on once it's approved. If you'd rather not handle card data, use checkout instead.

Send the card from your server:

bash
curl -X POST https://sandbox.api.biftpay.com/v1/payment_intents/pi_.../card \
  -H "Authorization: Bearer bp_test_..." \
  -H "Idempotency-Key: order-1042-card-1" \
  -H "Content-Type: application/json" \
  -d '{ "number": "4242424242424242", "exp_month": 12, "exp_year": 2030, "cvc": "123", "name": "Ada Obi" }'

The response's status says what happened:

statusWhat to do
successfulPaid. Fulfil the order (the webhook follows too).
failedDeclined: show the customer failure_code in your words and let them try another card.
processing with next_actionThe customer's bank wants something first (below).
processing without next_actionThe bank is still deciding: wait for the webhook.

Dollar card payments also need the card's billing address (line1, city, state, postal_code, country).

Biftpay never stores or logs the card number or CVV: only the last four digits and the brand, on the charge attempt.

When the bank asks for a code#

next_action.type is otp: show the customer next_action.message and ask for what the bank wants. next_action.input says what it is: otp (a one-time code, the default), pin (the card PIN), phone or birthday (YYYY-MM-DD). Send it:

bash
curl -X POST https://sandbox.api.biftpay.com/v1/payment_intents/pi_.../authenticate \
  -H "Authorization: Bearer bp_test_..." \
  -H "Idempotency-Key: order-1042-otp-1" \
  -H "Content-Type: application/json" \
  -d '{ "otp": "123456" }'

The answer has the same shape: successful, failed, or another next_action (some banks ask for a PIN, then a code). If the customer gives up, send { "cancel": true }.

When the bank shows its own page#

next_action.type is redirect: open next_action.url in the customer's browser (a new tab or a web view). They confirm with their bank there; the payment completes and the webhook arrives. Meanwhile you can check with GET /v1/payment_intents/{id}, which also shows next_action and bank_transfer.

Test cards#

In test mode, use the test cards: 4242 4242 4242 4242 succeeds, 5060 9900 0000 0008 asks for a PIN (1234) and then a code (123456), 4000 0000 0000 3220 opens a test bank page, and 4000 0000 0000 0002 is declined.

Errors#

CodeMeaning
card_api_not_enabledLive mode, and card payments by API aren't approved for your business yet.
card_api_unavailableCard payments by API aren't offered here yet. Use bank transfer or checkout.
payment_method_unavailableThat way of paying is turned off in your payment settings, or doesn't suit this currency.
payment_in_progressAnother request is working on this payment. Wait a moment and retrieve it.

See Errors, retries and lists for the rest.