digetpay_plugin 0.3.0
digetpay_plugin: ^0.3.0 copied to clipboard
Pure-Dart DigetPay payment plugin: hosted (PCI-safe) checkout via WebView plus transaction management over the DigetPay REST API.
DigetPay for Flutter #
[DigetPay — pure-Dart hosted checkout for Flutter]
Accept card payments in your Flutter app in minutes. DigetPay uses a
hosted, PCI-safe WebView checkout — raw card data never passes through your
app, so you keep your PCI scope small. Pure Dart, built on http and
webview_flutter; no native code to write or register.
await DigetPaySdk.cardPay()
.setOrder(DigetPaySaleOrder(id: orderId, description: 'Coffee', currency: 'SAR', amount: 15))
.setPayer(payer)
.onTransactionSuccess((res) => print('Paid ✓ ${res.transactionId}'))
.onTransactionFailure((res) => print('Failed: ${res.declineReason}'))
.onDismiss(() => print('User cancelled'))
.start(context);
Contents #
- Install
- Quick start
- What you can do
- Hosted checkout
- Listing checkout sessions
- Money movement
- Handling transaction data
- Debug logging
- Platform setup
- Security — mobile API key
- Not yet available
Install #
dependencies:
digetpay_plugin: ^0.3.0
flutter pub add digetpay_plugin
Quick start #
Initialize once at startup, then call the static API anywhere:
import 'package:digetpay_plugin/digetpay_plugin.dart';
DigetPaySdk.initialize(
apiKey: '<your-mobile-api-key>',
baseUrl: kDigetPayDefaultBaseUrl, // https://fin-api.digetpay.com/v1
);
Every call before
initialize()throwsDigetPaySdkIsNotInitializedException.
That's it — you're ready to open the checkout.
What you can do #
| ✅ Available now | Purpose |
|---|---|
cardPay() |
Hosted, PCI-safe card checkout (WebView) |
getCheckoutStatus(sessionId) |
Confirm a checkout's outcome |
getCheckoutSessions([filter]) |
List your checkout sessions (paginated) |
capture · refund · voidd |
Move money on an existing transaction |
| 🚧 Not yet available | Behaviour |
|---|---|
sale (raw-card S2S) |
Throws UnsupportedError — disabled to keep card data off-device |
applePay · externalPayment · recurring |
Throws UnsupportedError — no backend endpoint yet |
getTransactionById(id) |
Throws UnsupportedError — the lookup-by-id endpoint is no longer available; use getCheckoutStatus(sessionId) instead |
getTransactionByOrderId · getTransactionByRrn |
Throws UnsupportedError — no endpoint yet |
Hosted checkout (recommended card flow) #
await DigetPaySdk.cardPay()
.setOrder(DigetPaySaleOrder(
id: orderId, description: 'Coffee', currency: 'SAR', amount: 15))
.setPayer(DigetPayPayer(
firstName: 'Demo', lastName: 'User', address: 'Riyadh',
country: 'SA', city: 'Riyadh', zip: '00000',
email: 'demo@example.com', phone: '+966500000000'))
// .setResultUrls(...) is optional — omit to use the SDK defaults.
.onTransactionSuccess((res) { /* res.status, res.transactionId ... */ })
.onTransactionFailure((res) { /* res.declineReason, res.errorCode */ })
.onDismiss(() { /* user backed out */ })
.start(context);
start() calls POST /payment/checkout/intiate, opens the returned
redirectUrl in a WebView and — once the hosted page reaches a result page —
fetches GET /sdk/status?sessionId= and dispatches success or failure from the
transaction's paymentStatus. Exactly one callback fires.
Flutter app
│ DigetPaySdk.cardPay()…start(context)
▼
POST /payment/checkout/intiate ──▶ { id, redirectUrl }
│ push CheckoutPage → load redirectUrl
▼
WebView (hosted card form + 3-D Secure)
│
├─ reaches a result page ──▶ GET /sdk/status?sessionId=<id> (authoritative)
│ ├─ paymentStatus == APPROVED ─▶ onTransactionSuccess(res)
│ └─ otherwise ─▶ onTransactionFailure(res)
│
└─ user backs out (no result page) ──▶ onDismiss()
intiateis spelled that way on the backend — kept verbatim so the docs match the actual wire call (it is not a typo in your code).- The status response is authoritative — the SDK does not trust the result
URL alone, because the gateway can route to
/successbriefly before settling on/failure.
Listing checkout sessions #
getCheckoutSessions() returns a paginated PageDto<CheckoutSession> from
GET /payment/checkout/sessions:
final page = await DigetPaySdk.getCheckoutSessions(
const CheckoutSessionFilter(page: 1, limit: 20),
);
for (final session in page?.content ?? const <CheckoutSession>[]) {
print('${session.merchantOrderId} · ${session.amount} ${session.currency} · ${session.status}');
}
⚠️ Which id do I use?
A
CheckoutSessioncarries two ids. Forcapture/refund/voidd, always usesession.gatewayTransactionId— notsession.id(which identifies the checkout session, not the transaction).// ✅ correct await DigetPaySdk.refund(transactionId: session.gatewayTransactionId!, amount: session.amount!); // ❌ wrong — session.id is NOT a transaction id
PageDto exposes content, number (the backend's 1-based page), size,
totalElements, totalPages, first, and last.
Money movement #
Operate on an existing transaction by its gateway transaction id (no card data involved):
await DigetPaySdk.capture(transactionId: id, amount: 10); // POST /payment/s2s/capture
await DigetPaySdk.voidd(id); // POST /payment/s2s/void
await DigetPaySdk.refund(transactionId: id, amount: 10); // POST /payment/refund
These return a typed DigetPayResponse. HTTP errors and declines come back as an
unsuccessful response — they do not throw:
DigetPayResponse |
Meaning |
|---|---|
isSuccess |
2xx and not declined/failed |
status / result |
Gateway status text |
transactionId · orderId · rrn |
Identifiers |
message · errorCode · declineReason |
Failure detail |
data |
The raw decoded payload (for anything not typed above) |
Handling transaction data #
getCheckoutStatus returns a typed Transaction? (a DECLINED transaction is
still returned — inspect paymentStatus). Some fields are sensitive; the SDK
delivers them to you but never logs them:
pan— already masked by the backend (4323 2** **** 0853); safe to show.cardHolderName— PII; display it, but don't log/persist broadly.recurringToken— sensitive: it can initiate future charges. Keep it server-side, never log it, and don't surface it in the UI.
Debug logging #
Pass an optional logger to initialize for a redacted, step-by-step trace
— HTTP method + path + status, checkout milestones, and the terminal outcome. It
never receives the API key, card data, request/response bodies, or query values.
import 'dart:developer' as developer;
DigetPaySdk.initialize(
apiKey: '<key>',
logger: (message) => developer.log(message, name: 'DigetPay'),
);
💳 checkout: creating session for order 6f… (15.0 SAR)
➡️ → POST /payment/checkout/intiate
⬅️ ← 201 OK POST /payment/checkout/intiate {code=201, session=ab02…, redirect=https://fin-admin.digetpay.com/pay/checkout}
🌐 webview → https://fin-admin.digetpay.com/pay/checkout/success
➡️ → GET /sdk/status?sessionId
⬅️ ← 200 OK GET /sdk/status {code=200, status=APPROVED, txn=070ae…, msg=Success}
✅ checkout: APPROVED → onTransactionSuccess
Leave logger unset (the default) for zero logging in production.
Platform setup #
Pure-Dart plugin — no native code to write. You only need standard network/WebView configuration.
Android #
-
minSdkVersion24 (Android 7.0) inandroid/app/build.gradle. -
INTERNET permission in
android/app/src/main/AndroidManifest.xml(the release manifest does not include it automatically):<uses-permission android:name="android.permission.INTERNET"/>
iOS #
- Deployment target iOS 13.0+ in
ios/Podfileand the Runner target. - DigetPay endpoints and the hosted page are served over HTTPS, so the
default App Transport Security policy works with no changes. Avoid a blanket
NSAllowsArbitraryLoadsin production.
Hosted checkout targets Android and iOS (via webview_flutter); Flutter web
and desktop are not supported in this version.
Security — mobile API key #
DigetPayConfig.apiKey is embedded in your app binary, and anything shipped in
a mobile app can be extracted. Always initialize with a publishable /
restricted mobile key scoped to client operations — never a full server
secret. Rotate the key if it is ever exposed, and keep privileged operations
behind your own server. This is why the plugin implements only the hosted
(PCI-safe) card flow: raw card data never touches the device.
Not yet available #
sale, applePay, externalPayment, recurring, getTransactionById,
getTransactionByOrderId, and getTransactionByRrn throw UnsupportedError
until the corresponding backend endpoints exist (see the TODO(endpoint)
markers in the source). sale is intentionally disabled to keep raw card data
off-device — use cardPay()
instead. getTransactionById previously worked via
GET /payment/checkout/status?id=, which the backend has since retired; use
getCheckoutStatus(sessionId) or inspect
getCheckoutSessions() results instead.
MIT © DigetPay · Issues & source: https://github.com/DigetPay/digetpay_plugin