Integrate PayWay
Default product is library-first — run the SDK on your server with your credentials and IPN (docs/16-library-first-developer-guide.md, samples under packages/samples/library-first/). This page covers the optional hosted Track B path: ASP.NET Web Forms / Node / PHP / Woo calling POST /v1/charges, then redirect to checkoutUrl.
docs/16-library-first-developer-guide.md (overview: docs/15). Hosted guide: docs/14-implementation-guide.md. API Try it: /docs/charges.What PayWay is / isn’t
| Is | Isn’t |
|---|---|
| One charge API across many SSLCommerz / bKash slots | A wallet that holds customer payment money |
| Encrypted multi-slot secrets + priority failover | A replacement for your gateway merchant accounts |
| Prepaid SaaS fee credit in the portal wallet | Something that needs customer email/phone on charges |
Call PayWay only from your server. Never put pk_… keys in browser bundles.
Prerequisites
- PayWay API running (local:
http://localhost:5080). - Portal access (dev:
demo@payway.local/ChangeMe123!). - Shop currency BDT for go-live gateways.
- Absolute HTTPS success / fail / cancel URLs in production.
- At least one enabled credential slot for the path you choose.
Portal setup
- Sign in → Credentials → add SSLCommerz (
gatewayType2) and/or bKash (1) slots with encrypted secrets. - Set priority (lower runs first when preferred gateway is auto).
- API keys → create → store
pk_…once. - Check wallet / free-tier headroom for production volume.
Your app only needs base URL + API key. Gateway secrets stay in PayWay.
SSLCommerz — which fields go where
| SSLCommerz | Portal field | API secrets JSON |
|---|---|---|
Store ID (store_id) | Store ID | StoreId |
Store Password (store_passwd) | Store password | StorePassword |
| Sandbox vs live host | Sandbox toggle | IsSandbox |
Full doc: docs/20-sslcommerz-credentials-map.md. IPN is server-to-server (needs a public URL); customer card/OTP stay on SSL's page.
bKash — which fields go where
| bKash email | Portal field | API secrets JSON |
|---|---|---|
| app_key | App key | AppKey |
| app_secret | App secret | AppSecret |
| username | Username | Username |
| password | Password | Password |
Full doc: docs/19-bkash-credentials-map.md. Test wallet OTP/PIN are for the hosted pay page only — not credential fields.
bKash — paymentID vs paymentId
bKash uses different casing on different surfaces. Getting this wrong makes Execute/Query return HTTP 400 — the wallet pay can succeed while PayWay marks the charge failed.
| Where | Key | Notes |
|---|---|---|
| Create response | paymentID | Deserialize case-insensitively |
| Callback query string | paymentID | Webhook also accepts paymentId |
| Execute / Query / Refund body | paymentId | Official sample: { "paymentId": "TR…" } — not paymentID |
paymentId on Execute/Query/Refund. If you call bKash yourself (library-first / custom HTTP), copy the sample body keys exactly. Repo: docs/17-bkash-sandbox-integration.md.Path A — Keep manual SSL; add bKash via PayWay
Best when SSLCommerz already works in Web Forms and you only need bKash quickly.
| Method | Handled by |
|---|---|
| SSLCommerz | Your existing code |
| bKash | PayWay with preferredGateway: "bkash" |
Portal: add a bKash credential slot. SSL slot optional until Path B.
Path B — Align both through PayWay
One payment path. Portal holds SSL + bKash slots. Use preferredGateway: "auto" (or pin a provider / credentialId). Failover retries other slots on timeout / network / HTTP 5xx.
Replace SSL session-create with POST /v1/charges, then remove the old SSL client when stable.
Core flow
POST /v1/chargeswith Bearer API key (camelCase body).- Persist
sessionIdon your order. - Redirect the customer to
checkoutUrl. - On return, call GET /v1/charges/{sessionId}.
- Fulfill only when
status === "success"— never trust the return URL alone.
Create charge body (camelCase)
{
"amount": 500.00,
"currency": "BDT",
"orderId": "ORD-1001",
"successUrl": "https://shop.com/ok",
"failUrl": "https://shop.com/fail",
"cancelUrl": "https://shop.com/cancel",
"preferredGateway": "auto",
"allowFailover": true
}orderId, checkoutUrl). GET status returns snake_case (session_id, gateway_transaction_id).Web Forms deep dive (net48)
Install NuGet Sianik.PayWay (TFMs: net462+ through net8). Local pack: dotnet pack packages/dotnet/PayWay.Client -c Release -o artifacts/nuget. Older: packages/dotnet/samples/Net45 (Newtonsoft) · Net35 (HttpWebRequest). See repo packages/SUPPORT.md.
AppSettings
<appSettings>
<add key="PayWayBaseUrl" value="http://localhost:5080" />
<add key="PayWayApiKey" value="pk_live_…" />
</appSettings>Create charge and redirect
var client = new PayWay.PayWayClient(new PayWay.PayWayClientOptions {
BaseUrl = ConfigurationManager.AppSettings["PayWayBaseUrl"],
ApiKey = ConfigurationManager.AppSettings["PayWayApiKey"]
});
var baseSite = Request.Url.GetLeftPart(UriPartial.Authority);
var charge = client.CreateChargeAsync(new PayWay.CreateChargeRequest {
Amount = orderTotal,
OrderId = yourOrderId,
SuccessUrl = baseSite + ResolveUrl("~/PaymentSuccess.aspx"),
FailUrl = baseSite + ResolveUrl("~/PaymentFail.aspx"),
CancelUrl = baseSite + ResolveUrl("~/PaymentCancel.aspx"),
PreferredGateway = "bkash", // Path A; "auto" for Path B
}).GetAwaiter().GetResult();
Session["PayWaySessionId"] = charge.SessionId;
Response.Redirect(charge.CheckoutUrl);Return page — confirm before fulfill
var status = client.GetChargeAsync(sessionId).GetAwaiter().GetResult();
if (string.Equals(status.Status, "success", StringComparison.OrdinalIgnoreCase)) {
// Mark order paid
}Sample file: packages/dotnet/samples/WebForms/PayNow.aspx.cs.sample. Path A: separate SSL vs bKash buttons. Path B: one Pay button with auto.
Other stacks
| Stack | Install | Notes |
|---|---|---|
| Node | npm install @sianik/payway | Node 14+ — createCharge → redirect → getCharge |
| PHP | composer require sianik/payway | PHP 7.4+; older: packages/php/samples/php56-curl.php.sample |
| WooCommerce | Upload payway-woocommerce-*.zip | Settings: base URL + API key; plugin confirms on return |
See SDKs & plugins and repo packages/README.md.
Live checklist
PublicBaseUrlis public HTTPS so gateways can hit/v1/webhooks/….- Credential slots use live (not sandbox) when going live.
- SSLCommerz live: register PayWay’s outbound public IP on the store — not your shop’s IP.
- Absolute HTTPS success/fail/cancel URLs on your domain.
- API key only in server secrets / AppSettings.
- Always
GET /v1/charges/{sessionId}before marking paid. - Wallet / free-tier capacity checked for production volume.
Errors
Full table on Overview → Errors. Common: NO_ROUTE (no matching slot), WALLET_DEPLETED, VALIDATION_ERROR, GATEWAY_UNAVAILABLE.
Do / don’t
Do
- Call PayWay from the server only.
- Persist sessionId before redirect.
- Confirm status with PayWay before fulfillment.
- Send Idempotency-Key on create for your own safe retries (server dedupe not enforced yet).
Don’t
- Trust success URL query params alone.
- Embed API keys in JS / mobile apps.
- Send customer PII (not required).
- Assume your shop domain owns the SSL session.