diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..9a2360c
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,47 @@
+name: CI
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php: [ '8.1', '8.2', '8.3', '8.4', '8.5' ]
+ deps: [ 'stable' ]
+ include:
+ - php: '8.1'
+ deps: 'lowest'
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+
+ - name: Validate composer.json
+ run: composer validate --strict
+
+ - name: Install dependencies
+ run: |
+ if [ "${{ matrix.deps }}" = "lowest" ]; then
+ composer update --prefer-lowest --prefer-stable --no-interaction --no-progress
+ else
+ composer update --no-interaction --no-progress
+ fi
+
+ - name: Run lint
+ run: composer lint
+
+ - name: Run tests
+ run: composer test
+
+ - name: Run PHPStan
+ run: composer stan
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 6a9d6f8..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-language: php
-
-php:
- - 5.6
- - 7.0
- - 7.1
- - 7.2
- - 7.3
- - 7.4
-
-env:
- global:
- - setup=basic
-
-matrix:
- include:
- - php: 5.6
- env: setup=lowest
-
-sudo: false
-
-before_install:
- - travis_retry composer self-update
-
-install:
- - if [[ $setup = 'basic' ]]; then travis_retry composer install --no-interaction --prefer-dist; fi
- - if [[ $setup = 'lowest' ]]; then travis_retry composer update --prefer-dist --no-interaction --prefer-lowest --prefer-stable; fi
-
-script: vendor/bin/phpcs --exclude=Generic.Files.LineLength src/ --standard=PSR2 src && vendor/bin/phpunit --coverage-text
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
new file mode 100644
index 0000000..48efa81
--- /dev/null
+++ b/ARCHITECTURE.md
@@ -0,0 +1,82 @@
+# Architecture
+
+## Overview
+
+`omnipay-nabtransact` remains an Omnipay gateway package and keeps full Omnipay request/response compatibility.
+
+This modernization adds a transport layer so the package can run with:
+
+- Omnipay HTTP client (default, backward compatible)
+- Native cURL transport (framework-agnostic alternative)
+- Custom user transport via contract
+
+## Design Goals
+
+- Preserve Omnipay API surface and existing integrations.
+- Keep non-breaking behavior for request classes and gateway methods.
+- Improve reliability and testability through explicit transport contracts.
+- Keep dependencies minimal.
+
+## Project Map
+
+```text
+src/
+ DirectPostGateway.php
+ SecureXMLGateway.php
+ HostedPaymentGateway.php
+ UnionPayGateway.php
+
+ Message/
+ *Request.php
+ *Response.php
+
+ Transport/
+ TransportInterface.php
+ TransportResponse.php
+ OmnipayHttpClientTransport.php
+ CurlTransport.php
+
+ Enums/
+ TransactionType.php
+```
+
+## Runtime Flows
+
+### 1) SecureXML requests
+
+1. Gateway creates request through Omnipay (`authorize/purchase/capture/refund/echoTest`).
+2. Request builds XML payload.
+3. Request resolves transport in this order:
+ - explicit request transport (`setTransport()`)
+ - Omnipay adapter transport (default)
+4. Response XML is mapped to `SecureXMLResponse`.
+
+### 2) DirectPost / UnionPay
+
+- Redirect-based requests remain unchanged.
+- Fingerprint verification stays compatible.
+- Backward-compat typo alias (`vefiyFingerPrint`) remains supported.
+- DirectPost supports purchase, authorize, complete callbacks, store-only flow, and additive server-to-server operations (`capture`, `refund`, `void`).
+- EMV 3DS order management is exposed via `createEMV3DSOrder()`.
+
+### 3) Hosted Payment
+
+- Redirect form flow remains unchanged.
+- Environment endpoint selection now aligns correctly with `testMode`.
+
+## Extension Points
+
+- Implement `TransportInterface` for custom HTTP stacks.
+- Inject custom transport per request via `setTransport($transport)`.
+
+## Backward Compatibility
+
+- Omnipay gateway class names and behavior remain intact.
+- Existing request/response classes remain available.
+- Deprecated typo method remains callable.
+
+## Testing Strategy
+
+- Request/response behavior tests for existing Omnipay flows.
+- Dedicated tests for transport injection and endpoint correctness.
+- Fingerprint verification coverage (success/failure).
diff --git a/README.md b/README.md
index 10b1a1f..844b08c 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,10 @@
**NAB Transact driver for the Omnipay PHP payment processing library**
+[](https://github.com/sudiptpa/omnipay-nabtransact/actions/workflows/ci.yml)
[Omnipay](https://github.com/thephpleague/omnipay) is a framework agnostic, multi-gateway payment
processing library for PHP. This package implements NAB Transact support for Omnipay.
-[](https://styleci.io/repos/74269379)
-[](https://travis-ci.org/sudiptpa/omnipay-nabtransact)
[](https://packagist.org/packages/sudiptpa/omnipay-nabtransact)
[](https://packagist.org/packages/sudiptpa/omnipay-nabtransact)
[](https://raw.githubusercontent.com/sudiptpa/omnipay-nabtransact/master/LICENSE)
@@ -25,6 +24,7 @@ The following gateways are provided by this package:
* NABTransact_DirectPost (NAB Transact Direct Post v2)
* NABTransact_SecureXML (NAB Transact SecurePay XML)
+* NABTransact_HostedPayment (NAB Hosted Payment Page)
* NABTransact_UnionPay (UnionPay via NAB Transact)
### NAB Transact SecureXML API
@@ -117,23 +117,23 @@ The following gateways are provided by this package:
$gateway->setTestMode(true);
$gateway->setHasEMV3DSEnabled(true);
- $card = new CreditCard(array(
+ $card = new CreditCard([
'firstName' => 'Sujip',
'lastName' => 'Thapa',
'number' => '4444333322221111',
'expiryMonth' => '10',
'expiryYear' => '2030',
'cvv' => '123',
- ));
+ ]);
- $response = $gateway->purchase(array(
+ $response = $gateway->purchase([
'amount' => '12.00',
'transactionId' => 'ORDER-ZYX8',
- 'transactionReference' => '11fc42b0-bb7a-41a4-8b3c-096b3fd4d402'
+ 'transactionReference' => '11fc42b0-bb7a-41a4-8b3c-096b3fd4d402',
'currency' => 'AUD',
'card' => $card,
'clientIp' => '192.168.1.1'
- ))
+ ])
->send();
if ($response->isRedirect()) {
@@ -148,6 +148,82 @@ The following gateways are provided by this package:
```
+#### DirectPost Store Only
+
+```php
+ $gateway = Omnipay::create('NABTransact_DirectPost');
+ $gateway->setMerchantId('XYZ0010');
+ $gateway->setTransactionPassword('abcd1234');
+ $gateway->setTestMode(true);
+
+ $response = $gateway->store([
+ 'transactionId' => 'STORE-ORDER-100',
+ 'returnUrl' => 'http://example.com/payment/response',
+ 'card' => $card,
+ ])->send();
+
+ if ($response->isRedirect()) {
+ $response->redirect();
+ }
+```
+
+#### DirectPost Capture (Complete Preauth, Server-to-Server)
+
+```php
+ $gateway = Omnipay::create('NABTransact_DirectPost');
+ $gateway->setMerchantId('XYZ0010');
+ $gateway->setTransactionPassword('abcd1234');
+ $gateway->setTestMode(true);
+
+ $response = $gateway->capture([
+ 'transactionId' => 'CAPTURE-ORDER-100',
+ 'transactionReference' => 'NAB-ORIGINAL-TXN-ID',
+ 'amount' => '12.00',
+ 'currency' => 'AUD',
+ ])->send();
+
+ if ($response->isSuccessful()) {
+ echo 'Capture successful: '.$response->getTransactionReference();
+ }
+```
+
+#### DirectPost Refund (Server-to-Server)
+
+```php
+ $response = $gateway->refund([
+ 'transactionId' => 'REFUND-ORDER-100',
+ 'transactionReference' => 'NAB-SETTLED-TXN-ID',
+ 'amount' => '5.00',
+ 'currency' => 'AUD',
+ ])->send();
+```
+
+#### DirectPost Reversal/Void (Server-to-Server)
+
+```php
+ $response = $gateway->void([
+ 'transactionId' => 'VOID-ORDER-100',
+ 'transactionReference' => 'NAB-AUTH-TXN-ID',
+ 'amount' => '12.00',
+ ])->send();
+```
+
+#### EMV 3DS Order Creation API
+
+```php
+ $response = $gateway->createEMV3DSOrder([
+ 'amount' => '12.00',
+ 'currency' => 'AUD',
+ 'clientIp' => '203.0.113.10',
+ 'transactionReference' => 'ORDER-REF-100',
+ ])->send();
+
+ if ($response->isSuccessful()) {
+ echo 'Order ID: '.$response->getOrderId();
+ echo 'Simple Token: '.$response->getSimpleToken();
+ }
+```
+
### NAB Transact DirectPost v2 UnionPay Online Payment
```php
@@ -162,12 +238,12 @@ The following gateways are provided by this package:
* The parameter transactionId must be alpha-numeric and 8 to 32 characters in length
*/
- $response = $gateway->purchase(array(
+ $response = $gateway->purchase([
'amount' => '12.00',
'transactionId' => '1234566789205067',
'currency' => 'AUD',
'returnUrl' => 'http://example.com/payment/response',
- ))
+ ])
->send();
if ($response->isRedirect()) {
@@ -185,13 +261,13 @@ The following gateways are provided by this package:
$gateway->setTestMode(true);
- $response = $gateway->completePurchase(array(
+ $response = $gateway->completePurchase([
'amount' => '12.00',
'transactionId' => '1234566789205067',
- 'transactionReference' => '11fc42b0-bb7a-41a4-8b3c-096b3fd4d402'
+ 'transactionReference' => '11fc42b0-bb7a-41a4-8b3c-096b3fd4d402',
'currency' => 'AUD',
'returnUrl' => 'http://example.com/payment/response',
- ))
+ ])
->send();
if ($response->isSuccessful()) {
@@ -205,6 +281,33 @@ The following gateways are provided by this package:
For general usage instructions, please see the main [Omnipay](https://github.com/thephpleague/omnipay)
repository.
+## Framework-Agnostic Transport Option
+
+Omnipay support remains the default behavior. SecureXML requests can now also use a framework-agnostic cURL transport:
+
+```php
+use Omnipay\NABTransact\Transport\CurlTransport;
+
+$request = $gateway->purchase([
+ 'amount' => '10.00',
+ 'transactionId' => 'ORDER-1000',
+ 'card' => $card,
+]);
+
+$request->setTransport(new CurlTransport());
+$response = $request->send();
+```
+
+DirectPost server-to-server operations (`capture`, `refund`, `void`, `createEMV3DSOrder`) can use the same transport injection pattern.
+
+See `ARCHITECTURE.md` for design details and extension points.
+
+## NAB Feature Coverage
+
+Core payment features are mapped in `docs/feature-matrix.md` with request/response classes and test coverage.
+For a contributor-friendly package map, see `docs/features-implemented-tree.md`.
+This package targets payment-processing API coverage and does not include NAB admin/reporting portal features.
+
## Contributing
Contributions are **welcome** and will be fully **credited**.
@@ -223,3 +326,8 @@ you can subscribe to.
If you believe you have found a bug, please report it using the [GitHub issue tracker](https://github.com/sudiptpa/nabtransact/issues),
or better yet, fork the library and submit a pull request.
+
+
+## Architecture
+
+See `ARCHITECTURE.md` for package structure, flow, and extension points.
diff --git a/composer.json b/composer.json
index 111d3ea..6a5ebeb 100644
--- a/composer.json
+++ b/composer.json
@@ -1,45 +1,59 @@
{
- "name":"sudiptpa/omnipay-nabtransact",
- "type":"library",
- "description":"National Australia Bank (NAB) Transact driver for the Omnipay payment processing library.",
- "keywords":[
- "gateway",
- "merchant",
- "omnipay",
- "pay",
- "payment",
- "nabtransact"
- ],
- "homepage":"https://github.com/sudiptpa/nabtransact",
- "license":"MIT",
- "authors":[
- {
- "name":"Sujip Thapa",
- "email":"sudiptpa@gmail.com"
- }
- ],
- "autoload":{
- "psr-4":{
- "Omnipay\\NABTransact\\":"src/"
- }
- },
- "require":{
- "omnipay/common":"^3"
- },
- "require-dev":{
- "omnipay/tests":"^3",
- "squizlabs/php_codesniffer":"^3",
- "phpro/grumphp":"^0.14.0"
- },
- "extra":{
- "branch-alias":{
- "dev-master":"3.0.x-dev"
- }
- },
- "scripts":{
- "test":"vendor/bin/phpunit",
- "check-style":"phpcs -p --standard=PSR2 src/",
- "fix-style":"phpcbf -p --standard=PSR2 src/"
- },
- "prefer-stable":true
+ "name": "sudiptpa/omnipay-nabtransact",
+ "type": "library",
+ "description": "National Australia Bank (NAB) Transact driver for the Omnipay payment processing library.",
+ "keywords": [
+ "gateway",
+ "merchant",
+ "omnipay",
+ "pay",
+ "payment",
+ "nabtransact"
+ ],
+ "homepage": "https://github.com/sudiptpa/omnipay-nabtransact",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Sujip Thapa",
+ "email": "sudiptpa@gmail.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Omnipay\\NABTransact\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Omnipay\\NABTransact\\Tests\\Support\\": "tests/Support/"
+ }
+ },
+ "require": {
+ "php": "^7.2 || ^8.0",
+ "omnipay/common": "^3"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.12",
+ "phpunit/phpunit": "^8.5.14 || ^9.6",
+ "squizlabs/php_codesniffer": "^3"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1.x-dev"
+ }
+ },
+ "scripts": {
+ "test": "phpunit -c phpunit.xml.dist",
+ "stan": "phpstan analyse --configuration=phpstan.neon.dist --no-progress",
+ "lint": "find src tests -type f -name '*.php' -print0 | xargs -0 -n1 php -l",
+ "check-style": "phpcs -p --standard=PSR12 src tests",
+ "fix-style": "phpcbf -p --standard=PSR12 src tests"
+ },
+ "config": {
+ "sort-packages": true,
+ "allow-plugins": {
+ "php-http/discovery": true
+ }
+ },
+ "prefer-stable": true
}
diff --git a/docs/feature-matrix.md b/docs/feature-matrix.md
new file mode 100644
index 0000000..573b8db
--- /dev/null
+++ b/docs/feature-matrix.md
@@ -0,0 +1,72 @@
+# NAB Transact Feature Matrix
+
+This matrix documents payment API feature coverage in this package.
+
+## Scope
+
+In scope:
+- NAB Transact payment-processing APIs used in Omnipay flows.
+
+Out of scope:
+- NAB admin/reporting portal operations.
+- Merchant account management workflows.
+
+## SecureXML
+
+| Feature | Omnipay Method | Class | Status |
+| --- | --- | --- | --- |
+| Echo/Test | `echoTest()` | `SecureXMLEchoTestRequest` | Implemented |
+| Purchase | `purchase()` | `SecureXMLPurchaseRequest` | Implemented |
+| Risk-managed purchase | `purchase()` with `riskManagement=true` | `SecureXMLRiskPurchaseRequest` | Implemented |
+| Authorize | `authorize()` | `SecureXMLAuthorizeRequest` | Implemented |
+| Capture | `capture()` | `SecureXMLCaptureRequest` | Implemented |
+| Refund | `refund()` | `SecureXMLRefundRequest` | Implemented |
+| XML response mapping | n/a | `SecureXMLResponse` | Implemented |
+
+## DirectPost v2
+
+| Feature | Omnipay Method | Class | Status |
+| --- | --- | --- | --- |
+| Purchase redirect | `purchase()` | `DirectPostPurchaseRequest` | Implemented |
+| Authorize redirect | `authorize()` | `DirectPostAuthorizeRequest` | Implemented |
+| Store-only redirect | `store()` | `DirectPostStoreRequest` | Implemented |
+| Complete purchase callback | `completePurchase()` | `DirectPostCompletePurchaseRequest` | Implemented |
+| Complete authorize callback | `completeAuthorize()` | `DirectPostCompletePurchaseRequest` | Implemented |
+| Complete preauth (capture) | `capture()` | `DirectPostCaptureRequest` | Implemented |
+| Refund (server-to-server) | `refund()` | `DirectPostRefundRequest` | Implemented |
+| Reversal/void (server-to-server) | `void()` | `DirectPostReversalRequest` | Implemented |
+| DirectPost API response mapper | n/a | `DirectPostApiResponse` | Implemented |
+| Fingerprint verification helper | `webhook()` | `DirectPostWebhookRequest` | Implemented |
+| EMV 3DS txnType mapping | `setHasEMV3DSEnabled(true)` | `DirectPostAbstractRequest` | Implemented |
+| Risk-managed txnType mapping | `setHasRiskManagementEnabled(true)` | `DirectPostAbstractRequest` | Implemented |
+| Risk + EMV txnType mapping | both flags enabled | `DirectPostAbstractRequest` | Implemented |
+| EMV 3DS order creation API | `createEMV3DSOrder()` | `EMV3DSOrderRequest` | Implemented |
+
+## Hosted Payment
+
+| Feature | Omnipay Method | Class | Status |
+| --- | --- | --- | --- |
+| Hosted purchase redirect | `purchase()` | `HostedPaymentPurchaseRequest` | Implemented |
+| Hosted callback completion | `completePurchase()` | `HostedPaymentCompletePurchaseRequest` | Implemented |
+| Hosted callback response mapping | n/a | `HostedPaymentCompletePurchaseResponse` | Implemented |
+
+## UnionPay
+
+| Feature | Omnipay Method | Class | Status |
+| --- | --- | --- | --- |
+| UnionPay purchase redirect | `purchase()` | `UnionPayPurchaseRequest` | Implemented |
+| UnionPay callback completion | `completePurchase()` | `UnionPayCompletePurchaseRequest` | Implemented |
+| UnionPay completion response mapping | n/a | `UnionPayCompletePurchaseResponse` | Implemented |
+
+## Transport Layer
+
+| Feature | Class | Status |
+| --- | --- | --- |
+| Omnipay HTTP client adapter (default path) | `OmnipayHttpClientTransport` | Implemented |
+| Native cURL transport | `CurlTransport` | Implemented |
+| Custom transport contract | `TransportInterface` | Implemented |
+
+## Notes
+
+- Existing Omnipay request/response APIs are preserved for backward compatibility.
+- Additional methods were added additively (no removal of existing methods).
diff --git a/docs/features-implemented-tree.md b/docs/features-implemented-tree.md
new file mode 100644
index 0000000..9600504
--- /dev/null
+++ b/docs/features-implemented-tree.md
@@ -0,0 +1,76 @@
+# NAB Transact Implemented Features Tree
+
+This tree is generated from the current package code (gateway methods + request/response classes).
+
+```text
+omnipay-nabtransact
+├── Gateways
+│ ├── NABTransact_SecureXML (SecureXMLGateway)
+│ │ ├── echoTest() -> SecureXMLEchoTestRequest
+│ │ ├── authorize() -> SecureXMLAuthorizeRequest
+│ │ ├── purchase() -> SecureXMLPurchaseRequest
+│ │ ├── purchase() + riskManagement=true -> SecureXMLRiskPurchaseRequest
+│ │ ├── capture() -> SecureXMLCaptureRequest
+│ │ ├── refund() -> SecureXMLRefundRequest
+│ │ ├── transport override (setTransport/getTransport)
+│ │ └── timeout control (setTimeoutSeconds/getTimeoutSeconds)
+│ │
+│ ├── NABTransact_DirectPost (DirectPostGateway)
+│ │ ├── Redirect flows
+│ │ │ ├── authorize() -> DirectPostAuthorizeRequest
+│ │ │ ├── purchase() -> DirectPostPurchaseRequest
+│ │ │ ├── completeAuthorize() -> DirectPostCompletePurchaseRequest
+│ │ │ ├── completePurchase() -> DirectPostCompletePurchaseRequest
+│ │ │ └── store() -> DirectPostStoreRequest
+│ │ ├── Server-to-server operations
+│ │ │ ├── capture() -> DirectPostCaptureRequest
+│ │ │ ├── refund() -> DirectPostRefundRequest
+│ │ │ └── void() -> DirectPostReversalRequest
+│ │ ├── EMV 3DS order API
+│ │ │ └── createEMV3DSOrder() -> EMV3DSOrderRequest
+│ │ ├── Webhook/fingerprint helper
+│ │ │ └── webhook() -> DirectPostWebhookRequest
+│ │ ├── Security/txn behavior
+│ │ │ ├── fingerprint generation + verification
+│ │ │ ├── risk + EMV txnType resolution
+│ │ │ └── legacy typo alias support: vefiyFingerPrint()
+│ │ └── transport + timeout control for server-to-server calls
+│ │
+│ ├── NABTransact_HostedPayment (HostedPaymentGateway)
+│ │ ├── purchase() -> HostedPaymentPurchaseRequest
+│ │ ├── completePurchase() -> HostedPaymentCompletePurchaseRequest
+│ │ ├── payment alert configuration (paymentAlertEmail)
+│ │ └── return link text configuration (returnUrlText)
+│ │
+│ └── NABTransact_UnionPay (UnionPayGateway)
+│ ├── purchase() -> UnionPayPurchaseRequest
+│ └── completePurchase() -> UnionPayCompletePurchaseRequest
+│
+├── Response Models
+│ ├── SecureXMLResponse
+│ ├── DirectPostAuthorizeResponse
+│ ├── DirectPostCompletePurchaseResponse
+│ ├── DirectPostApiResponse
+│ ├── EMV3DSOrderResponse
+│ ├── HostedPaymentPurchaseResponse
+│ ├── HostedPaymentCompletePurchaseResponse
+│ ├── UnionPayPurchaseResponse
+│ └── UnionPayCompletePurchaseResponse
+│
+├── Transport Layer
+│ ├── TransportInterface (custom adapter contract)
+│ ├── TransportResponse (status/body container)
+│ ├── OmnipayHttpClientTransport (default compatibility path)
+│ └── CurlTransport (framework-agnostic native cURL)
+│
+└── Compatibility
+ ├── Existing Omnipay gateway names preserved
+ ├── Existing request/response classes preserved
+ ├── Additive APIs only (no removals in this modernization)
+ └── Legacy typo alias kept for webhook fingerprint method
+```
+
+## Related docs
+
+- Feature matrix: `docs/feature-matrix.md`
+- Architecture: `ARCHITECTURE.md`
diff --git a/grumphp.yml b/grumphp.yml
deleted file mode 100644
index bf81fa5..0000000
--- a/grumphp.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-parameters:
- git_dir: .
- bin_dir: vendor/bin
- tasks:
- phpunit:
- config_file: ~
- testsuite: ~
- group: []
- always_execute: false
- phpcs:
- standard: PSR2
- warning_severity: ~
- ignore_patterns:
- - tests/
- triggered_by: [php]
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 0000000..423e577
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,5 @@
+parameters:
+ level: 5
+ paths:
+ - src
+ inferPrivatePropertyTypeFromConstructor: true
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index f888782..8623653 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -7,16 +7,15 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
- stopOnFailure="false"
- syntaxCheck="false">
+ stopOnFailure="false">
./tests/
-
-
- ./src
-
-
+
+
+ ./src
+
+
diff --git a/src/DirectPostGateway.php b/src/DirectPostGateway.php
index 1a8ce70..c62edfe 100644
--- a/src/DirectPostGateway.php
+++ b/src/DirectPostGateway.php
@@ -3,6 +3,16 @@
namespace Omnipay\NABTransact;
use Omnipay\Common\AbstractGateway;
+use Omnipay\NABTransact\Message\DirectPostAuthorizeRequest;
+use Omnipay\NABTransact\Message\DirectPostCaptureRequest;
+use Omnipay\NABTransact\Message\DirectPostCompletePurchaseRequest;
+use Omnipay\NABTransact\Message\DirectPostPurchaseRequest;
+use Omnipay\NABTransact\Message\DirectPostRefundRequest;
+use Omnipay\NABTransact\Message\DirectPostReversalRequest;
+use Omnipay\NABTransact\Message\DirectPostStoreRequest;
+use Omnipay\NABTransact\Message\DirectPostWebhookRequest;
+use Omnipay\NABTransact\Message\EMV3DSOrderRequest;
+use Omnipay\NABTransact\Transport\TransportInterface;
/**
* NABTransact Direct Post Gateway.
@@ -70,6 +80,82 @@ public function setHasEMV3DSEnabled($value)
return $this->setParameter('hasEMV3DSEnabled', $value);
}
+ public function getHasRiskManagementEnabled()
+ {
+ return $this->getParameter('hasRiskManagementEnabled');
+ }
+
+ public function setHasRiskManagementEnabled($value)
+ {
+ return $this->setParameter('hasRiskManagementEnabled', $value);
+ }
+
+ /**
+ * Optional custom transport for server-to-server DirectPost calls.
+ *
+ * @param TransportInterface $value
+ *
+ * @return mixed
+ */
+ public function setTransport(TransportInterface $value)
+ {
+ return $this->setParameter('transport', $value);
+ }
+
+ /**
+ * @return TransportInterface|null
+ */
+ public function getTransport()
+ {
+ return $this->getParameter('transport');
+ }
+
+ /**
+ * Optional timeout in seconds for server-to-server DirectPost calls.
+ *
+ * @param int $value
+ *
+ * @return mixed
+ */
+ public function setTimeoutSeconds($value)
+ {
+ return $this->setParameter('timeoutSeconds', (int) $value);
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getTimeoutSeconds()
+ {
+ $timeout = $this->getParameter('timeoutSeconds');
+
+ if ($timeout === null) {
+ return null;
+ }
+
+ return (int) $timeout;
+ }
+
+ /**
+ * Backward-friendly alias.
+ */
+ public function getRiskManagement()
+ {
+ return $this->getHasRiskManagementEnabled();
+ }
+
+ /**
+ * Backward-friendly alias.
+ *
+ * @param mixed $value
+ *
+ * @return mixed
+ */
+ public function setRiskManagement($value)
+ {
+ return $this->setHasRiskManagementEnabled($value);
+ }
+
/**
* @param array $parameters
*
@@ -77,7 +163,10 @@ public function setHasEMV3DSEnabled($value)
*/
public function authorize(array $parameters = [])
{
- return $this->createRequest('\Omnipay\NABTransact\Message\DirectPostAuthorizeRequest', $parameters);
+ $request = $this->createRequest(DirectPostAuthorizeRequest::class, $parameters);
+ /** @var DirectPostAuthorizeRequest $request */
+
+ return $request;
}
/**
@@ -87,17 +176,23 @@ public function authorize(array $parameters = [])
*/
public function completeAuthorize(array $parameters = [])
{
- return $this->createRequest('\Omnipay\NABTransact\Message\DirectPostCompletePurchaseRequest', $parameters);
+ $request = $this->createRequest(DirectPostCompletePurchaseRequest::class, $parameters);
+ /** @var DirectPostCompletePurchaseRequest $request */
+
+ return $request;
}
/**
* @param array $parameters
*
- * @return \Omnipay\NABTransact\Message\DirectPostPurchaseRequest
+ * @return \Omnipay\Common\Message\AbstractRequest
*/
public function purchase(array $parameters = [])
{
- return $this->createRequest('\Omnipay\NABTransact\Message\DirectPostPurchaseRequest', $parameters);
+ $request = $this->createRequest(DirectPostPurchaseRequest::class, $parameters);
+ /** @var DirectPostPurchaseRequest $request */
+
+ return $request;
}
/**
@@ -107,6 +202,96 @@ public function purchase(array $parameters = [])
*/
public function completePurchase(array $parameters = [])
{
- return $this->createRequest('\Omnipay\NABTransact\Message\DirectPostCompletePurchaseRequest', $parameters);
+ $request = $this->createRequest(DirectPostCompletePurchaseRequest::class, $parameters);
+ /** @var DirectPostCompletePurchaseRequest $request */
+
+ return $request;
+ }
+
+ /**
+ * Complete preauth and capture funds using DirectPost server-to-server flow.
+ *
+ * @param array $parameters
+ *
+ * @return \Omnipay\NABTransact\Message\DirectPostCaptureRequest
+ */
+ public function capture(array $parameters = [])
+ {
+ $request = $this->createRequest(DirectPostCaptureRequest::class, $parameters);
+ /** @var DirectPostCaptureRequest $request */
+
+ return $request;
+ }
+
+ /**
+ * Refund a DirectPost transaction using server-to-server flow.
+ *
+ * @param array $parameters
+ *
+ * @return \Omnipay\NABTransact\Message\DirectPostRefundRequest
+ */
+ public function refund(array $parameters = [])
+ {
+ $request = $this->createRequest(DirectPostRefundRequest::class, $parameters);
+ /** @var DirectPostRefundRequest $request */
+
+ return $request;
+ }
+
+ /**
+ * Void/reverse a DirectPost transaction using server-to-server flow.
+ *
+ * @param array $parameters
+ *
+ * @return \Omnipay\NABTransact\Message\DirectPostReversalRequest
+ */
+ public function void(array $parameters = [])
+ {
+ $request = $this->createRequest(DirectPostReversalRequest::class, $parameters);
+ /** @var DirectPostReversalRequest $request */
+
+ return $request;
+ }
+
+ /**
+ * Create an EMV 3DS order (order management API).
+ *
+ * @param array $parameters
+ *
+ * @return \Omnipay\NABTransact\Message\EMV3DSOrderRequest
+ */
+ public function createEMV3DSOrder(array $parameters = [])
+ {
+ $request = $this->createRequest(EMV3DSOrderRequest::class, $parameters);
+ /** @var EMV3DSOrderRequest $request */
+
+ return $request;
+ }
+
+ /**
+ * Store card/token details without charging a real amount.
+ *
+ * @param array $parameters
+ *
+ * @return \Omnipay\NABTransact\Message\DirectPostStoreRequest
+ */
+ public function store(array $parameters = [])
+ {
+ $request = $this->createRequest(DirectPostStoreRequest::class, $parameters);
+ /** @var DirectPostStoreRequest $request */
+
+ return $request;
+ }
+
+ /**
+ * Convenience factory for webhook fingerprint verification payloads.
+ *
+ * @param array $data
+ *
+ * @return \Omnipay\NABTransact\Message\DirectPostWebhookRequest
+ */
+ public function webhook(array $data = [])
+ {
+ return new DirectPostWebhookRequest($data);
}
}
diff --git a/src/Enums/TransactionType.php b/src/Enums/TransactionType.php
index ee5aa1e..bb9bf83 100644
--- a/src/Enums/TransactionType.php
+++ b/src/Enums/TransactionType.php
@@ -17,4 +17,10 @@ class TransactionType
const PREAUTH_RISK_MANAGEMENT_3DS_EMV3DS = 7;
const STORE_ONLY = 8;
+
+ // DirectPost transaction type string variants from NAB docs.
+ const COMPLETE_PREAUTH = 'COMPLETE';
+ const REFUND = 'REFUND';
+ const REVERSAL = 'REVERSAL';
+ const ANTIFRAUD = 'ANTIFRAUD';
}
diff --git a/src/HostedPaymentGateway.php b/src/HostedPaymentGateway.php
index 567c95f..089f372 100644
--- a/src/HostedPaymentGateway.php
+++ b/src/HostedPaymentGateway.php
@@ -3,12 +3,24 @@
namespace Omnipay\NABTransact;
use Omnipay\Common\AbstractGateway;
+use Omnipay\NABTransact\Message\HostedPaymentCompletePurchaseRequest;
+use Omnipay\NABTransact\Message\HostedPaymentPurchaseRequest;
/**
* HostedPayment Gateway.
*/
class HostedPaymentGateway extends AbstractGateway
{
+ public function getDefaultParameters()
+ {
+ return [
+ 'merchantId' => '',
+ 'paymentAlertEmail' => '',
+ 'returnUrlText' => '',
+ 'testMode' => false,
+ ];
+ }
+
/**
* @param array $parameters
*
@@ -16,7 +28,10 @@ class HostedPaymentGateway extends AbstractGateway
*/
public function completePurchase(array $parameters = [])
{
- return $this->createRequest('\Omnipay\NABTransact\Message\HostedPaymentCompletePurchaseRequest', $parameters);
+ $request = $this->createRequest(HostedPaymentCompletePurchaseRequest::class, $parameters);
+ /** @var HostedPaymentCompletePurchaseRequest $request */
+
+ return $request;
}
/**
@@ -32,6 +47,42 @@ public function getName()
return 'NAB Hosted Payment';
}
+ /**
+ * @return string
+ */
+ public function getPaymentAlertEmail()
+ {
+ return $this->getParameter('paymentAlertEmail');
+ }
+
+ /**
+ * @param string $value
+ *
+ * @return mixed
+ */
+ public function setPaymentAlertEmail($value)
+ {
+ return $this->setParameter('paymentAlertEmail', $value);
+ }
+
+ /**
+ * @return string
+ */
+ public function getReturnUrlText()
+ {
+ return $this->getParameter('returnUrlText');
+ }
+
+ /**
+ * @param string $value
+ *
+ * @return mixed
+ */
+ public function setReturnUrlText($value)
+ {
+ return $this->setParameter('returnUrlText', $value);
+ }
+
/**
* @param array $parameters
*
@@ -39,7 +90,10 @@ public function getName()
*/
public function purchase(array $parameters = [])
{
- return $this->createRequest('\Omnipay\NABTransact\Message\HostedPaymentPurchaseRequest', $parameters);
+ $request = $this->createRequest(HostedPaymentPurchaseRequest::class, $parameters);
+ /** @var HostedPaymentPurchaseRequest $request */
+
+ return $request;
}
/**
diff --git a/src/Message/AbstractRequest.php b/src/Message/AbstractRequest.php
index 862054f..89c48f7 100644
--- a/src/Message/AbstractRequest.php
+++ b/src/Message/AbstractRequest.php
@@ -2,48 +2,32 @@
namespace Omnipay\NABTransact\Message;
+use Omnipay\NABTransact\Transport\TransportInterface;
+
/**
* NABTransact Abstract Request.
*/
abstract class AbstractRequest extends \Omnipay\Common\Message\AbstractRequest
{
- /**
- * @var string
- */
public $testEndpoint;
- /**
- * @var string
- */
public $liveEndpoint;
- /**
- * @return string
- */
public function getMerchantId()
{
return $this->getParameter('merchantId');
}
- /**
- * @param $value
- */
public function setMerchantId($value)
{
return $this->setParameter('merchantId', $value);
}
- /**
- * @return string
- */
public function getTransactionPassword()
{
return $this->getParameter('transactionPassword');
}
- /**
- * @param $value
- */
public function setTransactionPassword($value)
{
return $this->setParameter('transactionPassword', $value);
@@ -54,17 +38,58 @@ public function getHasEMV3DSEnabled()
return $this->getParameter('hasEMV3DSEnabled');
}
- /**
- * @param $value
- */
public function setHasEMV3DSEnabled($value)
{
return $this->setParameter('hasEMV3DSEnabled', $value);
}
+ public function getHasRiskManagementEnabled()
+ {
+ return $this->getParameter('hasRiskManagementEnabled');
+ }
+
+ public function setHasRiskManagementEnabled($value)
+ {
+ return $this->setParameter('hasRiskManagementEnabled', $value);
+ }
+
+ /**
+ * @param TransportInterface $transport
+ *
+ * @return $this
+ */
+ public function setTransport(TransportInterface $transport)
+ {
+ return $this->setParameter('transport', $transport);
+ }
+
/**
- * @return string
+ * @return TransportInterface|null
*/
+ public function getTransport()
+ {
+ return $this->getParameter('transport');
+ }
+
+ /**
+ * @return int
+ */
+ public function getTimeoutSeconds()
+ {
+ $timeout = $this->getParameter('timeoutSeconds');
+
+ if ($timeout === null) {
+ return 60;
+ }
+
+ return max(1, (int) $timeout);
+ }
+
+ public function setTimeoutSeconds($value)
+ {
+ return $this->setParameter('timeoutSeconds', (int) $value);
+ }
+
public function getEndpoint()
{
return $this->getTestMode() ? $this->testEndpoint : $this->liveEndpoint;
diff --git a/src/Message/DirectPostAbstractRequest.php b/src/Message/DirectPostAbstractRequest.php
index 465a62b..9412f16 100644
--- a/src/Message/DirectPostAbstractRequest.php
+++ b/src/Message/DirectPostAbstractRequest.php
@@ -2,6 +2,7 @@
namespace Omnipay\NABTransact\Message;
+use Omnipay\Common\CreditCard;
use Omnipay\NABTransact\Enums\TransactionType;
/**
@@ -19,6 +20,35 @@ abstract class DirectPostAbstractRequest extends AbstractRequest
*/
public $liveEndpoint = 'https://transact.nab.com.au/live/directpostv2/authorise';
+ /**
+ * @var string|int
+ */
+ protected $txnType = '0';
+
+ /**
+ * @return string
+ */
+ protected function resolveTxnType()
+ {
+ $isPreauth = (string) $this->txnType === (string) TransactionType::NORMAL_PREAUTH;
+ $risk = (bool) $this->getHasRiskManagementEnabled();
+ $emv = (bool) $this->getHasEMV3DSEnabled();
+
+ if ($risk && $emv) {
+ return (string) ($isPreauth ? TransactionType::PREAUTH_RISK_MANAGEMENT_3DS_EMV3DS : TransactionType::PAYMENT_RISK_MANAGEMENT_3DS_EMV3DS);
+ }
+
+ if ($risk) {
+ return (string) ($isPreauth ? TransactionType::PREAUTH_RISK_MANAGEMENT : TransactionType::PAYMENT_RISK_MANAGEMENT);
+ }
+
+ if ($emv) {
+ return (string) ($isPreauth ? TransactionType::PREAUTH_3DS_EMV3DS : TransactionType::PAYMENT_3DS_EMV3DS);
+ }
+
+ return (string) $this->txnType;
+ }
+
/**
* @param array $data
*/
@@ -53,7 +83,7 @@ public function getBaseData()
$data = [];
$data['EPS_MERCHANT'] = $this->getMerchantId();
- $data['EPS_TXNTYPE'] = $this->txnType;
+ $data['EPS_TXNTYPE'] = $this->resolveTxnType();
$data['EPS_REFERENCEID'] = $this->getTransactionId();
$data['EPS_AMOUNT'] = $this->getAmount();
$data['EPS_TIMESTAMP'] = gmdate('YmdHis');
@@ -69,32 +99,40 @@ public function getBaseData()
$data['EPS_CURRENCY'] = $currency;
}
- $card = $this->getCard();
+ $card = $this->getParameter('card');
- if ($billingPostcode = $card->getBillingPostcode()) {
- $data['EPS_ZIPCODE'] = $billingPostcode;
- }
+ if ($card instanceof CreditCard) {
+ if ($billingFirstName = $card->getBillingFirstName()) {
+ $data['EPS_FIRSTNAME'] = $billingFirstName;
+ }
- if ($billingCity = $card->getBillingCity()) {
- $data['EPS_TOWN'] = $billingCity;
- }
+ if ($billingLastName = $card->getBillingLastName()) {
+ $data['EPS_LASTNAME'] = $billingLastName;
+ }
- if ($billingCountry = $card->getBillingCountry()) {
- $data['EPS_BILLINGCOUNTRY'] = $billingCountry;
- }
+ if ($billingPostcode = $card->getBillingPostcode()) {
+ $data['EPS_ZIPCODE'] = $billingPostcode;
+ }
- if ($shippingCountry = $card->getShippingCountry()) {
- $data['EPS_DELIVERYCOUNTRY'] = $shippingCountry;
- }
+ if ($billingCity = $card->getBillingCity()) {
+ $data['EPS_TOWN'] = $billingCity;
+ }
+
+ if ($billingCountry = $card->getBillingCountry()) {
+ $data['EPS_BILLINGCOUNTRY'] = $billingCountry;
+ }
- if ($emailAddress = $card->getEmail()) {
- $data['EPS_EMAILADDRESS'] = $emailAddress;
+ if ($shippingCountry = $card->getShippingCountry()) {
+ $data['EPS_DELIVERYCOUNTRY'] = $shippingCountry;
+ }
+
+ if ($emailAddress = $card->getEmail()) {
+ $data['EPS_EMAILADDRESS'] = $emailAddress;
+ }
}
if ($this->getHasEMV3DSEnabled()) {
$data['EPS_ORDERID'] = $this->getTransactionReference();
-
- $data['EPS_TXNTYPE'] = TransactionType::PAYMENT_3DS_EMV3DS;
}
$data['EPS_FINGERPRINT'] = $this->generateFingerprint($data);
diff --git a/src/Message/DirectPostApiResponse.php b/src/Message/DirectPostApiResponse.php
new file mode 100644
index 0000000..a2ad6a6
--- /dev/null
+++ b/src/Message/DirectPostApiResponse.php
@@ -0,0 +1,97 @@
+getCode();
+
+ if ($code !== null) {
+ return in_array($code, ['00', '08', '11'], true);
+ }
+
+ $statusCode = $this->getHttpStatusCode();
+
+ if ($statusCode === null) {
+ return false;
+ }
+
+ return $statusCode >= 200 && $statusCode <= 299;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getHttpStatusCode()
+ {
+ if (isset($this->data['http_status_code'])) {
+ return (int) $this->data['http_status_code'];
+ }
+
+ return null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getCode()
+ {
+ foreach (['rescode', 'responsecode', 'statuscode', 'code'] as $key) {
+ if (isset($this->data[$key])) {
+ return (string) $this->data[$key];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getMessage()
+ {
+ foreach (['restext', 'responsetext', 'statusdescription', 'message', 'error'] as $key) {
+ if (isset($this->data[$key])) {
+ return (string) $this->data[$key];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getTransactionReference()
+ {
+ foreach (['txnid', 'transactionid', 'eps_txnid'] as $key) {
+ if (isset($this->data[$key])) {
+ return (string) $this->data[$key];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getRawResponse()
+ {
+ if (isset($this->data['raw'])) {
+ return (string) $this->data['raw'];
+ }
+
+ return null;
+ }
+}
diff --git a/src/Message/DirectPostAuthorizeResponse.php b/src/Message/DirectPostAuthorizeResponse.php
index d95bba7..4914dc9 100644
--- a/src/Message/DirectPostAuthorizeResponse.php
+++ b/src/Message/DirectPostAuthorizeResponse.php
@@ -18,8 +18,8 @@ class DirectPostAuthorizeResponse extends AbstractResponse implements RedirectRe
/**
* @param RequestInterface $request
- * @param $data
- * @param $redirectUrl
+ * @param $data
+ * @param $redirectUrl
*/
public function __construct(RequestInterface $request, $data, $redirectUrl)
{
diff --git a/src/Message/DirectPostCaptureRequest.php b/src/Message/DirectPostCaptureRequest.php
new file mode 100644
index 0000000..75288ce
--- /dev/null
+++ b/src/Message/DirectPostCaptureRequest.php
@@ -0,0 +1,21 @@
+
*/
public function getData()
{
@@ -24,7 +24,9 @@ public function getData()
}
/**
- * @param $data
+ * @param array $data
+ *
+ * @return string
*/
public function generateResponseFingerprint($data)
{
diff --git a/src/Message/DirectPostCompletePurchaseResponse.php b/src/Message/DirectPostCompletePurchaseResponse.php
index 3efce85..7630a26 100644
--- a/src/Message/DirectPostCompletePurchaseResponse.php
+++ b/src/Message/DirectPostCompletePurchaseResponse.php
@@ -14,41 +14,47 @@ class DirectPostCompletePurchaseResponse extends AbstractResponse
*/
public function isSuccessful()
{
- return $this->summaryCode() && in_array($this->getCode(), ['00', '08', '11']);
+ return $this->summaryCode() && in_array($this->getCode(), ['00', '08', '11'], true);
}
public function summaryCode()
{
- return isset($this->data['summarycode']) && (int) $this->data['summarycode'] == 1;
+ return isset($this->data['summarycode']) && (int) $this->data['summarycode'] === 1;
}
/**
- * @return string
+ * @return string|null
*/
public function getMessage()
{
if (isset($this->data['restext'])) {
return $this->data['restext'];
}
+
+ return null;
}
/**
- * @return string
+ * @return string|null
*/
public function getCode()
{
if (isset($this->data['rescode'])) {
return $this->data['rescode'];
}
+
+ return null;
}
/**
- * @return string
+ * @return string|null
*/
public function getTransactionReference()
{
if (isset($this->data['txnid'])) {
return $this->data['txnid'];
}
+
+ return null;
}
}
diff --git a/src/Message/DirectPostOperationRequest.php b/src/Message/DirectPostOperationRequest.php
new file mode 100644
index 0000000..0c09866
--- /dev/null
+++ b/src/Message/DirectPostOperationRequest.php
@@ -0,0 +1,155 @@
+validate('merchantId', 'transactionPassword', 'amount', 'transactionId', 'transactionReference');
+
+ $data = [
+ 'EPS_MERCHANT' => $this->getMerchantId(),
+ 'EPS_TXNTYPE' => $this->resolveTxnType(),
+ 'EPS_REFERENCEID' => $this->getTransactionId(),
+ 'EPS_AMOUNT' => $this->getAmount(),
+ $this->targetTransactionField => $this->getTransactionReference(),
+ 'EPS_TIMESTAMP' => gmdate('YmdHis'),
+ ];
+
+ if ($currency = $this->getCurrency()) {
+ $data['EPS_CURRENCY'] = $currency;
+ }
+
+ if ($returnUrl = $this->getReturnUrl()) {
+ $data['EPS_RESULTURL'] = $returnUrl;
+ }
+
+ $data['EPS_FINGERPRINT'] = $this->generateOperationFingerprint($data);
+
+ return $data;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return string
+ */
+ protected function generateOperationFingerprint(array $data)
+ {
+ $hashable = [
+ $data['EPS_MERCHANT'],
+ $this->getTransactionPassword(),
+ $data['EPS_TXNTYPE'],
+ $data['EPS_REFERENCEID'],
+ $data['EPS_AMOUNT'],
+ $data[$this->targetTransactionField],
+ $data['EPS_TIMESTAMP'],
+ ];
+
+ return hash_hmac('sha256', implode('|', $hashable), $this->getTransactionPassword());
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return DirectPostApiResponse
+ */
+ public function sendData($data)
+ {
+ $transport = $this->getTransport();
+ if ($transport === null) {
+ $transport = new OmnipayHttpClientTransport($this->httpClient);
+ }
+
+ $response = $transport->send(
+ 'POST',
+ $this->getEndpoint(),
+ ['Content-Type' => 'application/x-www-form-urlencoded'],
+ http_build_query($data, '', '&'),
+ $this->getTimeoutSeconds()
+ );
+
+ $parsed = $this->parseTransportResponse($response);
+
+ return $this->response = new DirectPostApiResponse($this, $parsed);
+ }
+
+ /**
+ * @return array
+ */
+ protected function parseTransportResponse(TransportResponse $response)
+ {
+ $body = trim((string) $response->getBody());
+ $trimmedBody = ltrim($body);
+ $looksLikeXml = $trimmedBody !== '' && strpos($trimmedBody, '<') === 0;
+
+ $parsed = [
+ 'http_status_code' => $response->getStatusCode(),
+ 'raw' => $body,
+ ];
+
+ if ($body === '') {
+ return $parsed;
+ }
+
+ $json = json_decode($body, true);
+ if (is_array($json)) {
+ return array_merge($parsed, $this->normalizeKeys($json));
+ }
+
+ if (!$looksLikeXml) {
+ $query = [];
+ parse_str($body, $query);
+ if (!empty($query)) {
+ return array_merge($parsed, $this->normalizeKeys($query));
+ }
+ }
+
+ if (function_exists('simplexml_load_string')) {
+ $xml = @simplexml_load_string($body);
+ if ($xml !== false) {
+ $xmlData = [];
+ foreach ($xml->children() as $node) {
+ $xmlData[strtolower($node->getName())] = (string) $node;
+ }
+
+ if (!empty($xmlData)) {
+ return array_merge($parsed, $xmlData);
+ }
+ }
+ }
+
+ return $parsed;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return array
+ */
+ private function normalizeKeys(array $data)
+ {
+ $normalized = [];
+
+ foreach ($data as $key => $value) {
+ $normalized[strtolower((string) $key)] = $value;
+ }
+
+ return $normalized;
+ }
+}
diff --git a/src/Message/DirectPostRefundRequest.php b/src/Message/DirectPostRefundRequest.php
new file mode 100644
index 0000000..7ebf3bd
--- /dev/null
+++ b/src/Message/DirectPostRefundRequest.php
@@ -0,0 +1,16 @@
+validate('returnUrl', 'card');
+
+ if (!$this->getAmount()) {
+ $this->setAmount('0.00');
+ }
+
+ return parent::getData();
+ }
+}
diff --git a/src/Message/DirectPostWebhookRequest.php b/src/Message/DirectPostWebhookRequest.php
index f5f5a9e..934cbb1 100644
--- a/src/Message/DirectPostWebhookRequest.php
+++ b/src/Message/DirectPostWebhookRequest.php
@@ -4,8 +4,14 @@
class DirectPostWebhookRequest extends DirectPostAbstractRequest
{
+ /**
+ * @var array
+ */
private $data = [];
+ /**
+ * @param array $data
+ */
public function __construct($data = [])
{
$this->data = $data;
@@ -27,16 +33,27 @@ public function generateResponseFingerprint($data)
return hash_hmac('sha256', $hash, $data['txn_password']);
}
- public function vefiyFingerPrint($fingerprint)
+ public function verifyFingerPrint($fingerprint)
{
$data = $this->data;
if ($fingerprint !== $this->generateResponseFingerprint($data)) {
- $data['restext'] = $data['restext'].', Invalid fingerprint.';
+ $existing = isset($data['restext']) ? trim((string) $data['restext']) : '';
+ $data['restext'] = $existing === '' ? 'Invalid fingerprint.' : $existing.', Invalid fingerprint.';
$data['summarycode'] = 3;
}
- return new DirectPostCompletePurchaseResponse($this, $data);
+ return $this->response = new DirectPostCompletePurchaseResponse($this, $data);
+ }
+
+ /**
+ * Backward-compatible typo alias.
+ *
+ * @deprecated Use verifyFingerPrint().
+ */
+ public function vefiyFingerPrint($fingerprint)
+ {
+ return $this->verifyFingerPrint($fingerprint);
}
public function getData()
@@ -46,5 +63,8 @@ public function getData()
public function sendData($data)
{
+ $fingerprint = isset($data['fingerprint']) ? $data['fingerprint'] : '';
+
+ return $this->verifyFingerPrint($fingerprint);
}
}
diff --git a/src/Message/EMV3DSOrderRequest.php b/src/Message/EMV3DSOrderRequest.php
new file mode 100644
index 0000000..81ae20d
--- /dev/null
+++ b/src/Message/EMV3DSOrderRequest.php
@@ -0,0 +1,124 @@
+validate('merchantId', 'transactionPassword', 'amount', 'currency', 'clientIp', 'transactionReference');
+
+ return [
+ 'amount' => $this->getAmountInteger(),
+ 'currency' => $this->getCurrency(),
+ 'ip' => $this->getClientIp(),
+ 'merchantId' => $this->getMerchantId(),
+ 'merchantOrderReference' => $this->getTransactionReference(),
+ 'orderType' => $this->getOrderType(),
+ 'intents' => $this->getIntents(),
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public function getOrderType()
+ {
+ return $this->getParameter('orderType') ?: 'PAYMENT';
+ }
+
+ /**
+ * @param string $value
+ *
+ * @return mixed
+ */
+ public function setOrderType($value)
+ {
+ return $this->setParameter('orderType', $value);
+ }
+
+ /**
+ * @return array
+ */
+ public function getIntents()
+ {
+ $intents = $this->getParameter('intents');
+
+ if (!is_array($intents) || empty($intents)) {
+ return ['THREED_SECURE'];
+ }
+
+ return array_values($intents);
+ }
+
+ /**
+ * @param array $value
+ *
+ * @return mixed
+ */
+ public function setIntents(array $value)
+ {
+ return $this->setParameter('intents', $value);
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return EMV3DSOrderResponse
+ */
+ public function sendData($data)
+ {
+ $transport = $this->getTransport();
+ if ($transport === null) {
+ $transport = new OmnipayHttpClientTransport($this->httpClient);
+ }
+
+ $payload = json_encode($data);
+
+ if ($payload === false) {
+ throw new InvalidRequestException('Unable to encode EMV 3DS order payload.');
+ }
+
+ $authorization = base64_encode($this->getMerchantId().':'.$this->getTransactionPassword());
+
+ $response = $transport->send(
+ 'POST',
+ $this->getEndpoint(),
+ [
+ 'Content-Type' => 'application/json; charset=UTF-8',
+ 'Authorization' => 'Basic '.$authorization,
+ ],
+ $payload,
+ $this->getTimeoutSeconds()
+ );
+
+ $parsed = json_decode((string) $response->getBody(), true);
+ if (!is_array($parsed)) {
+ $parsed = [];
+ }
+
+ $parsed['http_status_code'] = $response->getStatusCode();
+ $parsed['raw'] = (string) $response->getBody();
+
+ return $this->response = new EMV3DSOrderResponse($this, $parsed);
+ }
+}
diff --git a/src/Message/EMV3DSOrderResponse.php b/src/Message/EMV3DSOrderResponse.php
new file mode 100644
index 0000000..5666c54
--- /dev/null
+++ b/src/Message/EMV3DSOrderResponse.php
@@ -0,0 +1,123 @@
+getHttpStatusCode();
+
+ if ($statusCode === null) {
+ return false;
+ }
+
+ return $statusCode >= 200 && $statusCode <= 399;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getHttpStatusCode()
+ {
+ if (isset($this->data['http_status_code'])) {
+ return (int) $this->data['http_status_code'];
+ }
+
+ return null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getOrderId()
+ {
+ if (isset($this->data['orderId'])) {
+ return $this->data['orderId'];
+ }
+
+ return null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getSimpleToken()
+ {
+ if (isset($this->data['simpleToken'])) {
+ return $this->data['simpleToken'];
+ }
+
+ return null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getOrderToken()
+ {
+ if (isset($this->data['orderToken'])) {
+ return $this->data['orderToken'];
+ }
+
+ return null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getProviderClientId()
+ {
+ if (isset($this->data['threedSecure']['providerClientId'])) {
+ return $this->data['threedSecure']['providerClientId'];
+ }
+
+ return null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getSessionId()
+ {
+ if (isset($this->data['threedSecure']['sessionId'])) {
+ return $this->data['threedSecure']['sessionId'];
+ }
+
+ return null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getMessage()
+ {
+ foreach (['message', 'error', 'description'] as $key) {
+ if (isset($this->data[$key])) {
+ return (string) $this->data[$key];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getRawResponse()
+ {
+ if (isset($this->data['raw'])) {
+ return (string) $this->data['raw'];
+ }
+
+ return null;
+ }
+}
diff --git a/src/Message/HostedPaymentCompletePurchaseRequest.php b/src/Message/HostedPaymentCompletePurchaseRequest.php
new file mode 100644
index 0000000..baf0f52
--- /dev/null
+++ b/src/Message/HostedPaymentCompletePurchaseRequest.php
@@ -0,0 +1,37 @@
+httpRequest->query->all();
+ if (!empty($query)) {
+ return $query;
+ }
+
+ $request = $this->httpRequest->request->all();
+ if (!empty($request)) {
+ return $request;
+ }
+
+ return [];
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return HostedPaymentCompletePurchaseResponse
+ */
+ public function sendData($data)
+ {
+ return $this->response = new HostedPaymentCompletePurchaseResponse($this, $data);
+ }
+}
diff --git a/src/Message/HostedPaymentCompletePurchaseResponse.php b/src/Message/HostedPaymentCompletePurchaseResponse.php
index 8eb1a91..4f89742 100644
--- a/src/Message/HostedPaymentCompletePurchaseResponse.php
+++ b/src/Message/HostedPaymentCompletePurchaseResponse.php
@@ -12,7 +12,7 @@ class HostedPaymentCompletePurchaseResponse extends AbstractResponse
{
/**
* @param RequestInterface $request
- * @param $data
+ * @param $data
*/
public function __construct(RequestInterface $request, $data)
{
@@ -22,4 +22,44 @@ public function __construct(RequestInterface $request, $data)
parent::__construct($request, $data);
}
+
+ /**
+ * @return bool
+ */
+ public function isSuccessful()
+ {
+ return $this->summaryCode() && in_array($this->getCode(), ['00', '08', '11'], true);
+ }
+
+ /**
+ * @return bool
+ */
+ public function summaryCode()
+ {
+ return isset($this->data['summarycode']) && (int) $this->data['summarycode'] === 1;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getMessage()
+ {
+ return isset($this->data['restext']) ? $this->data['restext'] : null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getCode()
+ {
+ return isset($this->data['rescode']) ? $this->data['rescode'] : null;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getTransactionReference()
+ {
+ return isset($this->data['txnid']) ? $this->data['txnid'] : null;
+ }
}
diff --git a/src/Message/HostedPaymentPurchaseRequest.php b/src/Message/HostedPaymentPurchaseRequest.php
index 951bcad..2bbbf2a 100644
--- a/src/Message/HostedPaymentPurchaseRequest.php
+++ b/src/Message/HostedPaymentPurchaseRequest.php
@@ -10,12 +10,12 @@ class HostedPaymentPurchaseRequest extends AbstractRequest
/**
* @var string
*/
- public $liveEndpoint = 'https://transact.nab.com.au/test/hpp/payment';
+ public $liveEndpoint = 'https://transact.nab.com.au/live/hpp/payment';
/**
* @var string
*/
- public $testEndpoint = 'https://transact.nab.com.au/live/hpp/payment';
+ public $testEndpoint = 'https://transact.nab.com.au/test/hpp/payment';
/**
* @return array
diff --git a/src/Message/HostedPaymentPurchaseResponse.php b/src/Message/HostedPaymentPurchaseResponse.php
index 3e84a7a..11a8d41 100644
--- a/src/Message/HostedPaymentPurchaseResponse.php
+++ b/src/Message/HostedPaymentPurchaseResponse.php
@@ -18,8 +18,8 @@ class HostedPaymentPurchaseResponse extends AbstractResponse implements Redirect
/**
* @param RequestInterface $request
- * @param $data
- * @param $redirectUrl
+ * @param $data
+ * @param $redirectUrl
*/
public function __construct(RequestInterface $request, $data, $redirectUrl)
{
diff --git a/src/Message/SecureXMLAbstractRequest.php b/src/Message/SecureXMLAbstractRequest.php
index 0dc3330..62676e2 100644
--- a/src/Message/SecureXMLAbstractRequest.php
+++ b/src/Message/SecureXMLAbstractRequest.php
@@ -2,6 +2,8 @@
namespace Omnipay\NABTransact\Message;
+use Omnipay\NABTransact\Transport\OmnipayHttpClientTransport;
+use Omnipay\NABTransact\Transport\TransportInterface;
use SimpleXMLElement;
/**
@@ -25,9 +27,9 @@ abstract class SecureXMLAbstractRequest extends AbstractRequest
protected $requestType = 'Payment';
/**
- * @var string
+ * @var int
*/
- protected $txnType;
+ protected $txnType = 0;
/**
* @var array
@@ -64,8 +66,6 @@ public function generateMessageId()
*/
public function getMessageId()
{
- $messageId = $this->getParameter('messageId');
-
if (!$this->getParameter('messageId')) {
$this->setMessageId($this->generateMessageId());
}
@@ -73,11 +73,31 @@ public function getMessageId()
return $this->getParameter('messageId');
}
+ /**
+ * @return TransportInterface
+ */
+ protected function resolveTransport()
+ {
+ $transport = $this->getTransport();
+
+ if ($transport instanceof TransportInterface) {
+ return $transport;
+ }
+
+ return new OmnipayHttpClientTransport($this->httpClient);
+ }
+
public function sendData($data)
{
- $httpResponse = $this->httpClient->request('POST', $this->getEndpoint(), [], $data->asXML());
+ $response = $this->resolveTransport()->send(
+ 'POST',
+ $this->getEndpoint(),
+ [],
+ $data->asXML(),
+ $this->getTimeoutSeconds()
+ );
- $xml = new SimpleXMLElement($httpResponse->getBody()->getContents());
+ $xml = new SimpleXMLElement($response->getBody());
return $this->response = new SecureXMLResponse($this, $xml);
}
@@ -98,7 +118,7 @@ protected function getBaseXML()
$messageInfo = $xml->addChild('MessageInfo');
$messageInfo->messageID = $this->getMessageId();
$messageInfo->addChild('messageTimestamp', $this->generateTimestamp());
- $messageInfo->addChild('timeoutValue', 60);
+ $messageInfo->addChild('timeoutValue', '60');
$messageInfo->addChild('apiVersion', 'xml-4.2');
$merchantInfo = $xml->addChild('MerchantInfo');
@@ -121,13 +141,13 @@ protected function getBasePaymentXML()
$payment = $xml->addChild('Payment');
$txnList = $payment->addChild('TxnList');
- $txnList->addAttribute('count', 1);
+ $txnList->addAttribute('count', '1');
$transaction = $txnList->addChild('Txn');
- $transaction->addAttribute('ID', 1);
- $transaction->addChild('txnType', $this->txnType);
- $transaction->addChild('txnSource', 23);
- $transaction->addChild('txnChannel', 0);
- $transaction->addChild('amount', $this->getAmountInteger());
+ $transaction->addAttribute('ID', '1');
+ $transaction->addChild('txnType', (string) $this->txnType);
+ $transaction->addChild('txnSource', '23');
+ $transaction->addChild('txnChannel', '0');
+ $transaction->addChild('amount', (string) $this->getAmountInteger());
$transaction->addChild('currency', $this->getCurrency());
$transaction->addChild('purchaseOrderNo', $this->getTransactionId());
diff --git a/src/Message/SecureXMLResponse.php b/src/Message/SecureXMLResponse.php
index 60f1059..c82cc95 100644
--- a/src/Message/SecureXMLResponse.php
+++ b/src/Message/SecureXMLResponse.php
@@ -55,7 +55,7 @@ public function getRequestType()
/**
* Gateway approved string if available.
*
- * @return string
+ * @return bool
*/
public function getApproved()
{
@@ -107,7 +107,7 @@ public function getMessageTimestamp()
}
/**
- * @return string Unique NABTransact bank transaction reference.
+ * @return string|null Unique NABTransact bank transaction reference.
*/
public function getTransactionReference()
{
@@ -117,7 +117,7 @@ public function getTransactionReference()
}
/**
- * @return string Unique NABTransact bank transaction reference.
+ * @return string|null Unique NABTransact bank transaction reference.
*/
public function getTransactionId()
{
@@ -129,7 +129,7 @@ public function getTransactionId()
/**
* NABTransact transaction amount.
*
- * @return string
+ * @return string|null
*/
public function getTransactionAmount()
{
@@ -141,7 +141,7 @@ public function getTransactionAmount()
/**
* NABTransact transaction currency.
*
- * @return string
+ * @return string|null
*/
public function getTransactionCurrency()
{
diff --git a/src/Message/SecureXMLRiskPurchaseRequest.php b/src/Message/SecureXMLRiskPurchaseRequest.php
index bf278f3..2e04edf 100644
--- a/src/Message/SecureXMLRiskPurchaseRequest.php
+++ b/src/Message/SecureXMLRiskPurchaseRequest.php
@@ -29,7 +29,7 @@ class SecureXMLRiskPurchaseRequest extends SecureXMLAbstractRequest
public function setIp($value)
{
- $this->setParameter('ip', $value);
+ return $this->setParameter('ip', $value);
}
public function getIp()
@@ -45,26 +45,30 @@ public function getData()
$xml = $this->getBasePaymentXMLWithCard();
$buyer = $xml->addChild('BuyerInfo');
-
- $buyer->addChild('ip', $this->getIp('ip'));
+ $buyer->addChild('ip', $this->getIp());
$card = $this->getCard();
if ($firstName = $card->getFirstName()) {
$buyer->addChild('firstName', $firstName);
}
+
if ($lastName = $card->getLastName()) {
- $buyer->addChild('firstName', $lastName);
+ $buyer->addChild('lastName', $lastName);
}
+
if ($postCode = $card->getBillingPostcode()) {
$buyer->addChild('zipcode', $postCode);
}
+
if ($city = $card->getBillingCity()) {
$buyer->addChild('town', $city);
}
+
if ($country = $card->getBillingCountry()) {
$buyer->addChild('billingCountry', $country);
}
+
if ($email = $card->getEmail()) {
$buyer->addChild('emailAddress', $email);
}
diff --git a/src/Message/UnionPayCompletePurchaseResponse.php b/src/Message/UnionPayCompletePurchaseResponse.php
index 9011011..5aec4a4 100644
--- a/src/Message/UnionPayCompletePurchaseResponse.php
+++ b/src/Message/UnionPayCompletePurchaseResponse.php
@@ -11,7 +11,7 @@ class UnionPayCompletePurchaseResponse extends DirectPostCompletePurchaseRespons
{
/**
* @param RequestInterface $request
- * @param $data
+ * @param $data
*/
public function __construct(RequestInterface $request, $data)
{
diff --git a/src/Message/UnionPayPurchaseResponse.php b/src/Message/UnionPayPurchaseResponse.php
index 482f70c..1aedf5d 100644
--- a/src/Message/UnionPayPurchaseResponse.php
+++ b/src/Message/UnionPayPurchaseResponse.php
@@ -18,8 +18,8 @@ class UnionPayPurchaseResponse extends AbstractResponse implements RedirectRespo
/**
* @param RequestInterface $request
- * @param $data
- * @param $redirectUrl
+ * @param $data
+ * @param $redirectUrl
*/
public function __construct(RequestInterface $request, $data, $redirectUrl)
{
diff --git a/src/SecureXMLGateway.php b/src/SecureXMLGateway.php
index 53e7499..e4441fd 100644
--- a/src/SecureXMLGateway.php
+++ b/src/SecureXMLGateway.php
@@ -3,6 +3,13 @@
namespace Omnipay\NABTransact;
use Omnipay\Common\AbstractGateway;
+use Omnipay\NABTransact\Message\SecureXMLAuthorizeRequest;
+use Omnipay\NABTransact\Message\SecureXMLCaptureRequest;
+use Omnipay\NABTransact\Message\SecureXMLEchoTestRequest;
+use Omnipay\NABTransact\Message\SecureXMLPurchaseRequest;
+use Omnipay\NABTransact\Message\SecureXMLRefundRequest;
+use Omnipay\NABTransact\Message\SecureXMLRiskPurchaseRequest;
+use Omnipay\NABTransact\Transport\TransportInterface;
/**
* NABTransact Secure XML Gateway.
@@ -71,6 +78,52 @@ public function setTransactionPassword($value)
return $this->setParameter('transactionPassword', $value);
}
+ /**
+ * Optional custom transport for SecureXML requests.
+ *
+ * @param TransportInterface $value
+ *
+ * @return mixed
+ */
+ public function setTransport(TransportInterface $value)
+ {
+ return $this->setParameter('transport', $value);
+ }
+
+ /**
+ * @return TransportInterface|null
+ */
+ public function getTransport()
+ {
+ return $this->getParameter('transport');
+ }
+
+ /**
+ * Optional timeout in seconds for outbound verification calls.
+ *
+ * @param int $value
+ *
+ * @return mixed
+ */
+ public function setTimeoutSeconds($value)
+ {
+ return $this->setParameter('timeoutSeconds', (int) $value);
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getTimeoutSeconds()
+ {
+ $timeout = $this->getParameter('timeoutSeconds');
+
+ if ($timeout === null) {
+ return null;
+ }
+
+ return (int) $timeout;
+ }
+
/**
* @param array $parameters
*
@@ -78,7 +131,10 @@ public function setTransactionPassword($value)
*/
public function authorize(array $parameters = [])
{
- return $this->createRequest('\Omnipay\NABTransact\Message\SecureXMLAuthorizeRequest', $parameters);
+ $request = $this->createRequest(SecureXMLAuthorizeRequest::class, $parameters);
+ /** @var SecureXMLAuthorizeRequest $request */
+
+ return $request;
}
/**
@@ -88,21 +144,30 @@ public function authorize(array $parameters = [])
*/
public function capture(array $parameters = [])
{
- return $this->createRequest('\Omnipay\NABTransact\Message\SecureXMLCaptureRequest', $parameters);
+ $request = $this->createRequest(SecureXMLCaptureRequest::class, $parameters);
+ /** @var SecureXMLCaptureRequest $request */
+
+ return $request;
}
/**
* @param array $parameters
*
- * @return \Omnipay\NABTransact\Message\SecureXMLPurchaseRequest
+ * @return \Omnipay\NABTransact\Message\SecureXMLPurchaseRequest|\Omnipay\NABTransact\Message\SecureXMLRiskPurchaseRequest
*/
public function purchase(array $parameters = [])
{
if ($this->getRiskManagement()) {
- return $this->createRequest('\Omnipay\NABTransact\Message\SecureXMLRiskPurchaseRequest', $parameters);
+ $request = $this->createRequest(SecureXMLRiskPurchaseRequest::class, $parameters);
+ /** @var SecureXMLRiskPurchaseRequest $request */
+
+ return $request;
}
- return $this->createRequest('\Omnipay\NABTransact\Message\SecureXMLPurchaseRequest', $parameters);
+ $request = $this->createRequest(SecureXMLPurchaseRequest::class, $parameters);
+ /** @var SecureXMLPurchaseRequest $request */
+
+ return $request;
}
/**
@@ -112,7 +177,10 @@ public function purchase(array $parameters = [])
*/
public function refund(array $parameters = [])
{
- return $this->createRequest('\Omnipay\NABTransact\Message\SecureXMLRefundRequest', $parameters);
+ $request = $this->createRequest(SecureXMLRefundRequest::class, $parameters);
+ /** @var SecureXMLRefundRequest $request */
+
+ return $request;
}
/**
@@ -122,6 +190,9 @@ public function refund(array $parameters = [])
*/
public function echoTest(array $parameters = [])
{
- return $this->createRequest('\Omnipay\NABTransact\Message\SecureXMLEchoTestRequest', $parameters);
+ $request = $this->createRequest(SecureXMLEchoTestRequest::class, $parameters);
+ /** @var SecureXMLEchoTestRequest $request */
+
+ return $request;
}
}
diff --git a/src/Transport/CurlTransport.php b/src/Transport/CurlTransport.php
new file mode 100644
index 0000000..7e431a8
--- /dev/null
+++ b/src/Transport/CurlTransport.php
@@ -0,0 +1,56 @@
+ $headers
+ * @param string $body
+ * @param int $timeoutSeconds
+ *
+ * @return TransportResponse
+ */
+ public function send($method, $url, array $headers = [], $body = '', $timeoutSeconds = 60)
+ {
+ if (!function_exists('curl_init')) {
+ throw new RuntimeException('cURL extension is required for CurlTransport.');
+ }
+
+ $handle = curl_init();
+
+ $formattedHeaders = [];
+ foreach ($headers as $name => $value) {
+ $formattedHeaders[] = $name.': '.$value;
+ }
+
+ $options = [
+ CURLOPT_URL => $url,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_CUSTOMREQUEST => strtoupper($method),
+ CURLOPT_TIMEOUT => max(1, (int) $timeoutSeconds),
+ CURLOPT_HTTPHEADER => $formattedHeaders,
+ CURLOPT_POSTFIELDS => $body,
+ ];
+
+ curl_setopt_array($handle, $options);
+
+ $responseBody = curl_exec($handle);
+
+ if ($responseBody === false) {
+ $message = curl_error($handle);
+ curl_close($handle);
+
+ throw new RuntimeException('cURL transport request failed: '.$message);
+ }
+
+ $statusCode = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
+ curl_close($handle);
+
+ return new TransportResponse($statusCode, (string) $responseBody);
+ }
+}
diff --git a/src/Transport/OmnipayHttpClientTransport.php b/src/Transport/OmnipayHttpClientTransport.php
new file mode 100644
index 0000000..96d4f91
--- /dev/null
+++ b/src/Transport/OmnipayHttpClientTransport.php
@@ -0,0 +1,44 @@
+httpClient = $httpClient;
+ }
+
+ /**
+ * @param string $method
+ * @param string $url
+ * @param array $headers
+ * @param string $body
+ * @param int $timeoutSeconds
+ *
+ * @return TransportResponse
+ */
+ public function send($method, $url, array $headers = [], $body = '', $timeoutSeconds = 60)
+ {
+ $response = $this->httpClient->request(strtoupper($method), $url, $headers, $body);
+
+ $statusCode = method_exists($response, 'getStatusCode') ? (int) $response->getStatusCode() : 200;
+ $responseBody = '';
+
+ if (method_exists($response, 'getBody')) {
+ $responseBody = (string) $response->getBody();
+ }
+
+ return new TransportResponse($statusCode, $responseBody);
+ }
+}
diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php
new file mode 100644
index 0000000..16ba921
--- /dev/null
+++ b/src/Transport/TransportInterface.php
@@ -0,0 +1,17 @@
+ $headers
+ * @param string $body
+ * @param int $timeoutSeconds
+ *
+ * @return TransportResponse
+ */
+ public function send($method, $url, array $headers = [], $body = '', $timeoutSeconds = 60);
+}
diff --git a/src/Transport/TransportResponse.php b/src/Transport/TransportResponse.php
new file mode 100644
index 0000000..7f7af32
--- /dev/null
+++ b/src/Transport/TransportResponse.php
@@ -0,0 +1,57 @@
+
+ */
+ private $headers;
+
+ /**
+ * @param int $statusCode
+ * @param string $body
+ * @param array $headers
+ */
+ public function __construct($statusCode, $body, array $headers = [])
+ {
+ $this->statusCode = (int) $statusCode;
+ $this->body = (string) $body;
+ $this->headers = $headers;
+ }
+
+ /**
+ * @return int
+ */
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ /**
+ * @return string
+ */
+ public function getBody()
+ {
+ return $this->body;
+ }
+
+ /**
+ * @return array
+ */
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+}
diff --git a/src/UnionPayGateway.php b/src/UnionPayGateway.php
index a275ac1..66b4e5d 100644
--- a/src/UnionPayGateway.php
+++ b/src/UnionPayGateway.php
@@ -2,6 +2,9 @@
namespace Omnipay\NABTransact;
+use Omnipay\NABTransact\Message\UnionPayCompletePurchaseRequest;
+use Omnipay\NABTransact\Message\UnionPayPurchaseRequest;
+
/**
* NABTransact UnionPay Gateway.
*/
@@ -19,7 +22,10 @@ public function getName()
*/
public function purchase(array $parameters = [])
{
- return $this->createRequest('\Omnipay\NABTransact\Message\UnionPayPurchaseRequest', $parameters);
+ $request = $this->createRequest(UnionPayPurchaseRequest::class, $parameters);
+ /** @var UnionPayPurchaseRequest $request */
+
+ return $request;
}
/**
@@ -29,6 +35,9 @@ public function purchase(array $parameters = [])
*/
public function completePurchase(array $parameters = [])
{
- return $this->createRequest('\Omnipay\NABTransact\Message\UnionPayCompletePurchaseRequest', $parameters);
+ $request = $this->createRequest(UnionPayCompletePurchaseRequest::class, $parameters);
+ /** @var UnionPayCompletePurchaseRequest $request */
+
+ return $request;
}
}
diff --git a/tests/DirectPostGatewayTest.php b/tests/DirectPostGatewayTest.php
index 8bd1002..d0ec6db 100644
--- a/tests/DirectPostGatewayTest.php
+++ b/tests/DirectPostGatewayTest.php
@@ -2,11 +2,13 @@
namespace Omnipay\NABTransact;
-use Omnipay\Tests\GatewayTestCase;
+use Omnipay\NABTransact\Tests\Support\GatewayTestCase;
+use Omnipay\NABTransact\Transport\TransportInterface;
+use Omnipay\NABTransact\Transport\TransportResponse;
class DirectPostGatewayTest extends GatewayTestCase
{
- public function setUp()
+ protected function setUp(): void
{
parent::setUp();
@@ -18,7 +20,7 @@ public function testAuthorize()
{
$request = $this->gateway->authorize(['amount' => '10.00']);
- $this->assertInstanceOf('\Omnipay\NABTransact\Message\DirectPostAuthorizeRequest', $request);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostAuthorizeRequest::class, $request);
$this->assertSame('10.00', $request->getAmount());
}
@@ -26,7 +28,7 @@ public function testCompleteAuthorize()
{
$request = $this->gateway->completeAuthorize(['amount' => '10.00']);
- $this->assertInstanceOf('\Omnipay\NABTransact\Message\DirectPostCompletePurchaseRequest', $request);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostCompletePurchaseRequest::class, $request);
$this->assertSame('10.00', $request->getAmount());
}
@@ -34,7 +36,7 @@ public function testPurchase()
{
$request = $this->gateway->purchase(['amount' => '10.00']);
- $this->assertInstanceOf('\Omnipay\NABTransact\Message\DirectPostPurchaseRequest', $request);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostPurchaseRequest::class, $request);
$this->assertSame('10.00', $request->getAmount());
}
@@ -42,7 +44,78 @@ public function testCompletePurchase()
{
$request = $this->gateway->completePurchase(['amount' => '10.00']);
- $this->assertInstanceOf('\Omnipay\NABTransact\Message\DirectPostCompletePurchaseRequest', $request);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostCompletePurchaseRequest::class, $request);
$this->assertSame('10.00', $request->getAmount());
}
+
+ public function testRiskManagementAliases()
+ {
+ $this->gateway->setRiskManagement(true);
+
+ $this->assertTrue((bool) $this->gateway->getRiskManagement());
+ $this->assertTrue((bool) $this->gateway->getHasRiskManagementEnabled());
+ }
+
+ public function testWebhookFactory()
+ {
+ $request = $this->gateway->webhook(['merchant' => 'XYZ0010']);
+
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostWebhookRequest::class, $request);
+ }
+
+ public function testStore()
+ {
+ $request = $this->gateway->store(['amount' => '0.00', 'returnUrl' => 'https://example.com/return']);
+
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostStoreRequest::class, $request);
+ $this->assertSame('0.00', $request->getAmount());
+ }
+
+ public function testCapture()
+ {
+ $request = $this->gateway->capture(['amount' => '10.00']);
+
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostCaptureRequest::class, $request);
+ $this->assertSame('10.00', $request->getAmount());
+ }
+
+ public function testRefund()
+ {
+ $request = $this->gateway->refund(['amount' => '10.00']);
+
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostRefundRequest::class, $request);
+ $this->assertSame('10.00', $request->getAmount());
+ }
+
+ public function testVoid()
+ {
+ $request = $this->gateway->void(['amount' => '10.00']);
+
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostReversalRequest::class, $request);
+ $this->assertSame('10.00', $request->getAmount());
+ }
+
+ public function testCreateEmv3dsOrder()
+ {
+ $request = $this->gateway->createEMV3DSOrder(['amount' => '10.00']);
+
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\EMV3DSOrderRequest::class, $request);
+ $this->assertSame('10.00', $request->getAmount());
+ }
+
+ public function testSetTransportAndTimeout()
+ {
+ $transport = new class() implements TransportInterface {
+ public function send($method, $url, array $headers = [], $body = '', $timeoutSeconds = 60)
+ {
+ return new TransportResponse(200, '{}');
+ }
+ };
+
+ $this->gateway->setTransport($transport);
+ $this->gateway->setTimeoutSeconds(12);
+
+ $this->assertSame($transport, $this->gateway->getTransport());
+ $this->assertSame(12, $this->gateway->getTimeoutSeconds());
+ }
}
diff --git a/tests/HostedPaymentGatewayTest.php b/tests/HostedPaymentGatewayTest.php
new file mode 100644
index 0000000..9bdd242
--- /dev/null
+++ b/tests/HostedPaymentGatewayTest.php
@@ -0,0 +1,31 @@
+gateway = new HostedPaymentGateway($this->getHttpClient(), $this->getHttpRequest());
+ $this->gateway->setMerchantId('XYZ0010');
+ }
+
+ public function testPurchase()
+ {
+ $request = $this->gateway->purchase(['amount' => '10.00', 'transactionId' => 'ORDER-100']);
+
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\HostedPaymentPurchaseRequest::class, $request);
+ $this->assertSame('10.00', $request->getAmount());
+ }
+
+ public function testCompletePurchase()
+ {
+ $request = $this->gateway->completePurchase();
+
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\HostedPaymentCompletePurchaseRequest::class, $request);
+ }
+}
diff --git a/tests/Message/DirectPostApiResponseTest.php b/tests/Message/DirectPostApiResponseTest.php
new file mode 100644
index 0000000..3c1147c
--- /dev/null
+++ b/tests/Message/DirectPostApiResponseTest.php
@@ -0,0 +1,50 @@
+getHttpClient(), $this->getHttpRequest());
+
+ $response = new DirectPostApiResponse($request, [
+ 'rescode' => '00',
+ 'restext' => 'Approved',
+ 'txnid' => 'TXN-100',
+ 'http_status_code' => 500,
+ 'raw' => 'rescode=00',
+ ]);
+
+ $this->assertTrue($response->isSuccessful());
+ $this->assertSame('00', $response->getCode());
+ $this->assertSame('Approved', $response->getMessage());
+ $this->assertSame('TXN-100', $response->getTransactionReference());
+ $this->assertSame(500, $response->getHttpStatusCode());
+ $this->assertSame('rescode=00', $response->getRawResponse());
+ }
+
+ public function testSuccessfulByHttpStatusWhenNoResultCode()
+ {
+ $request = new DirectPostRefundRequest($this->getHttpClient(), $this->getHttpRequest());
+
+ $response = new DirectPostApiResponse($request, [
+ 'http_status_code' => 200,
+ 'message' => 'OK',
+ ]);
+
+ $this->assertTrue($response->isSuccessful());
+ $this->assertNull($response->getCode());
+ $this->assertSame('OK', $response->getMessage());
+ }
+
+ public function testFailureWhenNoSignals()
+ {
+ $request = new DirectPostRefundRequest($this->getHttpClient(), $this->getHttpRequest());
+ $response = new DirectPostApiResponse($request, []);
+
+ $this->assertFalse($response->isSuccessful());
+ }
+}
diff --git a/tests/Message/DirectPostAuthorizeRequestTest.php b/tests/Message/DirectPostAuthorizeRequestTest.php
index c8f2670..eebde96 100644
--- a/tests/Message/DirectPostAuthorizeRequestTest.php
+++ b/tests/Message/DirectPostAuthorizeRequestTest.php
@@ -2,12 +2,13 @@
namespace Omnipay\NABTransact\Message;
-use Omnipay\Tests\TestCase;
+use Omnipay\NABTransact\Tests\Support\TestCase;
class DirectPostAuthorizeRequestTest extends TestCase
{
- public function setUp()
+ protected function setUp(): void
{
+ parent::setUp();
$this->request = new DirectPostAuthorizeRequest($this->getHttpClient(), $this->getHttpRequest());
$this->request->initialize([
@@ -36,7 +37,7 @@ public function testSend()
{
$response = $this->request->send();
- $this->assertInstanceOf('Omnipay\NABTransact\Message\DirectPostAuthorizeResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostAuthorizeResponse::class, $response);
$this->assertFalse($response->isSuccessful());
$this->assertTrue($response->isRedirect());
$this->assertNull($response->getTransactionReference());
diff --git a/tests/Message/DirectPostCaptureRequestTest.php b/tests/Message/DirectPostCaptureRequestTest.php
new file mode 100644
index 0000000..e65d8af
--- /dev/null
+++ b/tests/Message/DirectPostCaptureRequestTest.php
@@ -0,0 +1,56 @@
+request = new DirectPostCaptureRequest($this->getHttpClient(), $this->getHttpRequest());
+
+ $this->request->initialize([
+ 'merchantId' => 'XYZ0010',
+ 'transactionPassword' => 'abcd1234',
+ 'amount' => '12.00',
+ 'currency' => 'AUD',
+ 'transactionId' => 'CAPTURE-ORDER-100',
+ 'transactionReference' => 'NAB-ORIG-100',
+ ]);
+ }
+
+ public function testGetDataBuildsCapturePayload()
+ {
+ $data = $this->request->getData();
+
+ $this->assertSame('COMPLETE', $data['EPS_TXNTYPE']);
+ $this->assertSame('NAB-ORIG-100', $data['EPS_TXNID']);
+ $this->assertSame('12.00', $data['EPS_AMOUNT']);
+ $this->assertSame('AUD', $data['EPS_CURRENCY']);
+ $this->assertArrayHasKey('EPS_FINGERPRINT', $data);
+ }
+
+ public function testSendParsesQueryStringResponse()
+ {
+ $request = $this->request;
+
+ $request->setTransport(new class() implements TransportInterface {
+ public function send($method, $url, array $headers = [], $body = '', $timeoutSeconds = 60)
+ {
+ return new TransportResponse(200, 'rescode=00&restext=Approved&txnid=CAPTURED-1');
+ }
+ });
+
+ $response = $request->send();
+
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostApiResponse::class, $response);
+ $this->assertTrue($response->isSuccessful());
+ $this->assertSame('00', $response->getCode());
+ $this->assertSame('Approved', $response->getMessage());
+ $this->assertSame('CAPTURED-1', $response->getTransactionReference());
+ }
+}
diff --git a/tests/Message/DirectPostCompletePurchaseRequestTest.php b/tests/Message/DirectPostCompletePurchaseRequestTest.php
index 2601563..ada860e 100644
--- a/tests/Message/DirectPostCompletePurchaseRequestTest.php
+++ b/tests/Message/DirectPostCompletePurchaseRequestTest.php
@@ -2,12 +2,13 @@
namespace Omnipay\NABTransact\Message;
-use Omnipay\Tests\TestCase;
+use Omnipay\NABTransact\Tests\Support\TestCase;
class DirectPostCompletePurchaseRequestTest extends TestCase
{
- public function setUp()
+ protected function setUp(): void
{
+ parent::setUp();
$this->request = new DirectPostCompletePurchaseRequest($this->getHttpClient(), $this->getHttpRequest());
}
@@ -56,7 +57,7 @@ public function testSuccess()
$response = $this->request->send();
- $this->assertInstanceOf('Omnipay\NABTransact\Message\DirectPostCompletePurchaseResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostCompletePurchaseResponse::class, $response);
$this->assertTrue($response->isSuccessful());
$this->assertFalse($response->isRedirect());
@@ -89,7 +90,7 @@ public function testFailure()
$response = $this->request->send();
- $this->assertInstanceOf('Omnipay\NABTransact\Message\DirectPostCompletePurchaseResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostCompletePurchaseResponse::class, $response);
$this->assertFalse($response->isSuccessful());
$this->assertFalse($response->isRedirect());
diff --git a/tests/Message/DirectPostPurchaseRequestTest.php b/tests/Message/DirectPostPurchaseRequestTest.php
index ecf0eaf..89d2493 100644
--- a/tests/Message/DirectPostPurchaseRequestTest.php
+++ b/tests/Message/DirectPostPurchaseRequestTest.php
@@ -2,12 +2,13 @@
namespace Omnipay\NABTransact\Message;
-use Omnipay\Tests\TestCase;
+use Omnipay\NABTransact\Tests\Support\TestCase;
class DirectPostPurchaseRequestTest extends TestCase
{
- public function setUp()
+ protected function setUp(): void
{
+ parent::setUp();
$this->request = new DirectPostPurchaseRequest($this->getHttpClient(), $this->getHttpRequest());
$this->request->initialize([
@@ -36,7 +37,7 @@ public function testSend()
{
$response = $this->request->send();
- $this->assertInstanceOf('Omnipay\NABTransact\Message\DirectPostAuthorizeResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\DirectPostAuthorizeResponse::class, $response);
$this->assertFalse($response->isSuccessful());
$this->assertTrue($response->isRedirect());
$this->assertNull($response->getTransactionReference());
diff --git a/tests/Message/DirectPostRefundRequestTest.php b/tests/Message/DirectPostRefundRequestTest.php
new file mode 100644
index 0000000..e88bdab
--- /dev/null
+++ b/tests/Message/DirectPostRefundRequestTest.php
@@ -0,0 +1,52 @@
+request = new DirectPostRefundRequest($this->getHttpClient(), $this->getHttpRequest());
+
+ $this->request->initialize([
+ 'merchantId' => 'XYZ0010',
+ 'transactionPassword' => 'abcd1234',
+ 'amount' => '5.00',
+ 'transactionId' => 'REFUND-ORDER-100',
+ 'transactionReference' => 'NAB-TXN-100',
+ ]);
+ }
+
+ public function testGetDataBuildsRefundPayload()
+ {
+ $data = $this->request->getData();
+
+ $this->assertSame('REFUND', $data['EPS_TXNTYPE']);
+ $this->assertSame('NAB-TXN-100', $data['EPS_ORIGINALTXNID']);
+ $this->assertArrayHasKey('EPS_FINGERPRINT', $data);
+ }
+
+ public function testSendParsesJsonResponse()
+ {
+ $request = $this->request;
+
+ $request->setTransport(new class() implements TransportInterface {
+ public function send($method, $url, array $headers = [], $body = '', $timeoutSeconds = 60)
+ {
+ return new TransportResponse(200, '{"rescode":"00","restext":"Refunded","txnid":"REF-1"}');
+ }
+ });
+
+ $response = $request->send();
+
+ $this->assertTrue($response->isSuccessful());
+ $this->assertSame('00', $response->getCode());
+ $this->assertSame('Refunded', $response->getMessage());
+ $this->assertSame('REF-1', $response->getTransactionReference());
+ }
+}
diff --git a/tests/Message/DirectPostReversalRequestTest.php b/tests/Message/DirectPostReversalRequestTest.php
new file mode 100644
index 0000000..a40ab52
--- /dev/null
+++ b/tests/Message/DirectPostReversalRequestTest.php
@@ -0,0 +1,52 @@
+request = new DirectPostReversalRequest($this->getHttpClient(), $this->getHttpRequest());
+
+ $this->request->initialize([
+ 'merchantId' => 'XYZ0010',
+ 'transactionPassword' => 'abcd1234',
+ 'amount' => '12.00',
+ 'transactionId' => 'VOID-ORDER-100',
+ 'transactionReference' => 'NAB-TXN-VOID-100',
+ ]);
+ }
+
+ public function testGetDataBuildsReversalPayload()
+ {
+ $data = $this->request->getData();
+
+ $this->assertSame('REVERSAL', $data['EPS_TXNTYPE']);
+ $this->assertSame('NAB-TXN-VOID-100', $data['EPS_ORIGINALTXNID']);
+ $this->assertArrayHasKey('EPS_FINGERPRINT', $data);
+ }
+
+ public function testSendParsesXmlResponse()
+ {
+ $request = $this->request;
+
+ $request->setTransport(new class() implements TransportInterface {
+ public function send($method, $url, array $headers = [], $body = '', $timeoutSeconds = 60)
+ {
+ return new TransportResponse(200, '00ReversedVOID-1');
+ }
+ });
+
+ $response = $request->send();
+
+ $this->assertTrue($response->isSuccessful());
+ $this->assertSame('00', $response->getCode());
+ $this->assertSame('Reversed', $response->getMessage());
+ $this->assertSame('VOID-1', $response->getTransactionReference());
+ }
+}
diff --git a/tests/Message/DirectPostStoreRequestTest.php b/tests/Message/DirectPostStoreRequestTest.php
new file mode 100644
index 0000000..6d57851
--- /dev/null
+++ b/tests/Message/DirectPostStoreRequestTest.php
@@ -0,0 +1,36 @@
+request = new DirectPostStoreRequest($this->getHttpClient(), $this->getHttpRequest());
+
+ $this->request->initialize([
+ 'merchantId' => 'XYZ0010',
+ 'transactionPassword' => 'abcd1234',
+ 'returnUrl' => 'https://www.example.com/return',
+ 'transactionId' => 'STORE-ORDER-100',
+ 'card' => [
+ 'number' => '4444333322221111',
+ 'expiryMonth' => '12',
+ 'expiryYear' => '2030',
+ 'cvv' => '123',
+ ],
+ ]);
+ }
+
+ public function testStoreDefaultsAmountAndUsesStoreTxnType()
+ {
+ $data = $this->request->getData();
+
+ $this->assertSame('8', $data['EPS_TXNTYPE']);
+ $this->assertSame('0.00', $data['EPS_AMOUNT']);
+ $this->assertArrayHasKey('EPS_FINGERPRINT', $data);
+ }
+}
diff --git a/tests/Message/DirectPostTransactionTypeRequestTest.php b/tests/Message/DirectPostTransactionTypeRequestTest.php
new file mode 100644
index 0000000..cb4d08b
--- /dev/null
+++ b/tests/Message/DirectPostTransactionTypeRequestTest.php
@@ -0,0 +1,88 @@
+purchaseRequest = new DirectPostPurchaseRequest($this->getHttpClient(), $this->getHttpRequest());
+ $this->purchaseRequest->initialize([
+ 'merchantId' => 'XYZ0010',
+ 'transactionPassword' => 'abcd1234',
+ 'amount' => '12.00',
+ 'returnUrl' => 'https://www.example.com/return',
+ 'transactionId' => 'ORDER-100',
+ 'transactionReference' => 'ORDER-REF-100',
+ 'card' => [
+ 'firstName' => 'Example',
+ 'lastName' => 'User',
+ 'number' => '4444333322221111',
+ 'expiryMonth' => '12',
+ 'expiryYear' => '2030',
+ 'cvv' => '123',
+ ],
+ ]);
+
+ $this->authorizeRequest = new DirectPostAuthorizeRequest($this->getHttpClient(), $this->getHttpRequest());
+ $this->authorizeRequest->initialize([
+ 'merchantId' => 'XYZ0010',
+ 'transactionPassword' => 'abcd1234',
+ 'amount' => '12.00',
+ 'returnUrl' => 'https://www.example.com/return',
+ 'transactionId' => 'ORDER-101',
+ 'transactionReference' => 'ORDER-REF-101',
+ 'card' => [
+ 'firstName' => 'Example',
+ 'lastName' => 'User',
+ 'number' => '4444333322221111',
+ 'expiryMonth' => '12',
+ 'expiryYear' => '2030',
+ 'cvv' => '123',
+ ],
+ ]);
+ }
+
+ public function testRiskManagementPurchaseTransactionType()
+ {
+ $this->purchaseRequest->setHasRiskManagementEnabled(true);
+
+ $data = $this->purchaseRequest->getData();
+
+ $this->assertSame('2', $data['EPS_TXNTYPE']);
+ }
+
+ public function testRiskManagementAuthorizeTransactionType()
+ {
+ $this->authorizeRequest->setHasRiskManagementEnabled(true);
+
+ $data = $this->authorizeRequest->getData();
+
+ $this->assertSame('3', $data['EPS_TXNTYPE']);
+ }
+
+ public function testRiskManagementWithEmv3dsPurchaseTransactionType()
+ {
+ $this->purchaseRequest->setHasRiskManagementEnabled(true);
+ $this->purchaseRequest->setHasEMV3DSEnabled(true);
+
+ $data = $this->purchaseRequest->getData();
+
+ $this->assertSame('6', $data['EPS_TXNTYPE']);
+ $this->assertArrayHasKey('EPS_ORDERID', $data);
+ }
+
+ public function testRiskManagementWithEmv3dsAuthorizeTransactionType()
+ {
+ $this->authorizeRequest->setHasRiskManagementEnabled(true);
+ $this->authorizeRequest->setHasEMV3DSEnabled(true);
+
+ $data = $this->authorizeRequest->getData();
+
+ $this->assertSame('7', $data['EPS_TXNTYPE']);
+ $this->assertArrayHasKey('EPS_ORDERID', $data);
+ }
+}
diff --git a/tests/Message/DirectPostWebhookRequestTest.php b/tests/Message/DirectPostWebhookRequestTest.php
new file mode 100644
index 0000000..0ac6446
--- /dev/null
+++ b/tests/Message/DirectPostWebhookRequestTest.php
@@ -0,0 +1,53 @@
+ 'XYZ0010',
+ 'txn_password' => 'abcd1234',
+ 'refid' => 'ORDER-123',
+ 'amount' => '10.00',
+ 'timestamp' => '20260222000000',
+ 'summarycode' => '1',
+ 'restext' => 'Approved',
+ 'rescode' => '00',
+ 'txnid' => '10001',
+ ];
+
+ $request = new DirectPostWebhookRequest($payload);
+ $fingerprint = $request->generateResponseFingerprint($payload);
+
+ $responseFromCorrectMethod = $request->verifyFingerPrint($fingerprint);
+ $responseFromTypoAlias = $request->vefiyFingerPrint($fingerprint);
+
+ $this->assertTrue($responseFromCorrectMethod->isSuccessful());
+ $this->assertTrue($responseFromTypoAlias->isSuccessful());
+ }
+
+ public function testInvalidFingerprintMarksFailure()
+ {
+ $payload = [
+ 'merchant' => 'XYZ0010',
+ 'txn_password' => 'abcd1234',
+ 'refid' => 'ORDER-123',
+ 'amount' => '10.00',
+ 'timestamp' => '20260222000000',
+ 'summarycode' => '1',
+ 'restext' => 'Approved',
+ 'rescode' => '00',
+ 'txnid' => '10001',
+ ];
+
+ $request = new DirectPostWebhookRequest($payload);
+ $response = $request->sendData(['fingerprint' => 'invalid']);
+
+ $this->assertFalse($response->isSuccessful());
+ $this->assertStringContainsString('Invalid fingerprint', (string) $response->getMessage());
+ }
+}
diff --git a/tests/Message/EMV3DSOrderRequestTest.php b/tests/Message/EMV3DSOrderRequestTest.php
new file mode 100644
index 0000000..4f1e0b4
--- /dev/null
+++ b/tests/Message/EMV3DSOrderRequestTest.php
@@ -0,0 +1,96 @@
+request = new EMV3DSOrderRequest($this->getHttpClient(), $this->getHttpRequest());
+
+ $this->request->initialize([
+ 'merchantId' => 'XYZ0010',
+ 'transactionPassword' => 'abcd1234',
+ 'amount' => '12.00',
+ 'currency' => 'AUD',
+ 'clientIp' => '1.2.3.4',
+ 'transactionReference' => 'EMV-ORDER-100',
+ ]);
+ }
+
+ public function testGetDataBuildsDefaultPayload()
+ {
+ $data = $this->request->getData();
+
+ $this->assertSame(1200, $data['amount']);
+ $this->assertSame('AUD', $data['currency']);
+ $this->assertSame('1.2.3.4', $data['ip']);
+ $this->assertSame('XYZ0010', $data['merchantId']);
+ $this->assertSame('EMV-ORDER-100', $data['merchantOrderReference']);
+ $this->assertSame('PAYMENT', $data['orderType']);
+ $this->assertSame(['THREED_SECURE'], $data['intents']);
+ }
+
+ public function testSendUsesTransportAndParsesResponse()
+ {
+ $capture = (object) ['method' => null, 'url' => null, 'headers' => [], 'body' => null, 'timeout' => null];
+
+ $request = $this->request;
+ $request->setTimeoutSeconds(15);
+ $request->setTransport(new class($capture) implements TransportInterface {
+ private $capture;
+
+ public function __construct($capture)
+ {
+ $this->capture = $capture;
+ }
+
+ public function send($method, $url, array $headers = [], $body = '', $timeoutSeconds = 60)
+ {
+ $this->capture->method = $method;
+ $this->capture->url = $url;
+ $this->capture->headers = $headers;
+ $this->capture->body = $body;
+ $this->capture->timeout = $timeoutSeconds;
+
+ return new TransportResponse(201, '{"orderId":"ORDER-1","simpleToken":"S-1","orderToken":"O-1","threedSecure":{"providerClientId":"P-1","sessionId":"SESS-1"}}');
+ }
+ });
+
+ $response = $request->send();
+
+ $this->assertSame('POST', $capture->method);
+ $this->assertSame('https://transact.nab.com.au/services/order-management/v2/payments/orders', $capture->url);
+ $this->assertArrayHasKey('Authorization', $capture->headers);
+ $this->assertStringContainsString('"merchantOrderReference":"EMV-ORDER-100"', $capture->body);
+ $this->assertSame(15, $capture->timeout);
+
+ $this->assertTrue($response->isSuccessful());
+ $this->assertSame('ORDER-1', $response->getOrderId());
+ $this->assertSame('S-1', $response->getSimpleToken());
+ $this->assertSame('O-1', $response->getOrderToken());
+ $this->assertSame('P-1', $response->getProviderClientId());
+ $this->assertSame('SESS-1', $response->getSessionId());
+ }
+
+ public function testSendHandlesInvalidJsonResponse()
+ {
+ $request = $this->request;
+ $request->setTransport(new class() implements TransportInterface {
+ public function send($method, $url, array $headers = [], $body = '', $timeoutSeconds = 60)
+ {
+ return new TransportResponse(500, 'Internal Error');
+ }
+ });
+
+ $response = $request->send();
+
+ $this->assertFalse($response->isSuccessful());
+ $this->assertSame('Internal Error', $response->getRawResponse());
+ }
+}
diff --git a/tests/Message/HostedPaymentCompletePurchaseRequestTest.php b/tests/Message/HostedPaymentCompletePurchaseRequestTest.php
new file mode 100644
index 0000000..4549ac0
--- /dev/null
+++ b/tests/Message/HostedPaymentCompletePurchaseRequestTest.php
@@ -0,0 +1,46 @@
+request = new HostedPaymentCompletePurchaseRequest($this->getHttpClient(), $this->getHttpRequest());
+ }
+
+ public function testReadDataFromQuery()
+ {
+ $this->getHttpRequest()->query->replace([
+ 'summarycode' => '1',
+ 'rescode' => '00',
+ 'restext' => 'Approved',
+ 'txnid' => 'TXN-100',
+ ]);
+
+ $response = $this->request->send();
+
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\HostedPaymentCompletePurchaseResponse::class, $response);
+ $this->assertTrue($response->isSuccessful());
+ $this->assertSame('00', $response->getCode());
+ $this->assertSame('TXN-100', $response->getTransactionReference());
+ }
+
+ public function testReadDataFromRequestBodyWhenQueryMissing()
+ {
+ $this->getHttpRequest()->request->replace([
+ 'summarycode' => '3',
+ 'rescode' => '06',
+ 'restext' => 'Declined',
+ 'txnid' => 'TXN-101',
+ ]);
+
+ $response = $this->request->send();
+
+ $this->assertFalse($response->isSuccessful());
+ $this->assertSame('Declined', $response->getMessage());
+ }
+}
diff --git a/tests/Message/HostedPaymentPurchaseRequestTest.php b/tests/Message/HostedPaymentPurchaseRequestTest.php
new file mode 100644
index 0000000..fd0eb0c
--- /dev/null
+++ b/tests/Message/HostedPaymentPurchaseRequestTest.php
@@ -0,0 +1,39 @@
+request = new HostedPaymentPurchaseRequest($this->getHttpClient(), $this->getHttpRequest());
+
+ $this->request->initialize([
+ 'merchantId' => 'XYZ0010',
+ 'paymentAlertEmail' => 'merchant@example.com',
+ 'amount' => '10.00',
+ 'currency' => 'AUD',
+ 'transactionId' => 'ORDER-123',
+ 'returnUrl' => 'https://example.com/return',
+ 'notifyUrl' => 'https://example.com/notify',
+ 'returnUrlText' => 'Return',
+ ]);
+ }
+
+ public function testUsesTestEndpointInTestMode()
+ {
+ $this->request->setTestMode(true);
+
+ $this->assertSame('https://transact.nab.com.au/test/hpp/payment', $this->request->getEndpoint());
+ }
+
+ public function testUsesLiveEndpointInLiveMode()
+ {
+ $this->request->setTestMode(false);
+
+ $this->assertSame('https://transact.nab.com.au/live/hpp/payment', $this->request->getEndpoint());
+ }
+}
diff --git a/tests/Message/SecureXMLAuthorizeRequestTest.php b/tests/Message/SecureXMLAuthorizeRequestTest.php
index 6480259..418db9e 100644
--- a/tests/Message/SecureXMLAuthorizeRequestTest.php
+++ b/tests/Message/SecureXMLAuthorizeRequestTest.php
@@ -2,12 +2,13 @@
namespace Omnipay\NABTransact\Message;
-use Omnipay\Tests\TestCase;
+use Omnipay\NABTransact\Tests\Support\TestCase;
class SecureXMLAuthorizeRequestTest extends TestCase
{
- public function setUp()
+ protected function setUp(): void
{
+ parent::setUp();
$this->request = new SecureXMLAuthorizeRequest($this->getHttpClient(), $this->getHttpRequest());
$this->request->initialize([
@@ -28,13 +29,13 @@ public function setUp()
public function testSendSuccess()
{
- $this->setMockHttpResponse('SecureXMLAuthorizeRequestSuccess.txt');
+ $this->queueFixtureResponse('SecureXMLAuthorizeRequestSuccess.txt');
$response = $this->request->send();
$data = $response->getData();
- $this->assertInstanceOf('Omnipay\NABTransact\Message\SecureXMLResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\SecureXMLResponse::class, $response);
$this->assertTrue($response->isSuccessful());
$this->assertFalse($response->isRedirect());
@@ -50,10 +51,10 @@ public function testSendSuccess()
public function testSendFailure()
{
- $this->setMockHttpResponse('SecureXMLAuthorizeRequestFail.txt');
+ $this->queueFixtureResponse('SecureXMLAuthorizeRequestFail.txt');
$response = $this->request->send();
- $this->assertInstanceOf('Omnipay\NABTransact\Message\SecureXMLResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\SecureXMLResponse::class, $response);
$this->assertFalse($response->isSuccessful());
$this->assertFalse($response->isRedirect());
@@ -64,10 +65,10 @@ public function testSendFailure()
public function testInsufficientFundsFailure()
{
- $this->setMockHttpResponse('SecureXMLAuthorizeRequestInsufficientFundsFail.txt');
+ $this->queueFixtureResponse('SecureXMLAuthorizeRequestInsufficientFundsFail.txt');
$response = $this->request->send();
- $this->assertInstanceOf('Omnipay\NABTransact\Message\SecureXMLResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\SecureXMLResponse::class, $response);
$this->assertFalse($response->isSuccessful());
$this->assertFalse($response->isRedirect());
@@ -78,10 +79,10 @@ public function testInsufficientFundsFailure()
public function testInvalidMerchantFailure()
{
- $this->setMockHttpResponse('SecureXMLAuthorizeRequestInvalidMerchantFail.txt');
+ $this->queueFixtureResponse('SecureXMLAuthorizeRequestInvalidMerchantFail.txt');
$response = $this->request->send();
- $this->assertInstanceOf('Omnipay\NABTransact\Message\SecureXMLResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\SecureXMLResponse::class, $response);
$this->assertFalse($response->isSuccessful());
$this->assertFalse($response->isRedirect());
@@ -92,10 +93,10 @@ public function testInvalidMerchantFailure()
public function testInvalidMerchantIDFailure()
{
- $this->setMockHttpResponse('SecureXMLAuthorizeRequestInvalidMerchantIDFail.txt');
+ $this->queueFixtureResponse('SecureXMLAuthorizeRequestInvalidMerchantIDFail.txt');
$response = $this->request->send();
- $this->assertInstanceOf('Omnipay\NABTransact\Message\SecureXMLResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\SecureXMLResponse::class, $response);
$this->assertFalse($response->isSuccessful());
$this->assertFalse($response->isRedirect());
diff --git a/tests/Message/SecureXMLEchoTestRequestTest.php b/tests/Message/SecureXMLEchoTestRequestTest.php
index ac61b7d..fc718d3 100644
--- a/tests/Message/SecureXMLEchoTestRequestTest.php
+++ b/tests/Message/SecureXMLEchoTestRequestTest.php
@@ -2,12 +2,13 @@
namespace Omnipay\NABTransact\Message;
-use Omnipay\Tests\TestCase;
+use Omnipay\NABTransact\Tests\Support\TestCase;
class SecureXMLEchoTestRequestTest extends TestCase
{
- public function setUp()
+ protected function setUp(): void
{
+ parent::setUp();
$this->request = new SecureXMLEchoTestRequest($this->getHttpClient(), $this->getHttpRequest());
$this->request->initialize([
@@ -19,12 +20,11 @@ public function setUp()
public function testSuccess()
{
- $this->setMockHttpResponse('SecureXMLEchoTestRequestSuccess.txt');
+ $this->queueFixtureResponse('SecureXMLEchoTestRequestSuccess.txt');
$response = $this->request->send();
- $data = $response->getData();
- $this->assertInstanceOf('Omnipay\NABTransact\Message\SecureXMLResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\SecureXMLResponse::class, $response);
$this->assertSame('Normal', $response->getMessage());
$this->assertFalse($response->isRedirect());
$this->assertSame('000', $response->getStatusCode());
diff --git a/tests/Message/SecureXMLPurchaseRequestTest.php b/tests/Message/SecureXMLPurchaseRequestTest.php
index 6838f4b..bd3ea12 100644
--- a/tests/Message/SecureXMLPurchaseRequestTest.php
+++ b/tests/Message/SecureXMLPurchaseRequestTest.php
@@ -2,12 +2,13 @@
namespace Omnipay\NABTransact\Message;
-use Omnipay\Tests\TestCase;
+use Omnipay\NABTransact\Tests\Support\TestCase;
class SecureXMLPurchaseRequestTest extends TestCase
{
- public function setUp()
+ protected function setUp(): void
{
+ parent::setUp();
$this->request = new SecureXMLPurchaseRequest($this->getHttpClient(), $this->getHttpRequest());
$this->request->initialize([
@@ -28,12 +29,12 @@ public function setUp()
public function testSendSuccess()
{
- $this->setMockHttpResponse('SecureXMLPurchaseRequestSendSuccess.txt');
+ $this->queueFixtureResponse('SecureXMLPurchaseRequestSendSuccess.txt');
$response = $this->request->send();
$data = $response->getData();
- $this->assertInstanceOf('Omnipay\NABTransact\Message\SecureXMLResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\SecureXMLResponse::class, $response);
$this->assertTrue($response->isSuccessful());
$this->assertFalse($response->isRedirect());
diff --git a/tests/Message/SecureXMLRiskPurchaseRequestTest.php b/tests/Message/SecureXMLRiskPurchaseRequestTest.php
new file mode 100644
index 0000000..1526924
--- /dev/null
+++ b/tests/Message/SecureXMLRiskPurchaseRequestTest.php
@@ -0,0 +1,45 @@
+request = new SecureXMLRiskPurchaseRequest($this->getHttpClient(), $this->getHttpRequest());
+
+ $this->request->initialize([
+ 'merchantId' => 'XYZ0010',
+ 'transactionPassword' => 'abcd1234',
+ 'testMode' => true,
+ 'amount' => '12.00',
+ 'transactionId' => '1234',
+ 'ip' => '1.1.1.1',
+ 'card' => [
+ 'firstName' => 'Example',
+ 'lastName' => 'User',
+ 'number' => '4444333322221111',
+ 'expiryMonth' => '12',
+ 'expiryYear' => '2030',
+ 'cvv' => '123',
+ 'email' => 'example@example.com',
+ 'billingPostcode' => '12345',
+ 'billingCity' => 'Billstown',
+ 'billingCountry' => 'US',
+ ],
+ ]);
+ }
+
+ public function testBuildsBuyerInfoWithLastNameField()
+ {
+ $xml = (string) $this->request->getData()->asXML();
+
+ $this->assertStringContainsString('Example', $xml);
+ $this->assertStringContainsString('User', $xml);
+ $this->assertStringNotContainsString('User', $xml);
+ $this->assertStringContainsString('1.1.1.1', $xml);
+ }
+}
diff --git a/tests/Message/SecureXMLTransportRequestTest.php b/tests/Message/SecureXMLTransportRequestTest.php
new file mode 100644
index 0000000..d374e61
--- /dev/null
+++ b/tests/Message/SecureXMLTransportRequestTest.php
@@ -0,0 +1,82 @@
+getHttpClient(), $this->getHttpRequest());
+
+ $request->initialize([
+ 'merchantId' => 'XYZ0010',
+ 'transactionPassword' => 'abcd1234',
+ 'testMode' => true,
+ 'amount' => '12.00',
+ 'transactionId' => '1234',
+ 'card' => [
+ 'number' => '4444333322221111',
+ 'expiryMonth' => '12',
+ 'expiryYear' => '2030',
+ 'cvv' => '123',
+ 'cardHolderName' => 'Sujip Thapa',
+ ],
+ ]);
+
+ $capture = (object) ['called' => false, 'method' => null, 'url' => null];
+
+ $transport = new class($capture) implements TransportInterface {
+ private $capture;
+
+ public function __construct($capture)
+ {
+ $this->capture = $capture;
+ }
+
+ public function send($method, $url, array $headers = [], $body = '', $timeoutSeconds = 60)
+ {
+ $this->capture->called = true;
+ $this->capture->method = $method;
+ $this->capture->url = $url;
+
+ return new TransportResponse(
+ 200,
+ <<<'XML'
+
+ 20260222000000000+0000
+ 000Normal
+ Payment
+
+
+
+ Yes
+ 00
+ Approved
+ 1234
+ 1000
+ 1200
+ AUD
+ 23
+
+
+
+
+XML
+ );
+ }
+ };
+
+ $request->setTransport($transport);
+
+ $response = $request->send();
+
+ $this->assertTrue($capture->called);
+ $this->assertSame('POST', $capture->method);
+ $this->assertTrue($response->isSuccessful());
+ $this->assertSame('00', $response->getCode());
+ }
+}
diff --git a/tests/Message/UnionPayCompletePurchaseRequestTest.php b/tests/Message/UnionPayCompletePurchaseRequestTest.php
index 8c4a2ee..e172d99 100644
--- a/tests/Message/UnionPayCompletePurchaseRequestTest.php
+++ b/tests/Message/UnionPayCompletePurchaseRequestTest.php
@@ -2,27 +2,22 @@
namespace Omnipay\NABTransact\Message;
-use Omnipay\Tests\TestCase;
+use Omnipay\NABTransact\Tests\Support\TestCase;
class UnionPayCompletePurchaseRequestTest extends TestCase
{
- public function setUp()
- {
- $this->request = new UnionPayCompletePurchaseRequest($this->getHttpClient(), $this->getHttpRequest());
- }
-
public function testUnionPayCompletePurchaseSuccess()
{
- $data = [];
-
- $data['restext'] = 'Approved';
- $data['rescode'] = '00';
- $data['summarycode'] = '1';
- $data['txnid'] = '12345';
+ $data = [
+ 'restext' => 'Approved',
+ 'rescode' => '00',
+ 'summarycode' => '1',
+ 'txnid' => '12345',
+ ];
$response = new UnionPayCompletePurchaseResponse($this->getMockRequest(), $data);
- $this->assertInstanceOf('Omnipay\NABTransact\Message\UnionPayCompletePurchaseResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\UnionPayCompletePurchaseResponse::class, $response);
$this->assertTrue($response->isSuccessful());
$this->assertFalse($response->isRedirect());
$this->assertSame('12345', $response->getTransactionReference());
@@ -33,16 +28,16 @@ public function testUnionPayCompletePurchaseSuccess()
public function testUnionPayCompletePurchaseFailure()
{
- $data = [];
-
- $data['restext'] = 'Error';
- $data['txnid'] = '12345';
- $data['summarycode'] = '3';
- $data['rescode'] = '06';
+ $data = [
+ 'restext' => 'Error',
+ 'txnid' => '12345',
+ 'summarycode' => '3',
+ 'rescode' => '06',
+ ];
$response = new UnionPayCompletePurchaseResponse($this->getMockRequest(), $data);
- $this->assertInstanceOf('Omnipay\NABTransact\Message\UnionPayCompletePurchaseResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\UnionPayCompletePurchaseResponse::class, $response);
$this->assertFalse($response->isSuccessful());
$this->assertFalse($response->isRedirect());
$this->assertSame('12345', $response->getTransactionReference());
diff --git a/tests/Message/UnionPayPurchaseRequestTest.php b/tests/Message/UnionPayPurchaseRequestTest.php
index 0885661..a264366 100644
--- a/tests/Message/UnionPayPurchaseRequestTest.php
+++ b/tests/Message/UnionPayPurchaseRequestTest.php
@@ -2,12 +2,13 @@
namespace Omnipay\NABTransact\Message;
-use Omnipay\Tests\TestCase;
+use Omnipay\NABTransact\Tests\Support\TestCase;
class UnionPayPurchaseRequestTest extends TestCase
{
- public function setUp()
+ protected function setUp(): void
{
+ parent::setUp();
$this->request = new UnionPayPurchaseRequest($this->getHttpClient(), $this->getHttpRequest());
$this->request->initialize([
@@ -31,7 +32,7 @@ public function testPurchase()
{
$response = $this->request->send();
- $this->assertInstanceOf('Omnipay\NABTransact\Message\UnionPayPurchaseResponse', $response);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\UnionPayPurchaseResponse::class, $response);
$this->assertFalse($response->isSuccessful());
$this->assertTrue($response->isRedirect());
diff --git a/tests/Message/UnionPayPurchaseResponseTest.php b/tests/Message/UnionPayPurchaseResponseTest.php
index bbfe4a2..470f706 100644
--- a/tests/Message/UnionPayPurchaseResponseTest.php
+++ b/tests/Message/UnionPayPurchaseResponseTest.php
@@ -2,7 +2,7 @@
namespace Omnipay\NABTransact\Message;
-use Omnipay\Tests\TestCase;
+use Omnipay\NABTransact\Tests\Support\TestCase;
class UnionPayPurchaseResponseTest extends TestCase
{
diff --git a/tests/SecureXMLGatewayTest.php b/tests/SecureXMLGatewayTest.php
index 17a4bab..4112383 100644
--- a/tests/SecureXMLGatewayTest.php
+++ b/tests/SecureXMLGatewayTest.php
@@ -2,11 +2,13 @@
namespace Omnipay\NABTransact;
-use Omnipay\Tests\GatewayTestCase;
+use Omnipay\NABTransact\Tests\Support\GatewayTestCase;
+use Omnipay\NABTransact\Transport\TransportInterface;
+use Omnipay\NABTransact\Transport\TransportResponse;
class SecureXMLGatewayTest extends GatewayTestCase
{
- public function setUp()
+ protected function setUp(): void
{
parent::setUp();
@@ -17,7 +19,7 @@ public function setUp()
public function testEcho()
{
$request = $this->gateway->echoTest(['amount' => '10.00', 'transactionId' => 'Order-YKHU67']);
- $this->assertInstanceOf('\Omnipay\NABTransact\Message\SecureXMLEchoTestRequest', $request);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\SecureXMLEchoTestRequest::class, $request);
$this->assertSame('10.00', $request->getAmount());
$this->assertSame('Order-YKHU67', $request->getTransactionId());
}
@@ -26,7 +28,7 @@ public function testAuthorize()
{
$request = $this->gateway->authorize(['amount' => '10.00']);
- $this->assertInstanceOf('\Omnipay\NABTransact\Message\SecureXMLAuthorizeRequest', $request);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\SecureXMLAuthorizeRequest::class, $request);
$this->assertSame('10.00', $request->getAmount());
}
@@ -34,7 +36,7 @@ public function testCapture()
{
$request = $this->gateway->capture(['amount' => '10.00', 'transactionId' => 'Order-YKHU67']);
- $this->assertInstanceOf('\Omnipay\NABTransact\Message\SecureXMLCaptureRequest', $request);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\SecureXMLCaptureRequest::class, $request);
$this->assertSame('10.00', $request->getAmount());
$this->assertSame('Order-YKHU67', $request->getTransactionId());
}
@@ -43,7 +45,7 @@ public function testPurchase()
{
$request = $this->gateway->purchase(['amount' => '10.00']);
- $this->assertInstanceOf('\Omnipay\NABTransact\Message\SecureXMLPurchaseRequest', $request);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\SecureXMLPurchaseRequest::class, $request);
$this->assertSame('10.00', $request->getAmount());
}
@@ -53,10 +55,10 @@ public function testPurchaseRiskManaged()
$gateway->setRiskManagement(true);
$request = $gateway->purchase(['card' => $this->getValidCard(), 'transactionId' => 'Test1234', 'ip' => '1.1.1.1', 'amount' => '25.00']);
- $this->assertInstanceOf('\Omnipay\NABTransact\Message\SecureXMLRiskPurchaseRequest', $request);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\SecureXMLRiskPurchaseRequest::class, $request);
$this->assertSame('25.00', $request->getAmount());
- $this->assertContains(
- '1.1.1.1ExampleUser12345BillstownUS',
+ $this->assertStringContainsString(
+ '1.1.1.1ExampleUser12345BillstownUS',
(string) $request->getData()->asXml()
);
}
@@ -65,8 +67,27 @@ public function testRefund()
{
$request = $this->gateway->refund(['amount' => '10.00', 'transactionId' => 'Order-YKHU67']);
- $this->assertInstanceOf('\Omnipay\NABTransact\Message\SecureXMLRefundRequest', $request);
+ $this->assertInstanceOf(\Omnipay\NABTransact\Message\SecureXMLRefundRequest::class, $request);
$this->assertSame('10.00', $request->getAmount());
$this->assertSame('Order-YKHU67', $request->getTransactionId());
}
+
+ public function testTransportAndTimeoutArePassedToRequest()
+ {
+ $transport = new class() implements TransportInterface {
+ public function send($method, $url, array $headers = [], $body = '', $timeoutSeconds = 60)
+ {
+ return new TransportResponse(200, '000NormalEcho');
+ }
+ };
+
+ $gateway = clone $this->gateway;
+ $gateway->setTransport($transport);
+ $gateway->setTimeoutSeconds(25);
+
+ $request = $gateway->echoTest();
+
+ $this->assertSame($transport, $request->getTransport());
+ $this->assertSame(25, $request->getTimeoutSeconds());
+ }
}
diff --git a/tests/Support/GatewayTestCase.php b/tests/Support/GatewayTestCase.php
new file mode 100644
index 0000000..cb843b0
--- /dev/null
+++ b/tests/Support/GatewayTestCase.php
@@ -0,0 +1,9 @@
+ */
+ private array $responses = [];
+
+ /** @var array> */
+ private array $requests = [];
+
+ /**
+ * @param int $statusCode
+ * @param array $headers
+ */
+ public function queueResponse(string $body, int $statusCode = 200, array $headers = []): void
+ {
+ $this->responses[] = new MockHttpResponse($statusCode, $headers, $body);
+ }
+
+ public function queueRawResponse(ResponseInterface $response): void
+ {
+ $this->responses[] = $response;
+ }
+
+ public function getRequests(): array
+ {
+ return $this->requests;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function request($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): ResponseInterface
+ {
+ $this->requests[] = [
+ 'method' => (string) $method,
+ 'uri' => (string) $uri,
+ 'headers' => $headers,
+ 'body' => $body,
+ 'protocolVersion' => (string) $protocolVersion,
+ ];
+
+ if (empty($this->responses)) {
+ throw new RuntimeException('No queued mock HTTP response for '.$method.' '.$uri);
+ }
+
+ $response = array_shift($this->responses);
+
+ if (!$response instanceof ResponseInterface) {
+ throw new RuntimeException('Queued mock HTTP response is invalid.');
+ }
+
+ return $response;
+ }
+}
diff --git a/tests/Support/MockHttpResponse.php b/tests/Support/MockHttpResponse.php
new file mode 100644
index 0000000..6418b7e
--- /dev/null
+++ b/tests/Support/MockHttpResponse.php
@@ -0,0 +1,159 @@
+> */
+ private array $headers;
+
+ private StreamInterface $body;
+
+ private int $statusCode;
+
+ private string $reasonPhrase;
+
+ /** @var array */
+ private static array $defaultPhrases = [
+ 200 => 'OK',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 500 => 'Internal Server Error',
+ ];
+
+ /**
+ * @param array|string> $headers
+ */
+ public function __construct(
+ int $statusCode = 200,
+ array $headers = [],
+ $body = '',
+ string $protocolVersion = '1.1',
+ string $reasonPhrase = ''
+ ) {
+ $this->statusCode = $statusCode;
+ $this->headers = [];
+
+ foreach ($headers as $name => $value) {
+ $values = is_array($value) ? $value : [(string) $value];
+ $this->headers[strtolower((string) $name)] = array_values(array_map('strval', $values));
+ }
+
+ $this->body = $body instanceof StreamInterface ? $body : new MockStream((string) $body);
+ $this->protocolVersion = (string) $protocolVersion;
+ $this->reasonPhrase = (string) $reasonPhrase;
+ }
+
+ public function getProtocolVersion(): string
+ {
+ return $this->protocolVersion;
+ }
+
+ public function withProtocolVersion($version): MessageInterface
+ {
+ $clone = clone $this;
+ $clone->protocolVersion = (string) $version;
+
+ return $clone;
+ }
+
+ public function getHeaders(): array
+ {
+ return $this->headers;
+ }
+
+ public function hasHeader($name): bool
+ {
+ return array_key_exists(strtolower((string) $name), $this->headers);
+ }
+
+ public function getHeader($name): array
+ {
+ $key = strtolower((string) $name);
+
+ return $this->headers[$key] ?? [];
+ }
+
+ public function getHeaderLine($name): string
+ {
+ return implode(', ', $this->getHeader($name));
+ }
+
+ public function withHeader($name, $value): MessageInterface
+ {
+ $clone = clone $this;
+ $values = is_array($value) ? $value : [(string) $value];
+ $clone->headers[strtolower((string) $name)] = array_values(array_map('strval', $values));
+
+ return $clone;
+ }
+
+ public function withAddedHeader($name, $value): MessageInterface
+ {
+ $clone = clone $this;
+ $key = strtolower((string) $name);
+ $values = is_array($value) ? $value : [(string) $value];
+
+ if (!isset($clone->headers[$key])) {
+ $clone->headers[$key] = [];
+ }
+
+ $clone->headers[$key] = array_merge($clone->headers[$key], array_values(array_map('strval', $values)));
+
+ return $clone;
+ }
+
+ public function withoutHeader($name): MessageInterface
+ {
+ $clone = clone $this;
+ unset($clone->headers[strtolower((string) $name)]);
+
+ return $clone;
+ }
+
+ public function getBody(): StreamInterface
+ {
+ return $this->body;
+ }
+
+ public function withBody($body): MessageInterface
+ {
+ $clone = clone $this;
+ $clone->body = $body instanceof StreamInterface ? $body : new MockStream((string) $body);
+
+ return $clone;
+ }
+
+ public function getStatusCode(): int
+ {
+ return $this->statusCode;
+ }
+
+ public function withStatus($code, $reasonPhrase = ''): ResponseInterface
+ {
+ $clone = clone $this;
+ $clone->statusCode = (int) $code;
+ $clone->reasonPhrase = (string) $reasonPhrase;
+
+ return $clone;
+ }
+
+ public function getReasonPhrase(): string
+ {
+ if ($this->reasonPhrase !== '') {
+ return $this->reasonPhrase;
+ }
+
+ return self::$defaultPhrases[$this->statusCode] ?? '';
+ }
+}
diff --git a/tests/Support/MockStream.php b/tests/Support/MockStream.php
new file mode 100644
index 0000000..1cc1175
--- /dev/null
+++ b/tests/Support/MockStream.php
@@ -0,0 +1,145 @@
+content = $content;
+ }
+
+ public function __toString(): string
+ {
+ return $this->content;
+ }
+
+ public function close(): void
+ {
+ // no-op
+ }
+
+ public function detach()
+ {
+ return null;
+ }
+
+ public function getSize(): ?int
+ {
+ return strlen($this->content);
+ }
+
+ public function tell(): int
+ {
+ return $this->position;
+ }
+
+ public function eof(): bool
+ {
+ return $this->position >= strlen($this->content);
+ }
+
+ public function isSeekable(): bool
+ {
+ return true;
+ }
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ $offset = (int) $offset;
+ $whence = (int) $whence;
+
+ if ($whence === SEEK_SET) {
+ $this->position = max(0, $offset);
+
+ return;
+ }
+
+ if ($whence === SEEK_CUR) {
+ $this->position = max(0, $this->position + $offset);
+
+ return;
+ }
+
+ if ($whence === SEEK_END) {
+ $this->position = max(0, strlen($this->content) + $offset);
+ }
+ }
+
+ public function rewind(): void
+ {
+ $this->position = 0;
+ }
+
+ public function isWritable(): bool
+ {
+ return true;
+ }
+
+ public function write($string): int
+ {
+ $string = (string) $string;
+
+ $prefix = substr($this->content, 0, $this->position);
+ $suffixStart = $this->position + strlen($string);
+ $suffix = '';
+
+ if ($suffixStart < strlen($this->content)) {
+ $suffix = substr($this->content, $suffixStart);
+ }
+
+ $this->content = $prefix.$string.$suffix;
+ $this->position += strlen($string);
+
+ return strlen($string);
+ }
+
+ public function isReadable(): bool
+ {
+ return true;
+ }
+
+ public function read($length): string
+ {
+ $length = max(0, (int) $length);
+
+ if ($length === 0 || $this->eof()) {
+ return '';
+ }
+
+ $chunk = substr($this->content, $this->position, $length);
+ $this->position += strlen($chunk);
+
+ return $chunk;
+ }
+
+ public function getContents(): string
+ {
+ if ($this->eof()) {
+ return '';
+ }
+
+ $chunk = substr($this->content, $this->position);
+ $this->position = strlen($this->content);
+
+ return $chunk;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if ($key === null) {
+ return [];
+ }
+
+ return null;
+ }
+}
+
diff --git a/tests/Support/TestCase.php b/tests/Support/TestCase.php
new file mode 100644
index 0000000..e2159e2
--- /dev/null
+++ b/tests/Support/TestCase.php
@@ -0,0 +1,88 @@
+httpClient = new MockHttpClient();
+ $this->httpRequest = Request::create('/');
+ }
+
+ protected function getHttpClient(): ClientInterface
+ {
+ return $this->httpClient;
+ }
+
+ protected function getHttpRequest(): Request
+ {
+ return $this->httpRequest;
+ }
+
+ protected function getMockRequest(): RequestInterface
+ {
+ return $this->createMock(RequestInterface::class);
+ }
+
+ protected function queueFixtureResponse(string $filename, int $statusCode = 200, array $headers = []): void
+ {
+ $body = $this->fixtureContents($filename);
+
+ $this->httpClient->queueResponse($body, $statusCode, $headers);
+ }
+
+ protected function fixtureContents(string $filename): string
+ {
+ $path = dirname(__DIR__).'/Mock/'.$filename;
+
+ if (!is_file($path)) {
+ $this->fail('Missing mock HTTP fixture: '.$filename);
+ }
+
+ $body = file_get_contents($path);
+ if ($body === false) {
+ $this->fail('Unable to read mock HTTP fixture: '.$filename);
+ }
+
+ // Legacy fixture files may contain an HTTP status line and headers.
+ // Keep only the actual payload body for request parsers.
+ if (preg_match('/^HTTP\\/\\d(?:\\.\\d)?\\s+\\d+/', $body) === 1) {
+ $parts = preg_split("/\\r?\\n\\r?\\n/", $body, 2);
+ if (is_array($parts) && isset($parts[1])) {
+ $body = $parts[1];
+ }
+ }
+
+ return $body;
+ }
+
+ protected function getValidCard(): array
+ {
+ return [
+ 'firstName' => 'Example',
+ 'lastName' => 'User',
+ 'number' => '4444333322221111',
+ 'expiryMonth' => '12',
+ 'expiryYear' => '2030',
+ 'cvv' => '123',
+ 'address1' => '123 Test Street',
+ 'city' => 'Billstown',
+ 'postcode' => '12345',
+ 'country' => 'US',
+ ];
+ }
+}