Skip to content

Commit

Permalink
Merge pull request #5 from niels-nijens/fix-proceed-request
Browse files Browse the repository at this point in the history
Fix ProceedRequest (WebdirectGateway::completeAuthorize)
  • Loading branch information
niels-nijens committed Apr 28, 2020
2 parents 68d2c55 + b3550cc commit 98f03ee
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 191 deletions.
20 changes: 0 additions & 20 deletions src/Message/CaptureRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
18 changes: 0 additions & 18 deletions src/Message/FakeSuccessfulProceedRequest.php

This file was deleted.

218 changes: 97 additions & 121 deletions src/Message/ProceedRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,102 +2,80 @@

namespace Omnipay\DocdataPayments\Message;

use SoapClient;
use stdClass;

/**
* DocdataPayments Proceed Request, to 'pay' without interaction from the user
*/
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
* {@inheritdoc}
*/
protected $authorizationResultType;
public function getData()
{
$this->validate('transactionReference', 'authorizationResultType');

/**
* The actual values needed to be sent with every supported payment method
*
* @var array
*/
protected $authorizationResult = [];
$data = parent::getData();
$data['paymentOrderKey'] = $this->getTransactionReference();

return $data;
}

/**
* 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
{
// 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;
}

$lastProceedResult = null;
$authorizedPayments = [];
if (is_array($payments) === false) {
$payments = [$payments];
}

foreach($payments as $payment) {
if (isset($payment->authorization->reversal)) {
$lastProceedResponse = new stdClass();
foreach ($payments as $payment) {
if (isset($payment->authorization->capture)) {
continue;
}

// try to 'proceed' every payment that has a valid state.
// states are, however, badly documented.
switch($payment->authorization->status) {
case 'REDIRECTED_FOR_AUTHENTICATION':
case 'AUTHORIZATION_REQUESTED':
case 'RISK_CHECK_OK':
// we can proceed
$data['paymentId'] = $payment->id;

if (!empty($this->getAuthorizationResultType())) {
$data[$this->getAuthorizationResultType()] = $this->getAuthorizationResult();
}

// we can't return here because there might be multiple payments that need to proceed
$lastProceedResult = $soapClient->__soapCall('proceed', [$data]);
break;
case 'AUTHORIZED':
if (
isset($payment->authorization->capture->status)
&& $payment->authorization->capture->status === 'STARTED'
) {
$authorizedPayments[] = $payment;
}
break;
if (isset($payment->authorization->refund)) {
continue;
}
}

if ($lastProceedResult === null) {
if (!empty($this->getAuthorizationResultType())) {
// we should have proceeded but we can't
return $this->createFakeProceedErrorResponseForNoValidPayments();
if (isset($payment->authorization->chargeback)) {
continue;
}

if (!empty($authorizedPayments)) {
// bank transfer. Promise to pay, but no money yet.
return $this->createSuccessfulProceedResponseForValidPaymentsButNothingToDo();
if (isset($payment->authorization->reversal)) {
continue;
}

unset($data['paymentOrderKey']);

$data['paymentId'] = $payment->id;
$data[$this->getAuthorizationResultType()] = $this->getAuthorizationResult();

$lastProceedResponse = $soapClient->__soapCall('proceed', [$data]);
}
// Even if we were to have multiple payments, the last one should be the most relevant
// and should have the result returned.
return $lastProceedResult;

$this->modifyProceedResponseToSuccessfulWhenPaymentAlreadyAcquirerApproved(
$lastProceedResponse,
$statusResponse
);

return $this->mergeResponses($statusResponse, $lastProceedResponse);
}

/**
Expand All @@ -111,86 +89,84 @@ 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;
}

/**
* @param string $authorizationResult
*/
public function setAuthorizationResult(array $authorizationResult)
{
$this->authorizationResult = $authorizationResult;
}

/**
* @return bool
*/
public function getSkipProceedRequest(): bool
{
return $this->skipProceedRequest;
}
$authorizationResult = $this->getParameter('authorizationResult');
if ($authorizationResult === null) {
$authorizationResult = [];
}

/**
* @param bool $skipProceedRequest
*/
public function setSkipProceedRequest(bool $skipProceedRequest)
{
$this->skipProceedRequest = $skipProceedRequest;
return $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.
* 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 \stdClass
* @return $this
*/
protected function createSuccessfulProceedResponseForValidPaymentsButNothingToDo()
public function setAuthorizationResult(array $authorizationResult)
{
$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;
return $this->setParameter('authorizationResult', $authorizationResult);
}

/**
* Create a stdClass that mimics a failed soap call to docdata, to be used instead of an exception
* Modifies the last proceed response to successful when the payments are already acquirer approved.
*
* @return \stdClass
* @param stdClass $lastProceedResponse
* @param stdClass $statusResponse
*/
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';
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;
}

return $response;
$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';
}
}
Loading

0 comments on commit 98f03ee

Please sign in to comment.