Pewang PaymentsExternal backends · loans, wallets, billing
Integrators

Integrate your loan app

Your backend calls this deployment over HTTPS — no Paystack secret in the mobile app. Collect repayments with Paystack Checkout, pay out to M-Pesa with the disburse API, and confirm everything with verify + webhooks.

Integrating from your backend: use the base URL the API owner gives you, GET /api/payments as the route index, and the flows below (initialize → verify; disburse → optional approve; optional transfer status). If Bearer auth is on, they give you a key. You do not configure server env vars — skip the Operators section unless you run this deployment. Deep reference: docs/LOAN-APP-PAYMENTS-INTEGRATION.md.
Canonical written spec (for PRs / offline): docs/LOAN-APP-PAYMENTS-INTEGRATION.md in the repository.
Machine catalog: GET /api/payments — JSON list of routes, bodies, and env vars.
Try flows in the browser: /payments/test (Checkout + disburse demos).

Authentication & security

  • PaystackPAYSTACK_SECRET_KEY is only on this API host. These routes call Paystack for you; you never send the Paystack secret in headers.
  • Optional Bearer — If PAYMENTS_REQUIRE_BEARER=true and PAYMENTS_API_KEYS is set, send Authorization: Bearer <key> on all /api/payments/* routes below (except Paystack’s own webhook, which uses x-paystack-signature).
  • Webhooks — Verify HMAC on the raw body per Paystack docs; invalid signature → our handler returns 400.

Flow: borrower repays (collect KES)

  1. POST /api/payments/initialize with email, amount_kes (major KES), callback_url, optional metadata (loan_id, etc.).
  2. Open authorization_url in the user's browser (full redirect or WebView).
  3. User returns to your callback_url with reference (or trxref) in the query string.
  4. Your server calls GET /api/payments/verify?reference=… and/or handles charge.success on the webhook (coordinate with the API owner for ledger updates or event forwarding).
  5. Persist reference idempotently — Paystack may retry webhooks.

When repayment "worked"

Verify JSON has status: "success" and a paid_at / amount in line with your order. See Paystack field notes in your verify response.

Flow: disburse to M-Pesa (payout)

Amounts < KES 2000 → one step, mode: "automatic". Amounts ≥ KES 2000 mode: "pending_approval" + approval_token, then POST /api/payments/disburse/approve with the OTP value the API owner documents for your integration.
  1. POST /api/payments/disburse with amount_kes, recipient_phone (0711… or 254…), recipient_name, optional reason.
  2. If automatic: read settlement + transfer.status. pending is often normal — use GET /api/payments/transfer or transfer webhooks for the final state.
  3. If pending approval: after your own checks, call approve with the token and OTP.

Balance: insufficient Paystack KES → 400 with a clear message before a transfer is queued.

Test line: 0711164069 / 254711164069

HTTP status & JSON errors

Successful business operations return 200 with a JSON body (shape depends on route). Failures use { "message": "…" } unless noted.

HTTPTypical cause
200Success — parse route-specific fields.
400Validation, insufficient Paystack balance, invalid approval token/OTP, bad query (e.g. missing reference on verify).
401Bearer required (PAYMENTS_REQUIRE_BEARER=true) but missing/wrong Authorization.
502Paystack rejected the call or upstream error — read message (e.g. invalid key, Paystack transfer error).

Disburse success shapes

FieldMeaning
modeautomatic | pending_approval | approved (after approve endpoint).
settlementprocessing | success | otp_required | failed | unknown — human-oriented read of Paystack transfer state.
messageExplains especially when Paystack still shows pending.
transferid, reference, status (raw Paystack), optional transfer_code.

Endpoint quick reference

Method & pathPurpose
GET /api/paymentsJSON catalog: all routes, env vars, webhook path.
POST /api/payments/initializeStart Checkout — returns authorization_url + reference.
GET /api/payments/verifyQuery reference — confirm a charge after redirect or as backfill.
POST /api/payments/disbursePayout to M-Pesa (auto or pending_approval).
POST /api/payments/disburse/approveComplete large transfer with approval_token + otp.
GET /api/payments/transferQuery reference or transfer_code — status + confirmation_message (SMS-ready).
# Initialize (repayment) curl -sS -X POST 'https://pewang.company/api/payments/initialize' \ -H 'Content-Type: application/json' \ -d '{"email":"borrower@example.com","amount_kes":500,"callback_url":"https://your-app.com/pay/return","metadata":{"loan_id":"LN-1"}}' # Verify curl -sS 'https://pewang.company/api/payments/verify?reference=PAYSTACK_REF' # Disburse curl -sS -X POST 'https://pewang.company/api/payments/disburse' \ -H 'Content-Type: application/json' \ -d '{"amount_kes":200,"recipient_phone":"0711164069","recipient_name":"Jane","reason":"Loan payout"}'

Paystack → your deployment

Register this URL in the Paystack dashboard (live & test):

https://payment.pewang.company/api/paystack/webhook

Handled for you today

  • transfer.success, transfer.failed, transfer.reversed — response JSON includes confirmation_message. Optional server-to-server POST to PAYMENTS_TRANSFER_NOTIFY_URL (see env table below).

You should implement

  • charge.success (and failures) to mark loans paid — ask the API owner to forward events to your service or to extend the webhook handler on this host for your ledger.

Deployment operators — environment

Integrators can skip this. The team that hosts this project sets variables on the deployment (e.g. Vercel). After changes, they redeploy. CLI: docs/VERCEL-CLI.md.

VariableRole
PAYSTACK_SECRET_KEYRequired — server → Paystack.
PAYSTACK_PUBLIC_KEYOptional — client Checkout if you build your own UI.
PAYSTACK_WEBHOOK_SECRETOptional — defaults to secret key for webhook HMAC.
PAYMENT_APPROVAL_OTPRequired for /disburse/approve when amount ≥ 2000 KES.
PAYMENTS_REQUIRE_BEARER / PAYMENTS_API_KEYSOptional extra gate on payment HTTP routes.
PAYSTACK_KES_MOBILE_BANK_CODEOptional — default MPESA for M-Pesa recipients.
PAYMENTS_TRANSFER_NOTIFY_URLOptional — POST JSON (incl. confirmation_message) after transfer webhooks.
PAYMENTS_TRANSFER_NOTIFY_SECRETOptional — sent as X-Pewang-Notify-Secret to your notify URL.

Repository documentation

  • Loan integration (this spec, long-form): docs/LOAN-APP-PAYMENTS-INTEGRATION.md
  • Curl recipes & notes: docs/PAYMENTS-API.md
  • Vercel CLI & env: docs/VERCEL-CLI.md

Payments product overview · API hub

Pewang Payments — same routes on pewang.company and payment.pewang.company when DNS points to this project.