Pay With Opay

Pay with OPay lets your customers pay you straight from their OPay wallet. OPay is a Nigerian digital wallet with a large retail user base.

The payment is redirect-based. You create the payment on Fincra, Fincra returns an OPay link, and your customer approves the debit inside OPay. OPay handles the login and security checks.

Availability: NGN only.

Environments: Sandbox base URL is https://api.dev.fincra.com. Sandbox redirects go to OPay's sandbox cashier at sandboxcashier.opaycheckout.com.

Hosted checkout pages: sandbox https://sandbox-checkout.fincra.com, production https://checkout.fincra.com.

How the payment works

An OPay payment takes four steps:

  1. Create the payment. Send the amount, currency and customer details. Fincra returns a payCode, the reference for this payment.
  2. Create the charge. Call the charge endpoint for that payCode with type: "opay". Fincra returns an OPay redirect link and the charge status pending.
  3. Redirect the customer. Send the customer to the link. They log in to OPay and approve the debit.
  4. Verify the transaction. Confirm the final status, amount and reference before you give value.

Authentication

Every request in this guide carries three headers.

HeaderValue
x-pub-keyYour public key. Sandbox keys start with pk_test_
api-keyYour API key. Keep it on your server; never expose it in a browser or app.
Content-Typeapplication/json

Check OPay is enabled

OPay must be enabled on your account before you can charge with it. Check before you create a payment:
GET /checkout-core/payments/available-methods?currency=NGN

["card", "bank_transfer", "palmpay", "opay"]

If opay is missing, requests return 403 with Access Denied. You're not authorized to access

Step 1: Create the payment

Collect the customer's name, email and phone number, then create the payment.
POST /checkout-core/payments

FieldTypeRequiredDescription
amountnumberYesAmount to collect, in naira. 500 = NGN 500.
currencystringYesMust be NGN.
feeBearerstringYesWho pays the fee. business = you; customer = added to the customer's amount.
customer.namestringYesCustomer's full name.
customer.emailstringYesCustomer's email address.
customer.phoneNumberstringYesCustomer's phone number, e.g. 08030000000.
redirectUrlstringNoWhere OPay returns the customer after payment. Must be a valid URL.
settlementDestinationstringYesWhere Fincra settles the funds. wallet = your Fincra NGN wallet.
settlementTimestringNoWhen Fincra settles: instant, next_day, t+3 or end_of_week.
referencestringNoYour own reference. Recommended: it comes back as reference in the charge response and webhook. If you omit it, reference defaults to the payCode.
metadataobjectNoAny data you want returned with the payment.
curl -X POST https://api.dev.fincra.com/checkout-core/payments \
  -H "x-pub-key: $PUBLIC_KEY" \
  -H "api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 500,
    "currency": "NGN",
    "reference": "181fb6a1-2049-4052-893d-16dc2aa8c954",
    "feeBearer": "business",
    "customer": {
      "name": "OPay Demo",
      "email": "[email protected]",
      "phoneNumber": "08030000000"
    },
    "redirectUrl": "https://merchant.example.com/payment/complete",
    "settlementDestination": "wallet"
  }'
{
  "status": true,
  "message": "Hosted link generated",
  "data": {
    "link": "https://checkout.dev.fincra.com/pay/fcr-p-123f5eeeef",
    "payCode": "fcr-p-123f5eeeef"
  }
}

Store data.payCode. You use it to create the charge in Step 2 and to match the payment later. The hosted data.link opens Fincra's checkout page. For a direct OPay integration, skip it and go to Step 2.

Step 2: Create the OPay charge

Create a charge on the payment with type: "opay" The type field is what selects Pay with OPay.
POST /checkout-core/payments/{payCode}/charge

FieldTypeRequiredDescription
payCode (path)stringYesThe payCode from Step 1.
typestringYesMust be opay.
curl -X POST https://api.dev.fincra.com/checkout-core/payments/fcr-p-123f5eeeef/charge \
  -H "x-pub-key: $PUBLIC_KEY" \
  -H "api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "opay"}'
{
  "status": true,
  "message": "Charge created",
  "data": {
    "id": 67588,
    "authorization": {
      "mode": "REDIRECT",
      "withCallback": true,
      "redirect": "https://sandboxcashier.opaycheckout.com/apiCashier/redirect/payment/cashier-list?orderToken=TOKEN.852760f84cb2462e973fd7eadc3542b0"
    },
    "auth_model": "REDIRECT",
    "amount": 500,
    "amountExpected": 500,
    "amountReceived": 0,
    "varianceType": null,
    "currency": "NGN",
    "fee": 20,
    "vat": 1.5,
    "electronicMoneyTransferLevy": 0,
    "message": "Awaiting payment approval in the OPay app",
    "actionRequired": null,
    "status": "pending",
    "reference": "fcr-p-123f5eeeef",
    "description": "checkout",
    "type": "opay",
    "customer": {
      "name": "OPay Demo",
      "email": "[email protected]",
      "phoneNumber": "08030000000"
    },
    "metadata": {}
  }
}

