diff --git a/src/Message/AcceptNotification.php b/src/Message/AcceptNotification.php index dd5b46c..b055dbd 100644 --- a/src/Message/AcceptNotification.php +++ b/src/Message/AcceptNotification.php @@ -8,16 +8,19 @@ use Omnipay\Common\Message\NotificationInterface; use Symfony\Component\HttpFoundation\Request as HttpRequest; -class AcceptNotification extends PurchaseRequest implements NotificationInterface { +class AcceptNotification extends PurchaseRequest implements NotificationInterface +{ protected $data; protected $transaction; - public function getData() { + public function getData() + { return $this->data; } - public function sendData($data) { + public function sendData($data) + { $sessionId = $this->httpRequest->query->get('sessionId') ?? $this->httpRequest->request->get('sessionId') ?? ''; if (empty($sessionId)) { @@ -32,8 +35,7 @@ public function sendData($data) { try { $httpResponse = $this->httpClient->request('GET', $this->getEndpoint('sessions/' . $sessionId), $headers); - } - catch (\Exception $exception) { + } catch (\Exception $exception) { throw new InvalidRequestException($exception->getMessage()); } @@ -45,31 +47,37 @@ public function sendData($data) { return $this; } - public function getTransaction() { + public function getTransaction() + { return $this->transaction; } - public function getTransactionReference() { + public function getTransactionReference() + { return $this->getTransaction()['id'] ?? ''; } - public function getTransactionStatus() { - if ($this->getTransaction() && $this->getAuthorised() && $this->getResponseText() === 'APPROVED') { + public function getTransactionStatus() + { + if ($this->getTransaction() && $this->getAuthorised() && $this->getResponseText() === 'APPROVED') { return static::STATUS_COMPLETED; } return static::STATUS_FAILED; } - public function getAuthorised() { + public function getAuthorised() + { return $this->getTransaction()['authorised'] ?? false; } - public function getResponseText() { + public function getResponseText() + { return strtoupper($this->getTransaction()['responseText']) ?? ''; } - public function getMessage() { + public function getMessage() + { return $this->getResponseText() ?? ''; } @@ -107,4 +115,4 @@ public function getTransactionResult() { return $this->getTransaction() ?? []; } -} \ No newline at end of file +} diff --git a/src/Message/BaseRequest.php b/src/Message/BaseRequest.php index a70e051..0e4c140 100644 --- a/src/Message/BaseRequest.php +++ b/src/Message/BaseRequest.php @@ -79,7 +79,8 @@ public function getLanguage() /** * @param $list - * Possible methods: ['card', 'account2account', 'alipay', 'applepay', 'googlepay', 'paypal', 'interac', 'unionpay', 'oxipay', 'visacheckout', 'wechat'] + * Possible methods: ['card', 'account2account', 'alipay', 'applepay', 'googlepay', 'paypal', 'interac', + * 'unionpay', 'oxipay', 'visacheckout', 'wechat'] * * @return PurchaseRequest */ @@ -91,8 +92,8 @@ public function setPaymentMethods($list) 'oxipay', 'visacheckout', 'wechat' ]; - foreach ( $list as $method ) { - if ( !in_array($method, $options) ) { + foreach ($list as $method) { + if (!in_array($method, $options)) { throw new InvalidRequestException("Unknown payment method: {$method}"); } } @@ -160,7 +161,7 @@ public function setStoredCardIndicator($value) 'resubmission', 'reauthorisation', 'delayedcharges', 'noshow' ]; - if ( ! in_array($value, $options) ) { + if (! in_array($value, $options)) { throw new InvalidRequestException("Invalid option '{$value}' set for StoredCardIndicator."); } @@ -192,7 +193,7 @@ public function setRecurringFrequency($value) 'twomonthly', 'threemonthly', 'fourmonthly', 'sixmonthly', 'annually' ]; - if ( ! in_array($value, $options) ) { + if (! in_array($value, $options)) { throw new InvalidRequestException("Invalid option '{$value}' set for RecurringFrequency."); } diff --git a/src/Message/CompletePurchaseRequest.php b/src/Message/CompletePurchaseRequest.php index cf80a27..af3cf9e 100644 --- a/src/Message/CompletePurchaseRequest.php +++ b/src/Message/CompletePurchaseRequest.php @@ -12,13 +12,19 @@ class CompletePurchaseRequest extends PurchaseRequest public function getData() { return [ - 'sessionId' => $this->getParameter('sessionId') ?? $this->httpRequest->query->get('sessionId') ?? $this->httpRequest->request->get('sessionId') ?? '', - 'username' => $this->getParameter('username') ?? $this->httpRequest->query->get('username') ?? $this->httpRequest->request->get('username') ?? '', + 'sessionId' => $this->getParameter('sessionId') + ?? $this->httpRequest->query->get('sessionId') + ?? $this->httpRequest->request->get('sessionId') + ?? '', + 'username' => $this->getParameter('username') + ?? $this->httpRequest->query->get('username') + ?? $this->httpRequest->request->get('username') + ?? '', ]; } public function sendData($data) { - if ( !$data['sessionId'] ) { + if (!$data['sessionId']) { throw new InvalidRequestException('Session id is required'); } diff --git a/src/Message/CompletePurchaseResponse.php b/src/Message/CompletePurchaseResponse.php index 25b9b12..4c6da4c 100644 --- a/src/Message/CompletePurchaseResponse.php +++ b/src/Message/CompletePurchaseResponse.php @@ -114,7 +114,7 @@ public function getCardType() public function getCardReference() { - if ( !in_array($this->getTransactionMethod(), ['card', 'visacheckout']) ) { + if (!in_array($this->getTransactionMethod(), ['card', 'visacheckout'])) { return null; } diff --git a/src/Message/PurchaseRequest.php b/src/Message/PurchaseRequest.php index 51ddfcf..ae3a0eb 100644 --- a/src/Message/PurchaseRequest.php +++ b/src/Message/PurchaseRequest.php @@ -9,76 +9,149 @@ */ class PurchaseRequest extends BaseRequest { - public function initialize(array $parameters = []) - { - return parent::initialize($parameters); - } - public function getData() { - $this->validate('apiUsername', 'apiKey', 'amount', 'currency'); - - $data = []; - - $data['type'] = $this->getType(); - $data['amount'] = $this->getAmount(); - $data['currency'] = $this->getCurrency(); - $data['callbackUrls'] = []; + $this->validate('apiUsername', 'apiKey', 'amount', 'currency', 'card'); + $card = $this->getCard(); + + $data = [ + 'type' => $this->getType(), + 'amount' => $this->getAmount(), + 'currency' => $this->getCurrency(), + 'callbackUrls' => [], + 'customer' => [ + 'email' => null, + 'shipping' => [], + 'billing' => [] + ], + ]; - if ( (bool) $this->getCreateToken() ) { + if ((bool) $this->getCreateToken()) { $data['storeCard'] = true; } - if ( $this->getStoredCardIndicator() ) { + if ($this->getStoredCardIndicator()) { $data['storedCardIndicator'] = $this->getStoredCardIndicator(); } - if ( $this->getRecurringExpiry() ) { + if ($this->getRecurringExpiry()) { $data['recurringExpiry'] = $this->getRecurringExpiry(); } - if ( $this->getRecurringFrequency() ) { + if ($this->getRecurringFrequency()) { $data['recurringFrequency'] = $this->getRecurringFrequency(); } - if ( $this->getToken() || $this->getCardReference() ) { + if ($this->getToken() || $this->getCardReference()) { $data['cardId'] = $this->getToken() ?? $this->getCardReference(); } - if ( is_array($this->getPaymentMethods()) ) { + if (is_array($this->getPaymentMethods())) { $data['methods'] = $this->getPaymentMethods(); } - if ( is_array($this->getCardTypes()) ) { + if (is_array($this->getCardTypes())) { $data['cardTypes'] = $this->getCardTypes(); } - if ( is_array($this->getMetadata()) ) { + if (is_array($this->getMetadata())) { $data['metaData'] = $this->getMetadata(); } $merchantReference = $this->getMerchantReference() ?? $this->getDescription(); - if ( $merchantReference ) { + if ($merchantReference) { $data['merchantReference'] = $merchantReference; } - if ( $this->getReturnUrl() ) { + if ($this->getReturnUrl()) { $data['callbackUrls']['approved'] = $this->getReturnUrl(); } - if ( $this->getDeclineUrl() ) { + if ($this->getDeclineUrl()) { $data['callbackUrls']['declined'] = $this->getDeclineUrl(); } - if ( $this->getCancelUrl() ) { + if ($this->getCancelUrl()) { $data['callbackUrls']['cancelled'] = $this->getCancelUrl(); } - if ( $this->getNotifyUrl() ) { + if ($this->getNotifyUrl()) { $data['notificationUrl'] = $this->getNotifyUrl(); } + if ($card->getEmail()) { + $data['customer']['email'] = $card->getEmail(); + } + + if ($card->getBillingName()) { + $data['customer']['billing']['name'] = $card->getBillingName(); + } + + if ($card->getBillingAddress1()) { + $data['customer']['billing']['address1'] = $card->getBillingAddress1(); + } + + if ($card->getBillingAddress2()) { + $data['customer']['billing']['address2'] = $card->getBillingAddress2(); + } + + if ($card->getBillingCity()) { + $data['customer']['billing']['city'] = $card->getBillingCity(); + } + + if ($card->getBillingCountry()) { + $data['customer']['billing']['countryCode'] = $card->getBillingCountry(); + } + + if ($card->getBillingPostcode()) { + $data['customer']['billing']['postalCode'] = $card->getBillingPostcode(); + } + + if ($card->getBillingPhone()) { + $data['customer']['billing']['phoneNumber'] = $card->getBillingPhone(); + } + + if ($card->getBillingState()) { + $data['customer']['billing']['state'] = $card->getBillingState(); + } + + if ($card->getShippingName()) { + $data['customer']['shipping']['name'] = $card->getShippingName(); + } + + if ($card->getShippingAddress1()) { + $data['customer']['shipping']['address1'] = $card->getShippingAddress1(); + } + + if ($card->getShippingAddress2()) { + $data['customer']['shipping']['address2'] = $card->getShippingAddress2(); + } + + if ($card->getShippingCity()) { + $data['customer']['shipping']['city'] = $card->getShippingCity(); + } + + if ($card->getShippingCountry()) { + $data['customer']['shipping']['countryCode'] = $card->getShippingCountry(); + } + + if ($card->getShippingPostcode()) { + $data['customer']['shipping']['postalCode'] = $card->getShippingPostcode(); + } + + if ($card->getShippingPhone()) { + $data['customer']['shipping']['phoneNumber'] = $card->getShippingPhone(); + } + + if ($card->getShippingState()) { + $data['customer']['shipping']['state'] = $card->getShippingState(); + } + + if (empty($data['customer']['shipping'])) { + unset($data['customer']['shipping']); + } + return $data; } @@ -90,7 +163,12 @@ public function sendData($data) 'Authorization' => 'Basic ' . $this->getAuthorization(), ]; - $httpResponse = $this->httpClient->request('POST', $this->getEndpoint('sessions'), $headers, json_encode($data)); + $httpResponse = $this->httpClient->request( + 'POST', + $this->getEndpoint('sessions'), + $headers, + json_encode($data) + ); try { $responseData = json_decode($httpResponse->getBody()->getContents()); diff --git a/src/Message/PurchaseResponse.php b/src/Message/PurchaseResponse.php index 043131e..20a71bc 100644 --- a/src/Message/PurchaseResponse.php +++ b/src/Message/PurchaseResponse.php @@ -9,18 +9,22 @@ /** * Windcave HPP Redirect Response */ -class PurchaseResponse extends AbstractResponse implements RedirectResponseInterface { +class PurchaseResponse extends AbstractResponse implements RedirectResponseInterface +{ - public function isSuccessful() { + public function isSuccessful() + { return false; } - public function isRedirect() { + public function isRedirect() + { return true; } - public function getRedirectUrl() { - foreach ( $this->data->links ?? [] as $link ) { + public function getRedirectUrl() + { + foreach ($this->data->links ?? [] as $link) { if ($link->rel === 'hpp') { return $link->href; } @@ -29,7 +33,8 @@ public function getRedirectUrl() { throw new InvalidResponseException('Invalid response from windcave server'); } - public function getRedirectData() { + public function getRedirectData() + { return []; } -} \ No newline at end of file +} diff --git a/src/Message/RefundRequest.php b/src/Message/RefundRequest.php index 3656956..ff0ba3f 100644 --- a/src/Message/RefundRequest.php +++ b/src/Message/RefundRequest.php @@ -33,7 +33,12 @@ public function sendData($data) 'Authorization' => 'Basic ' . $this->getAuthorization(), ]; - $httpResponse = $this->httpClient->request('POST', $this->getEndpoint('transactions'), $headers, json_encode($data)); + $httpResponse = $this->httpClient->request( + 'POST', + $this->getEndpoint('transactions'), + $headers, + json_encode($data) + ); try { $responseData = json_decode($httpResponse->getBody()->getContents()); @@ -43,4 +48,4 @@ public function sendData($data) return $this->response = new RefundResponse($this, $responseData ?? []); } -} \ No newline at end of file +} diff --git a/src/Message/RefundResponse.php b/src/Message/RefundResponse.php index 65948e4..3a6be4e 100644 --- a/src/Message/RefundResponse.php +++ b/src/Message/RefundResponse.php @@ -26,4 +26,4 @@ public function getTransactionReference() { return $this->data->id ?? null; } -} \ No newline at end of file +} diff --git a/tests/GatewayTest.php b/tests/GatewayTest.php index 3dc7d8f..fe070cc 100644 --- a/tests/GatewayTest.php +++ b/tests/GatewayTest.php @@ -25,6 +25,64 @@ public function setUp(): void 'returnUrl' => 'https://www.example.com/return', 'transactionId' => '123abc', 'testMode' => true, + 'card' => [ + 'email' => "test@example.net", + 'name' => "ABCDE FGHIJK", + 'phone' => "123 456 7890", + 'shippingAddress1' => "Ship 1 Test", + 'shippingAddress2' => "Ship 2 Test", + 'shippingCity' => "Ship 4 City", + 'shippingPostcode' => "Ship 5 Postcode", + 'shippingState' => "Ship 6 State", + 'shippingCountry' => "Ship 7 Country", + 'billingAddress1' => "Bill 1 Test", + 'billingAddress2' => "Bill 2 Test", + 'billingCity' => "Bill 4 City", + 'billingPostcode' => "Bill 5 Postcode", + 'billingState' => "Bill 6 State", + 'billingCountry' => "Bill 7 Country", + ], ]; } + + public function testPurchaseSuccess() + { + $this->setMockHttpResponse('PurchaseSuccess.txt'); + + $options = $this->options + ['type' => 'purchase']; + $request = $this->gateway->purchase($options); + + $data = $request->getData(); + $this->assertSame('test@example.net', $data['customer']['email']); + $this->assertSame('ABCDE FGHIJK', $data['customer']['billing']['name']); + $this->assertSame('Bill 1 Test', $data['customer']['billing']['address1']); + $this->assertSame('Bill 2 Test', $data['customer']['billing']['address2']); + $this->assertSame('Bill 4 City', $data['customer']['billing']['city']); + $this->assertSame('Bill 7 Country', $data['customer']['billing']['countryCode']); + $this->assertSame('Bill 5 Postcode', $data['customer']['billing']['postalCode']); + $this->assertSame('123 456 7890', $data['customer']['billing']['phoneNumber']); + $this->assertSame('Bill 6 State', $data['customer']['billing']['state']); + $this->assertSame('ABCDE FGHIJK', $data['customer']['shipping']['name']); + $this->assertSame('Ship 1 Test', $data['customer']['shipping']['address1']); + $this->assertSame('Ship 2 Test', $data['customer']['shipping']['address2']); + $this->assertSame('Ship 4 City', $data['customer']['shipping']['city']); + $this->assertSame('Ship 7 Country', $data['customer']['shipping']['countryCode']); + $this->assertSame('Ship 5 Postcode', $data['customer']['shipping']['postalCode']); + $this->assertSame('123 456 7890', $data['customer']['shipping']['phoneNumber']); + $this->assertSame('Ship 6 State', $data['customer']['shipping']['state']); + + $response = $request->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertTrue($response->isRedirect()); + $this->assertNull($response->getTransactionReference()); + $this->assertNull($response->getMessage()); + $this->assertSame('GET', $response->getRedirectMethod()); + $this->assertSame( + 'https://uat.windcave.com/pxmi3/EF4054F622D6C4C1B5D0F260ED2B2C143C1EDCADD7A86374ED601212E11C409319CD439CC8082194B', + $response->getRedirectUrl() + ); + $this->assertSame([], $response->getRedirectData()); + } + } diff --git a/tests/Mock/PurchaseSuccess.txt b/tests/Mock/PurchaseSuccess.txt new file mode 100644 index 0000000..5a15e66 --- /dev/null +++ b/tests/Mock/PurchaseSuccess.txt @@ -0,0 +1,19 @@ +HTTP/1.1 200 OK +Date: Thu, 10 November 2025 01:23:45 GMT +Content-Type: application/json; charset=utf-8 + +{ + "id": "Kdjc6dikdiodkdjsudjj53nr63uj6s42", + "state": "init", + "links": [ + { + "href": "https://uat.windcave.com/pxmi3/EF4054F622D6C4C1B5D0F260ED2B2C143C1EDCADD7A86374ED601212E11C409319CD439CC8082194B", + "rel": "hpp", + "method": "REDIRECT" + } + ], + "transactions": [ + {} + ], + "customerId": "32424324" +} \ No newline at end of file