diff --git a/Gateway/Http/Client/TransactionPosCloudSync.php b/Gateway/Http/Client/TransactionPosCloud.php
similarity index 81%
rename from Gateway/Http/Client/TransactionPosCloudSync.php
rename to Gateway/Http/Client/TransactionPosCloud.php
index fadc72578c..e4072306f2 100644
--- a/Gateway/Http/Client/TransactionPosCloudSync.php
+++ b/Gateway/Http/Client/TransactionPosCloud.php
@@ -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;
@@ -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());
diff --git a/Gateway/Request/PosCloudBuilder.php b/Gateway/Request/PosCloudBuilder.php
index cbbe92aeea..0b09025611 100644
--- a/Gateway/Request/PosCloudBuilder.php
+++ b/Gateway/Request/PosCloudBuilder.php
@@ -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
{
@@ -33,27 +33,6 @@ 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(
@@ -61,11 +40,53 @@ private function buildPosRequest(
);
}
- $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 = [
@@ -79,7 +100,7 @@ private function buildPosRequest(
'SaleID' => 'Magento2Cloud',
'POIID' => $poiId,
'ProtocolVersion' => '3.0',
- 'ServiceID' => $serviceID
+ 'ServiceID' => $this->getServiceId($payment),
],
'PaymentRequest' =>
[
@@ -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);
+ }
}
diff --git a/Gateway/Response/PaymentPosCloudHandler.php b/Gateway/Response/PaymentPosCloudHandler.php
index 1fc761ad8f..3a1ddd175c 100644
--- a/Gateway/Response/PaymentPosCloudHandler.php
+++ b/Gateway/Response/PaymentPosCloudHandler.php
@@ -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);
@@ -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(
diff --git a/Gateway/Validator/PosCloudResponseValidator.php b/Gateway/Validator/PosCloudResponseValidator.php
index c9acbbfebe..b9cb8fc302 100644
--- a/Gateway/Validator/PosCloudResponseValidator.php
+++ b/Gateway/Validator/PosCloudResponseValidator.php
@@ -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) {
@@ -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') {
diff --git a/Model/Api/AdyenPosCloud.php b/Model/Api/AdyenPosCloud.php
index 2e546b6f50..306d14932f 100644
--- a/Model/Api/AdyenPosCloud.php
+++ b/Model/Api/AdyenPosCloud.php
@@ -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;
@@ -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');
}
}
diff --git a/etc/di.xml b/etc/di.xml
index e7fc019f4f..5c65ea37f1 100755
--- a/etc/di.xml
+++ b/etc/di.xml
@@ -991,7 +991,7 @@
AdyenPaymentPosCloudAuthorizeRequest
Adyen\Payment\Gateway\Http\TransferFactory
- Adyen\Payment\Gateway\Http\Client\TransactionPosCloudSync
+ Adyen\Payment\Gateway\Http\Client\TransactionPosCloud
PosCloudResponseValidator
AdyenPaymentPosCloudResponseHandlerComposite
@@ -1789,7 +1789,7 @@
Magento\Checkout\Model\Session\Proxy
-
+
Magento\Checkout\Model\Session\Proxy
diff --git a/view/frontend/web/js/model/adyen-payment-service.js b/view/frontend/web/js/model/adyen-payment-service.js
index 3b246a1080..cbe1baabd3 100644
--- a/view/frontend/web/js/model/adyen-payment-service.js
+++ b/view/frontend/web/js/model/adyen-payment-service.js
@@ -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),
@@ -108,7 +108,7 @@ define(
} else {
serviceUrl = urlBuilder.createUrl(
'/adyen/guest-carts/:cartId/payments-details', {
- cartId: quote.getQuoteId(),
+ cartId: quoteId ?? quote.getQuoteId()
}
);
}