A new charge always returns status: "pending" and amountReceived: 0. The customer has not paid yet.

Store data.reference and data.id. Both stay the same in the status endpoint and the webhook, so either one ties the outcome back to this payment. This sample and the status-endpoint sample were made without a reference, so they show the payCode instead.

Step 3: Redirect the customer to OPay

Send the customer to data.authorization.redirect from the charge response. authorization.mode is REDIRECT for every OPay charge.

On the OPay page the customer:

  1. Logs in to their OPay account.
  2. Picks the OPay balance to pay from.
  3. Approves the debit.

authorization.withCallback: true means OPay sends the customer back after they finish on the cashier page, instead of leaving them on OPay. They land on the redirectUrl you set in Step 1; you cannot set it on the charge call. A return to your site is not proof of payment, so verify first (Step 4).Step 4: Verify the transaction

Confirm the final status before you give value. You can do this two ways:

  • Listen for the webhook Fincra sends when the charge reaches a final status.
  • Query the payment status with its reference (the payCode).

Whichever you use, check all four before you give value:

  • status is successful
  • reference matches the payCode you stored in Step 1
  • amountReceived equals amountExpected
  • currency is NGN

If amountReceived and amountExpected differ, varianceType shows the direction of the difference.

Charge response fields

FieldTypeDescription
idnumberFincra's ID for the charge.
authorization.modestringHow the customer authorises. Always REDIRECT for OPay.
authorization.withCallbackbooleantrue when the customer returns to your site after OPay.
authorization.redirectstringOPay link to send the customer to.
auth_modelstringSame as authorization.mode.
amountnumberPayment amount, in naira.
amountExpectednumberAmount Fincra expects to collect, in naira.
amountReceivednumberAmount collected so far, in naira. 0 until the customer pays.
varianceTypestring or nullDirection of any gap between expected and received. null when they match.
currencystringNGN.
feenumberFincra fee, in naira.
vatnumberVAT on the fee, in naira.
electronicMoneyTransferLevynumberElectronic Money Transfer Levy, in naira.
messagestringHuman-readable status, e.g. Awaiting payment approval in the OPay app.
actionRequiredstring or nullAny action the customer must still take.
statusstringCharge status. pending on creation.
referencestringThe payCode from Step 1.
descriptionstringPayment description.
typestringPayment method. opay.
customerobjectCustomer name, email and phone number.
metadataobjectMetadata attached to the payment.

Step 4: Verify the transaction

Confirm the final status before you give value. Use the webhook, the status endpoint, or both.

Option A: Webhook

Fincra sends a webhook when the charge reaches a final outcome. The event name is charge. plus the outcome:

EventMeaning
charge.successfulThe customer paid.
charge.failedThe charge did not complete.

The envelope is {event, type, data}:

{
  "event": "charge.successful",
  "type": "charge",
  "data": {
    "id": 67578,
    "chargeReference": "fcr-op-567f85d07ec9093853637",
    "reference": "181fb6a1-2049-4052-893d-16dc2aa8c954",
    "status": "success",
    "type": "opay",
    "message": "Payment confirmed by OPay",
    "currency": "NGN",
    "amount": 500,
    "amountExpected": 500,
    "amountReceived": 500,
    "amountToSettle": 478.5,
    "fee": 20,
    "vat": 1.5,
    "electronicMoneyTransferLevy": 0,
    "varianceType": null,
    "actionRequired": null,
    "auth_model": "REDIRECT",
    "authorization": {
      "mode": "REDIRECT",
      "redirect": "https://sandboxcashier.opaycheckout.com/apiCashier/redirect/payment/cashier-list?orderToken=TOKEN.f7b58976d24849ee8e5d5075940bc369",
      "withCallback": true
    },
    "customer": {
      "name": "QA User",
      "email": "[email protected]",
      "phoneNumber": "08012345678"
    },
    "description": "",
    "metadata": {}
  }
}

