├── .github └── workflows │ └── ci-cd.yml ├── .gitignore ├── README.md ├── composer.json ├── composer.lock ├── phpstan.neon ├── phpunit.xml ├── phpunit.xml.bak ├── src ├── Events │ ├── ApprovedRefundTransaction.php │ ├── ApprovedSaleTransaction.php │ ├── ApprovedTransaction.php │ ├── ApprovedVoidRefundTransaction.php │ ├── ApprovedVoidSaleTransaction.php │ ├── BaseTransactionEvent.php │ ├── DisallowedRequestEvent.php │ ├── UnverfiedTransaction.php │ └── VerfiedTransaction.php ├── Http │ ├── Controllers │ │ ├── ConfigController.php │ │ └── NotificationController.php │ ├── Middleware │ │ └── AllowedIps.php │ └── Requests │ │ └── GenerateSecureKeyRequest.php ├── Models │ └── MoamalatPayNotification.php ├── Pay.php ├── Providers │ └── MoamalatPayProvider.php ├── Refund.php ├── Transaction.php ├── View │ └── Components │ │ └── Pay.php ├── config │ └── moamalat-pay.php ├── database │ ├── factories │ │ └── MoamalatPayNotificationFactory.php.backup │ └── migrations │ │ └── 2022_09_12_000000_create_moamalat_pay_notifications_table.php ├── routes │ └── api.php └── views │ └── pay.blade.php └── tests ├── Feature ├── ConfigAPITest.php ├── GetTransactionTest.php ├── NotificationsAPITest.php ├── PayTest.php └── RefundTest.php ├── TestCase.php ├── Unit └── ExampleTest.php └── _fixtures └── transactions └── verfied.json /.github/workflows/ci-cd.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for Testing Laravel Package 2 | 3 | name: CI & Testing 4 | 5 | on: 6 | push: 7 | branches: [main] 8 | pull_request: 9 | branches: [main] 10 | 11 | jobs: 12 | laravel-ci: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | php-versions: ["8.2", "8.3", "8.4"] 19 | 20 | steps: 21 | - name: Setup PHP, with composer and extensions 22 | uses: shivammathur/setup-php@v2 23 | with: 24 | php-version: ${{ matrix.php-versions }} 25 | extensions: mbstring, dom, fileinfo 26 | coverage: xdebug #optional 27 | 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | 31 | - name: Show php version 32 | run: php -v 33 | 34 | - name: Install Dependencies 35 | run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist 36 | 37 | - name: Static Analysis via PHPStan 38 | run: composer analyse 39 | 40 | - name: Test via PHPUnit 41 | run: composer test 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | .phpunit.result.cache 3 | .phpunit.cache/test-results 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Moamalat Pay 2 | 3 | [![Build Status](https://github.com/alifaraun/laravel-moamalat-pay/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/alifaraun/laravel-moamalat-pay/actions) 4 | [![Latest Stable Version](https://poser.pugx.org/alifaraun/laravel-moamalat-pay/v/stable.svg)](https://packagist.org/packages/alifaraun/laravel-moamalat-pay) 5 | [![Total Downloads](https://poser.pugx.org/alifaraun/laravel-moamalat-pay/downloads.svg)](https://packagist.org/packages/alifaraun/laravel-moamalat-pay) 6 | [![License](https://poser.pugx.org/alifaraun/laravel-moamalat-pay/license)](https://packagist.org/packages/alifaraun/laravel-moamalat-pay) 7 | 8 | This package allows you to easily use Moamalat [ligthbox](https://docs.moamalat.net/lightBox.html) to add payment gateway in your laravel project. 9 | 10 | --- 11 | 12 | **NOTE** 13 | This is not official library from Moamalat , It is just an open source Library. 14 | 15 | --- 16 | 17 | ## Table of contents 18 | 19 | - [Installation](#installation) 20 | - [Configuration File](#configuration-file) 21 | - [Configuration for testing purpose](#configuration-for-testing-purpose) 22 | - [Merchant](#merchant) 23 | - [Card](#card) 24 | - [Usage](#usage) 25 | - [Laravel componet](#laravel-componet) 26 | - [Laravel methods](#laravel-methods) 27 | - [Front end Javascript](#front-end-javascript) 28 | - [Check processing status](#check-processing-status) 29 | - [Get Transaction in back-end](#get-transaction-in-back-end) 30 | - [Examples](#examples) 31 | - [Notifications Service (Webhook)](#notifications-service-webhook) 32 | - [Available Scopes](#available-scopes) 33 | - [Events](#events) 34 | - [Refund and Void Transactions](#refund-and-void-transactions) 35 | - [Examples](#examples-1) 36 | - [Testing](#testing) 37 | - [Security](#security) 38 | - [Credits](#credits) 39 | - [License](#license) 40 | 41 | ## Installation 42 | 43 | You can install the package via composer: 44 | 45 | ```bash 46 | composer require alifaraun/laravel-moamalat-pay 47 | ``` 48 | 49 | **Laravel Version Compatibility** 50 | 51 | | Laravel | Package | install command | 52 | | :------ | :------ | :------------------------------------------------------- | 53 | | 12.x.x | 6.x | `composer require alifaraun/laravel-moamalat-pay "^6.0"` | 54 | | 11.x.x | 5.x | `composer require alifaraun/laravel-moamalat-pay "^5.0"` | 55 | | 10.x.x | 4.x | `composer require alifaraun/laravel-moamalat-pay "^4.0"` | 56 | | 9.x.x | 3.x | `composer require alifaraun/laravel-moamalat-pay "^3.0"` | 57 | | 8.x.x | 2.x | `composer require alifaraun/laravel-moamalat-pay "^2.0"` | 58 | | ^7.25.0 | 1.x | `composer require alifaraun/laravel-moamalat-pay "^1.0"` | 59 | 60 | If you want to customize configurations: 61 | 62 | ```bash 63 | php artisan vendor:publish --tag="moamalat-pay" 64 | ``` 65 | 66 | ## Configuration File 67 | 68 | The configuration file **moamalat-pay.php** is located in the **config** folder. Following are its contents when published: 69 | 70 | ```php 71 | 72 | return [ 73 | /* 74 | |-------------------------------------------------------------------------- 75 | | Moamalat Payment Gateway Config 76 | |-------------------------------------------------------------------------- 77 | | 78 | | These options to set your configurations of muamalat 79 | | 80 | */ 81 | 82 | // MID => merchant_id or outlet_number 83 | 'merchant_id' => env('MOAMALATPAY_MID'), 84 | 85 | // TID => terminal_id 86 | 'terminal_id' => env('MOAMALATPAY_TID'), 87 | 88 | // Secure key 89 | 'key' => env('MOAMALATPAY_KEY'), 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | Production 94 | |-------------------------------------------------------------------------- 95 | | 96 | | If the production is set to "true", you will work on production environment 97 | | otherwise it will use testing environment 98 | | 99 | */ 100 | 'production' => env('MOAMALATPAY_PRODUCTION', false), 101 | 102 | /* 103 | |-------------------------------------------------------------------------- 104 | | Show 105 | |-------------------------------------------------------------------------- 106 | | 107 | | If the show_logs is set to "true", you will see configurations 108 | | and response of requests in browser console 109 | | 110 | */ 111 | 'show_logs' => false, 112 | 113 | /* 114 | |-------------------------------------------------------------------------- 115 | | Enable Cancel Event On Success 116 | |-------------------------------------------------------------------------- 117 | | 118 | | If the enable_cancel_event_on_success is set to "true", the cancel event will be triggered even if the payment is successful 119 | | 120 | */ 121 | 'enable_cancel_event_on_success' => true, 122 | 123 | /* 124 | |-------------------------------------------------------------------------- 125 | | Generate Secure Hash api 126 | |-------------------------------------------------------------------------- 127 | | 128 | | This is service (api) to generate secureHash to be used in pay in Lightbox. 129 | | 130 | | url is route of api of generate secureHash 131 | | 132 | | route_name is name of route of api of generate secureHash 133 | | 134 | */ 135 | 'generate-securekey' => [ 136 | 'url' => 'moamalat-pay/securekey', 137 | 'route_name' => 'moamalat_pay.generate_securekey', 138 | ], 139 | 140 | 141 | /* 142 | |-------------------------------------------------------------------------- 143 | | Notification (Webhook) api 144 | |-------------------------------------------------------------------------- 145 | | 146 | | This is service from moamalat on any transaction you will receive notification 147 | | on api (webhook) 148 | | 149 | | key is your private notification key to use it in validate transaction requests 150 | | 151 | | url is route to receive notification 152 | | 153 | | table is name of table that will be used to save notifications 154 | | 155 | | allowed_ips are ips that will receive notification from them 156 | | ['*'] means receive from any ip but it is not secure to receive notifcations from anyone 157 | | you should ask moamalat on ips of their servers and use them 158 | | 159 | */ 160 | 'notification' => [ 161 | 'key' => env('MOAMALATPAY_NOTIFICATION_KEY'), 162 | 'url' => 'moamalat-pay/notify', 163 | 'route_name' => 'moamalat_pay.notification', 164 | 'table' => 'moamalat_pay_notifications', 165 | 'allowed_ips' => ['*'], 166 | ] 167 | ]; 168 | ``` 169 | 170 | set your configurations in `.env` file: 171 | 172 | ```bash 173 | MOAMALATPAY_MID= 174 | MOAMALATPAY_TID= 175 | MOAMALATPAY_KEY= 176 | MOAMALATPAY_NOTIFICATION_KEY= 177 | MOAMALATPAY_PRODUCTION= 178 | ``` 179 | 180 | ## Configuration for testing purpose 181 | 182 | ### Merchant 183 | 184 | ```bash 185 | Merchant id : 10081014649 186 | Terminal Id : 99179395 187 | Secure key : 3a488a89b3f7993476c252f017c488bb 188 | ``` 189 | 190 | ### Card 191 | 192 | ```bash 193 | Card : 6395043835180860 194 | Card : 6395043165725698 195 | Card : 6395043170446256 196 | EXP : 01/27 197 | OTP : 111111 198 | ``` 199 | 200 | ## Usage 201 | 202 | #### Laravel componet 203 | 204 | ```blade 205 | // Initialize pay 206 | 207 | 208 | // when pass amount property, it will show pay form directly 209 | 210 | ``` 211 | 212 | #### Laravel methods 213 | 214 | To call it using methods 215 | 216 | ```php 217 | // Initialize pay 218 | app('moamalat-pay')->init(); 219 | 220 | // call pay 221 | // $amount int libyan dirham not dinar 222 | //$reference is optional 223 | app('moamalat-pay')->pay(int $amount,string $reference = ''); 224 | ``` 225 | 226 | #### Front end Javascript 227 | 228 | To call pay from js 229 | 230 | ```js 231 | _moamalatPay.pay(int amount,string reference = '') 232 | ``` 233 | 234 | #### Check processing status 235 | 236 | Available events to check if operation success or fail 237 | 238 | ```js 239 | addEventListener("moamalatCompleted", function (e) { 240 | e.detail; // response data 241 | /* e.detail 242 | { 243 | "TxnDate": "250303164850", 244 | "SystemReference": "1301679", 245 | "NetworkReference": "506216680586", 246 | "MerchantReference": "test-demo", 247 | "Amount": "1000", 248 | "Currency": "434", 249 | "PaidThrough": "Card", 250 | "PayerAccount": "639504XXXXXX3733", 251 | "PayerName": "ALI", 252 | "ProviderSchemeName": "", 253 | "SecureHash": "915B22C4E2A96DB03F755D4254F65C87486AE3B1C19B9BCDE9481836832F4822", 254 | "CardToken": null, 255 | "CustomerId": null 256 | } 257 | */ 258 | }); 259 | 260 | addEventListener("moamalatError", function (e) { 261 | e.detail; // response data 262 | /* e.detail 263 | { 264 | "error": "CUBEEX5212216:Authentication Failed", 265 | "Amount": "200.031", 266 | "MerchantReferenece": "", 267 | "DateTimeLocalTrxn": "220818232732", 268 | "SecureHash": "1C8B1301AD4C00BE66EC25FD45A81D0C4030C79EF53CA903FA5009ECCAD08D46" 269 | } 270 | */ 271 | }); 272 | 273 | addEventListener("moamalatCancel", function () {}); 274 | ``` 275 | 276 | #### Get Transaction in back-end 277 | 278 | ```php 279 | use MoamalatPay\Transaction; 280 | 281 | // get transaction from NPG(moamalat) 282 | $transaction = new Transaction($networkReference, $merchantReference); 283 | // Throws an exception if there is a problem in loading the transaction 284 | 285 | /** available methods to interact with transaction **/ 286 | 287 | /** 288 | * Get all properties of transaction 289 | * @return Array 290 | */ 291 | $transaction->getAll(); 292 | 293 | /** 294 | * Get property of transaction 295 | * @param $property key 296 | * @return mixed 297 | */ 298 | $transaction->get($property); 299 | 300 | 301 | /** 302 | * Get property of reponse , if property not exists return default value 303 | * 304 | * @param $property 305 | * @param $default 306 | * @return mixed 307 | */ 308 | $transaction->getWithDefault($property, $default = null); 309 | 310 | /** 311 | * Get all properties of reponse 312 | * @return Array 313 | */ 314 | $transaction->getResponse(); 315 | 316 | /** 317 | * Check status of transaction is Approved 318 | * 319 | * @param $amount 320 | * @param $card 321 | * @return boolean 322 | */ 323 | $transaction->checkApproved($amount = null, $card = null); 324 | 325 | ``` 326 | 327 | ##### Examples 328 | 329 | ```php 330 | use MoamalatPay\Transaction; 331 | 332 | // get transaction from NPG(moamalat) 333 | $transaction = new Transaction("223414600869","1641729671"); 334 | 335 | 336 | $transaction->getAll(); 337 | /* return 338 | [ 339 | "TransactionChannel" => "Card", 340 | "CardNo" => "639504XXXXXX0860", 341 | "SenderName" => "OMAR", 342 | "CardType" => null, 343 | "Amnt" => "1000", 344 | "AmountTrxn" => "1000", 345 | "FeeAmnt" => "0.000", 346 | "TipAmnt" => "0.000", 347 | "Status" => "Approved", 348 | "Currency" => "LYD", 349 | "TransType" => "Sale", 350 | "IsPointTrasnaction" => false, 351 | "STAN" => "625396", 352 | "RRN" => "506118625396", 353 | "MerchantReference" => "test-demo", 354 | "ReceiptNo" => "506118625396", 355 | "MobileNumber" => null, 356 | "TransactionId" => "1301669", 357 | "ExternalTxnId" => null, 358 | "TxnDateTime" => "02/03/25 18=>25", 359 | "IsRefundEnabled" => true, 360 | "ResCodeDesc" => "Approved", 361 | "IsSend" => false, 362 | "ISForceSendCVCForRefund" => true, 363 | "HasToken" => true, 364 | "AuthCode" => null, 365 | "RefundButton" => 0, 366 | "RemainingRefundAmount" => "1000.000", 367 | "TerminalId" => 49077229, 368 | "IsRefund" => false, 369 | "RefundReason" => "", 370 | "RefundSource" => "", 371 | "RefundUserCreator" => "", 372 | "OriginalTxnId" => "", 373 | "TxnIcon" => 2, 374 | "IsMustVoidTotalAmount" => false, 375 | "IsCardPresent" => false, 376 | "RelatedTxnTotalAmount" => null, 377 | "UserDefinedData" => [], 378 | "InvoiceServiceSummary" => null 379 | ] 380 | */ 381 | 382 | $transaction->get('CardNo'); 383 | // return 639504XXXXXX0860 384 | 385 | $transaction->getWithDefault('Card','card-not-found'); 386 | // return card-not-found 387 | 388 | $transaction->checkApproved(); 389 | // if transaction status is Approved it will return true 390 | 391 | $transaction->checkApproved(10000,'639504XXXXXX0860'); 392 | // if transaction is status is Approved , amount=10000 and CardNo=639504XXXXXX0860 it will return true 393 | ``` 394 | 395 | ### Notifications Service (Webhook) 396 | 397 | Notification Services allow merchants to receive notifications whenever a transaction is generated for their accounts 398 | 399 | We save all notifications in database with fire events depends on transaction type and status 400 | 401 | ```php 402 | /* 403 | MoamalatPay\Models\MoamalatPayNotification \\ Eloquent Model 404 | \\ properites 405 | id 406 | MerchantId 407 | TerminalId 408 | DateTimeLocalTrxn 409 | TxnType 410 | Message 411 | PaidThrough 412 | SystemReference 413 | NetworkReference 414 | MerchantReference 415 | Amount 416 | Currency 417 | PayerAccount 418 | PayerName 419 | ActionCode 420 | request 421 | ip 422 | verified 423 | created_at 424 | */ 425 | ``` 426 | 427 | #### Available Scopes 428 | 429 | ```php 430 | // filter to get approved transactions (ActionCode = 00) 431 | MoamalatPay\Models\MoamalatPayNotification::approved() 432 | 433 | // filter to get verified transactions (verified = 1) 434 | MoamalatPay\Models\MoamalatPayNotification::verified() 435 | 436 | // filter to get transactions for currency terminal_id and merchant_id in config 437 | MoamalatPay\Models\MoamalatPayNotification::currentCredential() 438 | ``` 439 | 440 | #### Events 441 | 442 | Example of how to add listener , check [laravel documentation](https://laravel.com/docs/events) for more info 443 | 444 | ```php 445 | // event will be fired when receive request from ip not exists in allowed_ips in config of moamalat-pay 446 | Event::listen(function (MoamalatPay\Events\DisallowedRequestEvent $event) { 447 | }); 448 | 449 | // event will be fired after check secureHas is unverified 450 | Event::listen(function (MoamalatPay\Events\UnverfiedTransaction $event) { 451 | $event->notification // Eloquent Model of transaction 452 | }); 453 | 454 | // event will be fired after check secureHas is verified 455 | Event::listen(function (MoamalatPay\Events\VerfiedTransaction $event) { 456 | $event->notification // Eloquent Model of transaction 457 | }); 458 | 459 | // event will be fired after check secureHas is verified and transaction status is approved 460 | Event::listen(function (MoamalatPay\Events\ApprovedTransaction $event) { 461 | $event->notification // Eloquent Model of transaction 462 | }); 463 | 464 | // event will be fired after check secureHas is verified and transaction status is approved 465 | // and type of transaction is : 1: Sale 466 | Event::listen(function (MoamalatPay\Events\ApprovedSaleTransaction $event) { 467 | $event->notification // Eloquent Model of transaction 468 | }); 469 | 470 | // event will be fired after check secureHas is verified and transaction status is approved 471 | // and type of transaction is : 2: Refund 472 | Event::listen(function (MoamalatPay\Events\ApprovedRefundTransaction $event) { 473 | $event->notification // Eloquent Model of transaction 474 | }); 475 | 476 | // event will be fired after check secureHas is verified and transaction status is approved 477 | // and type of transaction is : 3: Void Sale 478 | Event::listen(function (MoamalatPay\Events\ApprovedVoidSaleTransaction $event) { 479 | $event->notification // Eloquent Model of transaction 480 | }); 481 | 482 | // event will be fired after check secureHas is verified and transaction status is approved 483 | // and type of transaction is : 4: Void Refund 484 | Event::listen(function (MoamalatPay\Events\ApprovedVoidRefundTransaction $event) { 485 | $event->notification // Eloquent Model of transaction 486 | }); 487 | 488 | ``` 489 | 490 | #### Refund and Void Transactions 491 | 492 | When the refund is called before settlement (usually settlement at the end of the day), it will be void, otherwise it will be refunded 493 | 494 | ```php 495 | 496 | /** 497 | * Refund transaction by system reference of transaction 498 | * @param string|integer $systemReference 499 | * @param string|integer $amount 500 | * @return array content response of moamalat 501 | */ 502 | app('moamalat-pay-refund')->refundBySystemReference($systemReference, $amount)->getAll() 503 | // Throws an exception if there is a problem in refund the transaction 504 | 505 | 506 | /** 507 | * Refund transaction by network reference of transaction 508 | * @param string|integer $networkReference 509 | * @param string|integer $amount 510 | * @return array content response of moamalat 511 | */ 512 | app('moamalat-pay-refund')->refundByNetworkReference($networkReference, $amount)->getAll() 513 | // Throws an exception if there is a problem in refund the transaction 514 | 515 | /* response : return of getAll() method 516 | { 517 | "SystemTxnId": 0, 518 | "ActionCode": null, 519 | "AuthCode": null, 520 | "ExternalTxnId": null, 521 | "RefNumber": "1301681", // System reference for the new refund transaction 522 | "TxnDate": null, 523 | "ReceiptNumber": null, 524 | "ReceiverAccountNumber": null, 525 | "ReceiverName": null, 526 | "ReceiverScheme": null, 527 | "MerchantReference": null, 528 | "SystemReference": 0, 529 | "NetworkReference": null, 530 | "IsEnableRefund": false, 531 | "DecimalFraction": 3, 532 | "IsYallaRefundPending": false, 533 | "TransactionId": null, 534 | "ReferenceId": null, 535 | "Success": true, 536 | "Message": "Approved", 537 | "SecureHashData": null, 538 | "SecureHash": null, 539 | "StatusCode": 200 540 | } 541 | */ 542 | ``` 543 | 544 | ##### Examples 545 | 546 | ```php 547 | $r = app('moamalat-pay-refund')->refundBySystemReference("1233114", "10"); 548 | // or 549 | $r = app('moamalat-pay-refund')->refundByNetworkReference("223414600869", "10"); 550 | 551 | // will return instance of MoamalatPay\Refund class 552 | 553 | /** 554 | * Get all properties of reponse 555 | * @return array 556 | */ 557 | $r->getAll(); 558 | /* response 559 | { 560 | "SystemTxnId": 0, 561 | "ActionCode": null, 562 | "AuthCode": null, 563 | "ExternalTxnId": null, 564 | "RefNumber": "1301681", // System reference for the new refund transaction 565 | "TxnDate": null, 566 | "ReceiptNumber": null, 567 | "ReceiverAccountNumber": null, 568 | "ReceiverName": null, 569 | "ReceiverScheme": null, 570 | "MerchantReference": null, 571 | "SystemReference": 0, 572 | "NetworkReference": null, 573 | "IsEnableRefund": false, 574 | "DecimalFraction": 3, 575 | "IsYallaRefundPending": false, 576 | "TransactionId": null, 577 | "ReferenceId": null, 578 | "Success": true, 579 | "Message": "Approved", 580 | "SecureHashData": null, 581 | "SecureHash": null, 582 | "StatusCode": 200 583 | } 584 | */ 585 | 586 | /** 587 | * Get property of transaction 588 | * @param $property key 589 | * @return mixed 590 | */ 591 | $r->get($property); 592 | $r->get('Message'); 593 | // return Approved 594 | 595 | 596 | /** 597 | * Get property of reponse , if property not exists return default value 598 | * 599 | * @param $property 600 | * @param $default 601 | * @return mixed 602 | */ 603 | $r->getWithDefault($property, $default = null); 604 | $r->getWithDefault('Card', 'No Card'); 605 | // return No Card 606 | 607 | 608 | /** 609 | * Get SystemReference of new refund transaction 610 | * @return string|integer 611 | */ 612 | $r->getRefNumber(); 613 | // return 1233678 614 | 615 | ``` 616 | 617 | ## Testing 618 | 619 | Run the tests with: 620 | 621 | ``` 622 | composer test 623 | // or 624 | ./vendor/bin/phpunit 625 | ``` 626 | 627 | Run Static Analysis Tool (PHPStan) 628 | 629 | ``` 630 | composer analyse 631 | // or 632 | ./vendor/bin/phpstan analyse 633 | ``` 634 | 635 | 640 | 641 | ## Security 642 | 643 | If you discover any security related issues, please email ali1996426@hotmail.com instead of using the issue tracker. 644 | 645 | ## Credits 646 | 647 | - [Ali Faraun](https://github.com/alifaraun) 648 | - [All Contributors](../../contributors) 649 | 650 | ## License 651 | 652 | The MIT License (MIT) 653 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alifaraun/laravel-moamalat-pay", 3 | "type": "library", 4 | "description": "Easy - Moamalat Lightbox integration for Laravel.", 5 | "require": { 6 | "laravel/framework": "^12.0" 7 | }, 8 | "license": "MIT", 9 | "autoload": { 10 | "psr-4": { 11 | "MoamalatPay\\": "src/" 12 | } 13 | }, 14 | "autoload-dev": { 15 | "psr-4": { 16 | "MoamalatPay\\Tests\\": "tests" 17 | } 18 | }, 19 | "scripts": { 20 | "test": "vendor/bin/testbench package:test", 21 | "analyse": "vendor/bin/phpstan analyse --memory-limit=2G", 22 | "lint": [ 23 | "@php vendor/bin/pint", 24 | "@php vendor/bin/phpstan analyse" 25 | ] 26 | }, 27 | "extra": { 28 | "laravel": { 29 | "providers": [ 30 | "MoamalatPay\\Providers\\MoamalatPayProvider" 31 | ] 32 | } 33 | }, 34 | "authors": [ 35 | { 36 | "name": "Ali Faraun", 37 | "email": "ali1996426@hotmail.com" 38 | } 39 | ], 40 | "minimum-stability": "dev", 41 | "require-dev": { 42 | "guzzlehttp/guzzle": "7.9.2", 43 | "nunomaduro/collision": "^8.0", 44 | "laravel/legacy-factories": "1.x-dev", 45 | "laravel/pint": "dev-main", 46 | "larastan/larastan": "3.x-dev", 47 | "orchestra/testbench": "10.x-dev" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - vendor/larastan/larastan/extension.neon 3 | 4 | parameters: 5 | paths: 6 | - src 7 | 8 | # The level 9 is the highest level 9 | level: 5 10 | 11 | ignoreErrors: 12 | # Ignore "env outside of the config directory" errors 13 | - 14 | message: "#Called 'env' outside of the config directory.*#" 15 | paths: 16 | - src/config/moamalat-pay.php 17 | 18 | # Ignore "view-string" type errors 19 | - 20 | message: '#Parameter \#1 \$view of function view expects view-string\|null, string given.*#' 21 | paths: 22 | - src/Pay.php 23 | - src/View/Components/Pay.php 24 | 25 | # Uncomment to exclude specific files 26 | # excludePaths: 27 | # - ./*/*/FileToBeExcluded.php 28 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests/Unit 6 | 7 | 8 | ./tests/Feature 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ./src 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /phpunit.xml.bak: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests/Unit 6 | 7 | 8 | ./tests/Feature 9 | 10 | 11 | 12 | 13 | ./src 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Events/ApprovedRefundTransaction.php: -------------------------------------------------------------------------------- 1 | notification = $notification; 30 | } 31 | 32 | /** 33 | * Get the channels the event should broadcast on. 34 | * 35 | * @return \Illuminate\Broadcasting\Channel|array 36 | */ 37 | public function broadcastOn() 38 | { 39 | return new PrivateChannel('channel-name'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Events/DisallowedRequestEvent.php: -------------------------------------------------------------------------------- 1 | amount; 23 | $merchantReference = $request->merchantReference; 24 | $key = hex2bin(config('moamalat-pay.key')); 25 | $DateTimeLocalTrxn = time(); 26 | $encode_data = "Amount={$amount}&DateTimeLocalTrxn={$DateTimeLocalTrxn}&MerchantId={$MerchantId}&MerchantReference={$merchantReference}&TerminalId={$TerminalId}"; 27 | 28 | return response()->json([ 29 | 'secureHash' => hash_hmac('sha256', $encode_data, $key), 30 | 'DateTimeLocalTrxn' => $DateTimeLocalTrxn, 31 | ]); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Http/Controllers/NotificationController.php: -------------------------------------------------------------------------------- 1 | validate([ 25 | 'MerchantId' => 'nullable', 26 | 'TerminalId' => 'nullable', 27 | 'DateTimeLocalTrxn' => 'nullable', 28 | 'TxnType' => 'nullable', 29 | 'Message' => 'nullable', 30 | 'PaidThrough' => 'nullable', 31 | 'SystemReference' => 'nullable', 32 | 'NetworkReference' => 'nullable', 33 | 'MerchantReference' => 'nullable', 34 | 'Amount' => 'nullable', 35 | 'Currency' => 'nullable', 36 | 'PayerAccount' => 'nullable', 37 | 'PayerName' => 'nullable', 38 | 'ActionCode' => 'nullable', 39 | ]); 40 | 41 | $data['ip'] = $request->ip(); 42 | $data['request'] = json_encode($request->all()); 43 | $data['verified'] = $this->validateSecureHas( 44 | $request->input('SecureHash'), 45 | $request->input('Amount'), 46 | $request->input('Currency'), 47 | $request->input('DateTimeLocalTrxn'), 48 | $request->input('MerchantId'), 49 | $request->input('TerminalId') 50 | ); 51 | 52 | $notification = MoamalatPayNotification::create($data); 53 | 54 | $this->dispatchEvents($notification); 55 | 56 | return response()->json(['Message' => 'Success', 'Success' => true]); 57 | } 58 | 59 | protected function dispatchEvents(MoamalatPayNotification $notification) 60 | { 61 | if ($notification->verified) { 62 | VerfiedTransaction::dispatch($notification); 63 | if (/* $notification->Message == 'Approved' && */ $notification->ActionCode === '00') { // aproved 64 | ApprovedTransaction::dispatch($notification); 65 | switch ($notification->TxnType) { 66 | case '1': 67 | ApprovedSaleTransaction::dispatch($notification); 68 | break; 69 | case '2': 70 | ApprovedRefundTransaction::dispatch($notification); 71 | break; 72 | case '3': 73 | ApprovedVoidSaleTransaction::dispatch($notification); 74 | break; 75 | case '4': 76 | ApprovedVoidRefundTransaction::dispatch($notification); 77 | break; 78 | } 79 | } 80 | } else { 81 | UnverfiedTransaction::dispatch($notification); 82 | } 83 | } 84 | 85 | /** 86 | * Validate if secure has correct to make sure notification is comming from Moamalat 87 | */ 88 | protected function validateSecureHas($secureHash, $Amount, $Currency, $DateTimeLocalTrxn, $MerchantId, $TerminalId) 89 | { 90 | try { 91 | $encode_data = "Amount=$Amount&Currency=$Currency&DateTimeLocalTrxn=$DateTimeLocalTrxn&MerchantId=$MerchantId&TerminalId=$TerminalId"; 92 | $key = pack('H*', config('moamalat-pay.notification.key')); 93 | 94 | return strtoupper(hash_hmac('sha256', $encode_data, $key)) === strtoupper($secureHash); 95 | } catch (Exception $e) { 96 | return false; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Http/Middleware/AllowedIps.php: -------------------------------------------------------------------------------- 1 | ip(), $allowed)) { 20 | DisallowedRequestEvent::dispatch(); 21 | abort(403); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Http/Requests/GenerateSecureKeyRequest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | public function rules(): array 16 | { 17 | return [ 18 | 'MID' => 'required', 19 | 'TID' => 'required', 20 | 'amount' => 'required|integer|min:1', 21 | 'merchantReference' => 'nullable', 22 | ]; 23 | } 24 | 25 | /** 26 | * Get the "after" validation callables for the request. 27 | */ 28 | public function after(): array 29 | { 30 | return [ 31 | function (Validator $validator) { 32 | 33 | if (! $validator->errors()->has('MID') && $this->MID != config('moamalat-pay.merchant_id')) { 34 | $validator->errors()->add('MID', 'The MID is incorrect'); 35 | } 36 | 37 | if (! $validator->errors()->has('TID') && $this->TID != config('moamalat-pay.terminal_id')) { 38 | $validator->errors()->add('TID', 'The TID is incorrect'); 39 | } 40 | }, 41 | ]; 42 | } 43 | 44 | public function attributes() 45 | { 46 | return [ 47 | 'MID' => 'MID', 48 | 'TID' => 'TID', 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Models/MoamalatPayNotification.php: -------------------------------------------------------------------------------- 1 | 57 | */ 58 | protected function casts(): array 59 | { 60 | return [ 61 | 'MerchantId' => 'string', 62 | 'TerminalId' => 'string', 63 | 'DateTimeLocalTrxn' => 'string', 64 | 'TxnType' => 'string', 65 | 'Message' => 'string', 66 | 'PaidThrough' => 'string', 67 | 'SystemReference' => 'string', 68 | 'NetworkReference' => 'string', 69 | 'MerchantReference' => 'string', 70 | 'Amount' => 'string', 71 | 'Currency' => 'string', 72 | 'PayerAccount' => 'string', 73 | 'PayerName' => 'string', 74 | 'ActionCode' => 'string', 75 | 'request' => 'string', 76 | 'verified' => 'boolean', 77 | ]; 78 | } 79 | 80 | /** 81 | * Get the table associated with the model. 82 | * 83 | * @return string 84 | */ 85 | public function getTable() 86 | { 87 | return config('moamalat-pay.notification.table', parent::getTable()); 88 | } 89 | 90 | /** 91 | * Scope a query to only include approved transactions. 92 | * 93 | * @param \Illuminate\Database\Eloquent\Builder $query 94 | * @return void 95 | */ 96 | public function scopeApproved($query) 97 | { 98 | $query->where('ActionCode', '00'); 99 | } 100 | 101 | /** 102 | * Scope a query to only include verified transactions. 103 | * 104 | * @param \Illuminate\Database\Eloquent\Builder $query 105 | * @return void 106 | */ 107 | public function scopeVerified($query) 108 | { 109 | $query->where('verified', 1); 110 | } 111 | 112 | /** 113 | * Scope a query to only include transactions with current credentials terminal_id and mercahnt_id. 114 | * 115 | * @param \Illuminate\Database\Eloquent\Builder $query 116 | * @return void 117 | */ 118 | public function scopeCurrentCredential($query) 119 | { 120 | $query 121 | ->where('MerchantId', config('moamalat-pay.merchant_id')) 122 | ->where('TerminalId', config('moamalat-pay.terminal_id')); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Pay.php: -------------------------------------------------------------------------------- 1 | render(); 10 | } 11 | 12 | public function pay($amount, $reference = '') 13 | { 14 | return ""; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Providers/MoamalatPayProvider.php: -------------------------------------------------------------------------------- 1 | loadViewsFrom(__DIR__.'/../views', 'moamalat-pay'); 23 | $this->loadRoutesFrom(__DIR__.'/../routes/api.php'); 24 | $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); 25 | $this->loadFactoriesFrom(__DIR__.'/../database/factories'); 26 | Blade::component('moamalat-pay', PayComponent::class); 27 | 28 | $router = $this->app->make(Router::class); 29 | $router->aliasMiddleware('moamalat-allowed-ips', AllowedIps::class); 30 | } 31 | 32 | /** 33 | * Register any application services. 34 | * 35 | * @return void 36 | */ 37 | public function register() 38 | { 39 | $this->publishes([ 40 | __DIR__.'/../config/moamalat-pay.php' => config_path('moamalat-pay.php'), 41 | ], 'moamalat-pay'); 42 | 43 | $this->mergeConfigFrom(__DIR__.'/../config/moamalat-pay.php', 'moamalat-pay'); 44 | 45 | $this->app->singleton('moamalat-pay', function ($app) { 46 | return new Pay; 47 | }); 48 | 49 | $this->app->singleton('moamalat-pay-refund', function ($app) { 50 | return new Refund; 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Refund.php: -------------------------------------------------------------------------------- 1 | terminal_id = config('moamalat-pay.terminal_id'); 46 | $this->merchant_id = config('moamalat-pay.merchant_id'); 47 | $this->key = hex2bin(config('moamalat-pay.key')); 48 | } 49 | 50 | /** 51 | * Refund transaction 52 | * 53 | * @param array $extra 54 | * @return mixed 55 | */ 56 | protected function refund($extra) 57 | { 58 | $DateTimeLocalTrxn = time(); 59 | $encode_data = "DateTimeLocalTrxn={$DateTimeLocalTrxn}&MerchantId={$this->merchant_id}&TerminalId={$this->terminal_id}"; 60 | 61 | if (config('moamalat-pay.production')) { 62 | $url = 'https://npg.moamalat.net/cube/paylink.svc/api/RefundTransaction'; 63 | } else { 64 | $url = 'https://tnpg.moamalat.net/cube/paylink.svc/api/RefundTransaction'; 65 | } 66 | 67 | $response = Http::post($url, array_merge([ 68 | 'TerminalId' => $this->terminal_id, 69 | 'MerchantId' => $this->merchant_id, 70 | 'DateTimeLocalTrxn' => $DateTimeLocalTrxn, 71 | 'SecureHash' => hash_hmac('sha256', $encode_data, $this->key), 72 | ], $extra)); 73 | 74 | if ($response->status() != 200 || $response['Success'] != true) { 75 | throw new Exception($response->offsetGet('Message')); 76 | } 77 | 78 | $this->response = $response->json(); 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * Refund transaction by system reference of transaction 85 | * 86 | * @param string|int $systemReference 87 | * @param string|int $amount 88 | * @return $this 89 | */ 90 | public function refundBySystemReference($systemReference, $amount) 91 | { 92 | return $this->refund([ 93 | 'SystemReference' => $systemReference, 94 | 'AmountTrxn' => $amount, 95 | ]); 96 | } 97 | 98 | /** 99 | * Refund transaction by network reference of transaction 100 | * 101 | * @param string|int $networkReference 102 | * @param string|int $amount 103 | * @return $this 104 | */ 105 | public function refundByNetworkReference($networkReference, $amount) 106 | { 107 | return $this->refund([ 108 | 'NetworkReference' => $networkReference, 109 | 'AmountTrxn' => $amount, 110 | ]); 111 | } 112 | 113 | /** 114 | * Get all properties of reponse 115 | * 116 | * @return array 117 | */ 118 | public function getAll() 119 | { 120 | return $this->response; 121 | } 122 | 123 | /** 124 | * Get property of transaction 125 | * 126 | * @param $property key 127 | * @return mixed 128 | */ 129 | public function get($property) 130 | { 131 | return $this->response[$property]; 132 | } 133 | 134 | /** 135 | * Get property of reponse , if property not exists return default value 136 | * 137 | * @return mixed 138 | */ 139 | public function getWithDefault($property, $default = null) 140 | { 141 | if (array_key_exists($property, $this->response)) { 142 | return $this->response[$property]; 143 | } 144 | 145 | return $default; 146 | } 147 | 148 | /** 149 | * Get SystemReference of new refund transaction 150 | * 151 | * @return string|int 152 | */ 153 | public function getRefNumber() 154 | { 155 | return $this->get('RefNumber'); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Transaction.php: -------------------------------------------------------------------------------- 1 | $networkReference, 49 | 'MerchantReference' => $merchantReference, 50 | 'TerminalId' => $TerminalId, 51 | 'MerchantId' => $MerchantId, 52 | 'DisplayLength' => 1, 53 | 'DisplayStart' => 0, 54 | 'DateTimeLocalTrxn' => $DateTimeLocalTrxn, 55 | 'SecureHash' => hash_hmac('sha256', $encode_data, $key), 56 | ]); 57 | 58 | if ($response->status() != 200 || $response['TotalCountAllTransaction'] != 1) { 59 | throw new Exception($response->offsetGet('Message')); 60 | } 61 | 62 | $this->response = $response->json(); 63 | $this->data = $this->response['Transactions'][0]['DateTransactions'][0]; 64 | } 65 | 66 | /** 67 | * Get all properties of transaction 68 | * 69 | * @return array 70 | */ 71 | public function getAll() 72 | { 73 | return $this->data; 74 | } 75 | 76 | /** 77 | * Get property of transaction 78 | * 79 | * @param $property key 80 | * @return mixed 81 | */ 82 | public function get($property) 83 | { 84 | return $this->data[$property]; 85 | } 86 | 87 | /** 88 | * Get property of reponse , if property not exists return default value 89 | * 90 | * @return mixed 91 | */ 92 | public function getWithDefault($property, $default = null) 93 | { 94 | if (array_key_exists($property, $this->data)) { 95 | return $this->data[$property]; 96 | } 97 | 98 | return $default; 99 | } 100 | 101 | /** 102 | * Get all properties of reponse 103 | * 104 | * @return \Illuminate\Http\Client\Response 105 | */ 106 | public function getResponse() 107 | { 108 | return $this->response; 109 | } 110 | 111 | /** 112 | * Check status of transaction is Approved 113 | * 114 | * @return bool 115 | */ 116 | public function checkApproved($amount = null, $card = null) 117 | { 118 | $result = true; 119 | if ($amount != null) { 120 | $result = /* $result && */ $this->data['AmountTrxn'] == $amount; 121 | } 122 | if ($card != null) { 123 | $result = $result && $this->data['CardNo'] == $card; 124 | } 125 | 126 | return $result && $this->data['Status'] == 'Approved'; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/View/Components/Pay.php: -------------------------------------------------------------------------------- 1 | amount = $amount; 33 | $this->reference = $reference; 34 | } 35 | 36 | /** 37 | * Get the view / contents that represent the component. 38 | * 39 | * @return \Illuminate\View\View|\Closure|string 40 | */ 41 | public function render() 42 | { 43 | return view('moamalat-pay::pay'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/config/moamalat-pay.php: -------------------------------------------------------------------------------- 1 | merchant_id or outlet_number 14 | 'merchant_id' => env('MOAMALATPAY_MID'), 15 | 16 | // TID => terminal_id 17 | 'terminal_id' => env('MOAMALATPAY_TID'), 18 | 19 | // Secure key 20 | 'key' => env('MOAMALATPAY_KEY'), 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Production 25 | |-------------------------------------------------------------------------- 26 | | 27 | | If the production is set to "true", you will work on production environment 28 | | otherwise it will use testing environment 29 | | 30 | */ 31 | 'production' => env('MOAMALATPAY_PRODUCTION', false), 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Show 36 | |-------------------------------------------------------------------------- 37 | | 38 | | If the show_logs is set to "true", you will see configurations 39 | | and response of requests in browser console 40 | | 41 | */ 42 | 'show_logs' => false, 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Enable Cancel Event On Success 47 | |-------------------------------------------------------------------------- 48 | | 49 | | If the enable_cancel_event_on_success is set to "true", the cancel event will be triggered even if the payment is successful 50 | | 51 | */ 52 | 'enable_cancel_event_on_success' => true, 53 | 54 | /* 55 | |-------------------------------------------------------------------------- 56 | | Generate Secure Hash api 57 | |-------------------------------------------------------------------------- 58 | | 59 | | This is service (api) to generate secureHash to be used in pay in Lightbox. 60 | | 61 | | url is route of api of generate secureHash 62 | | 63 | | route_name is name of route of api of generate secureHash 64 | | 65 | */ 66 | 'generate-securekey' => [ 67 | 'url' => 'moamalat-pay/securekey', 68 | 'route_name' => 'moamalat_pay.generate_securekey', 69 | ], 70 | 71 | /* 72 | |-------------------------------------------------------------------------- 73 | | Notification (Webhook) api 74 | |-------------------------------------------------------------------------- 75 | | 76 | | This is service from moamalat on any transaction you will receive notification 77 | | on api (webhook) 78 | | 79 | | key is your private notification key to use it in validate transaction requests 80 | | 81 | | url is route to receive notification 82 | | 83 | | table is name of table that will be used to save notifications 84 | | 85 | | allowed_ips are ips that will receive notification from them 86 | | ['*'] means receive from any ip but it is not secure to receive notifcations from anyone 87 | | you should ask moamalat on ips of their servers and use them 88 | | 89 | */ 90 | 'notification' => [ 91 | 'key' => env('MOAMALATPAY_NOTIFICATION_KEY'), 92 | 'url' => 'moamalat-pay/notify', 93 | 'route_name' => 'moamalat_pay.notification', 94 | 'table' => 'moamalat_pay_notifications', 95 | 'allowed_ips' => ['*'], 96 | 97 | ], 98 | ]; 99 | -------------------------------------------------------------------------------- /src/database/factories/MoamalatPayNotificationFactory.php.backup: -------------------------------------------------------------------------------- 1 | config('moamalat-pay.merchant_id'), 19 | 'TerminalId' => config('moamalat-pay.terminal_id'), 20 | 'DateTimeLocalTrxn' => now(), 21 | 'TxnType' => $this->faker->numberBetween(1, 4), 22 | 'Message' => $this->faker->word(), 23 | 'PaidThrough' => $this->faker->randomElement(['Card', 'Tahweel']), 24 | 'SystemReference' => $this->faker->uuid(), 25 | 'NetworkReference' => $this->faker->uuid(), 26 | 'MerchantReference' => $this->faker->uuid(), 27 | 'Amount' => $this->faker->randomNumber(), 28 | 'Currency' => 434, 29 | 'PayerAccount' => $this->faker->creditCardNumber(), 30 | 'PayerName' => $this->faker->word(), 31 | 'ActionCode' => $this->faker->randomNumber(2), 32 | ]; 33 | 34 | return $data + [ 35 | 'request' => json_encode($data), 36 | 'verified' => $this->faker->boolean(), 37 | 'ip' => $this->faker->ipv4() 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/database/migrations/2022_09_12_000000_create_moamalat_pay_notifications_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('MerchantId', 190)->nullable(); 19 | $table->string('TerminalId', 190)->nullable(); 20 | $table->string('DateTimeLocalTrxn', 190)->nullable(); 21 | $table->string('TxnType', 190)->nullable(); 22 | $table->string('Message', 190)->nullable(); 23 | $table->string('PaidThrough', 190)->nullable(); 24 | $table->string('SystemReference', 190)->nullable(); 25 | $table->string('NetworkReference', 190)->nullable(); 26 | $table->string('MerchantReference', 190)->nullable(); 27 | $table->string('Amount', 190)->nullable(); 28 | $table->string('Currency', 190)->nullable(); 29 | $table->string('PayerAccount', 190)->nullable(); 30 | $table->string('PayerName', 190)->nullable(); 31 | $table->string('ActionCode', 190)->nullable(); 32 | $table->json('request')->nullable(); 33 | $table->string('ip', 190)->nullable(); 34 | $table->boolean('verified'); 35 | $table->timestamps(); 36 | 37 | $table->index(['verified', 'NetworkReference', 'MerchantReference', 'MerchantId', 'TerminalId'], 'verified_networkRef_merchantRef_mid_tid'); 38 | }); 39 | } 40 | 41 | /** 42 | * Reverse the migrations. 43 | * 44 | * @return void 45 | */ 46 | public function down() 47 | { 48 | Schema::dropIfExists(config('moamalat-pay.notification.table')); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /src/routes/api.php: -------------------------------------------------------------------------------- 1 | prefix('api') 20 | ->group(function () { 21 | 22 | Route::post(config('moamalat-pay.notification.url'), [NotificationController::class, 'store']) 23 | ->middleware('moamalat-allowed-ips') 24 | ->name(config('moamalat-pay.notification.route_name')); 25 | 26 | Route::get(config('moamalat-pay.generate-securekey.url'), [ConfigController::class, 'generateSecureKey']) 27 | ->name(config('moamalat-pay.generate-securekey.route_name')); 28 | }); 29 | -------------------------------------------------------------------------------- /src/views/pay.blade.php: -------------------------------------------------------------------------------- 1 | @once 2 | 3 | @if (config('moamalat-pay.production')) 4 | 5 | @else 6 | 7 | @endif 8 | 9 | 10 | 11 | 12 | 135 | 136 | 137 | @endonce 138 | -------------------------------------------------------------------------------- /tests/Feature/ConfigAPITest.php: -------------------------------------------------------------------------------- 1 | assertNotNull(config('moamalat-pay.generate-securekey.url')); 17 | $this->assertNotNull(config('moamalat-pay.generate-securekey.route_name')); 18 | } 19 | 20 | /** 21 | * Test generating a secure key with valid parameters. 22 | * 23 | * @return void 24 | */ 25 | public function test_generate_secure_key() 26 | { 27 | $params = [ 28 | 'MID' => config('moamalat-pay.merchant_id'), 29 | 'TID' => config('moamalat-pay.terminal_id'), 30 | 'amount' => '1000', 31 | ]; 32 | 33 | $this->getJson(route(config('moamalat-pay.generate-securekey.route_name'), $params)) 34 | ->assertOk() 35 | ->assertJsonStructure(['secureHash', 'DateTimeLocalTrxn']); 36 | } 37 | 38 | /** 39 | * Test validation errors when generating a secure key without required parameters. 40 | * 41 | * @return void 42 | */ 43 | public function test_generate_secure_key_validation() 44 | { 45 | $this->getJson(route(config('moamalat-pay.generate-securekey.route_name'))) 46 | ->assertUnprocessable() 47 | ->assertJson([ 48 | 'message' => 'The MID field is required. (and 2 more errors)', 49 | 'errors' => [ 50 | 'MID' => [ 51 | 'The MID field is required.', 52 | ], 53 | 'TID' => [ 54 | 'The TID field is required.', 55 | ], 56 | 'amount' => [ 57 | 'The amount field is required.', 58 | ], 59 | ], 60 | ], true); 61 | } 62 | 63 | /** 64 | * Test generating a secure key with incorrect merchant/terminal IDs. 65 | * 66 | * @return void 67 | */ 68 | public function test_generate_secure_key_with_incrorrect_configurations() 69 | { 70 | $params = [ 71 | 'MID' => '100000009', 72 | 'TID' => '400000029', 73 | 'amount' => '1000', 74 | ]; 75 | 76 | $this->getJson(route(config('moamalat-pay.generate-securekey.route_name'), $params)) 77 | ->assertUnprocessable() 78 | ->assertJson([ 79 | 'message' => 'The MID is incorrect (and 1 more error)', 80 | 'errors' => [ 81 | 'MID' => [ 82 | 'The MID is incorrect', 83 | ], 84 | 'TID' => [ 85 | 'The TID is incorrect', 86 | ], 87 | ], 88 | ], true); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/Feature/GetTransactionTest.php: -------------------------------------------------------------------------------- 1 | null, 22 | 'Success' => true, 23 | 'TotalAmountAllTransaction' => 1000, 24 | 'TotalAmountTipsTransaction' => 0, 25 | 'TotalCountAllTransaction' => 1, 26 | 'Transactions' => [ 27 | [ 28 | 'Date' => '03/09/2022', 29 | 'DateTotalAmount' => '1000', 30 | 'DateTransactions' => [ 31 | [ 32 | 'Amnt' => '1000', 33 | 'AmountTrxn' => '1000', 34 | 'AuthCode' => null, 35 | 'CardNo' => '639499XXXXXX2740', 36 | 'CardType' => '', 37 | 'Currency' => 'LYD', 38 | 'ExternalTxnId' => null, 39 | 'FeeAmnt' => '0', 40 | 'HasToken' => true, 41 | 'ISForceSendCVCForRefund' => true, 42 | 'IsMustVoidTotalAmount' => false, 43 | 'IsPointTrasnaction' => false, 44 | 'IsRefund' => false, 45 | 'IsRefundEnabled' => true, 46 | 'IsSend' => false, 47 | 'MerchantReference' => '475217323', 48 | 'MobileNumber' => null, 49 | 'OriginalTxnId' => '', 50 | 'RRN' => '224601434990', 51 | 'ReceiptNo' => '224601434990', 52 | 'RefundButton' => 0, 53 | 'RefundReason' => '', 54 | 'RefundSource' => '', 55 | 'RefundUserCreator' => '', 56 | 'RelatedTxnTotalAmount' => null, 57 | 'RemainingRefundAmount' => '1000', 58 | 'ResCodeDesc' => 'Approved', 59 | 'STAN' => '434990', 60 | 'SenderName' => 'MS', 61 | 'Status' => 'Approved', 62 | 'TipAmnt' => '0', 63 | 'TransType' => 'Sale', 64 | 'TransactionChannel' => 'Card', 65 | 'TransactionId' => '1233317', 66 | 'TxnDateTime' => '03/09/22 01:44', 67 | 'TxnIcon' => 2, 68 | ], 69 | ], 70 | ], 71 | ], 72 | 73 | ]; 74 | 75 | // set fake requests to make testing faster 76 | Http::fake([ 77 | // 'https://tnpg.moamalat.net/cube/paylink.svc/api/FilterTransactions' => Http::response($respone, 200), 78 | 'https://tnpg.moamalat.net/cube/paylink.svc/api/FilterTransactions' => function ($r) use ($respone) { 79 | if ($r->offsetGet('MerchantId') == 'testing_authentication_failed') { // response for testing authentication failed 80 | return Http::response([ 81 | 'Message' => 'Authentication failed.', 82 | 'StackTrace' => null, 83 | 'ExceptionType' => 'System.InvalidOperationException', 84 | ], 401); 85 | } 86 | if ($r->offsetGet('NetworkReference') == 'testing_not_found') { // response for testing transaction not found 87 | return Http::response([ 88 | 'Message' => 'Transaction not found', 89 | 'Success' => true, 90 | 'TotalAmountAllTransaction' => 0, 91 | 'TotalAmountTipsTransaction' => null, 92 | 'TotalCountAllTransaction' => 0, 93 | 'Transactions' => [], 94 | ]); 95 | } 96 | 97 | return $respone; // response for testing success request 98 | }, 99 | ]); 100 | 101 | $this->transaction = new Transaction('224601434990', '475217323'); 102 | } 103 | 104 | /** 105 | * Test config. 106 | */ 107 | public function test_transaction_not_found() 108 | { 109 | $this->expectExceptionMessage('Transaction not found'); 110 | new Transaction('testing_not_found', '475217323'); 111 | } 112 | 113 | /** 114 | * Test config. 115 | */ 116 | public function test_authentication_failed() 117 | { 118 | $this->expectExceptionMessage('Authentication failed.'); 119 | Config::set('moamalat-pay.merchant_id', 'testing_authentication_failed'); 120 | new Transaction('224601434990', '475217323'); 121 | } 122 | 123 | /** 124 | * Test config. 125 | */ 126 | public function test_get_property() 127 | { 128 | $this->assertEquals('639499XXXXXX2740', $this->transaction->get('CardNo')); 129 | // $this->expectExceptionMessage('Undefined index: CardNotFound'); 130 | // $this->assertEquals('639499XXXXXX2740', $this->transaction->get('CardNotFound')); 131 | } 132 | 133 | /** 134 | * Test config. 135 | */ 136 | public function test_get_property_with_default_value() 137 | { 138 | $this->assertEquals('card-not-found', $this->transaction->getWithDefault('Card', 'card-not-found')); 139 | } 140 | 141 | /** 142 | * Test config. 143 | */ 144 | public function test_check_approved() 145 | { 146 | $this->assertTrue($this->transaction->checkApproved()); 147 | $this->assertTrue($this->transaction->checkApproved(1000)); 148 | $this->assertTrue($this->transaction->checkApproved(1000, '639499XXXXXX2740')); 149 | $this->assertNotTrue($this->transaction->checkApproved(2000, '639499XXXXXX2740')); 150 | } 151 | 152 | /** 153 | * Test config. 154 | */ 155 | public function test_get_all() 156 | { 157 | $keys = [ 158 | 'Amnt', 159 | 'AmountTrxn', 160 | 'AuthCode', 161 | 'CardNo', 162 | 'CardType', 163 | 'Currency', 164 | 'ExternalTxnId', 165 | 'FeeAmnt', 166 | 'HasToken', 167 | 'ISForceSendCVCForRefund', 168 | 'IsMustVoidTotalAmount', 169 | 'IsPointTrasnaction', 170 | 'IsRefund', 171 | 'IsRefundEnabled', 172 | 'IsSend', 173 | 'MerchantReference', 174 | 'MobileNumber', 175 | 'OriginalTxnId', 176 | 'RRN', 177 | 'ReceiptNo', 178 | 'RefundButton', 179 | 'RefundReason', 180 | 'RefundSource', 181 | 'RefundUserCreator', 182 | 'RelatedTxnTotalAmount', 183 | 'RemainingRefundAmount', 184 | 'ResCodeDesc', 185 | 'STAN', 186 | 'SenderName', 187 | 'Status', 188 | 'TipAmnt', 189 | 'TransType', 190 | 'TransactionChannel', 191 | 'TransactionId', 192 | 'TxnDateTime', 193 | 'TxnIcon', 194 | ]; 195 | $this->assertEquals($keys, array_keys($this->transaction->getAll()), 'Keys are not equal'); 196 | } 197 | 198 | /** 199 | * Test config. 200 | */ 201 | public function test_get_response() 202 | { 203 | $expected = [ 204 | 'Message' => null, 205 | 'Success' => true, 206 | 'TotalAmountAllTransaction' => 1000, 207 | 'TotalAmountTipsTransaction' => 0, 208 | 'TotalCountAllTransaction' => 1, 209 | ]; 210 | $actual = $this->transaction->getResponse(); 211 | unset($actual['Transactions']); 212 | $this->assertEquals($expected, $actual); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /tests/Feature/NotificationsAPITest.php: -------------------------------------------------------------------------------- 1 | assertNotNull(config('moamalat-pay.merchant_id')); 28 | $this->assertNotNull(config('moamalat-pay.terminal_id')); 29 | $this->assertNotNull(config('moamalat-pay.key')); 30 | $this->assertNotNull(config('moamalat-pay.production')); 31 | $this->assertNotNull(config('moamalat-pay.show_logs')); 32 | $this->assertNotNull(config('moamalat-pay.notification.key')); 33 | } 34 | 35 | /** 36 | * Initialize base notifications api testing 37 | * 38 | * @param array $dispatched events should dispatched 39 | * @param array $notdispatched events should not dispatched 40 | * @param array $extraData override or add extra properties to transaction 41 | * @return void 42 | */ 43 | public function init_test_api_notifications_transaction($dispatched, $notdispatched, $extraData = []) 44 | { 45 | Event::fake(); 46 | 47 | // load request from json to array 48 | $data = json_decode(file_get_contents(__DIR__.'./../_fixtures/transactions/verfied.json'), true); 49 | $body = array_merge($data, $extraData); 50 | 51 | // call api notificaitons 52 | // $this->postJson(route(config('moamalat-pay.notification.route_name')), $body) 53 | // There is issuse with $this->postJson it sends body empty , that is why I use $this->withHeaders 54 | $this->withHeaders([])->post(route(config('moamalat-pay.notification.route_name')), $body) 55 | ->assertStatus(200) 56 | ->assertJson(['Message' => 'Success', 'Success' => true]); 57 | 58 | // assert dispatched events 59 | foreach ($dispatched as $e) { 60 | Event::assertDispatched($e, 1); 61 | } 62 | 63 | // assert not dispatched events 64 | foreach ($notdispatched as $e) { 65 | Event::assertNotDispatched($e); 66 | } 67 | 68 | // validated transaction saved in database 69 | $this->assertDatabaseCount(config('moamalat-pay.notification.table'), 1); 70 | } 71 | 72 | /** 73 | * Test approved transaction notification 74 | */ 75 | public function test_api_notifications_approved_transaction() 76 | { 77 | $this->init_test_api_notifications_transaction( 78 | [ // dispatched 79 | VerfiedTransaction::class, 80 | ApprovedTransaction::class, 81 | ApprovedSaleTransaction::class, 82 | ], 83 | [ // not dispatched 84 | ApprovedRefundTransaction::class, 85 | ApprovedVoidSaleTransaction::class, 86 | ApprovedVoidRefundTransaction::class, 87 | UnverfiedTransaction::class, 88 | DisallowedRequestEvent::class, 89 | ] 90 | ); 91 | } 92 | 93 | /** 94 | * Test approved refund transaction notification 95 | */ 96 | public function test_api_notifications_approved_refund_transaction() 97 | { 98 | $this->init_test_api_notifications_transaction( 99 | [ // dispatched 100 | VerfiedTransaction::class, 101 | ApprovedTransaction::class, 102 | ApprovedRefundTransaction::class, 103 | ], 104 | [ // not dispatched 105 | ApprovedSaleTransaction::class, 106 | ApprovedVoidSaleTransaction::class, 107 | ApprovedVoidRefundTransaction::class, 108 | UnverfiedTransaction::class, 109 | DisallowedRequestEvent::class, 110 | ], 111 | [ 112 | 'TxnType' => 2, 113 | ] 114 | ); 115 | } 116 | 117 | /** 118 | * Test approved void sale transaction notification 119 | */ 120 | public function test_api_notifications_approved_void_sale_transaction() 121 | { 122 | $this->init_test_api_notifications_transaction( 123 | [ // dispatched 124 | VerfiedTransaction::class, 125 | ApprovedTransaction::class, 126 | ApprovedVoidSaleTransaction::class, 127 | ], 128 | [ // not dispatched 129 | ApprovedSaleTransaction::class, 130 | ApprovedRefundTransaction::class, 131 | ApprovedVoidRefundTransaction::class, 132 | UnverfiedTransaction::class, 133 | DisallowedRequestEvent::class, 134 | ], 135 | [ 136 | 'TxnType' => 3, 137 | ] 138 | ); 139 | } 140 | 141 | /** 142 | * Test approved void refund transaction notification 143 | */ 144 | public function test_api_notifications_approved_void_refund_transaction() 145 | { 146 | $this->init_test_api_notifications_transaction( 147 | [ // dispatched 148 | VerfiedTransaction::class, 149 | ApprovedTransaction::class, 150 | ApprovedVoidRefundTransaction::class, 151 | ], 152 | [ // not dispatched 153 | ApprovedSaleTransaction::class, 154 | ApprovedRefundTransaction::class, 155 | ApprovedVoidSaleTransaction::class, 156 | UnverfiedTransaction::class, 157 | DisallowedRequestEvent::class, 158 | ], 159 | [ 160 | 'TxnType' => 4, 161 | ] 162 | ); 163 | } 164 | 165 | /** 166 | * Test approved transaction notification 167 | */ 168 | public function test_api_notifications_unverfied_transaction() 169 | { 170 | 171 | $this->init_test_api_notifications_transaction( 172 | [ // dispatched 173 | UnverfiedTransaction::class, 174 | ], 175 | [ // not dispatched 176 | VerfiedTransaction::class, 177 | ApprovedTransaction::class, 178 | ApprovedSaleTransaction::class, 179 | ApprovedRefundTransaction::class, 180 | ApprovedVoidSaleTransaction::class, 181 | ApprovedVoidRefundTransaction::class, 182 | ], 183 | [ 184 | 'SecureHash' => 'Invaild hash for testing unverfied', 185 | ] 186 | ); 187 | } 188 | 189 | /** 190 | * Test notify from disallowed ip 191 | */ 192 | public function test_api_notifications_notify_from_disallowed_ip() 193 | { 194 | // we set invalid ip as allowed ip, to check if api will catch our ip as disallowed ip 195 | Config::set('moamalat-pay.notification.allowed_ips', ['12.0.0.999']); 196 | 197 | Event::fake(); 198 | 199 | // call api notificaitons 200 | $this->postJson(route(config('moamalat-pay.notification.route_name'))) 201 | ->assertStatus(403); 202 | 203 | Event::assertDispatched(DisallowedRequestEvent::class, 1); 204 | Event::assertNotDispatched(UnverfiedTransaction::class); 205 | Event::assertNotDispatched(VerfiedTransaction::class); 206 | Event::assertNotDispatched(ApprovedTransaction::class); 207 | Event::assertNotDispatched(ApprovedSaleTransaction::class); 208 | Event::assertNotDispatched(ApprovedRefundTransaction::class); 209 | Event::assertNotDispatched(ApprovedVoidSaleTransaction::class); 210 | Event::assertNotDispatched(ApprovedVoidRefundTransaction::class); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /tests/Feature/PayTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Pay::class, app('moamalat-pay')); 19 | } 20 | 21 | /** 22 | * Test config. 23 | */ 24 | public function test_render() 25 | { 26 | $blade = app('moamalat-pay')->init(); 27 | $this->assertStringContainsString('class MoamalataPay', $blade); 28 | $this->assertStringContainsString('var _moamalatPay = new MoamalataPay(', $blade); 29 | $this->assertStringNotContainsString('_moamalatPay.pay(', $blade); 30 | $this->assertStringContainsString("_moamalatPay.pay(1000, '');", app('moamalat-pay')->pay(1000)); 31 | $this->assertStringContainsString("_moamalatPay.pay(1000, 'test-ref');", app('moamalat-pay')->pay(1000, 'test-ref')); 32 | } 33 | 34 | /** 35 | * Test config. 36 | */ 37 | public function test_component() 38 | { 39 | $view = $this->blade(''); 40 | $view->assertSeeText('class MoamalataPay'); 41 | $view->assertSeeText('var _moamalatPay = new MoamalataPay('); 42 | $view->assertSeeText('_moamalatPay.pay(1000, "");', false); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Feature/RefundTest.php: -------------------------------------------------------------------------------- 1 | 'Approved', 22 | 'Success' => true, 23 | 'ActionCode' => null, 24 | 'AuthCode' => null, 25 | 'DecimalFraction' => 3, 26 | 'ExternalTxnId' => null, 27 | 'IsEnableRefund' => false, 28 | 'MerchantReference' => null, 29 | 'NetworkReference' => null, 30 | 'ReceiptNumber' => null, 31 | 'ReceiverAccountNumber' => null, 32 | 'ReceiverName' => null, 33 | 'ReceiverScheme' => null, 34 | 'RefNumber' => '1233674', 35 | 'SystemReference' => 0, 36 | 'SystemTxnId' => 0, 37 | 'TxnDate' => null, 38 | ]; 39 | 40 | // set fake requests to make testing faster 41 | Http::fake([ 42 | // 'https://tnpg.moamalat.net/cube/paylink.svc/api/FilterTransactions' => Http::response($respone, 200), 43 | 'https://tnpg.moamalat.net/cube/paylink.svc/api/RefundTransaction' => function ($r) use ($respone) { 44 | if ($r->offsetGet('MerchantId') == 'testing_authentication_failed') { // response for testing authentication failed 45 | return Http::response([ 46 | 'Message' => 'Authentication failed.', 47 | 'StackTrace' => null, 48 | 'ExceptionType' => 'System.InvalidOperationException', 49 | ], 401); 50 | } 51 | if ($r->offsetExists('NetworkReference') && $r->offsetGet('NetworkReference') == 'testing_already_refunded') { // response for testing authentication failed 52 | return Http::response([ 53 | 'Message' => 'CUBEEX5250616:Transaction Already Refunded', 54 | 'Success' => false, 55 | 'ActionCode' => null, 56 | 'AuthCode' => null, 57 | 'DecimalFraction' => 3, 58 | 'ExternalTxnId' => null, 59 | 'IsEnableRefund' => false, 60 | 'MerchantReference' => null, 61 | 'NetworkReference' => null, 62 | 'ReceiptNumber' => null, 63 | 'ReceiverAccountNumber' => null, 64 | 'ReceiverName' => null, 65 | 'ReceiverScheme' => null, 66 | 'RefNumber' => null, 67 | 'SystemReference' => 0, 68 | 'SystemTxnId' => 0, 69 | 'TxnDate' => null, 70 | ], 200); 71 | } 72 | 73 | return $respone; // response for testing success request 74 | }, 75 | ]); 76 | 77 | $this->transaction = app(Refund::class); 78 | } 79 | 80 | /** 81 | * Load transaction to use it in test something 82 | */ 83 | protected function loadTransaction() 84 | { 85 | $this->transaction->refundByNetworkReference('226214209277', '10'); 86 | } 87 | 88 | /** 89 | * Test config. 90 | */ 91 | public function test_container_instance() 92 | { 93 | $this->assertInstanceOf(Refund::class, app('moamalat-pay-refund')); 94 | } 95 | 96 | /** 97 | * Test 98 | */ 99 | public function test_refund_by_system_reference() 100 | { 101 | $this->transaction->refundBySystemReference('226214209277', '10'); 102 | $this->assertEquals($this->transaction->get('Message'), 'Approved'); 103 | } 104 | 105 | /** 106 | * Test 107 | */ 108 | public function test_refund_by_network_reference() 109 | { 110 | $this->transaction->refundByNetworkReference('226214209277', '10'); 111 | $this->assertEquals($this->transaction->get('Message'), 'Approved'); 112 | } 113 | 114 | /** 115 | * Test 116 | */ 117 | public function test_already_refunded() 118 | { 119 | $this->expectExceptionMessage('Transaction Already Refunded'); 120 | (new Refund)->refundByNetworkReference('testing_already_refunded', '10'); 121 | } 122 | 123 | /** 124 | * Test 125 | */ 126 | public function test_authentication_failed() 127 | { 128 | $this->expectExceptionMessage('Authentication failed.'); 129 | Config::set('moamalat-pay.merchant_id', 'testing_authentication_failed'); 130 | (new Refund)->refundByNetworkReference('226214209277', '10'); 131 | } 132 | 133 | /** 134 | * Test 135 | */ 136 | public function test_get_ref_number() 137 | { 138 | $this->loadTransaction(); 139 | $this->assertEquals('1233674', $this->transaction->getRefNumber()); 140 | } 141 | 142 | /** 143 | * Test 144 | */ 145 | public function test_get_property() 146 | { 147 | $this->loadTransaction(); 148 | $this->assertEquals('Approved', $this->transaction->get('Message')); 149 | // $this->expectExceptionMessage('Undefined index: CardNotFound'); 150 | // $this->assertEquals('Approved', $this->transaction->get('CardNotFound')); 151 | } 152 | 153 | /** 154 | * Test 155 | */ 156 | public function test_get_property_with_default_value() 157 | { 158 | $this->loadTransaction(); 159 | $this->assertEquals('card-not-found', $this->transaction->getWithDefault('Card', 'card-not-found')); 160 | } 161 | 162 | /** 163 | * Test 164 | */ 165 | public function test_get_all() 166 | { 167 | $this->loadTransaction(); 168 | $keys = [ 169 | 'Message', 170 | 'Success', 171 | 'ActionCode', 172 | 'AuthCode', 173 | 'DecimalFraction', 174 | 'ExternalTxnId', 175 | 'IsEnableRefund', 176 | 'MerchantReference', 177 | 'NetworkReference', 178 | 'ReceiptNumber', 179 | 'ReceiverAccountNumber', 180 | 'ReceiverName', 181 | 'ReceiverScheme', 182 | 'RefNumber', 183 | 'SystemReference', 184 | 'SystemTxnId', 185 | 'TxnDate', 186 | ]; 187 | $this->assertEquals($keys, array_keys($this->transaction->getAll()), 'Keys are not equal'); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | withFactories(__DIR__.'/../src/database/factories'); 14 | } 15 | 16 | protected function getPackageProviders($app) 17 | { 18 | return [ 19 | MoamalatPayProvider::class, 20 | ]; 21 | } 22 | 23 | protected function getEnvironmentSetUp($app) 24 | { 25 | $app['config']->set('moamalat-pay', [ 26 | 'merchant_id' => '10004188779', 27 | 'terminal_id' => '49077229', 28 | 'key' => '39353638663431622D303136622D343235322D623330632D383361633838383965373965', 29 | 'production' => false, 30 | 'show_logs' => true, 31 | 'generate-securekey' => [ 32 | 'url' => 'moamalat-pay/securekey', 33 | 'route_name' => 'moamalat_pay.generate_securekey', 34 | ], 35 | 'notification' => [ 36 | 'key' => '39353638663431622D303136622D343235322D623330632D383361633838383965373965', 37 | 'url' => 'moamalat-pay/notify', 38 | 'route_name' => 'moamalat_pay.notification', 39 | 'table' => 'moamalat_pay_notifications', 40 | 'allowed_ips' => ['*'], 41 | ], 42 | ]); 43 | // perform environment setup 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Unit/ExampleTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/_fixtures/transactions/verfied.json: -------------------------------------------------------------------------------- 1 | { 2 | "MerchantId": "10004188779", 3 | "TerminalId": "49077229", 4 | "DateTimeLocalTrxn": "20220903014410", 5 | "AuthorizationDateTime": "20220903014409", 6 | "SecureHash": "5C919EA5A619D33101AA761B8DFB332C8993C867CB8E5E567770185B71ED72E6", 7 | "TxnType": 1, 8 | "Message": "Approved", 9 | "PaidThrough": "Card", 10 | "SystemReference": "1233317", 11 | "NetworkReference": "224601434990", 12 | "MerchantReference": "475217323", 13 | "Amount": "1000", 14 | "Tip": "0", 15 | "Currency": "434", 16 | "PayerAccount": "639499XXXXXX2740", 17 | "PayerName": "MS", 18 | "ActionCode": "00", 19 | "SID": null, 20 | "Token": null, 21 | "Email": null, 22 | "Status": "Success", 23 | "CustomerMobile": null, 24 | "MessageCustomer": null, 25 | "MerchantCustomerId": null, 26 | "CurrencyTwo": null, 27 | "CurrencyTwoValue": null, 28 | "AdditionalFees": "0", 29 | "OriginalTxnId": "0" 30 | } 31 | --------------------------------------------------------------------------------