digetpay_plugin 0.4.1 copy "digetpay_plugin: ^0.4.1" to clipboard
digetpay_plugin: ^0.4.1 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]

pub version pub points pub likes platforms license: MIT

Accept card payments in your Flutter app in minutes. DigetPay defaults to a hosted, PCI-safe WebView checkout — raw card data never passes through your app, so you keep your PCI scope small — plus a full REST API for money movement, recurring charges, and transaction history. 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 #

dependencies:
  digetpay_plugin: ^0.4.1
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, // sandbox: https://fin-api.digetpay.com/v1
  // production: https://api.digetpay.com/v1
);

Every call before initialize() throws DigetPaySdkIsNotInitializedException.

initialize() has no re-entrancy guard — call it again (e.g. after a user picks a different environment or updates their API key) to reconfigure the SDK at any time.

That's it — you're ready to open the checkout.

What you can do #

✅ Available now Purpose
cardPay() Hosted, PCI-safe card checkout (WebView)
sale(request) Headless S2S card sale/authorization — ⚠️ widens PCI scope, see below
getCheckoutStatus(sessionId) Confirm a checkout's outcome
getCheckoutSessions([filter]) List your checkout sessions (paginated)
getTransactionById(id) Full transaction details by gateway transaction id
getTransactionHistory([filter]) List raw transaction records (paginated)
capture · refund · voidd Move money on an existing transaction
recurring(request) Charge a recurringToken from a prior cardPay() transaction
getRecurringSubscriptions([filter]) List saved-card billing plans (paginated)
chargeSubscription(...) Charge an existing subscription — no card data needed
🚧 Not yet available Behaviour
applePay · externalPayment Throws UnsupportedError — no backend endpoint yet
getTransactionByOrderId · getTransactionByRrn Throws UnsupportedError — no endpoint yet
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()
  • intiate is 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 /success briefly before settling on /failure.

Direct (S2S) card sale #

⚠️ PCI scope. cardPay() is the recommended default — raw card data never passes through your app. sale() is an explicit opt-in for a headless integration: your app collects the PAN/CVV directly, which widens your PCI-DSS scope. Only use it if you've independently assessed that tradeoff.

final response = await DigetPaySdk.sale(
  SaleRequest(
    orderId: orderId,
    amount: 15,
    currency: 'SAR',
    auth: false, // true → authorization-only hold instead of a sale
    customer: Customer(name: 'Demo User', email: 'demo@example.com'),
    successUrl: 'https://example.com/success',
    failureUrl: 'https://example.com/failure',
    card: CardDto(
      cardNumber: '4111111111111111',
      cardHolder: 'Demo User',
      cardExpiryMonth: '12',
      cardExpiryYear: '2030',
      cardCvv: '123',
    ),
  ),
);

sale() POSTs to /payment/s2s/sale.

🚧 Hash status (temporary). The collection's request body carries a hash field. We've ported the algorithm it implies (MD5 of reverse(email) + apiKey + reverse(first6+last4 of the card number), uppercased) to computeSaleHash, but automatic injection is currently disabled pending confirmation of the exact algorithm/secret against this backend — sale() sends the request as-is. Set SaleRequest.hash yourself if your integration needs one in the meantime; this will switch back to auto-computed once confirmed (see the TODO(hash-decision) markers in lib/src/digetpay_sdk.dart and lib/src/models/requests/sale_request.dart).

Set auth: true to place an authorization-only hold instead of an immediate sale — the response's data['action'] reads "SALE" or "AUTH" accordingly.

Transaction history & lookup by id #

getTransactionById(id) fetches full details for a single transaction by its gateway transaction id (GET /payment/transactions/digetpay/{id}/details), including its businessUnitHierarchy. getTransactionHistory([filter]) lists raw transaction records the same way, paginated:

final txn = await DigetPaySdk.getTransactionById(gatewayTransactionId);

final history = await DigetPaySdk.getTransactionHistory(
  const TransactionFilter(pageNumber: 0, pageSize: 30, status: 'SUCCESS'),
);

Which method do I use? getCheckoutSessions() lists checkout sessions (PageDto<CheckoutSession>); getTransactionHistory() lists raw transactions (PageDto<Transaction> — purchases, recurring charges, and refunds all appear here). They are not interchangeable — pick based on which shape you need.

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 CheckoutSession carries two ids. For capture / refund / voidd, always use session.gatewayTransactionIdnot session.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)

Recurring charges & subscriptions #

recurring() charges a recurringToken obtained from a prior hosted checkout transaction (see Transaction.recurringToken) — no card data involved:

final response = await DigetPaySdk.recurring(
  RecurringRequest(
    transactionId: initialTransactionId,
    orderId: 'ORD-2025-887899',
    recurringToken: recurringToken,
    amount: 100,
    currency: 'SAR',
  ),
);

For saved-card billing plans (subscriptions), list them and charge one on demand — again, no card data needed, the subscription's saved token is charged server-side:

final page = await DigetPaySdk.getRecurringSubscriptions(
  const RecurringSubscriptionFilter(page: 1, limit: 20),
);

for (final sub in page?.content ?? const <RecurringSubscription>[]) {
  if (sub.isCompleted) continue;
  final result = await DigetPaySdk.chargeSubscription(
    subscriptionId: sub.id!,
    amount: 50,
    email: 'customer@example.com',
  );
  print(result?.isSuccess); // SubscriptionChargeResult — a flat, non-enveloped body
}

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.
  • recurringTokensensitive: 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 #

  • minSdkVersion 24 (Android 7.0) in android/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/Podfile and 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 NSAllowsArbitraryLoads in 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.

For card entry, prefer cardPay() — raw card data never touches your app process, so your PCI scope stays small. sale() is an explicit, opt-in exception to that: it carries raw PAN/CVV through your app, which widens your PCI-DSS scope. Only reach for it if you've independently assessed that tradeoff for your integration.

Not yet available #

applePay, externalPayment, getTransactionByOrderId, and getTransactionByRrn throw UnsupportedError until the corresponding backend endpoints exist (see the TODO(endpoint) markers in the source). getTransactionByOrderId — list sessions with getCheckoutSessions() and match on CheckoutSession.merchantOrderId, or filter getTransactionHistory() instead.


MIT © DigetPay · Issues & source: https://github.com/DigetPay/digetpay_plugin

1
likes
0
points
287
downloads

Publisher

unverified uploader

Weekly Downloads

Pure-Dart DigetPay payment plugin: hosted (PCI-safe) checkout via WebView plus transaction management over the DigetPay REST API.

Homepage
Repository (GitHub)
View/report issues

Topics

#payments #payment-gateway #checkout #webview

License

unknown (license)

Dependencies

crypto, flutter, http, uuid, webview_flutter

More

Packages that depend on digetpay_plugin