diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..71496fe --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,53 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [3.3.0] - 2025-11-14 + +### Added + +- Support for hosted purchases being in 'pending' state (notably for 3DS state webhook callbacks) +- Changelog! + +## [3.2.0] - 2025-08-08 + +### Added + +- Saved card support + +## [3.1.0] - 2025-07-28 + +### Added + +- `refund() support, including pending refunds +- `fetchTransaction()` support +- support for MOTO transactions + +### Changed + +- Complete purchase requests validate necessary URL parameters +- Use a common, local `AbstractRequest` + +## [3.0.1] - 2025-05-02 + +### Changed + +- Explicitly mark as e-commerce transactions +- Request method changed to class property + +### Fixed + +- Purchase should include authorisation and capture + +## [3.0.0] - 2025-05-01 + +Initial version + +[3.3.0]: https://github.com/Patronbase/omnipay-worldline/compare/v3.2.0...v3.3.0 +[3.2.0]: https://github.com/Patronbase/omnipay-worldline/compare/v3.1.0...v3.2.0 +[3.1.0]: https://github.com/Patronbase/omnipay-worldline/compare/v3.0.1...v3.1.0 +[3.0.1]: https://github.com/Patronbase/omnipay-worldline/compare/v3.0.0...v3.0.1 +[3.0.0]: https://github.com/Patronbase/omnipay-worldline/tree/v3.0.0 diff --git a/composer.json b/composer.json index b7abb22..3f7c4b0 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ }, "extra": { "branch-alias": { - "dev-main": "3.2.x-dev" + "dev-main": "3.3.x-dev" } }, "prefer-stable": true, diff --git a/src/Message/CompletePurchaseResponse.php b/src/Message/CompletePurchaseResponse.php index c282f7a..98eae0a 100644 --- a/src/Message/CompletePurchaseResponse.php +++ b/src/Message/CompletePurchaseResponse.php @@ -9,6 +9,16 @@ */ class CompletePurchaseResponse extends AbstractResponse { + /** + * Is the response pending? + * + * @return boolean + */ + public function isPending() + { + return $this->data->createdPaymentOutput->payment->statusOutput->statusCategory == 'PENDING_PAYMENT'; + } + /** * Is the response successful? * @@ -46,6 +56,7 @@ public function getMessage() */ public function getCardReference() { - return $this->data->createdPaymentOutput->payment->paymentOutput->cardPaymentMethodSpecificOutput->token ?? null; + return $this->data->createdPaymentOutput->payment->paymentOutput->cardPaymentMethodSpecificOutput->token + ?? null; } } diff --git a/tests/GatewayTest.php b/tests/GatewayTest.php index 18244a9..f19ca20 100644 --- a/tests/GatewayTest.php +++ b/tests/GatewayTest.php @@ -53,6 +53,7 @@ public function testCompletePurchaseSuccess() $response = $this->gateway->completePurchase($options)->send(); + $this->assertFalse($response->isPending()); $this->assertTrue($response->isSuccessful()); $this->assertFalse($response->isRedirect()); $this->assertSame('1234567890_0', $response->getTransactionReference()); @@ -60,6 +61,22 @@ public function testCompletePurchaseSuccess() $this->assertSame('12345678-90ab-cdef-1234-567890abcdef', $response->getCardReference()); } + public function testCompletePurchasePending() + { + $this->setMockHttpResponse('HostedCompletePurchasePending.txt'); + + $options = array_merge($this->options, ['hostedCheckoutId' => '0000000001']); + + $response = $this->gateway->completePurchase($options)->send(); + + $this->assertTrue($response->isPending()); + $this->assertFalse($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertSame('1234567890', $response->getTransactionReference()); + $this->assertSame('REDIRECTED', $response->getMessage()); + $this->assertNull($response->getCardReference()); + } + public function testCompletePurchaseFailure() { $this->setMockHttpResponse('HostedCompletePurchaseFailure.txt'); @@ -68,6 +85,7 @@ public function testCompletePurchaseFailure() $response = $this->gateway->completePurchase($options)->send(); + $this->assertFalse($response->isPending()); $this->assertFalse($response->isSuccessful()); $this->assertFalse($response->isRedirect()); $this->assertSame('1234567890_0', $response->getTransactionReference()); diff --git a/tests/Mock/HostedCompletePurchasePending.txt b/tests/Mock/HostedCompletePurchasePending.txt new file mode 100644 index 0000000..18d1cf0 --- /dev/null +++ b/tests/Mock/HostedCompletePurchasePending.txt @@ -0,0 +1,9 @@ +HTTP/1.1 200 OK +Date: Thu, 14 Nov 2025 12:34:56 GMT +Content-Length: 1302 +Content-Type: application/json; charset=utf-8 +Correlation-Id: 11111111-2222-3333-4444-abcdefabcdef +Api_Auth: true +Strict-Transport-Security: max-age=16000000; includeSubDomains; preload; + +{"createdPaymentOutput":{"payment":{"hostedCheckoutSpecificOutput":{"hostedCheckoutId":"0000000001"},"paymentOutput":{"amountOfMoney":{"amount":145,"currencyCode":"EUR"},"references":{"merchantReference":"123abc"},"acquiredAmount":{"amount":0,"currencyCode":"EUR"},"customer":{"device":{"ipAddressCountryCode":"99"}},"cardPaymentMethodSpecificOutput":{"paymentProductId":1,"card":{"cardNumber":"************1111","expiryDate":"1234","bin":"411111","countryCode":"NZ","cardType":"Credit","cardCorporateIndicator":false,"cardEffectiveDateIndicator":"1","cardEffectiveDate":"2013-11-15","cardPanType":"pan","cardProductCode":"F","cardProductName":"Visa Classic","cardProductUsageLabel":"credit","cardScheme":"Visa","issuerRegionCode":"c","issuingCountryCode":"NZ","panLuhnCheck":"1","panLengthMax":"16","panLengthMin":"16"},"fraudResults":{"fraudServiceResult":"no-advice","cvvResult":"P"},"threeDSecureResults":{"version":"2.2.0","flow":"frictionless","eci":"7","xid":"ZYXWVUTSRQP000=="},"acquirerInformation":{"name":"ACQUIRER"},"cobrandSelectionIndicator":"alternative"},"paymentMethod":"card"},"status":"REDIRECTED","statusOutput":{"isCancellable":false,"statusCategory":"PENDING_PAYMENT","statusCode":46,"isAuthorized":false,"isRefundable":false},"id":"1234567890"},"paymentStatusCategory":"STATUS_UNKNOWN"},"status":"PAYMENT_CREATED"} \ No newline at end of file