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

  1. Visit the Sandbox site

    • Go to billplz-sandbox.com and click Sign up — you'll be redirected to the SSO registration page

  2. Enter your details

    • Fill in the required fields—name, email, password, etc.—then click Create Account

  3. Verify your phone number

    • Add your phone number, click Send Code, enter 123456, then click Continue

  4. Verify your Individual identity

    1. Provide your personal bank account details and select “DUMMY BANK VERIFIED”.

    2. Ensure the bank account number is unique.

    3. You (as business owner or PIC) will serve as the verified individual

  5. Verify your Organization identity

    1. Provide your organization email, send the OTP, check your inbox/spam, then enter the 6-digit code and click Continue

    2. Fill in organization details and bank info—choose DUMMY BANK again.

  6. Complete SSO setup

    • Once both identities are verified, click Redirect to App and authorise by typing billplz-sandbox

  7. Add contact details

    • Enter your customer service contact information, which will be displayed on billing pages. You can update it later from your dashboard

  8. 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

  1. Register at Billplz

  2. Complete merchant verification

  3. 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:

  1. Bill Creation: Create Billplz bill with total amount

  2. Session Storage: Store payment session data

  3. 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:

Parameter
Description
Example
Required

collection_id

Your Billplz collection ID

6ofix5lq

Yes

email

Customer email address

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

callback_url

Webhook URL for payment notification

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.

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