Collect payments
Your customer pays OroPocket directly over UPI, and we buy and credit their gold or silver. No float to pre-fund, no money passing through you.
Every other buy on the Raw API is funded from your own prepaid wallet, so you collect from your customer first on your own rails. Payment collection removes that step: you create a payment order, your customer pays us, and the buy completes itself. It needs the payments capability, which OroPocket switches on per account — it is not self-serve. Once it is on, a Payments section appears in your dashboard.
| Checkout type | What you get back |
|---|---|
| intent | A raw upi:// link plus ready-made links for Google Pay, PhonePe, Paytm and CRED. Open it from your own app or page. |
| hosted | A checkout_url at pay.oropocket.com showing your name and logo, the amount, and the UPI apps that work on the customer's device (a QR code on desktop). Send the customer there. |
The gateway fee
UPI costs 0.118% (0.10% + GST). OroPocket decides, per account, who bears it — you can see which on Payments → Checkout.
| fee.bearer | Your customer pays ₹500.00 |
|---|---|
| oropocket | ₹500.00 buys metal. We absorb the ₹0.59. |
| end_user | ₹0.59 is deducted and ₹499.41 buys metal. The hosted checkout shows this line before the customer pays; show it in your own UI too if you use intent. |
| partner | ₹500.00 buys metal and you bear the ₹0.59. Each month we invoice you for the fees (fee + GST, totalling the fees) and deduct that invoice from the commission we transfer — your own commission invoice is unchanged. If OroPocket allows it, you can pass the fee on to your customer from Payments → Checkout. |
The fee is fixed when the order is created and never changes afterwards. funded_amount_inr is what buys metal and what the tax invoice shows. Your markup and commission are calculated on it exactly as on any other buy.
1. Create a payment order
/partner/payments/orderspayments| Field | Type | Description | |
|---|---|---|---|
| user_code | string | required | The customer, registered earlier with POST /partner/users/init. In sandbox, a usr_test_… code. |
| amount_inr | number | required | What the customer pays, up to 2 decimals. Your per-transaction limits apply, and a platform ceiling of ₹50,000. |
| checkout_type | string | required | "intent" or "hosted". |
| asset_type | string | optional | "gold" (default) or "silver". |
| reference | string | optional | Your own id for this payment, up to 120 characters. Echoed back everywhere, and unique per mode — a second order with the same reference is refused with 409 DUPLICATE_REFERENCE. |
| return_url | string | optional | Hosted only: where the checkout sends the customer when it finishes, with ?payment_id=…&status=… added. Must be https on an origin you registered under Payments → Checkout. |
Send an Idempotency-Key header so a retried request returns the same order instead of creating a second one. Keys are scoped per mode.
curl -X POST https://api.oropocket.com/partner/payments/orders \
-H "Authorization: Bearer oro_live_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-10482" \
-d '{
"user_code": "usr_0490fb2ff6f2007a55d39628c3807f85",
"amount_inr": 500,
"asset_type": "gold",
"checkout_type": "hosted",
"reference": "order-10482",
"return_url": "https://shop.example.com/orders/10482"
}'Response, 201:
{
"success": true,
"request_id": "req_…",
"mode": "live",
"idempotent_replay": false,
"data": {
"payment_id": "ppo_5f1c…",
"status": "created",
"checkout_type": "hosted",
"asset_type": "gold",
"user_code": "usr_0490fb2ff6f2007a55d39628c3807f85",
"reference": "order-10482",
"amount_inr": 500,
"fee": { "bearer": "end_user", "amount_inr": 0.59, "deducted_from_amount": true },
"funded_amount_inr": 499.41,
"estimate": { "asset_quantity": 0.046154, "rate_per_gram_inr": 10506, "final": false },
"checkout_url": "https://pay.oropocket.com/ppo_5f1c…",
"expires_at": "2026-09-26T09:40:00.000Z",
"created_at": "2026-09-26T09:20:00.000Z"
}
}estimate is indicative ("final": false): the order is priced when the money arrives, at that moment's rate. The order stays payable until expires_at (20 minutes).
payment_id is also the key to the hosted checkout. Anyone with the link can open that page (it never shows the customer's details). Send it only to the customer who is paying.2a. Intent: open the UPI app yourself
For "checkout_type": "intent", the response carries a upi block:
"upi": {
"url": "upi://pay?pa=…&pn=…&am=500.00&cu=INR&tr=…",
"apps": {
"gpay": { "android": "intent://pay?…#Intent;scheme=upi;package=com.google.android.apps.nbu.paisa.user;end",
"android_package": "com.google.android.apps.nbu.paisa.user",
"ios": "gpay://upi/pay?…" },
"phonepe": "phonepe://pay?…",
"paytm": "paytmmp://pay?…",
"cred": { "android": "intent://pay?…#Intent;scheme=upi;package=com.dreamplug.androidapp;end",
"android_package": "com.dreamplug.androidapp",
"ios": "credpay://upi/pay?…" }
}
}| App | How to open it |
|---|---|
| Google Pay · Android | Native app: the raw upi.url with setPackage(android_package). From a web page or WebView: navigate the SAME frame to apps.gpay.android (Chrome's intent:// URL). Do not add a browser fallback — it navigates away from your page. |
| Google Pay · iOS | apps.gpay.ios (gpay://upi/pay?…). Not tez:// — Google Pay for iOS does not handle that scheme. |
| PhonePe · Paytm | Open apps.phonepe / apps.paytm as they are, on either platform. |
| CRED | Android: apps.cred.android. iOS: apps.cred.ios (credpay://upi/pay). One-off payments only — CRED is not offered for SIP mandates. |
| Any UPI app | upi.url on Android shows the system chooser. On desktop, render upi.url as a QR code. |
// Android (Kotlin): open Google Pay directly, no chooser.
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(upi.url)) // the raw upi:// link
intent.setPackage("com.google.android.apps.nbu.paisa.user") // upi.apps.gpay.android_package
startActivity(intent)
// Omit setPackage to show the system's UPI app chooser.2b. Hosted: send the customer to the checkout
Redirect or link to checkout_url. The page shows your company name (always), your logo and accent if you set them, the amount and the fee line, and only the UPI apps that work on that device. When the order finishes it shows the outcome and, if you passed a return_url, sends the customer back to it. Never rely on that redirect alone to confirm payment — use the webhook or the status endpoint.
3. Know when it is paid
Payment orders fire webhooks to the endpoint on your Webhooks page, signed the same way as every other event.
| Event | When |
|---|---|
| payment.completed | The customer paid and the metal is credited. fulfilment carries the final grams and rate. This is the one to fulfil your order on. |
| payment.failed | The order closed without a purchase. If refund.status is "pending", the customer's money arrived but the purchase could not be completed — OroPocket retries or refunds it. |
| payment.expired | No payment arrived before expires_at. |
| payment.refunded | A refund_due order was refunded to the customer. refund.reference is the UTR or gateway refund id. |
{
"event": "payment.completed",
"event_id": "evt_…",
"created_at": "2026-09-26T09:22:41.000Z",
"data": {
"user_code": "usr_0490fb2ff6f2007a55d39628c3807f85",
"payment_id": "ppo_5f1c…",
"status": "completed",
"reference": "order-10482",
"amount_inr": 500,
"fee": { "bearer": "end_user", "amount_inr": 0.59, "deducted_from_amount": true },
"funded_amount_inr": 499.41,
"fulfilment": { "asset_quantity": 0.046131, "rate_per_gram_inr": 10510.2, "final": true },
"completed_at": "2026-09-26T09:22:40.000Z"
}
}A completed payment order is also a buy, so the ordinary buy.completed event fires for it as well. Match on payment.completed for payment orders so you don't count them twice.
/partner/payments/orders/:payment_idpaymentsThe same object as the create response, plus fulfilment once completed. Polling it also checks the gateway, so it is the reliable fallback if a webhook is missed.
/partner/payments/orderspaymentsNewest first. Query: status (comma-separated), reference, page, limit (max 100).
Statuses
| status | Meaning |
|---|---|
| created | Waiting for the customer to pay. |
| paid | Money arrived; the purchase is being completed (seconds). |
| completed | Paid and credited. Final. |
| failed | Closed without a purchase — the gateway closed it, or it could not be created. |
| expired | Nobody paid in time. If money arrives late anyway, the order still completes. |
| refund_due | Paid, but the purchase could not be completed. Being retried or refunded — it can still move to completed. |
| refunded | The customer's money went back to them. Final. |
SIPs: recurring buys with UPI AutoPay
A SIP buys the same amount of gold or silver every day, week or month. Your customer approves a UPI AutoPay mandate once. The first installment is taken with that approval, and every later one is debited automatically.
/partner/payments/sipspayments| Field | Type | Description | |
|---|---|---|---|
| user_code | string | required | The customer. |
| installment_amount_inr | number | required | What each debit takes. Minimum ₹100. |
| frequency | string | required | "daily", "weekly" or "monthly". |
| asset_type | string | optional | "gold" (default) or "silver". |
| name | string | optional | What the customer calls it, up to 60 characters. Default "Gold SIP". |
| reference | string | optional | Your id, unique per mode. |
| return_url | string | optional | Where the hosted page sends the customer once AutoPay is set up. Registered origins only. |
curl -X POST https://api.oropocket.com/partner/payments/sips \
-H "Authorization: Bearer oro_live_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sip-10482" \
-d '{
"user_code": "usr_0490fb2ff6f2007a55d39628c3807f85",
"installment_amount_inr": 500,
"frequency": "monthly",
"asset_type": "gold",
"name": "Wedding gold",
"reference": "sip-10482"
}'{
"id": "ppo_8e21…",
"sip_id": 1840,
"status": "pending_authorisation",
"installment_amount_inr": 500,
"frequency": "monthly",
"fee": { "bearer": "end_user", "per_installment_inr": 5.9, "deducted_from_installment": true },
"funded_installment_inr": 494.1,
"checkout_url": "https://pay.oropocket.com/ppo_8e21…",
"mandate": { "upi": { "url": "upi://mandate?…", "apps": { "gpay": { "android": "intent://mandate?…", "ios": "gpay://upi/mandate?…" }, "phonepe": "phonepe://mandate?…", "paytm": "paytmmp://mandate?…" } } },
"authorise_by": "2026-09-26T10:20:00.000Z"
}Send the customer to checkout_url, or open mandate.upi yourself with the same per-app rules as a payment. The verb is mandate, so use Google Pay's gpay://upi/mandate on iOS, and never turn a mandate link into a pay link. The link must be approved before authorise_by (one hour).
sip_id is the id every sip.* webhook carries: sip.created, then sip.activated once the bank approves, one sip.installment per debit (with fee_inr and funded_amount_inr), or sip.mandate_failed if it is never approved.
/partner/payments/sips/:idpaymentsAccepts either id or sip_id. It checks the provider live and adds progress: installments paid, total invested, grams and the next date. Statuses are pending_authorisation, active, paused, cancelled, completed and failed (not approved in time).
/partner/payments/sipspayments/partner/payments/sips/:id/cancelpaymentsStops the mandate so no further amounts are debited. Gold already bought stays with the customer.
Sandbox
With an oro_test_ key, orders never touch a gateway: intent returns a sandbox UPI link that pays nobody, and the hosted page shows "Simulate" buttons instead of apps. Finish an order from the API with:
/partner/payments/orders/:payment_id/simulatepayments/partner/payments/sips/:id/simulatepaymentsFor a SIP, the outcome is "activate", "installment" or "failure".
Body { "outcome": "success" } or { "outcome": "failure" }. The matching webhook fires with "mode": "sandbox" in its data.
Errors
| Code | Meaning |
|---|---|
| 403 PAYMENT_INFRA_NOT_ENABLED | Payment collection is not on for your account. |
| 503 PAYMENT_INFRA_DISABLED | Collection is paused platform-wide. Retry later. |
| 503 PAYMENT_GATEWAY_UNAVAILABLE | No gateway is available right now. Retry later. |
| 503 UPI_INTENT_UNAVAILABLE | Intent checkout is temporarily unavailable. Create the order with checkout_type "hosted" instead — it always works. Nothing was charged. |
| 400 INVALID_CHECKOUT_TYPE · INVALID_AMOUNT · INVALID_ASSET_TYPE | Fix the field named in the message. |
| 400 AMOUNT_BELOW_MIN · AMOUNT_ABOVE_MAX | Outside your per-transaction limits or the ₹50,000 ceiling. |
| 400 RETURN_URL_NOT_ALLOWED | Register the origin under Payments → Checkout first. |
| 404 USER_CODE_NOT_FOUND | Register the customer with POST /partner/users/init. |
| 409 SIP_LIMIT_REACHED | The customer already has the most SIPs allowed. They can stop one first. |
| 409 DUPLICATE_REFERENCE | An order with this reference exists; details.payment_id names it. |
| 502 GATEWAY_ERROR | The gateway could not create the order. Nothing was charged; retry with a new Idempotency-Key. |