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

Paypal express payment #1077

Merged
12 changes: 12 additions & 0 deletions jest/sfccPathSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ jest.mock('*/cartridge/adyen/scripts/expressPayments/shippingMethods', () => {
return require('../src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/shippingMethods');
}, {virtual: true});

jest.mock('*/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentsCall', () => {
return require('../src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentsCall');
}, {virtual: true});

jest.mock('*/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentDetailsCall', () => {
return require('../src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentDetailsCall');
}, {virtual: true});

jest.mock('*/cartridge/adyen/scripts/expressPayments/paypal/saveShopperData', () => {
return require('../src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/expressPayments/paypal/saveShopperData');
}, {virtual: true});

jest.mock('*/cartridge/adyen/scripts/partialPayments/fetchGiftCards', () => {
return require('../src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/partialPayments/fetchGiftCards');
}, {virtual: true});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,51 @@
const { getPaymentMethods } = require('./commons');
const helpers = require('./adyen_checkout/helpers');

const PAYPAL = 'paypal';

function handlePaypalResponse(response, component) {
if (response?.action) {
component.handleAction(response.action);
} else {
component.handleError();
}
}

function callPaymentFromComponent(data, component) {
return $.ajax({
url: window.makeExpressPaymentsCall,
type: 'post',
data: {
data: JSON.stringify(data),
},
success(response) {
helpers.createShowConfirmationForm(window.showConfirmationAction);
helpers.setOrderFormData(response);
handlePaypalResponse(response, component);
},
});
}

function saveShopperDetails(details) {
return $.ajax({
url: window.saveShopperData,
type: 'post',
data: {
shopperDetails: JSON.stringify(details),
},
});
}

function makeExpressPaymentDetailsCall(data) {
return $.ajax({
type: 'POST',
url: window.makeExpressPaymentDetailsCall,
data: JSON.stringify({ data }),
contentType: 'application/json; charset=utf-8',
async: false,
});
}

