diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 2e4aaa7..ba58495 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -33,7 +33,9 @@ jobs: steps: - name: "Checkout" uses: "actions/checkout@v4" - + with: + # Fetch arbitrary more-than-one commit or Scrutinizer will error + fetch-depth: 10 - name: "Install PHP" uses: "shivammathur/setup-php@v2" with: @@ -63,3 +65,11 @@ jobs: - name: "Tests (PHPUnit 10+)" if: ${{ matrix.php-version >= '8.1' }} run: "vendor/bin/phpunit" + + - name: Upload Scrutinizer coverage + continue-on-error: true + uses: sudo-bot/action-scrutinizer@latest + # Do not run this step on forked versions of the main repository (example: contributor forks) + if: github.repository == 'patronbase/omnipay-bpoint' + with: + cli-args: "--format=php-clover build/logs/clover.xml --revision=${{ github.event.pull_request.head.sha || github.sha }}" diff --git a/composer.json b/composer.json index 835616d..35e313b 100644 --- a/composer.json +++ b/composer.json @@ -34,10 +34,10 @@ "omnipay/common": "^3.1" }, "require-dev": { - "omnipay/tests": "^4.1", + "omnipay/tests": "^4.2", "squizlabs/php_codesniffer": "^3.5", - "symfony/psr-http-message-bridge": "~1.1.0|^2.0", - "http-interop/http-factory-guzzle": "^1.1" + "symfony/psr-http-message-bridge": "~1.1.1|^2.0", + "guzzlehttp/psr7": "^2.0" }, "scripts": { "test": "phpunit", diff --git a/src/Message/CompletePurchaseResponse.php b/src/Message/CompletePurchaseResponse.php index 74d6e18..47e339b 100644 --- a/src/Message/CompletePurchaseResponse.php +++ b/src/Message/CompletePurchaseResponse.php @@ -27,7 +27,7 @@ public function isSuccessful() */ public function getTransactionReference() { - return isset($this->data['AuthoriseId']) ? $this->data['AuthoriseId'] : null; + return $this->data['AuthoriseId'] ?? null; } /** @@ -37,7 +37,7 @@ public function getTransactionReference() */ public function getMessage() { - return isset($this->data['ResponseText']) ? $this->data['ResponseText'] : null; + return $this->data['ResponseText'] ?? null; } /** @@ -47,6 +47,16 @@ public function getMessage() */ public function getCardType() { - return isset($this->data['CardType']) ? $this->data['CardType'] : null; + return $this->data['CardType'] ?? null; + } + + /** + * Get the card reference (payment token) if available + * + * @return null|string + */ + public function getCardReference() + { + return $this->data['DVToken'] ?? null; } } diff --git a/src/Message/PurchaseRequest.php b/src/Message/PurchaseRequest.php index 32d4e46..0ca8948 100644 --- a/src/Message/PurchaseRequest.php +++ b/src/Message/PurchaseRequest.php @@ -45,6 +45,16 @@ public function setMerchantNumber($value) return $this->setParameter('merchantNumber', $value); } + public function getCreateToken() + { + return $this->getParameter('createToken'); + } + + public function setCreateToken($value) + { + return $this->setParameter('createToken', $value); + } + public function getMerchantShortName() { return $this->getParameter('merchantShortName'); @@ -55,6 +65,21 @@ public function setMerchantShortName($value) return $this->setParameter('merchantShortName', $value); } + public function getBillerCode() + { + return $this->getParameter('billerCode'); + } + + /** + * Set biller code for the transaction (differentiate income streams); required for using stored card tokens + * + * @param string $value String, max 50 characters + */ + public function setBillerCode($value) + { + return $this->setParameter('billerCode', $value); + } + public function getCustomerReferenceNumber1() { return $this->getParameter('customerReferenceNumber1'); @@ -63,7 +88,7 @@ public function getCustomerReferenceNumber1() /** * Set customer configurable reference #1 * - * @param bool $value String, max 50 characters + * @param string $value String, max 50 characters */ public function setCustomerReferenceNumber1($value) { @@ -78,7 +103,7 @@ public function getCustomerReferenceNumber2() /** * Set customer configurable reference #2 * - * @param bool $value String, max 50 characters + * @param string $value String, max 50 characters */ public function setCustomerReferenceNumber2($value) { @@ -93,26 +118,91 @@ public function getCustomerReferenceNumber3() /** * Set customer configurable reference #3 * - * @param bool $value String, max 50 characters + * @param string $value String, max 50 characters */ public function setCustomerReferenceNumber3($value) { return $this->setParameter('customerReferenceNumber3', $value); } + public function getHideBillerCode() + { + return $this->getParameter('hideBillerCode'); + } + + /** + * Whether to hide the biller code from the end-user on the hosted checkout page + * + * @param bool $value + */ + public function setHideBillerCode($value) + { + return $this->setParameter('hideBillerCode', $value); + } + + public function getHideCustomerReferenceNumber1() + { + return $this->getParameter('hideCustomerReferenceNumber1'); + } + + /** + * Whether to hide the customer configurable reference #1 from the end-user on the hosted checkout page + * + * @param bool $value + */ + public function setHideCustomerReferenceNumber1($value) + { + return $this->setParameter('hideCustomerReferenceNumber1', $value); + } + + public function getHideCustomerReferenceNumber2() + { + return $this->getParameter('hideCustomerReferenceNumber2'); + } + + /** + * Whether to hide the customer configurable reference #2 from the end-user on the hosted checkout page + * + * @param bool $value + */ + public function setHideCustomerReferenceNumber2($value) + { + return $this->setParameter('hideCustomerReferenceNumber2', $value); + } + + public function getHideCustomerReferenceNumber3() + { + return $this->getParameter('hideCustomerReferenceNumber3'); + } + + /** + * Whether to hide the customer configurable reference #3 from the end-user on the hosted checkout page + * + * @param bool $value + */ + public function setHideCustomerReferenceNumber3($value) + { + return $this->setParameter('hideCustomerReferenceNumber3', $value); + } + + /** + * @deprecated Alias. Use standard `getCreateToken()` instead. + */ public function getGenerateToken() { - return $this->getParameter('generateToken'); + return $this->getCreateToken(); } /** * Indicate whether or not to generate a token for the card used in the transaction * + * @deprecated Alias. Use standard `setCreateToken()` instead. + * * @param bool $value Generate a token or not */ public function setGenerateToken($value) { - return $this->setParameter('generateToken', $value); + return $this->setCreateToken($value); } public function getCustomerNumber() @@ -123,7 +213,7 @@ public function getCustomerNumber() /** * Set the unique customer ID in the merchant system * - * @param bool $value Customer number to set + * @param string $value Customer number to set */ public function setCustomerNumber($value) { @@ -136,16 +226,23 @@ public function getData() $amount = $this->getAmountInteger(); $data = array( + 'HppParameters' => array( + 'HideBillerCode' => (bool) $this->getHideBillerCode(), + 'HideCrn1' => (bool) $this->getHideCustomerReferenceNumber1(), + 'HideCrn2' => (bool) $this->getHideCustomerReferenceNumber2(), + 'HideCrn3' => (bool) $this->getHideCustomerReferenceNumber3(), + ), 'ProcessTxnData' => array( 'Action' => $amount > 0 ? 'payment' : 'verify_only', 'TestMode' => $this->getTestMode(), 'Amount' => $this->getAmountInteger(), + 'BillerCode' => $this->getBillerCode(), 'Crn1' => $this->getCustomerReferenceNumber1(), 'Crn2' => $this->getCustomerReferenceNumber2(), 'Crn3' => $this->getCustomerReferenceNumber3(), 'Currency' => $this->getCurrency(), // 1 - no; 3 - always (don't leave it up to system or customer) - 'TokenisationMode' => $this->getGenerateToken() ? 3 : 1, + 'TokenisationMode' => $this->getCreateToken() ? 3 : 1, 'MerchantReference' => $this->getTransactionId(), 'SubType' => 'single', 'Type' => 'internet', @@ -153,6 +250,10 @@ public function getData() 'RedirectionUrl' => $this->getReturnUrl(), 'WebHookUrl' => $this->getNotifyUrl(), ); + if ($this->getCancelUrl()) { + $data['HppParameters']['ReturnBarLabel'] = 'Cancel'; + $data['HppParameters']['ReturnBarUrl'] = $this->getCancelUrl(); + } // add item details if available $items = $this->getItems(); if ($items) { @@ -202,6 +303,14 @@ public function getData() } } + // add stored card token if available + if ($this->getToken() || $this->getCardReference()) { + $data['ProcessTxnData']['DVTokenData'] = array( + 'DVToken' => $this->getToken() ?? $this->getCardReference(), + 'UpdateDVTokenExpiryDate' => false, + ); + } + return $data; } diff --git a/tests/Message/CompletePurchaseResponseTest.php b/tests/Message/CompletePurchaseResponseTest.php index 79b7358..30a0acd 100644 --- a/tests/Message/CompletePurchaseResponseTest.php +++ b/tests/Message/CompletePurchaseResponseTest.php @@ -104,6 +104,7 @@ public function testCompletePurchaseSuccess() $this->assertSame('Approved', $this->response->getMessage()); $this->assertSame('372626', $this->response->getTransactionReference()); $this->assertSame('MC', $this->response->getCardType()); + $this->assertNull($this->response->getCardReference()); // confirm the request format was valid $requestData = $this->response->getRequest()->getData(); @@ -138,6 +139,7 @@ public function testCompletePurchaseFailure() $this->assertSame('Invalid card number', $this->response->getMessage()); $this->assertNull($this->response->getTransactionReference()); $this->assertNull($this->response->getCardType()); + $this->assertNull($this->response->getCardReference()); // confirm the request format was valid $requestData = $this->response->getRequest()->getData(); @@ -165,9 +167,22 @@ public function testCompletePurchaseError() $this->assertSame('Invalid credentials', $this->response->getMessage()); $this->assertNull($this->response->getTransactionReference()); $this->assertNull($this->response->getCardType()); + $this->assertNull($this->response->getCardReference()); $data = $this->response->getData(); $this->assertSame(1, $data['ResponseCode']); } + + public function testCompletePurchaseReturnsCardReference() + { + // adjust to have a token + $this->responseData['DVToken'] = '1234567890123456'; + + $this->response = new CompletePurchaseResponse($this->getMockRequest(), $this->responseData); + + $this->assertTrue($this->response->isSuccessful()); + $this->assertFalse($this->response->isRedirect()); + $this->assertSame('1234567890123456', $this->response->getCardReference()); + } } diff --git a/tests/Message/PurchaseRequestTest.php b/tests/Message/PurchaseRequestTest.php index ebe37b4..96d7436 100644 --- a/tests/Message/PurchaseRequestTest.php +++ b/tests/Message/PurchaseRequestTest.php @@ -22,10 +22,11 @@ public function setUp(): void 'password' => 'DemoPassword!', 'merchantNumber' => '5353109000000000', 'merchantShortName' => 'DEMO123', + 'billerCode' => '1234567', 'customerReferenceNumber1' => 'cr1', 'customerReferenceNumber2' => 'cr2', 'customerReferenceNumber3' => 'cr3', - 'generateToken' => false, + 'createToken' => false, 'customerNumber' => 'cust456', 'notifyUrl' => 'https://www.example.com/notify', 'returnUrl' => 'https://www.example.com/return', @@ -39,9 +40,16 @@ public function testGetData() { $data = $this->request->getData(); + $this->assertArrayHasKey('HppParameters', $data); + $this->assertFalse($data['HppParameters']['HideBillerCode']); + $this->assertFalse($data['HppParameters']['HideCrn1']); + $this->assertFalse($data['HppParameters']['HideCrn2']); + $this->assertFalse($data['HppParameters']['HideCrn3']); + $this->assertSame('payment', $data['ProcessTxnData']['Action']); $this->assertTrue($data['ProcessTxnData']['TestMode']); $this->assertSame(145, $data['ProcessTxnData']['Amount']); + $this->assertSame('1234567', $data['ProcessTxnData']['BillerCode']); $this->assertSame('cr1', $data['ProcessTxnData']['Crn1']); $this->assertSame('cr2', $data['ProcessTxnData']['Crn2']); $this->assertSame('cr3', $data['ProcessTxnData']['Crn3']); @@ -54,13 +62,90 @@ public function testGetData() $this->assertSame('https://www.example.com/notify', $data['WebHookUrl']); } + public function testHideFlags() + { + $this->options = array_merge($this->options, array( + 'hideBillerCode' => true, + 'hideCustomerReferenceNumber1' => true, + 'hideCustomerReferenceNumber2' => true, + 'hideCustomerReferenceNumber3' => true, + )); + $this->request->initialize($this->options); + + $data = $this->request->getData(); + + $this->assertArrayHasKey('HppParameters', $data); + $this->assertTrue($data['HppParameters']['HideBillerCode']); + $this->assertTrue($data['HppParameters']['HideCrn1']); + $this->assertTrue($data['HppParameters']['HideCrn2']); + $this->assertTrue($data['HppParameters']['HideCrn3']); + } + + public function testCancelUrl() + { + $this->options = array_merge($this->options, array('cancelUrl' => 'https://www.example.com/cancel')); + $this->request->initialize($this->options); + + $data = $this->request->getData(); + + $this->assertArrayHasKey('HppParameters', $data); + $this->assertArrayHasKey('ReturnBarLabel', $data['HppParameters']); + $this->assertSame('Cancel', $data['HppParameters']['ReturnBarLabel']); + $this->assertArrayHasKey('ReturnBarUrl', $data['HppParameters']); + $this->assertSame('https://www.example.com/cancel', $data['HppParameters']['ReturnBarUrl']); + } + + public function testCardReference() + { + $this->options = array_merge($this->options, array('cardReference' => '1234567890123456')); + $this->request->initialize($this->options); + + $data = $this->request->getData(); + + $this->assertArrayHasKey('ProcessTxnData', $data); + $this->assertArrayHasKey('DVTokenData', $data['ProcessTxnData']); + $this->assertArrayHasKey('DVToken', $data['ProcessTxnData']['DVTokenData']); + $this->assertSame('1234567890123456', $data['ProcessTxnData']['DVTokenData']['DVToken']); + $this->assertArrayHasKey('UpdateDVTokenExpiryDate', $data['ProcessTxnData']['DVTokenData']); + $this->assertFalse($data['ProcessTxnData']['DVTokenData']['UpdateDVTokenExpiryDate']); + } + + public function testToken() + { + $this->options = array_merge($this->options, array('token' => '1234567890123456')); + $this->request->initialize($this->options); + + $data = $this->request->getData(); + + $this->assertArrayHasKey('ProcessTxnData', $data); + $this->assertArrayHasKey('DVTokenData', $data['ProcessTxnData']); + $this->assertArrayHasKey('DVToken', $data['ProcessTxnData']['DVTokenData']); + $this->assertSame('1234567890123456', $data['ProcessTxnData']['DVTokenData']['DVToken']); + $this->assertArrayHasKey('UpdateDVTokenExpiryDate', $data['ProcessTxnData']['DVTokenData']); + $this->assertFalse($data['ProcessTxnData']['DVTokenData']['UpdateDVTokenExpiryDate']); + } + public function testGetDataOnlyGetToken() { // override some data - $this->options = array_merge($this->options, array('generateToken' => true, 'amount' => '0.00')); + $this->options = array_merge($this->options, array('createToken' => true, 'amount' => '0.00')); $this->request->initialize($this->options); + $this->assertTrue($this->request->getCreateToken()); + $data = $this->request->getData(); + $this->assertSame('verify_only', $data['ProcessTxnData']['Action']); + $this->assertSame(0, $data['ProcessTxnData']['Amount']); + $this->assertSame(3, $data['ProcessTxnData']['TokenisationMode']); + } + public function testGetDataDeprecatedGetToken() + { + // override some data + $this->options = array_merge($this->options, array('generateToken' => true, 'amount' => '0.00')); + $this->request->initialize($this->options); + $this->assertTrue($this->request->getGenerateToken()); + + $data = $this->request->getData(); $this->assertSame('verify_only', $data['ProcessTxnData']['Action']); $this->assertSame(0, $data['ProcessTxnData']['Amount']); $this->assertSame(3, $data['ProcessTxnData']['TokenisationMode']); diff --git a/tests/RedirectGatewayTest.php b/tests/RedirectGatewayTest.php index f3c08db..b342b23 100644 --- a/tests/RedirectGatewayTest.php +++ b/tests/RedirectGatewayTest.php @@ -29,10 +29,11 @@ public function setUp(): void 'password' => 'DemoPassword!', 'merchantNumber' => '5353109000000000', 'merchantShortName' => 'DEMO123', + 'billerCode' => '1234567', 'customerReferenceNumber1' => 'cr1', 'customerReferenceNumber2' => 'cr2', 'customerReferenceNumber3' => 'cr3', - 'generateToken' => true, + 'createToken' => true, 'customerNumber' => 'cust456', 'notifyUrl' => 'https://www.example.com/notify', 'returnUrl' => 'https://www.example.com/return',