Payments API
Base URL: https://pay.codeappear.com/api/v1. Authenticate every request with your API key as
a bearer token: Authorization: Bearer fk_live_.... All amounts are in BDT.
An active subscription is required.
Create a payment
POST /api/v1/payments
{
"amount": 500, // required — integer or decimal, min 1
"invoice_number": "ORDER-42", // required — your order reference; max 100 chars; idempotency key
"customer_id": 1, // optional — an existing customer belonging to your merchant account
"success_url": "https://shop.example.com/thanks", // optional — customer returns here after paying
"cancel_url": "https://shop.example.com/cart", // optional — customer returns here if they cancel
"metadata": {"channel": "web"} // optional — up to 20 keys, max 500 chars per value
}
Pass customer_id to attach a payment to a customer you already created via the
Customers API.
It must belong to your merchant account — an unknown or foreign customer_id returns
422 validation_error.
Response 201 — a payment intent only returns the fields that are actually
meaningful before checkout; provider, payment method, and verification details aren't known
yet and appear once you retrieve the payment:
{
"data": {
"id": "01jxyz...",
"amount": "500.00",
"amount_due": "505.00", // amount the customer must actually send (includes any cash-out charge)
"currency": "BDT",
"livemode": true,
"status": "pending",
"invoice_number": "ORDER-42",
"customer_id": null, // the customer_id you passed, or set automatically via a payment link
"metadata": {"channel": "web"},
"success_url": "https://shop.example.com/thanks",
"cancel_url": "https://shop.example.com/cart",
"checkout_url": "https://pay.codeappear.com/pay/01jxyz...",
"expires_at": "2026-06-12T15:00:00+06:00",
"created_at": "2026-06-12T14:30:00+06:00"
}
}
Redirect your customer to checkout_url. Payments expire after
15 minutes if the customer does not
submit a transaction ID. When success_url/cancel_url are set, the
customer is returned there once checkout resolves, with payment_id and
invoice_number appended to the URL.
Idempotency
invoice_number is your order reference and acts as an idempotency key. If a
payment already exists for that invoice_number and hasn't reached
failed or expired, creating another one is rejected with
HTTP 409 and code: "duplicate_invoice_number" — so a dropped
connection never double-charges. Look up the existing payment with
GET https://pay.codeappear.com/api/v1/payments?invoice_number=… instead of retrying blindly.
A new payment can only be created once the previous one for that invoice has
failed or expired.
Payment lifecycle
pending → customer submits TrxID → awaiting_verification →
SMS matched → completed. Terminal states: failed
(e.g. duplicate_trx_id, rejected_by_admin),
expired, partially_refunded, and refunded.
Failed payments carry a failure_reason string.
Retrieve a payment
GET /api/v1/payments/{id}
Returns HTTP 404 if the payment doesn't belong to your merchant, or if it was
created in a different mode
than the key you're using (a live key can't retrieve a test payment, and vice versa).
List payments
GET /api/v1/payments?status=completed&per_page=25
GET /api/v1/payments?invoice_number=ORDER-42 // look up by your own reference
Returns a paginated list, newest first, scoped to your key's live/test mode. status,
invoice_number, and per_page (max 100, default 25) are optional filters.
Refund a payment
POST /api/v1/payments/{id}/refund
{
"amount": 200 // optional — omit to refund the full remaining balance
}
Refunds a completed or partially_refunded payment. Omit
amount (or the whole request body) to refund whatever is still refundable;
pass a smaller amount to refund only part of it. Reaching the full amount — across one or
more calls — marks the payment refunded; anything less marks it
partially_refunded, which can still receive further refunds.
Returns the updated payment object, including refunded_amount, with HTTP
200. Returns HTTP 422 if the payment isn't completed
or partially_refunded, if amount exceeds the remaining refundable
balance, or HTTP 404 if it belongs to a different mode than your key (same rule
as above). Firing this endpoint dispatches a payment.refunded webhook once the
payment is fully refunded, or payment.partially_refunded otherwise, to all
subscribed endpoints.
Refund history
GET /api/v1/payments/{id}/refunds
Every refund call is recorded as its own entry, so partial refunds don't just add up into one number — you can see exactly when each one happened and how much it was for:
{
"data": [
{
"id": 2,
"amount": "600.00",
"source": "api",
"created_at": "2026-06-13T09:15:00+06:00"
},
{
"id": 1,
"amount": "400.00",
"source": "panel",
"created_at": "2026-06-12T16:40:00+06:00"
}
]
}
Returns a paginated list, newest first. source is api or
panel depending on where the refund was issued from.
Polling vs webhooks
You can poll GET /api/v1/payments/{id} until the status is terminal, but we
recommend webhooks
for instant notification.