The Bayarcash SDK provides an expressive, idiomatic interface for interacting with Bayarcash's Payment Gateway API from .NET. It supports both API v2 (default) and v3, with additional query features available in v3. It is a feature-parity port of the official Bayarcash PHP SDK.
- Requirements
- Installation
- Getting Started
- Configuration
- 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
- Dependency Injection
- Security Recommendations
- Support
- .NET targeting
netstandard2.0or newer (works on .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5/6/7/8+). - No third-party runtime dependencies — the SDK uses only
System.Net.Http,System.Security.Cryptography, andSystem.Text.Json.
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.
dotnet add package Bayarcashusing Bayarcash;
using var bayarcash = new BayarcashClient("YOUR_API_TOKEN");
bayarcash.UseSandbox(); // remove this line in productionYou may also construct the client with options:
using var bayarcash = new BayarcashClient(
"YOUR_API_TOKEN",
new BayarcashOptions
{
Sandbox = true, // switch to the sandbox environment
ApiVersion = "v3", // "v2" (default) or "v3"
Timeout = 60, // request timeout in seconds (default 30)
});The fluent setters mirror the PHP SDK and can be chained. Call them before making
requests, and omit UseSandbox() in production to hit the live gateway.
bayarcash
.UseSandbox() // switch to the sandbox environment
.SetApiVersion("v3") // "v2" (default) or "v3"
.SetTimeout(60); // request timeout in seconds
bayarcash.GetApiVersion(); // read back the current versionA complete FPX payment flow, from creating the payment to verifying the result:
using Bayarcash;
using var bayarcash = new BayarcashClient("YOUR_API_TOKEN");
bayarcash.UseSandbox();
const string apiSecretKey = "YOUR_API_SECRET_KEY";
// 1. Build the payment request
var data = new Dictionary<string, object?>
{
["portal_key"] = "your_portal_key",
["payment_channel"] = PaymentChannel.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(apiSecretKey, data);
// 3. Create the payment intent and redirect the payer to Bayarcash
var paymentIntent = await bayarcash.CreatePaymentIntentAsync(data);
return Redirect(paymentIntent.Url!);After payment, Bayarcash calls your callback_url (server-to-server) and redirects the payer
to your return_url. Verify both — see Handling Callbacks.
Pass one of these constants (or an array of them) as payment_channel:
PaymentChannel.Fpx // 1 FPX Online Banking
PaymentChannel.ManualTransfer // 2 Manual Bank Transfer
PaymentChannel.FpxDirectDebit // 3 FPX Direct Debit
PaymentChannel.FpxLineOfCredit // 4 FPX Line of Credit
PaymentChannel.DuitNowDobw // 5 DuitNow Online Banking
PaymentChannel.DuitNowQr // 6 DuitNow QR
PaymentChannel.SPayLater // 7 ShopeePayLater
PaymentChannel.BoostPayFlex // 8 Boost PayFlex
PaymentChannel.QrisOb // 9 QRIS Online Banking
PaymentChannel.QrisWallet // 10 QRIS Wallet
PaymentChannel.Nets // 11 NETS
PaymentChannel.CreditCard // 12 Credit Card
PaymentChannel.Alipay // 13 Alipay
PaymentChannel.WeChatPay // 14 WeChat Pay
PaymentChannel.PromptPay // 15 PromptPay
PaymentChannel.TouchNGo // 16 Touch 'n Go eWallet
PaymentChannel.BoostWallet // 17 Boost Wallet
PaymentChannel.GrabPay // 18 GrabPay
PaymentChannel.GrabPayLater // 19 Grab PayLater
PaymentChannel.ShopeePay // 21 ShopeePay (note: there is no channel id 20)var paymentIntent = await bayarcash.CreatePaymentIntentAsync(data);Request fields:
| Field | Required | Description |
|---|---|---|
portal_key |
Yes | Your portal key. |
order_number |
Yes | Your reference. Max 30 chars. |
amount |
Yes | String with up to 2 decimals, e.g. "10.00". |
payer_name |
Yes | Max 150 chars. |
payer_email |
Yes | Valid email, max 250 chars. |
payment_channel |
No | A PaymentChannel id, or an array of ids. If omitted, the payer chooses on the Bayarcash page. |
payer_telephone_number |
No | Required for e-wallet / DuitNow channels. |
return_url |
No | Where the payer's browser is redirected after payment. |
callback_url |
No | Server-to-server notification URL. |
metadata |
No | Any extra data you want echoed back. |
checksum |
No | Recommended. See below. |
The checksum protects the request from tampering. Generate it after building the request
and append it as checksum:
data["checksum"] = bayarcash.CreatePaymentIntentChecksumValue(apiSecretKey, data);The checksum is an HMAC-SHA256 (lowercase hex) computed from payment_channel,
order_number, amount, payer_name, and payer_email.
Bayarcash sends two kinds of notification. Always verify them with your API secret key
before trusting the data. Each verifier returns true only when the checksum matches, using a
constant-time comparison.
| Notification | How it arrives | Read it from |
|---|---|---|
callback_url (transaction) |
Server-to-server POST (form-encoded) | Request form |
return_url (payer redirect) |
Browser redirect — POST on v2, GET query on v3 | Request form / query |
// callbackData is an IReadOnlyDictionary<string, string?> built from the request form/query.
var callbackData = Request.Form.ToDictionary(kv => kv.Key, kv => (string?)kv.Value);
// Transaction callback (sent to your callback_url)
if (bayarcash.VerifyTransactionCallbackData(callbackData, apiSecretKey))
{
// Data is authentic — safe to process.
}
// Payer redirect (sent to your return_url)
if (bayarcash.VerifyReturnUrlCallbackData(callbackData, apiSecretKey))
{
// ...
}
// Pre-transaction callback (sent before the transaction record)
if (bayarcash.VerifyPreTransactionCallbackData(callbackData, apiSecretKey))
{
// ...
}See FPX Direct Debit for mandate-specific callback verifiers.
Transaction status is an integer code. Use the Fpx helper instead of hardcoding numbers:
using Bayarcash;
Fpx.StatusNew; // 0
Fpx.StatusPending; // 1
Fpx.StatusFailed; // 2
Fpx.StatusSuccess; // 3
Fpx.StatusCancelled; // 4
if (int.Parse(callbackData["status"]!) == Fpx.StatusSuccess)
{
// Payment successful
}
Fpx.GetStatusText(3); // "Successful"// Get a single transaction (v2 and v3)
var transaction = await bayarcash.GetTransactionAsync("transaction_id");The following query helpers require API v3 and throw InvalidOperationException on v2:
bayarcash.SetApiVersion("v3");
var result = await bayarcash.GetAllTransactionsAsync(new Dictionary<string, object?>
{
["order_number"] = "INV-1001",
["status"] = "3",
["payment_channel"] = PaymentChannel.Fpx,
["exchange_reference_number"] = "REF123",
["payer_email"] = "ahmad@example.com",
});
// result.Data => IReadOnlyList<Transaction>, result.Meta => pagination metadata (JsonElement)
var byOrder = await bayarcash.GetTransactionByOrderNumberAsync("INV-1001");
var byEmail = await bayarcash.GetTransactionsByPayerEmailAsync("ahmad@example.com");
var byStatus = await bayarcash.GetTransactionsByStatusAsync("3");
var byChannel = await bayarcash.GetTransactionsByPaymentChannelAsync(PaymentChannel.Fpx);
var byRef = await bayarcash.GetTransactionByReferenceNumberAsync("REF123"); // single or null
// Get a payment intent by id (v3 only)
var intent = await bayarcash.GetPaymentIntentAsync("payment_intent_id");
// Cancel a payment intent (v3 only)
await bayarcash.CancelPaymentIntentAsync("payment_intent_id");FPX Direct Debit lets you set up a recurring mandate and later maintain or terminate it.
Constants live on the FpxDirectDebit class:
using Bayarcash;
// 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"var data = new Dictionary<string, object?>
{
["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(apiSecretKey, data);
var mandate = await bayarcash.CreateFpxDirectDebitEnrollmentAsync(data);
return Redirect(mandate.Url!); // redirect payer to the enrolment pagevar data = new Dictionary<string, object?>
{
["amount"] = "15.00",
["payer_email"] = "ahmad@example.com",
["payer_telephone_number"] = "0123456789",
["application_reason"] = "Update amount",
["frequency_mode"] = FpxDirectDebit.ModeMonthly,
};
data["checksum"] = bayarcash.CreateFpxDirectDebitMaintenanceChecksumValue(apiSecretKey, data);
var mandate = await bayarcash.CreateFpxDirectDebitMaintenanceAsync(mandateId, data);
return Redirect(mandate.Url!);var mandate = await bayarcash.CreateFpxDirectDebitTerminationAsync(mandateId, new Dictionary<string, object?>
{
["application_reason"] = "Customer cancelled",
});
return Redirect(mandate.Url!);var mandate = await bayarcash.GetFpxDirectDebitAsync(mandateId);
var transaction = await bayarcash.GetFpxDirectDebitTransactionAsync(transactionId);
// Mandate callback verifiers
bayarcash.VerifyDirectDebitBankApprovalCallbackData(callbackData, apiSecretKey);
bayarcash.VerifyDirectDebitAuthorizationCallbackData(callbackData, apiSecretKey);
bayarcash.VerifyDirectDebitTransactionCallbackData(callbackData, apiSecretKey);Submit a manual (offline) bank transfer with proof of payment. The proof file is uploaded as multipart form data.
var response = await bayarcash.CreateManualBankTransferAsync(new Dictionary<string, object?>
{
["portal_key"] = "your_portal_key",
["payment_gateway"] = PaymentChannel.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", // or "Cash Deposit Machine (CDM)"
["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
});
if (response.Success)
{
// response.HtmlForm is an auto-submit form; response.FormData / response.ReturnUrl are parsed out.
}Update the status of an existing transfer:
using Bayarcash;
await bayarcash.UpdateManualBankTransferStatusAsync(
"ref_no_here",
Fpx.StatusSuccess.ToString(),
"10.00");// All portals for your account
var portals = await bayarcash.GetPortalsAsync();
// Payment channels available for a portal
var channels = await bayarcash.GetChannelsAsync("your_portal_key");
// FPX banks (for building a bank selector)
var banks = await bayarcash.FpxBanksListAsync();Failed API calls throw typed exceptions, all deriving from BayarcashException:
using Bayarcash.Exceptions;
try
{
var paymentIntent = await bayarcash.CreatePaymentIntentAsync(data);
}
catch (ValidationException ex)
{
// 422 — invalid request data
var errors = ex.Errors; // IReadOnlyDictionary<string, JsonElement>
}
catch (NotFoundException)
{
// 404 — resource not found
}
catch (RateLimitException ex)
{
// 429 — too many requests
var resetAt = ex.RateLimitResetsAt; // unix timestamp or null
}
catch (FailedActionException ex)
{
// 400 — request failed
var message = ex.Message;
}
catch (BayarcashApiException ex)
{
// any other non-success status
var status = ex.StatusCode;
}| Exception | HTTP | Meaning |
|---|---|---|
ValidationException |
422 | Invalid data. Errors holds the decoded detail. |
FailedActionException |
400 | Request failed. Message has the reason. |
NotFoundException |
404 | Resource not found. |
RateLimitException |
429 | Rate limited. RateLimitResetsAt holds the reset time. |
BayarcashApiException |
other | Unmapped failure. StatusCode / Body hold the response. |
TimeoutException |
— | Polling / retry timeout. |
API methods return typed response objects. Any field the API omits is null.
PaymentIntent (from CreatePaymentIntentAsync / GetPaymentIntentAsync)
paymentIntent.Url; // checkout URL to redirect the payer to
paymentIntent.Id;
paymentIntent.Status;
paymentIntent.Amount; // decimal?
paymentIntent.OrderNumber;
paymentIntent.PayerName;
paymentIntent.PayerEmail;Transaction (from GetTransactionAsync / transaction queries)
transaction.Id;
transaction.Status; // status code — see Fpx constants
transaction.StatusDescription;
transaction.Amount; // decimal?
transaction.OrderNumber;
transaction.ExchangeReferenceNumber;
transaction.PayerName;
transaction.PayerEmail;BayarcashClient accepts an HttpClient, so it plays well with IHttpClientFactory:
services.AddHttpClient();
services.AddSingleton(sp =>
{
var httpClient = sp.GetRequiredService<IHttpClientFactory>().CreateClient();
return new BayarcashClient("YOUR_API_TOKEN", new BayarcashOptions { ApiVersion = "v3" }, httpClient);
});When you supply your own HttpClient, you own its lifetime; otherwise the client creates and
disposes an internal one.
- 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.
For full API details, see the Official Bayarcash API Documentation.
For support questions, contact Bayarcash support or open an issue in this repository.
See CHANGELOG.md for the version history.
Open-sourced software licensed under the MIT license.