PayWay
Login
Implementation guide

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.

Repo copy. Library-first: 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

IsIsn’t
One charge API across many SSLCommerz / bKash slotsA wallet that holds customer payment money
Encrypted multi-slot secrets + priority failoverA replacement for your gateway merchant accounts
Prepaid SaaS fee credit in the portal walletSomething that needs customer email/phone on charges

Call PayWay only from your server. Never put pk_… keys in browser bundles.

Prerequisites

  1. PayWay API running (local: http://localhost:5080).
  2. Portal access (dev: demo@payway.local / ChangeMe123!).
  3. Shop currency BDT for go-live gateways.
  4. Absolute HTTPS success / fail / cancel URLs in production.
  5. At least one enabled credential slot for the path you choose.

Portal setup

  1. Sign in → Credentials → add SSLCommerz (gatewayType 2) and/or bKash (1) slots with encrypted secrets.
  2. Set priority (lower runs first when preferred gateway is auto).
  3. API keys → create → store pk_… once.
  4. 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

SSLCommerzPortal fieldAPI secrets JSON
Store ID (store_id)Store IDStoreId
Store Password (store_passwd)Store passwordStorePassword
Sandbox vs live hostSandbox toggleIsSandbox

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 emailPortal fieldAPI secrets JSON
app_keyApp keyAppKey
app_secretApp secretAppSecret
usernameUsernameUsername
passwordPasswordPassword

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.

WhereKeyNotes
Create responsepaymentIDDeserialize case-insensitively
Callback query stringpaymentIDWebhook also accepts paymentId
Execute / Query / Refund bodypaymentIdOfficial sample: { "paymentId": "TR…" } — not paymentID
Hosted PayWay already sends 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.

MethodHandled by
SSLCommerzYour existing code
bKashPayWay 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

  1. POST /v1/charges with Bearer API key (camelCase body).
  2. Persist sessionId on your order.
  3. Redirect the customer to checkoutUrl.
  4. On return, call GET /v1/charges/{sessionId}.
  5. 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
}
Casing. Create charge request/response use camelCase (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

StackInstallNotes
Nodenpm install @sianik/paywayNode 14+ — createCharge → redirect → getCharge
PHPcomposer require sianik/paywayPHP 7.4+; older: packages/php/samples/php56-curl.php.sample
WooCommerceUpload payway-woocommerce-*.zipSettings: base URL + API key; plugin confirms on return

See SDKs & plugins and repo packages/README.md.

Live checklist

  • PublicBaseUrl is 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.