bayarcash 1.0.0
bayarcash: ^1.0.0 copied to clipboard
Dart SDK for the Bayarcash payment gateway. A feature-parity, idiomatic port of the official PHP SDK, usable from Flutter apps and server-side Dart.
Bayarcash Payment Gateway Dart SDK #
The Bayarcash SDK provides an expressive interface for interacting with Bayarcash's Payment Gateway API from Dart. It is a feature-parity, idiomatic port of the official PHP SDK.
It is a pure Dart package with no Flutter dependency, so it works in both Flutter apps and server-side Dart. It supports API v2 (default) and v3, with additional query features available in v3.
Table of Contents #
- Requirements
- Installation
- Getting Started
- Quick Start: Accept a Payment
- Payment Channels
- Creating a Payment Intent
- Handling Callbacks
- Payment & Transaction Status
- Transactions
- FPX Direct Debit
- Manual Bank Transfer
- Portals & FPX Banks
- Error Handling
- Response Objects
- Testing
- Security Recommendations
- Support
Requirements #
- Dart SDK
>=2.17.0 <4.0.0(works in Flutter and server Dart)
Installation #
Install with Dart's package manager:
dart pub add bayarcash
Or add it to your pubspec.yaml:
dependencies:
bayarcash: ^3.0.0
Then import it:
import 'package:bayarcash/bayarcash.dart';
You will need two credentials from your Bayarcash console:
- API token — used to authenticate SDK requests.
- API secret key — used to generate request checksums and verify callbacks.
Getting Started #
import 'package:bayarcash/bayarcash.dart';
final bayarcash = Bayarcash(
token: 'YOUR_API_TOKEN',
secretKey: 'YOUR_API_SECRET_KEY',
sandbox: true, // remove in production
);
The secretKey is optional at construction time — you can pass it per-call to
the checksum and callback-verification helpers instead. When configured on the
client, it is used as the default.
Configuration #
All configuration can be passed to the constructor, or set fluently afterwards (each setter returns the client for chaining):
final bayarcash = Bayarcash(token: 'YOUR_API_TOKEN')
..useSandbox() // switch to the sandbox environment
..setApiVersion('v3') // 'v2' (default) or 'v3'
..setTimeout(60); // request timeout in seconds (default 30)
bayarcash.getApiVersion(); // read back the current version
| Option | Default | Description |
|---|---|---|
token |
— (required) | API token used to authenticate requests. |
secretKey |
null |
API secret key, used to sign and verify. |
sandbox |
false |
Target the sandbox environment. |
apiVersion |
'v2' |
'v2' or 'v3'. |
timeout |
30s |
Per-request timeout. |
httpClient |
auto | Inject your own http.Client (useful for tests). |
Set
sandbox/apiVersionbefore making requests. Omitsandboxin production to hit the live gateway.
Base URIs #
| Version | Environment | Base URI |
|---|---|---|
| v2 | production | https://console.bayar.cash/api/v2/ |
| v2 | sandbox | https://console.bayarcash-sandbox.com/api/v2/ |
| v3 | production | https://api.console.bayar.cash/v3/ |
| v3 | sandbox | https://api.console.bayarcash-sandbox.com/v3/ |
Quick Start: Accept a Payment #
A complete FPX payment flow, from creating the payment to verifying the result:
import 'package:bayarcash/bayarcash.dart';
Future<String> startPayment() async {
final bayarcash = Bayarcash(
token: 'YOUR_API_TOKEN',
secretKey: 'YOUR_API_SECRET_KEY',
sandbox: true,
);
// 1. Build the payment request
final data = <String, dynamic>{
'portal_key': 'your_portal_key',
'payment_channel': Bayarcash.fpx,
'order_number': 'INV-1001',
'amount': '10.00',
'payer_name': 'Ahmad bin Abdullah',
'payer_email': 'ahmad@example.com',
'payer_telephone_number': '0123456789',
'return_url': 'https://your-site.com/payment/return',
'callback_url': 'https://your-site.com/payment/callback',
};
// 2. Sign it (recommended)
data['checksum'] = bayarcash.createPaymentIntentChecksumValue(data);
// 3. Create the payment intent and redirect the payer to Bayarcash
final paymentIntent = await bayarcash.createPaymentIntent(data);
return paymentIntent.url!; // redirect the payer here
}
After payment, Bayarcash calls your callback_url (server-to-server) and
redirects the payer to your return_url. Verify both — see
Handling Callbacks.
Payment Channels #
Pass one of these constants (or a list of them) as payment_channel:
Bayarcash.fpx // 1 FPX Online Banking
Bayarcash.manualTransfer // 2 Manual Bank Transfer
Bayarcash.fpxDirectDebit // 3 FPX Direct Debit
Bayarcash.fpxLineOfCredit // 4 FPX Line of Credit
Bayarcash.duitnowDobw // 5 DuitNow Online Banking
Bayarcash.duitnowQr // 6 DuitNow QR
Bayarcash.spaylater // 7 ShopeePayLater
Bayarcash.boostPayflex // 8 Boost PayFlex
Bayarcash.qrisob // 9 QRIS Online Banking
Bayarcash.qriswallet // 10 QRIS Wallet
Bayarcash.nets // 11 NETS
Bayarcash.creditCard // 12 Credit Card
Bayarcash.alipay // 13 Alipay
Bayarcash.wechatpay // 14 WeChat Pay
Bayarcash.promptpay // 15 PromptPay
Bayarcash.touchNGo // 16 Touch 'n Go eWallet
Bayarcash.boostWallet // 17 Boost Wallet
Bayarcash.grabpay // 18 GrabPay
Bayarcash.grabpl // 19 Grab PayLater
Bayarcash.shopeePay // 21 ShopeePay (note: there is no id 20)
Creating a Payment Intent #
final paymentIntent = await bayarcash.createPaymentIntent(data);
Request fields:
| Field | Required | Description |
|---|---|---|
portal_key |
✅ | Your portal key. |
order_number |
✅ | Your reference. Max 30 chars. |
amount |
✅ | String with up to 2 decimals, e.g. '10.00'. |
payer_name |
✅ | Max 150 chars. |
payer_email |
✅ | Valid email, max 250 chars. |
payment_channel |
➖ | A Bayarcash.* channel id, or a list of ids. |
payer_telephone_number |
➖ | Required for e-wallet / DuitNow channels. |
return_url |
➖ | Where the payer's browser is redirected after payment. |
callback_url |
➖ | Server-to-server notification URL. |
metadata |
➖ | Any extra data you want echoed back. |
checksum |
➖ | Recommended. See below. |
Checksum #
Generate the checksum after building the request and append it as
checksum:
data['checksum'] = bayarcash.createPaymentIntentChecksumValue(data);
// or pass the secret explicitly:
data['checksum'] =
bayarcash.createPaymentIntentChecksumValue(data, secretKey: apiSecretKey);
The checksum is computed from payment_channel, order_number, amount,
payer_name, and payer_email.
Handling Callbacks #
Bayarcash sends two kinds of notification. Always verify them with your API secret key before trusting the data. Checksum comparison uses a constant-time algorithm to resist timing attacks.
| Notification | How it arrives | Read it from |
|---|---|---|
callback_url (transaction) |
Server-to-server POST (form-encoded) | request body |
return_url (payer redirect) |
Browser redirect — POST on v2, GET query on v3 | request body / query |
final callbackData = <String, dynamic>{/* parsed request body or query */};
// Transaction callback (sent to your callback_url)
if (bayarcash.verifyTransactionCallbackData(callbackData)) {
// Data is authentic — safe to process.
}
// Payer redirect (sent to your return_url)
if (bayarcash.verifyReturnUrlCallbackData(callbackData)) {
// ...
}
// Pre-transaction callback (sent before the transaction record)
if (bayarcash.verifyPreTransactionCallbackData(callbackData)) {
// ...
}
Each verifier returns true only when the checksum matches. See
FPX Direct Debit for mandate-specific callback verifiers.
Payment & Transaction Status #
Transaction status is an integer code. Use the Fpx helper instead of
hardcoding numbers:
Fpx.statusNew; // 0
Fpx.statusPending; // 1
Fpx.statusFailed; // 2
Fpx.statusSuccess; // 3
Fpx.statusCancelled; // 4
if (int.parse(callbackData['status'].toString()) == Fpx.statusSuccess) {
// Payment successful
}
Fpx.getStatusText(3); // "Successful"
Transactions #
// Get a single transaction (v2 and v3)
final transaction = await bayarcash.getTransaction('transaction_id');
The following query helpers require API v3 and throw on v2:
bayarcash.setApiVersion('v3');
final result = await bayarcash.getAllTransactions({
'order_number': 'INV-1001',
'status': '3',
'payment_channel': Bayarcash.fpx,
'exchange_reference_number': 'REF123',
'payer_email': 'ahmad@example.com',
});
// result.data => List<TransactionResource>, result.meta => pagination meta
final byOrder = await bayarcash.getTransactionByOrderNumber('INV-1001');
final byEmail = await bayarcash.getTransactionsByPayerEmail('ahmad@example.com');
final byStatus = await bayarcash.getTransactionsByStatus('3');
final byChannel = await bayarcash.getTransactionsByPaymentChannel(Bayarcash.fpx);
final byRef = await bayarcash.getTransactionByReferenceNumber('REF123'); // or null
// Get a payment intent by id (v3 only)
final intent = await bayarcash.getPaymentIntent('payment_intent_id');
// Cancel a payment intent (v3 only)
await bayarcash.cancelPaymentIntent('payment_intent_id');
FPX Direct Debit #
FPX Direct Debit lets you set up a recurring mandate and later maintain or
terminate it. Constants live on the FpxDirectDebit class:
// Payer ID type
FpxDirectDebit.nric; // 1 (New IC)
FpxDirectDebit.oldIc; // 2
FpxDirectDebit.passport; // 3
FpxDirectDebit.businessRegistration; // 4
FpxDirectDebit.others; // 5
// Frequency mode
FpxDirectDebit.modeDaily; // 'DL'
FpxDirectDebit.modeWeekly; // 'WK'
FpxDirectDebit.modeMonthly; // 'MT'
FpxDirectDebit.modeYearly; // 'YR'
1. Enrolment #
final data = <String, dynamic>{
'portal_key': 'your_portal_key',
'order_number': 'DD-1001',
'amount': '10.00',
'payer_name': 'Ahmad bin Abdullah',
'payer_id_type': FpxDirectDebit.nric,
'payer_id': '900101011234',
'payer_email': 'ahmad@example.com',
'payer_telephone_number': '0123456789',
'application_reason': 'Monthly subscription',
'frequency_mode': FpxDirectDebit.modeMonthly,
'effective_date': '2026-08-01', // optional, yyyy-MM-dd
'expiry_date': '2027-08-01', // optional, yyyy-MM-dd
'return_url': 'https://your-site.com/mandate/return',
};
data['checksum'] = bayarcash.createFpxDirectDebitEnrolmentChecksumValue(data);
final mandate = await bayarcash.createFpxDirectDebitEnrollment(data);
// redirect the payer to mandate.url
2. Maintenance #
final data = <String, dynamic>{
'amount': '15.00',
'payer_email': 'ahmad@example.com',
'payer_telephone_number': '0123456789',
'application_reason': 'Update amount',
'frequency_mode': FpxDirectDebit.modeMonthly,
};
data['checksum'] =
bayarcash.createFpxDirectDebitMaintenanceChecksumValue(data);
final mandate =
await bayarcash.createFpxDirectDebitMaintenance(mandateId, data);
3. Termination #
final mandate = await bayarcash.createFpxDirectDebitTermination(mandateId, {
'application_reason': 'Customer cancelled',
});
Retrieving mandates & verifying mandate callbacks #
final mandate = await bayarcash.getFpxDirectDebit(mandateId);
final transaction =
await bayarcash.getFpxDirectDebitTransaction(transactionId);
// Mandate callback verifiers
bayarcash.verifyDirectDebitBankApprovalCallbackData(callbackData);
bayarcash.verifyDirectDebitAuthorizationCallbackData(callbackData);
bayarcash.verifyDirectDebitTransactionCallbackData(callbackData);
Manual Bank Transfer #
Submit a manual (offline) bank transfer with proof of payment. The proof file is uploaded as multipart form data.
final response = await bayarcash.createManualBankTransfer({
'portal_key': 'your_portal_key',
'payment_gateway': Bayarcash.manualTransfer, // must be 2
'order_no': 'MT-1001',
'buyer_name': 'Ahmad bin Abdullah',
'buyer_email': 'ahmad@example.com',
'buyer_tel_no': '0123456789', // optional
'order_amount': '10.00',
'merchant_bank_name': 'Maybank',
'merchant_bank_account': '1234567890',
'merchant_bank_account_holder': 'Your Company Sdn Bhd',
'bank_transfer_type': 'Internet Banking',
'bank_transfer_notes': 'Payment for order MT-1001',
'bank_transfer_date': '2026-07-22', // optional, defaults to today
'proof_of_payment': '/path/to/receipt.jpg', // jpeg/png/gif/pdf
});
Update the status of an existing transfer:
await bayarcash.updateManualBankTransferStatus(
'ref_no_here',
Fpx.statusSuccess.toString(),
'10.00',
);
Portals & FPX Banks #
// All portals for your account
final portals = await bayarcash.getPortals();
// Payment channels available for a portal
final channels = await bayarcash.getChannels('your_portal_key');
// FPX banks (for building a bank selector)
final banks = await bayarcash.fpxBanksList();
Error Handling #
Failed API calls throw typed exceptions. Catch them to handle errors
gracefully. Every exception subclasses BayarcashException.
try {
final paymentIntent = await bayarcash.createPaymentIntent(data);
} on ValidationException catch (e) {
// 422 — invalid request data
final errors = e.errors;
} on NotFoundException {
// 404 — resource not found
} on RateLimitException catch (e) {
// 429 — too many requests
final resetAt = e.rateLimitResetsAt; // unix timestamp or null
} on FailedActionException catch (e) {
// 400 — request failed
final message = e.message;
} on BayarcashApiException catch (e) {
// any other non-2xx response
final status = e.statusCode;
}
| Exception | HTTP | Meaning |
|---|---|---|
ValidationException |
422 | Invalid data. Read .errors for details. |
FailedActionException |
400 | Request failed. .message has the reason. |
NotFoundException |
404 | Resource not found. |
RateLimitException |
429 | Rate limited. .rateLimitResetsAt holds the reset time. |
BayarcashApiException |
other | Any other non-2xx response. |
Response Objects #
API methods return typed resource objects. Any missing field is null, and the
raw JSON is available through resource.raw and resource['snake_case_key'].
PaymentIntentResource (from createPaymentIntent / getPaymentIntent)
paymentIntent.url; // checkout URL to redirect the payer to
paymentIntent.id;
paymentIntent.status;
paymentIntent.amount;
paymentIntent.orderNumber;
paymentIntent.payerName;
paymentIntent.payerEmail;
TransactionResource (from getTransaction / transaction queries)
transaction.id;
transaction.status; // int status code as a string — see Fpx
transaction.statusDescription;
transaction.amount;
transaction.orderNumber;
transaction.exchangeReferenceNumber;
transaction.payerName;
transaction.payerEmail;
transaction.toMap(); // the raw JSON as a map
Testing #
Inject a mock http.Client to test without hitting the network:
import 'package:bayarcash/bayarcash.dart';
import 'package:http/testing.dart';
import 'package:http/http.dart' as http;
final client = MockClient((request) async {
return http.Response('{"url": "https://pay.example/checkout"}', 200);
});
final bayarcash = Bayarcash(token: 'test', httpClient: client);
Security Recommendations #
- Always send a
checksumwith payment and mandate requests. - Verify every callback with the provided verification methods before acting on it.
- Store and check transaction ids to prevent duplicate processing.
- Use HTTPS for your
return_urlandcallback_url. - Keep your API token and secret key out of source control.
API Documentation #
For full API details, see the Official Bayarcash API Documentation.
Support #
For support questions, contact Bayarcash support or open an issue in this repository.
Changelog #
See CHANGELOG.md for the version history.
License #
Open-sourced software licensed under the MIT license.