Home / Docs / UddoktaPay Stable
UddoktaPay
Status: stable · Driver: uddoktapay · Refunds: no (see below) · Sync: yes, once the customer has submitted a payment
[!WARNING] UddoktaPay is not a licensed payment gateway. It is self-hosted software that you install on your own server. Customers pay straight into mobile-wallet or bank accounts that you own, often personal bKash, Nagad, Rocket or Upay accounts, and the software then detects those payments. There is no acquirer, no payment service provider and no settlement in between.
Collecting business payments through personal wallet accounts may breach your wallet provider's terms and Bangladesh Bank rules. You alone are responsible for your accounts, KYC, tax and regulatory compliance, disputes and refunds. This package only talks to the API your installation exposes and makes no claim that using it is permitted. If you need a licensed gateway, use SSLCommerz, bKash, Nagad, PayStation or another regulated provider.
Setup
UDDOKTAPAY_BASE_URL=https://pay.yourdomain.com
UDDOKTAPAY_API_KEY=your-api-key
UDDOKTAPAY_SANDBOX=false
// config/bdpaygate.php → gateways
'uddoktapay' => [
'driver' => 'uddoktapay',
'base_url' => env('UDDOKTAPAY_BASE_URL'),
'api_key' => env('UDDOKTAPAY_API_KEY'),
'sandbox' => env('UDDOKTAPAY_SANDBOX', false),
],
base_urlis required for live use. Use your UddoktaPay installation's own domain. You can paste the panel URL or a full API URL: anything from/apionwards is stripped, sohttps://pay.yourdomain.com/api/checkout-v2also works.api_keycomes from your UddoktaPay dashboard. It is sent in theRT-UDDOKTAPAY-API-KEYheader.- With
sandboxon and nobase_url, the driver uses UddoktaPay's public sandbox,https://sandbox.uddoktapay.com. Its shared test API key is published on UddoktaPay's API information page. Withsandboxoff and nobase_url,charge()throws. - Only BDT is supported, through the
checkout-v2API. UddoktaPay'scheckout-v2/globalvariant for other currencies is not wired in.
Docs: https://uddoktapay.readme.io/reference/overview
How a payment is verified
UddoktaPay creates its invoice_id only after the customer submits a payment on the checkout page. Before that, the payment has no id that UddoktaPay can be asked about.
-
charge()callsPOST {base_url}/api/checkout-v2with the customer's name and email, the amount as a string, andmetadataholding this attempt'sgateway_reference, your own reference and the package's payment id. It also sends:redirect_url:/bdpaygate/uddoktapay/success, withreturn_type: GETcancel_url:/bdpaygate/uddoktapay/cancel?ref=…webhook_url:/bdpaygate/uddoktapay/ipn
The customer is sent to the returned
payment_url. -
On success, UddoktaPay returns the customer with an
invoice_id. It arrives as a query parameter, or as a POST field if your installation posts it; both are accepted. -
The package calls
POST {base_url}/api/verify-paymentwith thatinvoice_id. It finds the payment from themetadatain the verified response, never from the callback.COMPLETED: completes the payment once the amount matches.invoice_idis stored asgateway_payment_idandtransaction_idasgateway_transaction_id.PENDING: the customer submitted a payment that has not been matched yet, for example while you approve it manually. The payment stays pending and theinvoice_idis kept, soBdPayGate::sync()andbdpaygate:synccan settle it later.ERROR(for example "No Data Found"): the callback is rejected and the payment is unchanged.
-
Webhook (
/ipn): UddoktaPay's only webhook check is that theRT-UDDOKTAPAY-API-KEYheader equals your API key. There is no signature, timestamp or replay protection.- A missing or wrong key is rejected with HTTP 400 before any API call.
- A correct key is still not trusted: the posted
invoice_idgoes throughverify-paymentexactly like a redirect, and the webhook body is ignored.
-
Cancel: UddoktaPay's cancel link is your
cancel_urlas given, with noinvoice_id, and verify-payment cannot report a failed or cancelled state. So a cancel cannot be confirmed:- If the payment already has an
invoice_id, the package verifies it and completes the payment when UddoktaPay says it is paid. - Otherwise the payment stays pending and the customer is sent to your
pendingpage. Show a "try again" option there.charge()with the same reference creates a fresh attempt.
- If the payment already has an
Refunds
Not supported. UddoktaPay documents POST /api/refund-payment, but both its success and error responses are documented as an empty object {}. There is no way to tell whether a refund was accepted. Money also sits in your own wallet accounts, so refund it from there.
BdPayGate::refund() throws UnsupportedOperationException.
Limits and quirks
-
amountis sent as a string with two decimals. -
emailis mandatory. If the customer has none, the driver sendsnoemail@example.com. -
The amount check uses
amountfrom verify-payment, the invoice amount.charged_amountcan differ when your installation adds a fee. -
sync()on a payment with noinvoice_idreturns it unchanged without calling UddoktaPay. -
UddoktaPay's documentation says
redirect_urlis used to build the success, failure and cancel URLs, but does not show the exact form. This driver keepsredirect_urlfree of query parameters so an appended?invoice_id=cannot break it. -
The webhook is documented as an "IPN callback for manual data submission from the admin dashboard". Do not rely on it arriving for every payment. Schedule
bdpaygate:sync:Schedule::command('bdpaygate:sync')->everyTenMinutes();
Sandbox
The public sandbox (https://sandbox.uddoktapay.com, shared API key on the API information page) accepts checkout-v2 and verify-payment calls. Its checkout page asks the customer to type in a wallet transaction id.
Because the sandbox is shared, other people's test invoices live there too. Use it only to try the flow, never for anything real.
Local testing note
In a sandbox run (2026-10-15) the UddoktaPay sandbox dropped the port from the return URL (http://127.0.0.1:8123/... came back as http://127.0.0.1/...). Use a URL without a custom port (a tunnel, or port 80) when testing locally. Production URLs are unaffected.