We will work on Apr 26th (Saturday) and will be off from Apr 30th (Wednesday) until May 2nd (Friday) for public holiday in our country

Commit 16ca2716 authored by Alessio Zampatti's avatar Alessio Zampatti Committed by Aleffio

PW-489 Implement terminal API (#279)

* PW-489: Added Initiate call before placing the order

* PW-489: Construct sync call request on Magento instead of on the Util Library, add encryptor on pos api key

* PW-489: Removed logger/comments

* PW-458: Implemented status call every 5 seconds, added timeout on Initiate call

* PW-458: Fixed terminalAPI guest checkout, added configuration for pos_timeout, return immediately if response from Initiate call

* PW-458: Do not show pos_timeout in config

* PW-458: Retrieve quoteid from the backend, added PHP library version requirement, using settimeout instead of setinterval, removed pos_timeout from adyen_pos_cloud xml, renamed Initiate button label back to Place Order, replaced the .done().fail() flow with .always() on the frontend
parent 752e76f3
<?php
/**
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
*
* Adyen Payment Module
*
* Copyright (c) 2018 Adyen B.V.
* This file is open source and available under the MIT license.
* See the LICENSE file for more info.
*
* Author: Adyen <magento@adyen.com>
*/
namespace Adyen\Payment\Api;
interface AdyenInitiateTerminalApiInterface
{
/**
* Trigger sync call on terminal
* @return mixed
*/
public function initiate();
}
\ No newline at end of file
......@@ -50,6 +50,7 @@ class TransactionPosCloudSync implements ClientInterface
$client = new \Adyen\Client();
$client->setApplicationName("Magento 2 plugin");
$client->setXApiKey($apiKey);
$client->setTimeout(5);
if ($this->_adyenHelper->isDemoMode()) {
$client->setEnvironment(\Adyen\Environment::TEST);
} else {
......@@ -72,17 +73,51 @@ class TransactionPosCloudSync implements ClientInterface
public function placeRequest(\Magento\Payment\Gateway\Http\TransferInterface $transferObject)
{
$request = $transferObject->getBody();
if(!empty($request['response'])){
//Initiate has already a response
return $request['response'];
}
//always do status call and return the response of the status call
$service = new \Adyen\Service\PosPayment($this->_client);
$transactionType = \Adyen\TransactionType::GOODS_SERVICES;
$poiId = $this->_adyenHelper->getPoiId();
$json = Util::buildPosPaymentRequest($poiId, $request['amount']['value'], $request['amount']['currency'],
$request['reference'], $transactionType);
$params = json_decode($json, true); //Create associative array for passing along
$newServiceID = date("dHis");
//Provide receipt to the shopper
$jsonStatus='{
"SaleToPOIRequest": {
"MessageHeader": {
"ProtocolVersion": "3.0",
"MessageClass": "Service",
"MessageCategory": "TransactionStatus",
"MessageType": "Request",
"ServiceID": "' . $newServiceID . '",
"SaleID": "Magento2CloudStatus",
"POIID": "' . $poiId . '"
},
"TransactionStatusRequest": {
"MessageReference": {
"MessageCategory": "Payment",
"SaleID": "Magento2Cloud",
"ServiceID": "' . $request['serviceID'] . '"
},
"DocumentQualifier" : [
"CashierReceipt",
"CustomerReceipt"
],
"ReceiptReprintFlag" : true
}
}
}';
$params = json_decode($jsonStatus, true); //Create associative array for passing along
try {
$response = $service->runTenderSync($params);
} catch (\Adyen\AdyenException $e) {
$response['error'] = $e->getMessage();
}
return $response;
}
}
\ No newline at end of file
......@@ -38,6 +38,8 @@ class PosCloudBuilder implements BuilderInterface
*/
private $_adyenLogger;
protected $_quoteRepository;
/**
* PaymentDataBuilder constructor.
*
......@@ -46,10 +48,12 @@ class PosCloudBuilder implements BuilderInterface
*/
public function __construct(
\Adyen\Payment\Logger\AdyenLogger $adyenLogger,
\Adyen\Payment\Helper\Data $adyenHelper
\Adyen\Payment\Helper\Data $adyenHelper,
\Magento\Quote\Api\CartRepositoryInterface $quoteRepository
) {
$this->_adyenLogger = $adyenLogger;
$this->adyenHelper = $adyenHelper;
$this->_quoteRepository = $quoteRepository;
}
/**
......@@ -59,20 +63,16 @@ class PosCloudBuilder implements BuilderInterface
public function build(array $buildSubject)
{
$paymentDataObject = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($buildSubject);
$order = $paymentDataObject->getOrder();
$payment = $paymentDataObject->getPayment();
$fullOrder = $payment->getOrder();
$currencyCode = $fullOrder->getOrderCurrencyCode();
$grandTotal = $fullOrder->getGrandTotal();
$amount = [
'currency' => $currencyCode,
'value' => $grandTotal
];
$quote = $this->_quoteRepository->getActive($fullOrder->getQuoteId());
return [
"amount" => $amount,
"reference" => $order->getOrderIncrementId()
"response" => $quote->getPayment()->getAdditionalInformation("terminalResponse"),
"serviceID" => $quote->getPayment()->getAdditionalInformation("serviceID")
];
}
}
\ No newline at end of file
......@@ -63,7 +63,6 @@ class PaymentPosCloudHandler implements HandlerInterface
*/
public function handle(array $handlingSubject, array $response)
{
$payment = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($handlingSubject);
/** @var OrderPaymentInterface $payment */
......@@ -75,8 +74,12 @@ class PaymentPosCloudHandler implements HandlerInterface
// no not send order confirmation mail
$payment->getOrder()->setCanSendNewEmailFlag(false);
// set transaction
if (!empty($response['SaleToPOIResponse']['PaymentResponse']['PaymentResult']['PaymentAcquirerData']['AcquirerTransactionID']['TransactionID'])) {
// set transaction(status)
if (!empty($response['SaleToPOIResponse']['TransactionStatusResponse']['RepeatedMessageResponse']['RepeatedResponseMessageBody']['PaymentResponse']['PaymentResult']['PaymentAcquirerData']['AcquirerTransactionID']['TransactionID'])) {
$pspReference = $response['SaleToPOIResponse']['TransactionStatusResponse']['RepeatedMessageResponse']['RepeatedResponseMessageBody']['PaymentResponse']['PaymentResult']['PaymentAcquirerData']['AcquirerTransactionID']['TransactionID'];
$payment->setTransactionId($pspReference);
// set transaction(payment)
} elseif (!empty($response['SaleToPOIResponse']['PaymentResponse']['PaymentResult']['PaymentAcquirerData']['AcquirerTransactionID']['TransactionID'])) {
$pspReference = $response['SaleToPOIResponse']['PaymentResponse']['PaymentResult']['PaymentAcquirerData']['AcquirerTransactionID']['TransactionID'];
$payment->setTransactionId($pspReference);
} else {
......
......@@ -58,7 +58,13 @@ class PosCloudResponseValidator extends AbstractValidator
$isValid = true;
$response = \Magento\Payment\Gateway\Helper\SubjectReader::readResponse($validationSubject);
if ((!empty($response['SaleToPOIResponse']['PaymentResponse']['Response']['Result']) &&
// Check In Progress status call
if (!empty($response['SaleToPOIResponse']['TransactionStatusResponse']['Response']['Result']) && $response['SaleToPOIResponse']['TransactionStatusResponse']['Response']['Result'] == "Failure") {
$errorMsg = __('In Progress');
$errorMessages[] = $errorMsg;
throw new \Magento\Framework\Exception\LocalizedException(__($errorMsg));
}
elseif ((!empty($response['SaleToPOIResponse']['PaymentResponse']['Response']['Result']) &&
$response['SaleToPOIResponse']['PaymentResponse']['Response']['Result'] != 'Success'
) || empty($response['SaleToPOIResponse']['PaymentResponse']['Response']['Result'])
) {
......
......@@ -1188,10 +1188,10 @@ class Data extends AbstractHelper
{
switch ($this->isDemoMode()) {
case true:
$apiKey = $this->getAdyenPosCloudConfigData('api_key_test');
$apiKey = $this->_encryptor->decrypt(trim($this->getAdyenPosCloudConfigData('api_key_test')));
break;
default:
$apiKey = $this->getAdyenPosCloudConfigData('api_key_live');
$apiKey = $this->_encryptor->decrypt(trim($this->getAdyenPosCloudConfigData('api_key_live')));
break;
}
return $apiKey;
......
<?php
/**
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
*
* Adyen Payment Module
*
* Copyright (c) 2018 Adyen B.V.
* This file is open source and available under the MIT license.
* See the LICENSE file for more info.
*
* Author: Adyen <magento@adyen.com>
*/
namespace Adyen\Payment\Model;
use Adyen\Payment\Api\AdyenInitiateTerminalApiInterface;
use Adyen\Payment\Model\Ui\AdyenPosCloudConfigProvider;
use Adyen\Util\Util;
use Magento\Payment\Gateway\Http\ClientInterface;
class AdyenInitiateTerminalApi implements AdyenInitiateTerminalApiInterface
{
private $_encryptor;
private $_adyenHelper;
private $_adyenLogger;
private $_recurringType;
private $_appState;
protected $_checkoutSession;
/**
* AdyenInitiateTerminalApi constructor.
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Encryption\EncryptorInterface $encryptor
* @param \Adyen\Payment\Helper\Data $adyenHelper
* @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger
* @param RecurringType $recurringType
* @param array $data
*/
public function __construct(
\Magento\Framework\Model\Context $context,
\Magento\Framework\Encryption\EncryptorInterface $encryptor,
\Adyen\Payment\Helper\Data $adyenHelper,
\Adyen\Payment\Logger\AdyenLogger $adyenLogger,
\Adyen\Payment\Model\RecurringType $recurringType,
\Magento\Checkout\Model\Session $_checkoutSession,
array $data = []
) {
$this->_encryptor = $encryptor;
$this->_adyenHelper = $adyenHelper;
$this->_adyenLogger = $adyenLogger;
$this->_recurringType = $recurringType;
$this->_appState = $context->getAppState();
$this->_checkoutSession = $_checkoutSession;
// initialize client
$apiKey = $this->_adyenHelper->getApiKey();
$client = new \Adyen\Client();
$client->setApplicationName("Magento 2 plugin");
$client->setXApiKey($apiKey);
//Set configurable option in M2
$posTimeout = $this->_adyenHelper->getAdyenPosCloudConfigData('pos_timeout');
if (!empty($posTimeout)) {
$client->setTimeout($posTimeout);
}
if ($this->_adyenHelper->isDemoMode()) {
$client->setEnvironment(\Adyen\Environment::TEST);
} else {
$client->setEnvironment(\Adyen\Environment::LIVE);
}
// assign magento log
$client->setLogger($adyenLogger);
$this->_client = $client;
}
/**
* Trigger sync call on terminal
* @return mixed
*/
public function initiate()
{
$quote = $this->_checkoutSession->getQuote();
$payment = $quote->getPayment();
$payment->setMethod(AdyenPosCloudConfigProvider::CODE);
$reference = $quote->reserveOrderId()->getReservedOrderId();
$service = new \Adyen\Service\PosPayment($this->_client);
$transactionType = \Adyen\TransactionType::NORMAL;
$poiId = $this->_adyenHelper->getPoiId();
$serviceID = date("dHis");
$timeStamper = date("Y-m-d") . "T" . date("H:i:s+00:00");
$json = '{
"SaleToPOIRequest": {
"MessageHeader": {
"MessageType": "Request",
"MessageClass": "Service",
"MessageCategory": "Payment",
"SaleID": "Magento2Cloud",
"POIID": "' . $poiId . '",
"ProtocolVersion": "3.0",
"ServiceID": "' . $serviceID . '"
},
"PaymentRequest": {
"SaleData": {
"SaleTransactionID": {
"TransactionID": "' . $reference . '",
"TimeStamp": "' . $timeStamper . '"
},
"TokenRequestedType": "Customer"
},
"PaymentTransaction": {
"AmountsReq": {
"Currency": "' . $quote->getCurrency()->getQuoteCurrencyCode() . '",
"RequestedAmount": ' . $quote->getGrandTotal() . '
}
},
"PaymentData": {
"PaymentType": "' . $transactionType . '"
}
}
}
}
';
$params = json_decode($json, true); //Create associative array for passing along
$quote->getPayment()->getMethodInstance()->getInfoInstance()->setAdditionalInformation('serviceID',
$serviceID);
try {
$response = $service->runTenderSync($params);
} catch (\Adyen\AdyenException $e) {
//Not able to perform a payment
$this->_adyenLogger->addAdyenDebug("adyenexception");
$response['error'] = $e->getMessage();
} catch (\Exception $e) {
//Probably timeout
$quote->getPayment()->getMethodInstance()->getInfoInstance()->setAdditionalInformation('terminalResponse',
null);
$quote->save();
$response['error'] = $e->getMessage();
throw $e;
}
$quote->getPayment()->getMethodInstance()->getInfoInstance()->setAdditionalInformation('terminalResponse',
$response);
$quote->save();
return $response;
}
}
\ No newline at end of file
<?php
/**
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
*
* Adyen Payment Module
*
* Copyright (c) 2018 Adyen B.V.
* This file is open source and available under the MIT license.
* See the LICENSE file for more info.
*
* Author: Adyen <magento@adyen.com>
*/
namespace Adyen\Payment\Model;
class AdyenPaymentInformationManagement extends \Magento\Checkout\Model\PaymentInformationManagement
{
/**
* {@inheritDoc}
*/
public function savePaymentInformationAndPlaceOrder(
$cartId,
\Magento\Quote\Api\Data\PaymentInterface $paymentMethod,
\Magento\Quote\Api\Data\AddressInterface $billingAddress = null
) {
$this->savePaymentInformation($cartId, $paymentMethod, $billingAddress);
try {
$orderId = $this->cartManagement->placeOrder($cartId);
} catch (\Exception $e) {
throw $e;
}
return $orderId;
}
}
\ No newline at end of file
......@@ -167,6 +167,7 @@
<can_refund>1</can_refund>
<can_void>1</can_void>
<can_cancel>1</can_cancel>
<pos_timeout>30</pos_timeout>
<group>adyen</group>
</adyen_pos_cloud>
<adyen_pay_by_mail>
......
......@@ -915,4 +915,6 @@
<preference for="Adyen\Payment\Api\GuestAdyenPaymentMethodManagementInterface" type="Adyen\Payment\Model\GuestAdyenPaymentMethodManagement" />
<preference for="Adyen\Payment\Api\AdyenPaymentMethodManagementInterface" type="Adyen\Payment\Model\AdyenPaymentMethodManagement" />
<preference for="Adyen\Payment\Api\AdyenRequestMerchantSessionInterface" type="Adyen\Payment\Model\AdyenRequestMerchantSession" />
<preference for="Adyen\Payment\Api\AdyenInitiateTerminalApiInterface" type="Adyen\Payment\Model\AdyenInitiateTerminalApi" />
<preference for="Magento\Checkout\Api\PaymentInformationManagementInterface" type="Adyen\Payment\Model\AdyenPaymentInformationManagement" />
</config>
\ No newline at end of file
......@@ -50,4 +50,11 @@
<resource ref="anonymous"/>
</resources>
</route>
<route url="/V1/adyen/initiate" method="POST">
<service class="Adyen\Payment\Api\AdyenInitiateTerminalApiInterface" method="initiate"/>
<resources>
<resource ref="anonymous"/>
</resources>
</route>
</routes>
\ No newline at end of file
......@@ -25,11 +25,22 @@
define(
[
'ko',
'jquery',
'Magento_Checkout/js/view/payment/default',
'Adyen_Payment/js/action/set-payment-method',
'Magento_Checkout/js/model/payment/additional-validators'
'Magento_Checkout/js/model/payment/additional-validators',
'Magento_Checkout/js/action/place-order',
'Magento_Checkout/js/model/quote',
'Magento_CheckoutAgreements/js/model/agreements-assigner',
'Magento_Customer/js/model/customer',
'Magento_Checkout/js/model/url-builder',
'mage/storage',
'Magento_Checkout/js/model/full-screen-loader',
'Magento_Checkout/js/model/error-processor',
'Magento_Ui/js/model/messages',
'Magento_Checkout/js/action/redirect-on-success'
],
function (ko, Component, setPaymentMethodAction, additionalValidators) {
function (ko, $, Component, setPaymentMethodAction, additionalValidators, placeOrderAction, quote, agreementsAssigner, customer, urlBuilder, storage, fullScreenLoader, errorProcessor, Messages, redirectOnSuccessAction) {
'use strict';
return Component.extend({
......@@ -38,7 +49,50 @@ define(
template: 'Adyen_Payment/payment/pos-cloud-form'
},
showLogo: function() {
initiate: function () {
var self = this,
serviceUrl,
paymentData = quote.paymentMethod();
// use core code to assign the agreement
agreementsAssigner(paymentData);
serviceUrl = urlBuilder.createUrl('/adyen/initiate', {});
fullScreenLoader.startLoader();
return storage.post(
serviceUrl
).always(function(){
self.placeOrderPos()});
return false;
},
posComplete: function () {
this.afterPlaceOrder();
if (this.redirectAfterPlaceOrder) {
redirectOnSuccessAction.execute();
}
},
placeOrderPos: function () {
var self = this;
return $.when(
placeOrderAction(self.getData(), new Messages())
).fail(
function (response) {
if (response.responseText.indexOf("In Progress") > -1) {
window.setTimeout(function(){
self.placeOrderPos()},5000);
return;
}
errorProcessor.process(response);
fullScreenLoader.stopLoader();
self.isPlaceOrderActionAllowed(true);
}
).done(
function () {
self.posComplete();
}
)
},
showLogo: function () {
return window.checkoutConfig.payment.adyen.showLogo;
},
validate: function () {
......
......@@ -60,7 +60,7 @@
<button class="action primary checkout"
type="submit"
data-bind="
click: placeOrder,
click: initiate,
attr: {title: $t('Place Order')},
enable: (getCode() == isChecked()),
css: {disabled: !isPlaceOrderActionAllowed()}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment