From b4591a73baee1d93f3dc7f042f5e945afa9bb559 Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Fri, 24 Apr 2020 11:51:12 +0200 Subject: [PATCH 01/13] Fix unsuccessful status requests in ProceedRequest::runTransaction --- src/Message/ProceedRequest.php | 38 +++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/Message/ProceedRequest.php b/src/Message/ProceedRequest.php index 357a5a5..d804131 100755 --- a/src/Message/ProceedRequest.php +++ b/src/Message/ProceedRequest.php @@ -23,6 +23,19 @@ class ProceedRequest extends SoapAbstractRequest */ protected $authorizationResult = []; + /** + * {@inheritdoc} + */ + public function getData() + { + $this->validate('transactionReference'); + + $data = parent::getData(); + $data['paymentOrderKey'] = $this->getTransactionReference(); + + return $data; + } + /** * Run the SOAP transaction * @@ -35,24 +48,21 @@ class ProceedRequest extends SoapAbstractRequest */ protected function runTransaction(\SoapClient $soapClient, array $data): \stdClass { - // We only have the paymentOrderKey / Transaction Reference, and not the paymentId. - // Use a STATUS request to get the reference, to be used in the proceed request. - $statusData = $data; - $statusData['paymentOrderKey'] = $this->getTransactionReference(); - $status = $soapClient->__soapCall('status', [$statusData]); - - $payments = $status->statusSuccess->report->payment; - - if (\is_array($payments) === false) { - $payments = [ - $payments - ]; + $statusResponse = $soapClient->__soapCall('status', [$data]); + + $payments = []; + if (isset($statusResponse->statusSuccess->report->payment)) { + $payments = $statusResponse->statusSuccess->report->payment; + } + + if (is_array($payments) === false) { + $payments = [$payments]; } $lastProceedResult = null; $authorizedPayments = []; - foreach($payments as $payment) { + foreach ($payments as $payment) { if (isset($payment->authorization->reversal)) { continue; } @@ -63,7 +73,7 @@ protected function runTransaction(\SoapClient $soapClient, array $data): \stdCla case 'REDIRECTED_FOR_AUTHENTICATION': case 'AUTHORIZATION_REQUESTED': case 'RISK_CHECK_OK': - // we can proceed + unset($data['paymentOrderKey']); $data['paymentId'] = $payment->id; if (!empty($this->getAuthorizationResultType())) { From 6b6fba748f9b3af885d777b0592f60a0bf0909fd Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Fri, 24 Apr 2020 12:00:28 +0200 Subject: [PATCH 02/13] Fix name of the payment authorization status from 'REDIRECTED_FOR_AUTHENTICATION' to 'REDIRECTED_FOR_AUTHORIZATION' --- src/Message/ProceedRequest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Message/ProceedRequest.php b/src/Message/ProceedRequest.php index d804131..9fb6970 100755 --- a/src/Message/ProceedRequest.php +++ b/src/Message/ProceedRequest.php @@ -70,12 +70,12 @@ protected function runTransaction(\SoapClient $soapClient, array $data): \stdCla // try to 'proceed' every payment that has a valid state. // states are, however, badly documented. switch($payment->authorization->status) { - case 'REDIRECTED_FOR_AUTHENTICATION': + case 'REDIRECTED_FOR_AUTHORIZATION': case 'AUTHORIZATION_REQUESTED': case 'RISK_CHECK_OK': unset($data['paymentOrderKey']); $data['paymentId'] = $payment->id; - + if (!empty($this->getAuthorizationResultType())) { $data[$this->getAuthorizationResultType()] = $this->getAuthorizationResult(); } From 48478900ccfcdbf5f9cfb6a49c8934eef7634a86 Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Fri, 24 Apr 2020 12:01:42 +0200 Subject: [PATCH 03/13] Rename $lastProceedResult to $lastProceedResponse in ProceedRequest::runTransaction --- src/Message/ProceedRequest.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Message/ProceedRequest.php b/src/Message/ProceedRequest.php index 9fb6970..a26d2dd 100755 --- a/src/Message/ProceedRequest.php +++ b/src/Message/ProceedRequest.php @@ -59,7 +59,7 @@ protected function runTransaction(\SoapClient $soapClient, array $data): \stdCla $payments = [$payments]; } - $lastProceedResult = null; + $lastProceedResponse = null; $authorizedPayments = []; foreach ($payments as $payment) { @@ -81,7 +81,7 @@ protected function runTransaction(\SoapClient $soapClient, array $data): \stdCla } // we can't return here because there might be multiple payments that need to proceed - $lastProceedResult = $soapClient->__soapCall('proceed', [$data]); + $lastProceedResponse = $soapClient->__soapCall('proceed', [$data]); break; case 'AUTHORIZED': if ( @@ -94,7 +94,7 @@ protected function runTransaction(\SoapClient $soapClient, array $data): \stdCla } } - if ($lastProceedResult === null) { + if ($lastProceedResponse === null) { if (!empty($this->getAuthorizationResultType())) { // we should have proceeded but we can't return $this->createFakeProceedErrorResponseForNoValidPayments(); @@ -105,9 +105,10 @@ protected function runTransaction(\SoapClient $soapClient, array $data): \stdCla return $this->createSuccessfulProceedResponseForValidPaymentsButNothingToDo(); } } + // Even if we were to have multiple payments, the last one should be the most relevant // and should have the result returned. - return $lastProceedResult; + return $lastProceedResponse; } /** From c0cc4de299b776d223e92eb23f1fa0e2b18f1a9c Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Fri, 24 Apr 2020 12:05:04 +0200 Subject: [PATCH 04/13] Improve isset() calls in ProceedResponse --- src/Message/ProceedResponse.php | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Message/ProceedResponse.php b/src/Message/ProceedResponse.php index 07f3f5a..d82a113 100755 --- a/src/Message/ProceedResponse.php +++ b/src/Message/ProceedResponse.php @@ -7,7 +7,7 @@ use Omnipay\Common\Message\RedirectResponseInterface; /** - * Cancel Request Response + * Proceed Request Response */ class ProceedResponse extends AbstractResponse { @@ -22,9 +22,9 @@ class ProceedResponse extends AbstractResponse const PAYMENT_SUCCESS_STATUS_AUTHORIZED = 'AUTHORIZED'; /** - * @var string When the proceed request was cancelled (they have a typo in their api) + * @var string When the proceed request was cancelled */ - const PAYMENT_SUCCESS_STATUS_CANCELLED = 'CANCELED'; // [sic] + const PAYMENT_SUCCESS_STATUS_CANCELLED = 'CANCELED'; /** * {@inheritdoc} @@ -32,41 +32,39 @@ class ProceedResponse extends AbstractResponse public function isSuccessful() { if ( - isset($this->data->proceedSuccess) + isset($this->data->proceedSuccess->success->code) && $this->data->proceedSuccess->success->code === self::PROCEEDSUCCESS_CODE_SUCCESSFUL - && isset($this->data->proceedSuccess->paymentResponse->paymentSuccess) + && isset($this->data->proceedSuccess->paymentResponse->paymentSuccess->status) && $this->data->proceedSuccess->paymentResponse->paymentSuccess->status === self::PAYMENT_SUCCESS_STATUS_AUTHORIZED ) { return true; } + return false; } /** - * @inheritDoc + * {@inheritdoc} */ public function isCancelled() { if ( - isset($this->data->proceedSuccess) + isset($this->data->proceedSuccess->success->code) && $this->data->proceedSuccess->success->code === self::PROCEEDSUCCESS_CODE_SUCCESSFUL - && isset($this->data->proceedSuccess->paymentResponse->paymentSuccess) + && isset($this->data->proceedSuccess->paymentResponse->paymentSuccess->status) && $this->data->proceedSuccess->paymentResponse->paymentSuccess->status === self::PAYMENT_SUCCESS_STATUS_CANCELLED ) { return true; } + return false; } /** - * Get a reference provided by the gateway to represent the payment. - * This is the same as the transactionReference from the createRequest. - * - * @return null|string + * {@inheritdoc} */ public function getTransactionReference() { - /** @var AbstractRequest $this->>request */ return $this->request->getTransactionReference(); } } From ea9eb7ce22327a2cd8f30accdc1e51e2c9846961 Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Fri, 24 Apr 2020 12:08:48 +0200 Subject: [PATCH 05/13] Add use-statements for SoapClient and stdClass in ProceedRequest --- src/Message/ProceedRequest.php | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/Message/ProceedRequest.php b/src/Message/ProceedRequest.php index a26d2dd..214feca 100755 --- a/src/Message/ProceedRequest.php +++ b/src/Message/ProceedRequest.php @@ -2,6 +2,9 @@ namespace Omnipay\DocdataPayments\Message; +use SoapClient; +use stdClass; + /** * DocdataPayments Proceed Request, to 'pay' without interaction from the user */ @@ -39,14 +42,12 @@ public function getData() /** * Run the SOAP transaction * - * @param \SoapClient $soapClient Configured SoapClient - * @param array $data Formatted data to be sent to Docdata - * - * @return \stdClass + * @param SoapClient $soapClient Configured SoapClient + * @param array $data Formatted data to be sent to Docdata * - * @throws \SoapFault + * @return stdClass */ - protected function runTransaction(\SoapClient $soapClient, array $data): \stdClass + protected function runTransaction(SoapClient $soapClient, array $data): stdClass { $statusResponse = $soapClient->__soapCall('status', [$data]); @@ -174,17 +175,17 @@ public function setSkipProceedRequest(bool $skipProceedRequest) * This is used when there were no (authorized) payments to proceed. * This occurs when a user chooses bank transfer. * - * @return \stdClass + * @return stdClass */ protected function createSuccessfulProceedResponseForValidPaymentsButNothingToDo() { - $response = new \stdClass(); - $response->proceedSuccess = new \stdClass(); - $response->proceedSuccess->success = new \stdClass(); + $response = new stdClass(); + $response->proceedSuccess = new stdClass(); + $response->proceedSuccess->success = new stdClass(); $response->proceedSuccess->success->_ = 'All payments were already processed so no action was taken.'; $response->proceedSuccess->success->code = 'SUCCESS'; - $response->proceedSuccess->paymentResponse = new \stdClass(); - $response->proceedSuccess->paymentResponse->paymentSuccess = new \stdClass(); + $response->proceedSuccess->paymentResponse = new stdClass(); + $response->proceedSuccess->paymentResponse->paymentSuccess = new stdClass(); $response->proceedSuccess->paymentResponse->paymentSuccess->status = 'AUTHORIZED'; return $response; @@ -193,13 +194,13 @@ protected function createSuccessfulProceedResponseForValidPaymentsButNothingToDo /** * Create a stdClass that mimics a failed soap call to docdata, to be used instead of an exception * - * @return \stdClass + * @return stdClass */ private function createFakeProceedErrorResponseForNoValidPayments() { - $response = new \stdClass(); - $response->proceedErrors = new \stdClass(); - $response->proceedErrors->error = new \stdClass(); + $response = new stdClass(); + $response->proceedErrors = new stdClass(); + $response->proceedErrors->error = new stdClass(); $response->proceedErrors->error->_ = 'No Proceed executed because there were no valid payments'; return $response; From 1f821bea66e0d779824fd2e4341e2999d9fea9ea Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Fri, 24 Apr 2020 12:17:02 +0200 Subject: [PATCH 06/13] Move CaptureRequest::mergeResponses into SoapAbstractRequest --- src/Message/CaptureRequest.php | 20 ---------- src/Message/SoapAbstractRequest.php | 62 ++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/Message/CaptureRequest.php b/src/Message/CaptureRequest.php index 80be09d..46333af 100755 --- a/src/Message/CaptureRequest.php +++ b/src/Message/CaptureRequest.php @@ -148,24 +148,4 @@ private function modifyCaptureResponseToSuccessfulWhenAlreadyCaptured( $captureResponse->captureSuccess->success->code = 'SUCCESS'; $captureResponse->captureSuccess->success->_ = $captureResponse->captureErrors->error->_; } - - /** - * Returns an stdClass with the multiple responses. - * - * @param stdClass ...$responses - * - * @return stdClass - */ - private function mergeResponses(stdClass ...$responses): stdClass - { - $mergedResponse = new stdClass(); - foreach ($responses as $response) { - $properties = get_object_vars($response); - foreach ($properties as $propertyKey => $propertyValue) { - $mergedResponse->{$propertyKey} = $propertyValue; - } - } - - return $mergedResponse; - } } diff --git a/src/Message/SoapAbstractRequest.php b/src/Message/SoapAbstractRequest.php index 52319ca..dc13a9f 100644 --- a/src/Message/SoapAbstractRequest.php +++ b/src/Message/SoapAbstractRequest.php @@ -2,9 +2,13 @@ namespace Omnipay\DocdataPayments\Message; +use Exception; use Omnipay\Common\Http\ClientInterface; use Omnipay\Common\Message\AbstractRequest as OmnipayAbstractRequest; use Omnipay\Common\Message\ResponseInterface; +use SoapClient; +use SoapFault; +use stdClass; use Symfony\Component\HttpFoundation\Request as HttpRequest; use Omnipay\Common\Exception\InvalidRequestException; @@ -43,7 +47,7 @@ abstract class SoapAbstractRequest extends OmnipayAbstractRequest protected $liveEndpoint = 'https://secure.docdatapayments.com/ps/services/paymentservice/1_3?wsdl'; /** - * @var \SoapClient + * @var SoapClient */ protected $soapClient; @@ -73,15 +77,15 @@ abstract class SoapAbstractRequest extends OmnipayAbstractRequest /** * Create a new Request * - * @param ClientInterface $httpClient A Guzzle client to make API calls with - * @param HttpRequest $httpRequest A Symfony HTTP request object - * @param \SoapClient|null $soapClient Configured SoapClient; If null, a new - * one will be created with default values + * @param ClientInterface $httpClient A Guzzle client to make API calls with + * @param HttpRequest $httpRequest A Symfony HTTP request object + * @param SoapClient|null $soapClient Configured SoapClient; If null, a new + * one will be created with default values */ public function __construct( ClientInterface $httpClient, HttpRequest $httpRequest, - \SoapClient $soapClient = null + SoapClient $soapClient = null ) { parent::__construct($httpClient, $httpRequest); @@ -276,7 +280,7 @@ public function getTransactionId() } return $this->getParameter('transactionId'); } - + /** * Get the request pending URL. * @@ -286,7 +290,7 @@ public function getPendingUrl() { return $this->getParameter('pendingUrl'); } - + /** * Sets the request return URL. * @@ -330,11 +334,11 @@ public function getData() /** * Build the SOAP Client and the internal request object * - * @return \SoapClient + * @return SoapClient * - * @throws \Exception + * @throws Exception */ - public function buildSoapClient(): \SoapClient + public function buildSoapClient(): SoapClient { if ($this->soapClient !== null) { return $this->soapClient; @@ -368,7 +372,7 @@ public function buildSoapClient(): \SoapClient $soap_options['cache_wsdl'] = WSDL_CACHE_BOTH; } - $this->soapClient = new \SoapClient($this->getEndpoint(), $soap_options); + $this->soapClient = new SoapClient($this->getEndpoint(), $soap_options); return $this->soapClient; } @@ -378,17 +382,17 @@ public function buildSoapClient(): \SoapClient * * Over-ride this in sub classes. * - * @param \SoapClient $soapClient Configured SoapClient - * @param array $data All data to be sent in the transaction + * @param SoapClient $soapClient Configured SoapClient + * @param array $data All data to be sent in the transaction * - * @return \stdClass + * @return stdClass * - * @throws \SoapFault + * @throws SoapFault */ abstract protected function runTransaction( - \SoapClient $soapClient, + SoapClient $soapClient, array $data - ): \stdClass; + ): stdClass; /** * Send Data to the Gateway @@ -397,7 +401,7 @@ abstract protected function runTransaction( * * @return ResponseInterface * - * @throws \Exception + * @throws Exception */ public function sendData($data) { @@ -428,4 +432,24 @@ public function getEndpoint() * @return string */ abstract protected function getResponseName(): string; + + /** + * Returns an stdClass with the multiple responses. + * + * @param stdClass ...$responses + * + * @return stdClass + */ + protected function mergeResponses(stdClass ...$responses): stdClass + { + $mergedResponse = new stdClass(); + foreach ($responses as $response) { + $properties = get_object_vars($response); + foreach ($properties as $propertyKey => $propertyValue) { + $mergedResponse->{$propertyKey} = $propertyValue; + } + } + + return $mergedResponse; + } } From 0d48c8f882f2fc9bc0ebaf4fb4624f9f88df3dbc Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Fri, 24 Apr 2020 13:24:39 +0200 Subject: [PATCH 07/13] Remove unused FakeSuccessfulProceedRequest --- src/Message/FakeSuccessfulProceedRequest.php | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100755 src/Message/FakeSuccessfulProceedRequest.php diff --git a/src/Message/FakeSuccessfulProceedRequest.php b/src/Message/FakeSuccessfulProceedRequest.php deleted file mode 100755 index 35802b4..0000000 --- a/src/Message/FakeSuccessfulProceedRequest.php +++ /dev/null @@ -1,18 +0,0 @@ -createSuccessfulProceedResponseForValidPaymentsButNothingToDo(); - } -} From 4ea5ac6b2f472907f1bee96171a6aaf8b3716312 Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Fri, 24 Apr 2020 13:30:29 +0200 Subject: [PATCH 08/13] Remove unused ProceedRequest::getSkipProceedRequest and ProceedRequest::setSkipProceedRequest. The ProceedRequest should never be skipped. --- src/Message/ProceedRequest.php | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/Message/ProceedRequest.php b/src/Message/ProceedRequest.php index 214feca..01060ee 100755 --- a/src/Message/ProceedRequest.php +++ b/src/Message/ProceedRequest.php @@ -154,22 +154,6 @@ public function setAuthorizationResult(array $authorizationResult) $this->authorizationResult = $authorizationResult; } - /** - * @return bool - */ - public function getSkipProceedRequest(): bool - { - return $this->skipProceedRequest; - } - - /** - * @param bool $skipProceedRequest - */ - public function setSkipProceedRequest(bool $skipProceedRequest) - { - $this->skipProceedRequest = $skipProceedRequest; - } - /** * Create a stdClass that mimics a successful soap call to docdata. * This is used when there were no (authorized) payments to proceed. From 5d35a9dd7fb77976285efb524f56e4a1d88e787f Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Fri, 24 Apr 2020 13:54:32 +0200 Subject: [PATCH 09/13] Force validation of 'authorizationResultType' as it is always required in a proceed request and change 'authorizationResultType' and 'authorizationResult' to Omnipay parameters --- src/Message/ProceedRequest.php | 80 ++++++++++++++-------------------- 1 file changed, 32 insertions(+), 48 deletions(-) diff --git a/src/Message/ProceedRequest.php b/src/Message/ProceedRequest.php index 01060ee..fdae578 100755 --- a/src/Message/ProceedRequest.php +++ b/src/Message/ProceedRequest.php @@ -10,28 +10,12 @@ */ class ProceedRequest extends SoapAbstractRequest { - /** - * Name of the authorizationResult for the PROCEED request. - * E.g. iDealAuthorizationResult , belfiusAuthorizationResult. - * - * @see https://test.docdatapayments.com/ps/orderapi-1_3.wsdl #part 5. Proceed - * @var string - */ - protected $authorizationResultType; - - /** - * The actual values needed to be sent with every supported payment method - * - * @var array - */ - protected $authorizationResult = []; - /** * {@inheritdoc} */ public function getData() { - $this->validate('transactionReference'); + $this->validate('transactionReference', 'authorizationResultType'); $data = parent::getData(); $data['paymentOrderKey'] = $this->getTransactionReference(); @@ -75,11 +59,9 @@ protected function runTransaction(SoapClient $soapClient, array $data): stdClass case 'AUTHORIZATION_REQUESTED': case 'RISK_CHECK_OK': unset($data['paymentOrderKey']); - $data['paymentId'] = $payment->id; - if (!empty($this->getAuthorizationResultType())) { - $data[$this->getAuthorizationResultType()] = $this->getAuthorizationResult(); - } + $data['paymentId'] = $payment->id; + $data[$this->getAuthorizationResultType()] = $this->getAuthorizationResult(); // we can't return here because there might be multiple payments that need to proceed $lastProceedResponse = $soapClient->__soapCall('proceed', [$data]); @@ -96,11 +78,6 @@ protected function runTransaction(SoapClient $soapClient, array $data): stdClass } if ($lastProceedResponse === null) { - if (!empty($this->getAuthorizationResultType())) { - // we should have proceeded but we can't - return $this->createFakeProceedErrorResponseForNoValidPayments(); - } - if (!empty($authorizedPayments)) { // bank transfer. Promise to pay, but no money yet. return $this->createSuccessfulProceedResponseForValidPaymentsButNothingToDo(); @@ -123,35 +100,57 @@ protected function getResponseName(): string } /** - * @return string|null + * Returns the name of the authorizationResult for the proceed request. + * + * @return string */ public function getAuthorizationResultType() { - return $this->authorizationResultType; + return $this->getParameter('authorizationResultType'); } /** + * Sets the name of the authorization result for the proceed request. + * (eg. iDealAuthorizationResult, belfiusAuthorizationResult) + * + * @see https://test.docdatapayments.com/ps/orderapi-1_3.wsdl #part 5. Proceed + * * @param string $authorizationResultType + * + * @return $this */ public function setAuthorizationResultType(string $authorizationResultType) { - $this->authorizationResultType = $authorizationResultType; + return $this->setParameter('authorizationResultType', $authorizationResultType); } /** - * @return string + * Returns the authorization result for the proceed request. + * + * @return array */ public function getAuthorizationResult(): array { - return $this->authorizationResult; + $authorizationResult = $this->getParameter('authorizationResult'); + if ($authorizationResult === null) { + $authorizationResult = []; + } + + return $authorizationResult; } /** - * @param string $authorizationResult + * Sets the authorization result for the proceed request. + * + * @see https://test.docdatapayments.com/ps/orderapi-1_3.wsdl #part 5. Proceed + * + * @param array $authorizationResult + * + * @return $this */ public function setAuthorizationResult(array $authorizationResult) { - $this->authorizationResult = $authorizationResult; + return $this->setParameter('authorizationResult', $authorizationResult); } /** @@ -174,19 +173,4 @@ protected function createSuccessfulProceedResponseForValidPaymentsButNothingToDo return $response; } - - /** - * Create a stdClass that mimics a failed soap call to docdata, to be used instead of an exception - * - * @return stdClass - */ - private function createFakeProceedErrorResponseForNoValidPayments() - { - $response = new stdClass(); - $response->proceedErrors = new stdClass(); - $response->proceedErrors->error = new stdClass(); - $response->proceedErrors->error->_ = 'No Proceed executed because there were no valid payments'; - - return $response; - } } From bfda4800e71e1033c0d4469c11d311ebe49945eb Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Fri, 24 Apr 2020 14:02:13 +0200 Subject: [PATCH 10/13] Remove creating fake responses from ProceedRequest --- src/Message/ProceedRequest.php | 42 ---------------------------------- 1 file changed, 42 deletions(-) diff --git a/src/Message/ProceedRequest.php b/src/Message/ProceedRequest.php index fdae578..7b9fb49 100755 --- a/src/Message/ProceedRequest.php +++ b/src/Message/ProceedRequest.php @@ -45,13 +45,7 @@ protected function runTransaction(SoapClient $soapClient, array $data): stdClass } $lastProceedResponse = null; - $authorizedPayments = []; - foreach ($payments as $payment) { - if (isset($payment->authorization->reversal)) { - continue; - } - // try to 'proceed' every payment that has a valid state. // states are, however, badly documented. switch($payment->authorization->status) { @@ -66,21 +60,6 @@ protected function runTransaction(SoapClient $soapClient, array $data): stdClass // we can't return here because there might be multiple payments that need to proceed $lastProceedResponse = $soapClient->__soapCall('proceed', [$data]); break; - case 'AUTHORIZED': - if ( - isset($payment->authorization->capture->status) - && $payment->authorization->capture->status === 'STARTED' - ) { - $authorizedPayments[] = $payment; - } - break; - } - } - - if ($lastProceedResponse === null) { - if (!empty($authorizedPayments)) { - // bank transfer. Promise to pay, but no money yet. - return $this->createSuccessfulProceedResponseForValidPaymentsButNothingToDo(); } } @@ -152,25 +131,4 @@ public function setAuthorizationResult(array $authorizationResult) { return $this->setParameter('authorizationResult', $authorizationResult); } - - /** - * Create a stdClass that mimics a successful soap call to docdata. - * This is used when there were no (authorized) payments to proceed. - * This occurs when a user chooses bank transfer. - * - * @return stdClass - */ - protected function createSuccessfulProceedResponseForValidPaymentsButNothingToDo() - { - $response = new stdClass(); - $response->proceedSuccess = new stdClass(); - $response->proceedSuccess->success = new stdClass(); - $response->proceedSuccess->success->_ = 'All payments were already processed so no action was taken.'; - $response->proceedSuccess->success->code = 'SUCCESS'; - $response->proceedSuccess->paymentResponse = new stdClass(); - $response->proceedSuccess->paymentResponse->paymentSuccess = new stdClass(); - $response->proceedSuccess->paymentResponse->paymentSuccess->status = 'AUTHORIZED'; - - return $response; - } } From cb0b1066a38a4aca1011bc84dfd01ca3278b9883 Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Fri, 24 Apr 2020 14:02:55 +0200 Subject: [PATCH 11/13] Add returning both the status and last proceed response to ProceedRequest::runTransaction --- src/Message/ProceedRequest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Message/ProceedRequest.php b/src/Message/ProceedRequest.php index 7b9fb49..25aa399 100755 --- a/src/Message/ProceedRequest.php +++ b/src/Message/ProceedRequest.php @@ -44,7 +44,7 @@ protected function runTransaction(SoapClient $soapClient, array $data): stdClass $payments = [$payments]; } - $lastProceedResponse = null; + $lastProceedResponse = new stdClass(); foreach ($payments as $payment) { // try to 'proceed' every payment that has a valid state. // states are, however, badly documented. @@ -65,7 +65,7 @@ protected function runTransaction(SoapClient $soapClient, array $data): stdClass // Even if we were to have multiple payments, the last one should be the most relevant // and should have the result returned. - return $lastProceedResponse; + return $this->mergeResponses($statusResponse, $lastProceedResponse); } /** From 3af0ef0d6c13d21c1cfac57441799a6e22c4a9be Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Fri, 24 Apr 2020 15:18:02 +0200 Subject: [PATCH 12/13] Add modifying the last proceed response based on the 'totalAcquirerApproved' of the status request to prevent any race conditions --- src/Message/ProceedRequest.php | 35 ++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/Message/ProceedRequest.php b/src/Message/ProceedRequest.php index 25aa399..c2a7d21 100755 --- a/src/Message/ProceedRequest.php +++ b/src/Message/ProceedRequest.php @@ -63,8 +63,11 @@ protected function runTransaction(SoapClient $soapClient, array $data): stdClass } } - // Even if we were to have multiple payments, the last one should be the most relevant - // and should have the result returned. + $this->modifyProceedResponseToSuccessfulWhenPaymentAlreadyAcquirerApproved( + $lastProceedResponse, + $statusResponse + ); + return $this->mergeResponses($statusResponse, $lastProceedResponse); } @@ -131,4 +134,32 @@ public function setAuthorizationResult(array $authorizationResult) { return $this->setParameter('authorizationResult', $authorizationResult); } + + /** + * Modifies the last proceed response to successful when the payments are already acquirer approved. + * + * @param stdClass $lastProceedResponse + * @param stdClass $statusResponse + */ + private function modifyProceedResponseToSuccessfulWhenPaymentAlreadyAcquirerApproved( + stdClass $lastProceedResponse, + stdClass $statusResponse + ): void { + if (isset($statusResponse->statusSuccess) === false) { + return; + } + + $statusResponseApproximateTotals = $statusResponse->statusSuccess->report->approximateTotals; + if ($statusResponseApproximateTotals->totalRegistered !== $statusResponseApproximateTotals->totalAcquirerApproved) { + return; + } + + $lastProceedResponse->proceedSuccess = new stdClass(); + $lastProceedResponse->proceedSuccess->success = new stdClass(); + $lastProceedResponse->proceedSuccess->success->_ = 'All payments already acquirer approved.'; + $lastProceedResponse->proceedSuccess->success->code = 'SUCCESS'; + $lastProceedResponse->proceedSuccess->paymentResponse = new stdClass(); + $lastProceedResponse->proceedSuccess->paymentResponse->paymentSuccess = new stdClass(); + $lastProceedResponse->proceedSuccess->paymentResponse->paymentSuccess->status = 'AUTHORIZED'; + } } From b3550cc59dabcdd32f7d38d641cfbfb58f0f2676 Mon Sep 17 00:00:00 2001 From: Niels Nijens Date: Fri, 24 Apr 2020 15:19:58 +0200 Subject: [PATCH 13/13] Change proceed request call conditions from the status string to the if the payment is captured, refunded, chargeback or reversal --- src/Message/ProceedRequest.php | 35 ++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/Message/ProceedRequest.php b/src/Message/ProceedRequest.php index c2a7d21..e13c3bc 100755 --- a/src/Message/ProceedRequest.php +++ b/src/Message/ProceedRequest.php @@ -46,21 +46,28 @@ protected function runTransaction(SoapClient $soapClient, array $data): stdClass $lastProceedResponse = new stdClass(); foreach ($payments as $payment) { - // try to 'proceed' every payment that has a valid state. - // states are, however, badly documented. - switch($payment->authorization->status) { - case 'REDIRECTED_FOR_AUTHORIZATION': - case 'AUTHORIZATION_REQUESTED': - case 'RISK_CHECK_OK': - unset($data['paymentOrderKey']); - - $data['paymentId'] = $payment->id; - $data[$this->getAuthorizationResultType()] = $this->getAuthorizationResult(); - - // we can't return here because there might be multiple payments that need to proceed - $lastProceedResponse = $soapClient->__soapCall('proceed', [$data]); - break; + if (isset($payment->authorization->capture)) { + continue; } + + if (isset($payment->authorization->refund)) { + continue; + } + + if (isset($payment->authorization->chargeback)) { + continue; + } + + if (isset($payment->authorization->reversal)) { + continue; + } + + unset($data['paymentOrderKey']); + + $data['paymentId'] = $payment->id; + $data[$this->getAuthorizationResultType()] = $this->getAuthorizationResult(); + + $lastProceedResponse = $soapClient->__soapCall('proceed', [$data]); } $this->modifyProceedResponseToSuccessfulWhenPaymentAlreadyAcquirerApproved(