Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue/2867 use async pos payments #2868

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
use Magento\Payment\Gateway\Http\TransferInterface;
use Magento\Store\Model\StoreManagerInterface;

class TransactionPosCloudSync implements ClientInterface
class TransactionPosCloud implements ClientInterface
{
protected int $storeId;
protected mixed $timeout;
Expand Down Expand Up @@ -60,9 +60,15 @@ public function placeRequest(TransferInterface $transferObject): array
$request = $transferObject->getBody();
$service = $this->adyenHelper->createAdyenPosPaymentService($this->client);

$this->adyenHelper->logRequest($request, '', '/sync');
$this->adyenHelper->logRequest($request, '', '/async');
try {
$response = $service->runTenderSync($request);
if (!empty($request['SaleToPOIRequest']['PaymentRequest'])) {
// Use async for payment requests
// Note: Async requests do not have a response
$response = $service->runTenderAsync($request) ?? ['async' => true];
} else {
$response = $service->runTenderSync($request);
}
} catch (AdyenException $e) {
//Not able to perform a payment
$this->adyenLogger->addAdyenDebug($response['error'] = $e->getMessage());
Expand Down
76 changes: 51 additions & 25 deletions Gateway/Request/PosCloudBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use Magento\Framework\Exception\LocalizedException;
use Magento\Payment\Gateway\Helper\SubjectReader;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Payment;

class PosCloudBuilder implements BuilderInterface
{
Expand All @@ -33,39 +33,60 @@ public function __construct(ChargedCurrency $chargedCurrency, PointOfSale $point

public function build(array $buildSubject): array
{
$paymentDataObject = SubjectReader::readPayment($buildSubject);

$payment = $paymentDataObject->getPayment();
$order = $payment->getOrder();

$request['body'] = $this->buildPosRequest(
$order,
$payment->getAdditionalInformation('terminal_id'),
$payment->getAdditionalInformation('funding_source'),
$payment->getAdditionalInformation('number_of_installments'),
);

return $request;
}

private function buildPosRequest(
Order $order,
string $terminalId,
?string $fundingSource,
?string $numberOfInstallments
): array {
// Validate JSON that has just been parsed if it was in a valid format
if (json_last_error() !== JSON_ERROR_NONE) {
throw new LocalizedException(
__('Terminal API initiate request was not a valid JSON')
);
}

$poiId = $terminalId;
$paymentDataObject = SubjectReader::readPayment($buildSubject);
$payment = $paymentDataObject->getPayment();
if (!$payment instanceof Payment) {
throw new LocalizedException(__('Expecting instance of ' . Payment::class));
}
if ($payment->hasAdditionalInformation('pos_request')) {
// POS status request
$request['body'] = $this->buildPosStatusRequest($payment);
} else {
// New POS request
$request['body'] = $this->buildPosRequest($payment);
$payment->setAdditionalInformation('pos_request', $payment->getAdditionalInformation('terminal_id'));
}

return $request;
}

private function buildPosStatusRequest(Payment $payment): array {
$request = [
'SaleToPOIRequest' => [
'MessageHeader' => [
'MessageType' => 'Request',
'MessageClass' => 'Service',
'MessageCategory' => 'TransactionStatus',
'SaleID' => 'Magento2Cloud',
'POIID' => $payment->getAdditionalInformation('pos_request'),
'ProtocolVersion' => '3.0',
'ServiceID' => $this->getServiceId($payment),
],
'TransactionStatusRequest' => [
'ReceiptReprintFlag' => false,
],
],
];
$request['SaleToPOIRequest']['MessageHeader']['MessageCategory'] = 'TransactionStatus';

return $request;
}

private function buildPosRequest(Payment $payment) {
$order = $payment->getOrder();
$poiId = $payment->getAdditionalInformation('terminal_id');
$fundingSource = $payment->getAdditionalInformation('funding_source');
$numberOfInstallments = $payment->getAdditionalInformation('number_of_installments');
$transactionType = \Adyen\TransactionType::NORMAL;
$amountCurrency = $this->chargedCurrency->getOrderAmountCurrency($order);

$serviceID = date("dHis");
$timeStamper = date("Y-m-d") . "T" . date("H:i:s+00:00");

$request = [
Expand All @@ -79,7 +100,7 @@ private function buildPosRequest(
'SaleID' => 'Magento2Cloud',
'POIID' => $poiId,
'ProtocolVersion' => '3.0',
'ServiceID' => $serviceID
'ServiceID' => $this->getServiceId($payment),
],
'PaymentRequest' =>
[
Expand Down Expand Up @@ -137,4 +158,9 @@ private function buildPosRequest(

return $this->pointOfSale->addSaleToAcquirerData($request, $order);
}

private function getServiceId(Payment $payment): string
{
return substr(sha1(uniqid($payment->getOrder()->getIncrementId(), true)), 0, 10);
}
}
27 changes: 25 additions & 2 deletions Gateway/Response/PaymentPosCloudHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,30 @@ public function __construct(

public function handle(array $handlingSubject, array $response)
{
$paymentResponse = $response['SaleToPOIResponse']['PaymentResponse'];
$paymentDataObject = SubjectReader::readPayment($handlingSubject);

$payment = $paymentDataObject->getPayment();
if (!empty($response['async'])) {
// Async payment request, save Order
$order = $payment->getOrder();
$message = __('Pos payment initiated');
$order->addCommentToStatusHistory($message);
$order->save();

return;
}

$errorCondition = $response
['SaleToPOIResponse']
['TransactionStatusResponse']
['Response']
['ErrorCondition'] ?? null;
if ($errorCondition === 'InProgress') {
// Payment in progress
return;
}

$paymentResponse = $response['SaleToPOIResponse']['PaymentResponse']
?? $response['SaleToPOIResponse']['TransactionStatusResponse']['RepeatedMessageResponse']['RepeatedResponseMessageBody']['PaymentResponse'];

// set transaction not to processing by default wait for notification
$payment->setIsTransactionPending(true);
Expand Down Expand Up @@ -93,6 +113,9 @@ public function handle(array $handlingSubject, array $response)
$payment->setIsTransactionClosed(false);
$payment->setShouldCloseParentTransaction(false);

// Transaction is final
$payment->unsAdditionalInformation('pos_request');

if ($resultCode === PaymentResponseHandler::POS_SUCCESS) {
$order = $payment->getOrder();
$status = $this->statusResolver->getOrderStatusByState(
Expand Down
20 changes: 19 additions & 1 deletion Gateway/Validator/PosCloudResponseValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ public function validate(array $validationSubject): ResultInterface

$this->adyenLogger->addAdyenDebug(json_encode($response));

// Do not validate (async) payment requests
if (!empty($response['async'])) {
// Async payment request
return $this->createResult(true, []);
}

// Do not validate in progress status response
$errorCondition = $response
['SaleToPOIResponse']
['TransactionStatusResponse']
['Response']
['ErrorCondition'] ?? null;
if ($errorCondition === 'InProgress') {
// Payment in progress
return $this->createResult(true, []);
}

// Check for errors
if (!empty($response['error'])) {
if (!empty($response['code']) && $response['code'] == CURLE_OPERATION_TIMEOUTED) {
Expand All @@ -54,7 +71,8 @@ public function validate(array $validationSubject): ResultInterface
}
} else {
// We have a paymentResponse from the terminal
$paymentResponse = $response['SaleToPOIResponse']['PaymentResponse'];
$paymentResponse = $response['SaleToPOIResponse']['PaymentResponse']
?? $response['SaleToPOIResponse']['TransactionStatusResponse']['RepeatedMessageResponse']['RepeatedResponseMessageBody']['PaymentResponse'];
}

if (!empty($paymentResponse) && $paymentResponse['Response']['Result'] != 'Success') {
Expand Down
8 changes: 8 additions & 0 deletions Model/Api/AdyenPosCloud.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Adyen\Payment\Api\AdyenPosCloudInterface;
use Adyen\Payment\Logger\AdyenLogger;
use Adyen\Payment\Model\Sales\OrderRepository;
use Exception;
use Magento\Payment\Gateway\Command\CommandPoolInterface;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Payment\Gateway\Data\PaymentDataObjectFactoryInterface;
Expand Down Expand Up @@ -50,5 +51,12 @@ protected function execute(OrderInterface $order): void
$paymentDataObject = $this->paymentDataObjectFactory->create($payment);
$posCommand = $this->commandPool->get('authorize');
$posCommand->execute(['payment' => $paymentDataObject]);
if (!$payment->hasAdditionalInformation('pos_request')) {
return;
}

// Pending POS payment, add a short delay to avoid a flood of requests
sleep(2);
throw new Exception('In Progress');
}
}
4 changes: 2 additions & 2 deletions etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@
<arguments>
<argument name="requestBuilder" xsi:type="object">AdyenPaymentPosCloudAuthorizeRequest</argument>
<argument name="transferFactory" xsi:type="object">Adyen\Payment\Gateway\Http\TransferFactory</argument>
<argument name="client" xsi:type="object">Adyen\Payment\Gateway\Http\Client\TransactionPosCloudSync</argument>
<argument name="client" xsi:type="object">Adyen\Payment\Gateway\Http\Client\TransactionPosCloud</argument>
<argument name="validator" xsi:type="object">PosCloudResponseValidator</argument>
<argument name="handler" xsi:type="object">AdyenPaymentPosCloudResponseHandlerComposite</argument>
</arguments>
Expand Down Expand Up @@ -1789,7 +1789,7 @@
<argument name="checkoutSession" xsi:type="object">Magento\Checkout\Model\Session\Proxy</argument>
</arguments>
</type>
<type name="Adyen\Payment\Gateway\Http\Client\TransactionPosCloudSync">
<type name="Adyen\Payment\Gateway\Http\Client\TransactionPosCloud">
<arguments>
<argument name="session" xsi:type="object">Magento\Checkout\Model\Session\Proxy</argument>
</arguments>
Expand Down
4 changes: 2 additions & 2 deletions view/frontend/web/js/model/adyen-payment-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ define(
);
},

paymentDetails: function(data, orderId, isMultishipping = false) {
paymentDetails: function(data, orderId, isMultishipping = false, quoteId = null) {
let serviceUrl;
let payload = {
'payload': JSON.stringify(data),
Expand All @@ -108,7 +108,7 @@ define(
} else {
serviceUrl = urlBuilder.createUrl(
'/adyen/guest-carts/:cartId/payments-details', {
cartId: quote.getQuoteId(),
cartId: quoteId ?? quote.getQuoteId()
}
);
}
Expand Down
Loading