data also carries the charge fields listed under Charge response fields. Fincra sends webhooks to the callback URL in your business settings, and only when webhooks are enabled there.

FieldWhat to know
chargeReferenceFincra's internal charge reference. Pay with OPay charges start with fcr-op-. Quote it in support tickets; do not match on it, because Step 2 does not return it.
referenceA separate identifier. Do not use it in place of chargeReference.
idFincra's charge ID, the same as data.id in Step 2.
statussuccess on a paid charge, while the event name is charge.successful.
amountToSettleWhat Fincra settles to you, in naira: amount minus fee minus vat when feeBearer is business. Here 500 − 20 − 1.5 = NGN 478.5.

Fincra sends webhooks to the callback URL in your business settings, and only when webhooks are enabled there.

Option B: Status endpoint

GET /checkout-core/payments/{payCode}/charges

{
  "status": true,
  "message": "Charges fetched",
  "data": [{
    "id": 67588,
    "type": "opay",
    "status": "failed",
    "amount": 500,
    "amountExpected": 500,
    "amountReceived": 500,
    "currency": "NGN",
    "fee": 20,
    "vat": 1.5,
    "message": "Order closed by merchant",
    "reference": "fcr-p-123f5eeeef",
    "authorization": {
      "mode": "REDIRECT",
      "withCallback": true,
      "redirect": "https://sandboxcashier.opaycheckout.com/..."
    },
    "customer": {},
    "metadata": {}
  }]
}

Use the plural /charges route. The singular GET /checkout-core/payments/{payCode}/charge only returns a charge still in progress. Once a charge fails or expires it returns No charge found for this payment with data: null. Use the singular route to resume a live charge, never to confirm an outcome.
GET /checkout-core/payments/{payCode} also works and returns the payment-level status and amountReceived.

Status values

StatusFinal?Meaning
successYesPaid. Give value after the checks below.
failedYesDid not complete.
expiredYesThe customer did not pay in time.
rejectedYesDeclined.
initiated, pending, processingNoStill in progress. Wait for the webhook or check again.
reversal-initiated, awaiting-reversal, reversed—Reversal states after a payment.
awaiting-captureNoAuthorised, not yet captured.

The charge status on success is success, but the webhook event is charge.successful. Check for the right word in each place.

An expired OPay charge shows the charge as failed with message Order closed by merchant, and the payment as expired.

Checks before you give value

• status is success
• reference matches the payCode you stored in Step 1
• amountReceived equals amountExpected
• currency is NGN

If amountReceived and amountExpected differ, varianceType shows the direction of the difference.

Charge response fields

FieldTypeDescription
idnumberFincra's ID for the charge.
authorization.modestringHow the customer authorises. Always REDIRECT for OPay.
authorization.withCallbackbooleantrue when the customer returns to your site after OPay.
authorization.redirectstringOPay link to send the customer to.
auth_modelstringSame as authorization.mode.
amountnumberPayment amount, in naira.
amountExpectednumberAmount Fincra expects to collect, in naira.
amountReceivednumberAmount collected so far, in naira. 0 until the customer pays.
varianceTypestring or nullDirection of any gap between expected and received. null when they match.
currencystringNGN.
feenumberFincra fee, in naira.
vatnumberVAT on the fee, in naira.
electronicMoneyTransferLevynumberElectronic Money Transfer Levy, in naira.
messagestringHuman-readable status, e.g. Awaiting payment approval in the OPay app.
actionRequiredstring or nullAny action the customer must still take.
statusstringCharge status. pending on creation; see Status values in Step 4.
referencestringThe payCode from Step 1.
descriptionstringPayment description.
typestringPayment method. opay.
customerobjectCustomer name, email and phone number.
metadataobjectMetadata attached to the payment.

Errors

HTTPCauseResponse
400amount missing or below 1["Amount cannot be less than 1", "amount should not be empty"]
400customer.name is a single wordCustomer's full name is required
400feeBearer not allowed["feeBearer must be one of the following values: customer, business"]
400Charge type not allowed["type must be one of the following values: card, bank_transfer, payattitude, mobile_money, apple_pay, direct_debit, eft, palmpay, opay, wallet_transfer"]
403OPay not enabled on your account{"message": "Access Denied. You're not authorized to access <Product> product"}
404payCode not found{"message": "Payment not found", "error": "Not Found"}

Did this page help you?