digetpay_plugin 0.2.0
digetpay_plugin: ^0.2.0 copied to clipboard
Pure-Dart DigetPay payment plugin: hosted (PCI-safe) checkout via WebView plus transaction management over the DigetPay REST API.
example/lib/main.dart
import 'dart:developer' as developer;
import 'package:digetpay_plugin/digetpay_plugin.dart';
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
/// A pretty, greppable logger for the DigetPay flow.
///
/// The SDK hands us plain messages; here we tag each with an emoji so the whole
/// request/checkout flow is easy to scan in the console during development.
void digetPayLog(String message) {
final m = message.toLowerCase();
final icon =
(m.contains('fail') ||
m.contains('error') ||
m.contains('✗') ||
m.contains('✖'))
? '❌'
: (m.contains('success') || m.contains(' ok ') || m.contains('✓'))
? '✅'
: (m.contains('dismiss') || m.contains('cancel'))
? '↩️'
: m.startsWith('webview')
? '🌐'
: m.startsWith('→')
? '➡️'
: m.startsWith('←')
? '⬅️'
: '💳';
developer.log('$icon $message', name: 'DigetPay');
}
/// Your DigetPay **publishable / restricted mobile** API key.
///
/// Use a client-scoped key here — never a server secret (anything shipped in an
/// app can be extracted). Prefer passing it at build time, e.g.
/// `flutter run --dart-define=DIGETPAY_API_KEY=...`.
const String _apiKey = String.fromEnvironment(
'DIGETPAY_API_KEY',
defaultValue: '<your-api-key>',
);
void main() {
// Initialize the SDK once at startup.
DigetPaySdk.initialize(
apiKey: _apiKey,
baseUrl: kDigetPayDefaultBaseUrl,
logger: digetPayLog, // remove in production, or gate behind kDebugMode
);
runApp(const DigetPayExampleApp());
}
/// A demo product in the example store.
class Product {
final String name;
final double price;
final String currency;
const Product(this.name, this.price, this.currency);
}
const List<Product> _products = [
Product('Coffee', 15, 'SAR'),
Product('Book', 60, 'SAR'),
Product('Headphones', 250, 'SAR'),
];
class DigetPayExampleApp extends StatelessWidget {
const DigetPayExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'DigetPay Example',
theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
home: const ProductsPage(),
);
}
}
class ProductsPage extends StatelessWidget {
const ProductsPage({super.key});
Future<void> _pay(BuildContext context, Product product) async {
final messenger = ScaffoldMessenger.of(context);
void banner(String message, Color color) {
messenger
..clearSnackBars()
..showSnackBar(
SnackBar(content: Text(message), backgroundColor: color),
);
}
final orderId = const Uuid().v4();
digetPayLog(
'tap Pay → ${product.name} (${product.price} ${product.currency})',
);
// The THREE callbacks below are exactly how you learn the outcome after the
// user returns from the hosted WebView. Exactly one of them fires:
// • onTransactionSuccess → paid (handle: fulfil the order)
// • onTransactionFailure → failed (handle: show retry)
// • onDismiss → cancelled (user backed out; do nothing/retry)
await DigetPaySdk.cardPay()
.setOrder(
DigetPaySaleOrder(
id: orderId,
description: product.name,
currency: product.currency,
amount: product.price,
),
)
.setPayer(
const DigetPayPayer(
firstName: 'Demo',
lastName: 'User',
address: 'King Fahd Rd',
country: 'SA',
city: 'Riyadh',
zip: '00000',
email: 'demo@example.com',
phone: '+966500000000',
),
)
.setDesignType(DigetPayDesignType.one)
.setLanguage(DigetPayLanguage.en)
// setResultUrls(...) is optional — the SDK uses built-in defaults.
.onTransactionSuccess((r) {
digetPayLog(
'✅ SUCCESS status=${r.status} txn=${r.transactionId} rrn=${r.rrn}',
);
banner('Paid ✓ (${r.status ?? 'APPROVED'})', Colors.green.shade700);
})
.onTransactionFailure((r) {
// Prefer the transaction's own reason/status; NOT r.message, which is
// the envelope text ("Success") even when the payment did not go through.
final reason =
r.declineReason ?? r.status ?? r.errorCode ?? 'declined/failed';
digetPayLog('❌ FAILED status=${r.status} reason=$reason');
banner('Failed ✗ $reason', Colors.red.shade700);
})
.onDismiss(() {
digetPayLog('↩️ CANCELLED by user');
banner('Checkout cancelled', Colors.grey.shade700);
})
.start(context);
}
/// Demonstrates the paginated sessions listing.
///
/// Note we read `gatewayTransactionId` (the id to use for refunds/lookups),
/// NOT the session `id`.
Future<void> _showRecentSessions(BuildContext context) async {
final messenger = ScaffoldMessenger.of(context);
final page = await DigetPaySdk.getCheckoutSessions(
const CheckoutSessionFilter(page: 1, limit: 20),
);
final sessions = page?.content ?? const <CheckoutSession>[];
digetPayLog('sessions: ${sessions.length} of ${page?.totalElements ?? 0}');
final summary = sessions.isEmpty
? 'No sessions found (check your API key).'
: 'Latest: ${sessions.first.merchantOrderId} → '
'txn ${sessions.first.gatewayTransactionId} '
'(${page?.totalElements} total)';
messenger
..clearSnackBars()
..showSnackBar(SnackBar(content: Text(summary)));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('DigetPay Store'),
actions: [
IconButton(
icon: const Icon(Icons.receipt_long_outlined),
tooltip: 'Recent sessions',
onPressed: () => _showRecentSessions(context),
),
],
),
body: ListView.separated(
itemCount: _products.length,
separatorBuilder: (_, _) => const Divider(height: 1),
itemBuilder: (context, index) {
final product = _products[index];
return ListTile(
leading: const Icon(Icons.shopping_bag_outlined),
title: Text(product.name),
subtitle: Text('${product.price} ${product.currency}'),
trailing: FilledButton(
onPressed: () => _pay(context, product),
child: const Text('Pay'),
),
);
},
),
);
}
}