Payment (Billplz + PHP)
What is Billplz
Billplz is a Malaysian online payment platform that allows businesses to collect payments quickly and affordably through various channels, primarily online banking (FPX), but also supports e-wallets, cards, and other methods.
Sign Up Billplz Sandbox Account
Visit the Sandbox site
Go to billplz-sandbox.com and click Sign up — you'll be redirected to the SSO registration page
Enter your details
Fill in the required fields—name, email, password, etc.—then click Create Account
Verify your phone number
Add your phone number, click Send Code, enter 123456, then click Continue
Verify your Individual identity
Provide your personal bank account details and select “DUMMY BANK VERIFIED”.
Ensure the bank account number is unique.
You (as business owner or PIC) will serve as the verified individual
Verify your Organization identity
Provide your organization email, send the OTP, check your inbox/spam, then enter the 6-digit code and click Continue
Fill in organization details and bank info—choose DUMMY BANK again.
Complete SSO setup
Once both identities are verified, click Redirect to App and authorise by typing billplz-sandbox
Add contact details
Enter your customer service contact information, which will be displayed on billing pages. You can update it later from your dashboard
Access the dashboard
You’ll then be taken to the Billplz Sandbox dashboard
For detailed instructions on how to register, kindly refer to the official guide provided by Billplz: https://help.billplz.com/article/164-sign-up-a-sandbox-account
API Reference:
Setup and Configuration
Billplz Account Setup
Register at Billplz
Complete merchant verification
Obtain your API credentials:
API Key: Used for creating bills
X Signature Key: Used for callback verification
Billplz Configuration
API Credentials
In dart file:
static const String billplzApiKey = 'your-api-key-here';
static const String billplzCollectionId = 'your-collection-id';
static const String billplzUrl ='https://www.billplz-sandbox.com/api/v3/bills';
In PHP:
$billplz_signature_key = 'your-x-signature-key-here';
Callback URLs
Configure these URLs in your Billplz dashboard:
Callback URL: https://yourdomain.com/billplz_callback.php
Redirect URL: myapp://payment-success
Fallback URL: myapp://payment-fail
Frontend Implementation (Flutter)
Payment Processor Class
The main payment processing logic is handled in the PaymentProcessor class:
class PaymentProcessor {
static Future<void> processPayment({
required BuildContext context,
required List<Map<String, dynamic>> bills,
required String? unitId,
required String? residentId,
required Function(bool) onProcessingStateChange,
}) async {
// Implementation details in your code
}
}
Payment Flow:
Bill Creation: Create Billplz bill with total amount
Session Storage: Store payment session data
Payment Launch: Open Billplz payment page in external browser
Billplz Bill Creation API
The core of the payment process is creating a bill through the Billplz API:
try {
return await http.post(
Uri.parse(billplzUrl),
headers: {
'Authorization':
'Basic ${base64Encode(utf8.encode('$billplzApiKey:'))}',
},
body: {
'collection_id': billplzCollectionId,
'email': '[email protected]',
'name': 'Condo Resident',
'amount': amountInSen.toString(),
'description': description,
'callback_url': 'https://mycondo.loca.lt/billplz_callback.php',
'redirect_url': 'myapp://payment-success',
'fallback_url': 'myapp://payment-fail'
},
);
} catch (e) {
print('Error creating Billplz bill: $e');
PayBillsHelpers.showError(context, "Failed to initiate payment");
return null;
}
API Parameters Explained:
collection_id
Your Billplz collection ID
6ofix5lq
Yes
name
Customer name
Condo Resident
Yes
amount
Amount in sen (1 RM = 100 sen)
5000 (RM 50.00)
Yes
description
Payment description
Condo Bill Payment: Water Bill (RM25.00)
Yes
redirect_url
Success redirect URL
myapp://payment-success
No
fallback_url
Failure redirect URL
myapp://payment-fail
No
Authentication
Billplz uses Basic Authentication with your API key:
'Authorization':'Basic ${base64Encode(utf8.encode('$billplzApiKey:'))}',
Note the colon (:) after the API key - this is required by Basic Auth format.
Amount Handling
// Calculate total in sen (1 RM = 100 sen)
final int amountInSen = (totalAmount * 100).toInt();
Response Handling
Successful API call returns:
{
"id": "8X0Iyzaw",
"collection_id": "6ofix5lq",
"paid": false,
"state": "due",
"amount": 2550,
"paid_amount": 0,
"due_at": "2023-12-31",
"email": "[email protected]",
"mobile": null,
"name": "Condo Resident",
"url": "https://www.billplz-sandbox.com/bills/8X0Iyzaw",
"paid_at": null
}
The url field contains the payment page URL that users will be redirected to.
Deep Link Handling
Configure deep links in your Flutter app to handle payment returns:
Path: ..\android\app\src\main\AndroidManifest.xml
Example:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="payment-success"/>
<data android:scheme="myapp" android:host="payment-fail"/>
</intent-filter>
Backend Implementation (PHP)
Callback Handler Structure
The PHP callback handler (billplz_callback.php) processes Billplz webhook notifications:
// Main callback handling
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Verify signature
// Process payment
// Update database records
}
Signature Verification
Critical Security Feature: Always verify Billplz signatures:
// Create signature string according to Billplz documentation
// Format: billplzid|billplzpaid|billplzpaid_at|billplzx_signature
$signatureData = [
'billplzid' => $billplzId,
'billplzpaid' => $paid,
'billplzpaid_at' => $paidAt,
'billplzx_signature' => $xSignature
];
// Sort the array by key
ksort($signatureData);
// Create the signature string
$signatureString = '';
foreach ($signatureData as $key => $value) {
$signatureString .= $key . $value;
}
error_log("Signature string: $signatureString");
$calculatedSignature = hash_hmac('sha256', $signatureString, $billplz_signature_key);
Security Considerations:
Amount Validation
// Verify amount matches (convert from sen to RM)
$expectedAmount = floatval($paymentSession['total_amount']);
$receivedAmount = floatval($amount) / 100; // Convert sen to RM
if (abs($expectedAmount - $receivedAmount) > 0.01) {
error_log("Amount mismatch - Expected: $expectedAmount, Received: $receivedAmount");
echo json_encode(['status' => 'error', 'message' => 'Amount mismatch']);
exit();
}
Last updated