sitehandy / omnipay-toyyibpay
toyyibPay driver for the Omnipay payment processing library
Installs: 1 980
Dependents: 0
Suggesters: 0
Security: 0
Stars: 4
Watchers: 1
Forks: 5
Open Issues: 0
Requires
- php: ^7.4 || ^8.0
- ext-curl: *
- ext-json: *
- guzzlehttp/guzzle: ^7.0
- omnipay/common: ^3.0
- php-http/guzzle7-adapter: ^1.0
Requires (Dev)
- mockery/mockery: ^1.4
- phpunit/phpunit: ^9.0 || ^10.0 || ^11.0
- squizlabs/php_codesniffer: ^3.5
This package is auto-updated.
Last update: 2025-08-19 17:28:52 UTC
README
toyyibPay driver for the Omnipay PHP payment processing library
Omnipay is a framework agnostic, multi-gateway payment processing library for PHP. This package implements toyyibPay support for Omnipay.
Requirements
- PHP 7.4 or higher
- PHP 8.0, 8.1, 8.2, 8.3, 8.4 supported
- cURL extension
- JSON extension
- Omnipay 3.x
Installation
Omnipay is installed via Composer. To install, simply require league/omnipay
and sitehandy/omnipay-toyyibpay
with Composer:
composer require league/omnipay sitehandy/omnipay-toyyibpay
Note: Composer will automatically select the latest stable version. If you need a specific version, you can specify it, but using version constraints like
^1.0
may cause warnings.
Version Information
- v1.0.0: First stable release with PHP 7.4-8.4 support, modern HTTP client, and comprehensive testing
- Requirements: PHP 7.4+ with cURL and JSON extensions
- Compatibility: Omnipay 3.x framework
Table of Contents
- Basic Usage
- Quick Start
- Advanced Configuration
- Error Handling
- Testing
- Security Best Practices
- Troubleshooting
- API Reference
Basic Usage
For general usage instructions, please see the main Omnipay repository.
toyyibPay Category & Bill
Each payment is a Bill
, which is under a Category
. To begin, you need to open an account at toyyibPay.com and then create a Category. Then, retrieve the Category Code
for the gateway setup.
Quick Start
use Omnipay\Omnipay; // Initialize the gateway $gateway = Omnipay::create('ToyyibPay'); $gateway->setTestMode(true); // Use false for production // Create a simple payment $response = $gateway->purchase([ 'userSecretKey' => 'your-secret-key', 'categoryCode' => 'your-category-code', 'billName' => 'Product Purchase', 'billDescription' => 'Payment for product XYZ', 'billAmount' => 50.00, // RM 50.00 'billReturnUrl' => 'https://yoursite.com/return', 'billCallbackUrl' => 'https://yoursite.com/callback', 'billExternalReferenceNo' => 'ORDER-' . time(), 'billTo' => 'John Doe', 'billEmail' => 'john@example.com', 'billPhone' => '0123456789' ])->send(); if ($response->isRedirect()) { $response->redirect(); } else { echo 'Error: ' . $response->getMessage(); }
Advanced Configuration
Complete Payment with All Options
use Omnipay\Omnipay; $gateway = Omnipay::create('ToyyibPay'); $gateway->setTestMode(true); $response = $gateway->purchase([ // Required parameters 'userSecretKey' => 'your-secret-key', 'categoryCode' => 'your-category-code', 'billName' => 'Premium Product Package', 'billDescription' => 'Complete package with premium features', 'billAmount' => 299.99, 'billReturnUrl' => 'https://yoursite.com/payment/return', 'billCallbackUrl' => 'https://yoursite.com/payment/callback', 'billExternalReferenceNo' => 'ORD-' . date('YmdHis') . '-' . rand(1000, 9999), 'billTo' => 'Jane Smith', 'billEmail' => 'jane.smith@example.com', 'billPhone' => '+60123456789', // Optional parameters 'billPriceSetting' => 0, // 0=fixed, 1=open amount 'billPayorInfo' => 1, // 0=optional, 1=required 'billSplitPayment' => 0, // 0=disabled, 1=enabled 'billSplitPaymentArgs' => '', // JSON string for split payment 'billPaymentChannel' => 0, // 0=all, 1=FPX only, 2=credit card only 'billDisplayMerchant' => 1, // 0=hide, 1=show merchant info 'billContentEmail' => 'Thank you for your purchase! Your order will be processed within 24 hours.', 'billChargeToCustomer' => 2, // 1=merchant, 2=customer pays fees // Custom fields (JSON encoded) 'billAdditionalField' => [ 'product_id' => 'PROD-12345', 'customer_tier' => 'premium', 'promotion_code' => 'SAVE20', 'notes' => 'Express delivery requested' ] ])->send(); if ($response->isRedirect()) { // Store transaction details before redirect $_SESSION['transaction_id'] = $response->getTransactionReference(); $_SESSION['order_id'] = $options['billExternalReferenceNo']; $response->redirect(); } else { throw new Exception('Payment creation failed: ' . $response->getMessage()); }
Split Payment Configuration
// Example for split payment between multiple merchants $splitPaymentArgs = [ [ 'userSecretKey' => 'merchant1-secret-key', 'amount' => 150.00 ], [ 'userSecretKey' => 'merchant2-secret-key', 'amount' => 149.99 ] ]; $response = $gateway->purchase([ 'userSecretKey' => 'main-merchant-secret-key', 'categoryCode' => 'your-category-code', 'billName' => 'Marketplace Order', 'billDescription' => 'Order from multiple vendors', 'billAmount' => 299.99, 'billSplitPayment' => 1, 'billSplitPaymentArgs' => json_encode($splitPaymentArgs), // ... other required parameters ])->send();
Custom Payment Channels
// FPX (Online Banking) only $response = $gateway->purchase([ 'billPaymentChannel' => 1, // FPX only // ... other parameters ]); // Credit/Debit Card only $response = $gateway->purchase([ 'billPaymentChannel' => 2, // Card only // ... other parameters ]); // All payment methods (default) $response = $gateway->purchase([ 'billPaymentChannel' => 0, // All methods // ... other parameters ]);
Example
How to create a Bill
Reference: https://toyyibpay.com/apireference/#cb
Bill serves as an invoice to your customer. The example below explains how you can create a toyyibPay Bill and then redirect the customer to the payment page.
$gateway = Omnipay::create('ToyyibPay'); // Use sandbox mode for testing $gateway->setTestMode(1); // You need to pass the following parameters to generate bill code $options = [ 'userSecretKey' => 'YOURSECRETKEY', 'categoryCode' => 'CATEGORYCODE', 'billName' => 'Product Name', 'billDescription' => 'Product Description', 'billPriceSetting'=> 0, 'billPayorInfo'=> 1, 'billAmount'=> 1, 'billReturnUrl'=>'https://yourwebsite.com/returnurl', 'billCallbackUrl'=>'https://yourwebsite.com/callbackurl', 'billExternalReferenceNo' => 'ORDER123', 'billTo'=>'Customer Name', 'billEmail'=>'customer@sampleemail.test', 'billPhone'=>'0123456789', 'billSplitPayment'=> 0, 'billSplitPaymentArgs'=>'', 'billPaymentChannel'=> 0, 'billDisplayMerchant'=> 1, 'billContentEmail' => 'Sample email content', 'billChargeToCustomer' => 2 ]; // Send a purchase request to create a bill $response = $gateway->purchase($options)->send(); // Available response method // $response->isSuccessful(); // is the response successful? // $response->isRedirect(); // is the response a redirect? // $response->getTransactionReference(); // a reference generated by the payment gateway // $response->getTransactionId(); // the reference set by the originating website if available. // $response->getMessage(); // a message generated by the payment gateway // Now redirect the customer to the toyyibPay bill payment page if ($response->isRedirect()) { // Do whatever you want here for example saving the data to the database etc // and then redirect the customer to the offsite payment gateway $response->redirect(); } else { // Display error message exit($response->getMessage()); }
How to get Bill Transactions
Reference: https://toyyibpay.com/apireference/#gbt
You may check bill payment status by submitting Bill Code and Bill Payment Status(Optional).
The code below gives an example how to retrieve the bill transactions from the payment gateway server:
$gateway = Omnipay::create('ToyyibPay'); // Parameters required $options = [ 'billCode' => 'samplebillcode' ]; // Send a complete purchase request to get the bill transactions $response = $gateway->completePurchase($options)->send(); // Available response method // $response->isSuccessful(); // is the response successful? // $response->isRedirect(); // is the response a redirect? // $response->getTransactionReference(); // a reference generated by the payment gateway // $response->getTransactionId(); // the reference set by the originating website if available. // $response->getMessage(); // a message generated by the payment gateway if ($response->isSuccessful()) { // Do whatever you want here for example saving the data to the database etc echo $responsive->getTransactionReference(); } elseif ($response->isRedirect()) { // If the payment is not successful, redirect the customer to the payment page for completion $response->redirect(); } else { // Display error message exit($response->getMessage()); }
Error Handling
Comprehensive Error Handling
use Omnipay\Omnipay; use Omnipay\Common\Exception\InvalidRequestException; use Exception; try { $gateway = Omnipay::create('ToyyibPay'); $gateway->setTestMode(true); $response = $gateway->purchase([ 'userSecretKey' => 'your-secret-key', 'categoryCode' => 'your-category-code', 'billName' => 'Test Payment', 'billDescription' => 'Test payment description', 'billAmount' => 10.00, 'billReturnUrl' => 'https://yoursite.com/return', 'billCallbackUrl' => 'https://yoursite.com/callback', 'billExternalReferenceNo' => 'TEST-' . time(), 'billTo' => 'Test Customer', 'billEmail' => 'test@example.com', 'billPhone' => '0123456789' ])->send(); if ($response->isRedirect()) { // Success - redirect to payment page $response->redirect(); } else { // Handle payment creation failure $errorMessage = $response->getMessage(); error_log('toyyibPay payment creation failed: ' . $errorMessage); // Show user-friendly error message throw new Exception('Unable to process payment. Please try again later.'); } } catch (InvalidRequestException $e) { // Handle validation errors (missing required fields, etc.) error_log('toyyibPay validation error: ' . $e->getMessage()); throw new Exception('Payment information is incomplete. Please check your details.'); } catch (Exception $e) { // Handle general errors error_log('toyyibPay general error: ' . $e->getMessage()); throw new Exception('Payment service is temporarily unavailable. Please try again later.'); }
Payment Completion Error Handling
try { $response = $gateway->completePurchase([ 'billCode' => $_GET['billcode'] ?? null ])->send(); if ($response->isSuccessful()) { // Payment successful $transactionId = $response->getTransactionId(); $transactionRef = $response->getTransactionReference(); // Update your database updateOrderStatus($transactionId, 'paid'); // Redirect to success page header('Location: /payment/success?ref=' . $transactionRef); exit; } elseif ($response->isRedirect()) { // Payment still pending - redirect back to payment page $response->redirect(); } else { // Payment failed $errorMessage = $response->getMessage(); error_log('Payment verification failed: ' . $errorMessage); // Redirect to failure page header('Location: /payment/failed?error=' . urlencode($errorMessage)); exit; } } catch (Exception $e) { error_log('Payment completion error: ' . $e->getMessage()); header('Location: /payment/error'); exit; }
Testing
Unit Testing Example
use PHPUnit\Framework\TestCase; use Omnipay\Omnipay; class ToyyibPayGatewayTest extends TestCase { private $gateway; protected function setUp(): void { $this->gateway = Omnipay::create('ToyyibPay'); $this->gateway->setTestMode(true); } public function testPurchaseSuccess() { $options = [ 'userSecretKey' => 'test-secret-key', 'categoryCode' => 'test-category', 'billName' => 'Test Product', 'billDescription' => 'Test Description', 'billAmount' => 50.00, 'billReturnUrl' => 'https://example.com/return', 'billCallbackUrl' => 'https://example.com/callback', 'billExternalReferenceNo' => 'TEST-123', 'billTo' => 'John Doe', 'billEmail' => 'john@example.com', 'billPhone' => '0123456789' ]; $response = $this->gateway->purchase($options)->send(); $this->assertTrue($response->isRedirect()); $this->assertFalse($response->isSuccessful()); $this->assertNotEmpty($response->getRedirectUrl()); } public function testPurchaseWithMissingParameters() { $this->expectException(InvalidRequestException::class); $response = $this->gateway->purchase([ 'billName' => 'Test Product' // Missing required parameters ])->send(); } public function testCompletePurchase() { $response = $this->gateway->completePurchase([ 'billCode' => 'test-bill-code' ])->send(); // Response should be either successful, redirect, or have error message $this->assertTrue( $response->isSuccessful() || $response->isRedirect() || !empty($response->getMessage()) ); } }
Integration Testing with Mock Data
// Test with sandbox environment $gateway = Omnipay::create('ToyyibPay'); $gateway->setTestMode(true); // Always use test mode for testing // Use test credentials (get these from toyyibPay sandbox) $testOptions = [ 'userSecretKey' => 'sandbox-secret-key', 'categoryCode' => 'sandbox-category-code', 'billName' => 'Test Payment - ' . date('Y-m-d H:i:s'), 'billDescription' => 'Integration test payment', 'billAmount' => 1.00, // Use small amount for testing 'billReturnUrl' => 'https://httpbin.org/get?return=true', 'billCallbackUrl' => 'https://httpbin.org/post', 'billExternalReferenceNo' => 'TEST-' . uniqid(), 'billTo' => 'Test Customer', 'billEmail' => 'test@example.com', 'billPhone' => '0123456789' ]; $response = $gateway->purchase($testOptions)->send(); if ($response->isRedirect()) { echo "Test payment created successfully!\n"; echo "Payment URL: " . $response->getRedirectUrl() . "\n"; echo "Bill Code: " . $response->getTransactionReference() . "\n"; } else { echo "Test failed: " . $response->getMessage() . "\n"; }
Security Best Practices
1. Secure Credential Management
// Use environment variables for sensitive data $gateway = Omnipay::create('ToyyibPay'); $gateway->setTestMode(getenv('TOYYIBPAY_TEST_MODE') === 'true'); $options = [ 'userSecretKey' => getenv('TOYYIBPAY_SECRET_KEY'), // Never hardcode 'categoryCode' => getenv('TOYYIBPAY_CATEGORY_CODE'), // ... other options ];
2. Validate Callback Data
// In your callback handler function handleCallback() { // Verify the callback is from toyyibPay $billCode = $_POST['billcode'] ?? null; $status = $_POST['status_id'] ?? null; if (!$billCode || !$status) { http_response_code(400); exit('Invalid callback data'); } // Verify payment status with toyyibPay API $gateway = Omnipay::create('ToyyibPay'); $response = $gateway->completePurchase(['billCode' => $billCode])->send(); if ($response->isSuccessful()) { // Update order status in your database updateOrderStatus($billCode, 'paid'); // Send confirmation email sendPaymentConfirmation($billCode); } // Always respond with 200 OK to acknowledge receipt http_response_code(200); echo 'OK'; }
3. Input Validation and Sanitization
function createPayment($orderData) { // Validate and sanitize input data $billAmount = filter_var($orderData['amount'], FILTER_VALIDATE_FLOAT); if ($billAmount === false || $billAmount <= 0) { throw new InvalidArgumentException('Invalid amount'); } $billEmail = filter_var($orderData['email'], FILTER_VALIDATE_EMAIL); if ($billEmail === false) { throw new InvalidArgumentException('Invalid email address'); } $billPhone = preg_replace('/[^0-9+]/', '', $orderData['phone']); if (strlen($billPhone) < 10) { throw new InvalidArgumentException('Invalid phone number'); } // Sanitize text fields $billName = htmlspecialchars(trim($orderData['product_name']), ENT_QUOTES, 'UTF-8'); $billTo = htmlspecialchars(trim($orderData['customer_name']), ENT_QUOTES, 'UTF-8'); return [ 'billAmount' => $billAmount, 'billEmail' => $billEmail, 'billPhone' => $billPhone, 'billName' => $billName, 'billTo' => $billTo ]; }
Troubleshooting
Common Issues and Solutions
1. "Invalid Secret Key" Error
Problem: Getting authentication errors when creating bills.
Solutions:
- Verify your secret key is correct and active
- Check if you're using the right environment (sandbox vs production)
- Ensure the secret key matches the category code
// Debug: Check your credentials $gateway = Omnipay::create('ToyyibPay'); $gateway->setTestMode(true); // Make sure this matches your secret key environment // Test with minimal required parameters first $response = $gateway->purchase([ 'userSecretKey' => 'your-secret-key', 'categoryCode' => 'your-category-code', 'billName' => 'Debug Test', 'billDescription' => 'Testing credentials', 'billAmount' => 1.00, 'billReturnUrl' => 'https://example.com/return', 'billCallbackUrl' => 'https://example.com/callback', 'billExternalReferenceNo' => 'DEBUG-' . time(), 'billTo' => 'Test', 'billEmail' => 'test@example.com', 'billPhone' => '0123456789' ])->send();
2. Callback Not Working
Problem: Not receiving payment notifications.
Solutions:
- Ensure callback URL is publicly accessible (not localhost)
- Check server logs for incoming requests
- Verify callback URL returns HTTP 200 status
- Test callback URL manually
// Debug callback handler function debugCallback() { // Log all incoming data error_log('Callback received: ' . print_r($_POST, true)); error_log('Headers: ' . print_r(getallheaders(), true)); // Always respond with 200 http_response_code(200); echo 'OK'; }
3. Amount Formatting Issues
Problem: Incorrect amount being charged.
Solution: toyyibPay expects amount in cents (multiply by 100).
// Correct amount formatting $amountInRM = 50.00; // RM 50.00 $response = $gateway->purchase([ 'billAmount' => $amountInRM, // Library handles conversion automatically // ... other parameters ]); // The library automatically converts RM to cents internally // RM 50.00 becomes 5000 cents
4. SSL/TLS Issues
Problem: Connection errors when making API calls.
Solutions:
- Ensure your server supports TLS 1.2 or higher
- Update cURL and OpenSSL libraries
- Check firewall settings
// Test SSL connectivity $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'https://toyyibpay.com/'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 30); $result = curl_exec($ch); $error = curl_error($ch); curl_close($ch); if ($error) { echo "SSL Error: " . $error; } else { echo "SSL connection successful"; }
Debug Mode
// Enable debug logging $gateway = Omnipay::create('ToyyibPay'); $gateway->setTestMode(true); // Log all requests and responses class DebugHttpClient implements \Http\Client\HttpClient { private $client; public function __construct($client) { $this->client = $client; } public function sendRequest($request) { error_log('Request: ' . $request->getMethod() . ' ' . $request->getUri()); error_log('Body: ' . $request->getBody()); $response = $this->client->sendRequest($request); error_log('Response: ' . $response->getStatusCode()); error_log('Response Body: ' . $response->getBody()); return $response; } } // Use debug client $gateway->setHttpClient(new DebugHttpClient($gateway->getHttpClient()));
API Reference
Gateway Methods
purchase(array $options)
Creates a new bill for payment.
Required Parameters:
userSecretKey
(string): Your toyyibPay secret keycategoryCode
(string): Category code from your toyyibPay accountbillName
(string): Name/title of the billbillDescription
(string): Description of the paymentbillAmount
(float): Amount in RM (e.g., 50.00 for RM 50)billReturnUrl
(string): URL to redirect after paymentbillCallbackUrl
(string): URL for payment notificationsbillExternalReferenceNo
(string): Your internal reference numberbillTo
(string): Customer namebillEmail
(string): Customer emailbillPhone
(string): Customer phone number
Optional Parameters:
billPriceSetting
(int): 0=fixed amount, 1=open amountbillPayorInfo
(int): 0=optional customer info, 1=requiredbillSplitPayment
(int): 0=disabled, 1=enabledbillSplitPaymentArgs
(string): JSON string for split payment configurationbillPaymentChannel
(int): 0=all methods, 1=FPX only, 2=cards onlybillDisplayMerchant
(int): 0=hide merchant info, 1=showbillContentEmail
(string): Custom email contentbillChargeToCustomer
(int): 1=merchant pays fees, 2=customer pays feesbillAdditionalField
(array): Custom fields (will be JSON encoded)
completePurchase(array $options)
Retrieves bill transaction status.
Required Parameters:
billCode
(string): Bill code returned from purchase request
Optional Parameters:
billpaymentStatus
(int): Filter by payment status
Response Methods
Purchase Response
isRedirect()
: Returns true if customer should be redirectedgetRedirectUrl()
: Gets the payment page URLgetTransactionReference()
: Gets the bill codegetMessage()
: Gets error message if failed
Complete Purchase Response
isSuccessful()
: Returns true if payment was successfulisRedirect()
: Returns true if payment is still pendinggetTransactionId()
: Gets the payment invoice numbergetTransactionReference()
: Gets the bill permalinkgetMessage()
: Gets status message
Payment Status Codes
1
: Successful transaction2
: Pending transaction3
: Unsuccessful transaction4
: Pending (alternative status)
Support
If you are having general issues with Omnipay, we suggest posting on Stack Overflow. Be sure to add the omnipay tag so it can be easily found.
If you want to keep up to date with release anouncements, discuss ideas for the project, or ask more detailed questions, there is also a mailing list which you can subscribe to.
If you believe you have found a bug, please report it using the GitHub issue tracker, or better yet, fork the library and submit a pull request.