async function mountPaypalComponent() {
try {
const data = await getPaymentMethods();
Expand All @@ -26,6 +70,16 @@ async function mountPaypalComponent() {
configuration: paypalConfig,
returnUrl: window.returnUrl,
isExpress: true,
onSubmit: (state, component) => {
callPaymentFromComponent(state.data, component);
},
onShopperDetails: async (shopperDetails, rawData, actions) => {
saveShopperDetails(shopperDetails);
actions.resolve();
},
onAdditionalDetails: (state) => {
makeExpressPaymentDetailsCall(state.data);
},
};

const paypalExpressButton = checkout.create(PAYPAL, paypalButtonConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
pdict.AdyenHelper.getBasketAmount().currencyCode
)}";
window.basketAmount = "${pdict.AdyenHelper.getBasketAmount()}".replace(/"/g, '\"');
window.makeExpressPaymentsCall = "${URLUtils.https('Adyen-MakeExpressPaymentsCall')}";
window.makeExpressPaymentDetailsCall = "${URLUtils.https('Adyen-MakeExpressPaymentDetailsCall')}";
window.saveShopperData = "${URLUtils.https('Adyen-SaveShopperData')}";
</script>
<div class="mb-sm-3">
<a
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
const URLUtils = require('dw/web/URLUtils');
const OrderMgr = require('dw/order/OrderMgr');
const Transaction = require('dw/system/Transaction');
const BasketMgr = require('dw/order/BasketMgr');
const AdyenHelper = require('*/cartridge/adyen/utils/adyenHelper');
const adyenCheckout = require('*/cartridge/adyen/scripts/payments/adyenCheckout');
const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');
const COHelpers = require('*/cartridge/scripts/checkout/checkoutHelpers');

function setBillingAndShippingAddress(currentBasket) {
let { billingAddress } = currentBasket;
let { shippingAddress } = currentBasket.getDefaultShipment();
Transaction.wrap(() => {
if (!shippingAddress) {
shippingAddress = currentBasket
.getDefaultShipment()
.createShippingAddress();
}
if (!billingAddress) {
billingAddress = currentBasket.createBillingAddress();
}
});

const shopperDetails = JSON.parse(session.privacy.shopperDetails);

Transaction.wrap(() => {
billingAddress.setFirstName(shopperDetails.shopperName.firstName);
billingAddress.setLastName(shopperDetails.shopperName.lastName);
billingAddress.setAddress1(shopperDetails.billingAddress.street);
billingAddress.setCity(shopperDetails.billingAddress.city);
billingAddress.setPhone(shopperDetails.telephoneNumber);
billingAddress.setPostalCode(shopperDetails.billingAddress.postalCode);
billingAddress.setStateCode(shopperDetails.billingAddress.stateOrProvince);
billingAddress.setCountryCode(shopperDetails.billingAddress.country);

shippingAddress.setFirstName(shopperDetails.shopperName.firstName);
shippingAddress.setLastName(shopperDetails.shopperName.lastName);
shippingAddress.setAddress1(shopperDetails.shippingAddress.street);
shippingAddress.setCity(shopperDetails.shippingAddress.city);
shippingAddress.setPhone(shopperDetails.telephoneNumber);
shippingAddress.setPostalCode(shopperDetails.shippingAddress.postalCode);
shippingAddress.setStateCode(
shopperDetails.shippingAddress.stateOrProvince,
);
shippingAddress.setCountryCode(shopperDetails.shippingAddress.country);

currentBasket.setCustomerEmail(shopperDetails.shopperEmail);

// Setting the session variable to null after assigning the shopper data to basket level
session.privacy.shopperDetails = null;
});
}

/*
* Makes a payment details call to Adyen to confirm the current status of a payment.
It is currently used only for PayPal Express Flow
*/
function makeExpressPaymentDetailsCall(req, res, next) {
try {
const request = JSON.parse(req.body);
const currentBasket = BasketMgr.getCurrentBasket();

const paymentsDetailsResponse = adyenCheckout.doPaymentsDetailsCall(
request.data,
);

setBillingAndShippingAddress(currentBasket);
const order = OrderMgr.createOrder(currentBasket);
const fraudDetectionStatus = { status: 'success' };
const placeOrderResult = COHelpers.placeOrder(order, fraudDetectionStatus);
if (placeOrderResult.error) {
AdyenLogs.error_log('Failed to place the PayPal express order');
}

const response = AdyenHelper.createAdyenCheckoutResponse(
paymentsDetailsResponse,
);

response.orderNo = order.orderNo;
response.orderToken = order.orderToken;
res.json(response);
return next();
} catch (e) {
AdyenLogs.error_log(
`Could not verify express /payment/details: ${e.toString()} in ${
e.fileName
}:${e.lineNumber}`,
);
res.redirect(URLUtils.url('Error-ErrorCode', 'err', 'general'));
return next();
}
}

module.exports = makeExpressPaymentDetailsCall;
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const BasketMgr = require('dw/order/BasketMgr');
const PaymentMgr = require('dw/order/PaymentMgr');
const Transaction = require('dw/system/Transaction');
const adyenCheckout = require('*/cartridge/adyen/scripts/payments/adyenCheckout');
const constants = require('*/cartridge/adyen/config/constants');
const collections = require('*/cartridge/scripts/util/collections');
const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');

function makeExpressPaymentsCall(req, res, next) {
try {
const currentBasket = BasketMgr.getCurrentBasket();
let paymentInstrument;
Transaction.wrap(() => {
collections.forEach(currentBasket.getPaymentInstruments(), (item) => {
currentBasket.removePaymentInstrument(item);
});

paymentInstrument = currentBasket.createPaymentInstrument(
constants.METHOD_ADYEN_COMPONENT,
currentBasket.totalGrossPrice,
);
const { paymentProcessor } = PaymentMgr.getPaymentMethod(
paymentInstrument.paymentMethod,
);
paymentInstrument.paymentTransaction.paymentProcessor = paymentProcessor;
paymentInstrument.custom.adyenPaymentData = req.form.data;
});

let result;
Transaction.wrap(() => {
result = adyenCheckout.createPaymentRequest({
Order: '',
PaymentInstrument: paymentInstrument,
});
});
res.json(result);
return next();
} catch (ex) {
AdyenLogs.fatal_log(`${ex.toString()} in ${ex.fileName}:${ex.lineNumber}`);
return next();
}
}

module.exports = makeExpressPaymentsCall;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');

function saveShopperData(req, res, next) {
try {
const shopperDetails = JSON.parse(req.form.shopperDetails);
session.privacy.shopperDetails = JSON.stringify(shopperDetails);
res.json({ success: true });
return next();
} catch (ex) {
AdyenLogs.error_log(
`Failed to save the shopper details ${ex.toString()} in ${ex.fileName}:${
ex.lineNumber
}`,
);
res.json({ success: false });
return next();
}
}

module.exports = saveShopperData;
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ const fetchGiftCards = require('*/cartridge/adyen/scripts/partialPayments/fetchG
const showConfirmationPaymentFromComponent = require('*/cartridge/adyen/scripts/showConfirmation/showConfirmationPaymentFromComponent');
const showConfirmation = require('*/cartridge/adyen/scripts/showConfirmation/showConfirmation');
const notify = require('*/cartridge/adyen/webhooks/notify');
const makeExpressPaymentsCall = require('*/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentsCall');
const makeExpressPaymentDetailsCall = require('*/cartridge/adyen/scripts/expressPayments/paypal/makeExpressPaymentDetailsCall');
const saveShopperData = require('*/cartridge/adyen/scripts/expressPayments/paypal/saveShopperData');

module.exports = {
getCheckoutPaymentMethods,
Expand All @@ -30,4 +33,7 @@ module.exports = {
showConfirmation,
showConfirmationPaymentFromComponent,
notify,
makeExpressPaymentsCall,
makeExpressPaymentDetailsCall,
saveShopperData,
};
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ function createPaymentRequest(args) {

paymentRequest = AdyenHelper.add3DS2Data(paymentRequest);
const paymentMethodType = paymentRequest.paymentMethod.type;
const isPayPalExpress =
paymentRequest.paymentMethod.type === 'paypal' &&
paymentRequest.paymentMethod.subtype === 'express';

// Add Risk data
if (AdyenConfigs.getAdyenBasketFieldsEnabled()) {
Expand Down Expand Up @@ -211,18 +214,22 @@ function createPaymentRequest(args) {
};
}

// Create billing and delivery address objects for new orders,
// no address fields for credit cards through My Account
paymentRequest = AdyenHelper.createAddressObjects(
order,
paymentMethodType,
paymentRequest,
);
// Create shopper data fields
paymentRequest = AdyenHelper.createShopperObject({
order,
paymentRequest,
});
// Address object and shopper data fields are filled later for PayPal Express
if (!isPayPalExpress) {
// Create billing and delivery address objects for new orders,
// no address fields for credit cards through My Account
paymentRequest = AdyenHelper.createAddressObjects(
order,
paymentMethodType,
paymentRequest,
);

// Create shopper data fields
paymentRequest = AdyenHelper.createShopperObject({
order,
paymentRequest,
});
}

if (session.privacy.adyenFingerprint) {
paymentRequest.deviceFingerprint = session.privacy.adyenFingerprint;
Expand Down
22 changes: 22 additions & 0 deletions src/cartridges/int_adyen_SFRA/cartridge/controllers/Adyen.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,28 @@ server.post(
*/
server.post('partialPayment', server.middleware.https, adyen.partialPayment);

/**
* Called by Adyen to make /payments call for PayPal Express flow
*/
server.post(
'MakeExpressPaymentsCall',
server.middleware.https,
adyen.makeExpressPaymentsCall,
);

/**
* Called by Adyen to make /paymentsDetails for PayPal Express flow
*/
server.post(
'MakeExpressPaymentDetailsCall',
server.middleware.https,
adyen.makeExpressPaymentDetailsCall,
);

/**
* Called by Adyen to save the shopper data coming from PayPal Express
*/
server.post('SaveShopperData', server.middleware.https, adyen.saveShopperData);
/**
* Called by Adyen to fetch applied giftcards
*/
Expand Down
Loading