diff --git a/extension/docs/IntegrationGuide.md b/extension/docs/IntegrationGuide.md index eb23f5e69..dba48af9c 100644 --- a/extension/docs/IntegrationGuide.md +++ b/extension/docs/IntegrationGuide.md @@ -102,7 +102,8 @@ or by asynchronous process like [commercetools-payment-to-order-processor job](h ### Validate payment There must be at least one CTP payment object of type Adyen (`Payment.paymentMethodInfo.paymentInterface = ctp-adyen-integration`) -AND this CTP payment must have `Payment.interfaceId`. +AND this CTP payment must have `Payment.custom.fields.merchantReference`. +The combination of `Payment.custom.fields.merchantReference` and `Payment.paymentMethodInfo.paymentInterface` must be unique across the project. This field must not be changed once set. ### Validate payment transaction Cart's payment counts as successful if there is at least one payment object diff --git a/extension/resources/payment-custom-types.json b/extension/resources/payment-custom-types.json index 608679ba5..43a4b79f6 100644 --- a/extension/resources/payment-custom-types.json +++ b/extension/resources/payment-custom-types.json @@ -7,6 +7,17 @@ "payment" ], "fieldDefinitions": [ + { + "type": { + "name": "String" + }, + "name": "merchantReference", + "label": { + "en": "merchantReference" + }, + "required": true, + "inputHint": "SingleLine" + }, { "type": { "name": "String" diff --git a/extension/src/api/payment/payment.controller.js b/extension/src/api/payment/payment.controller.js index ce18ac3df..b6b43c3b1 100644 --- a/extension/src/api/payment/payment.controller.js +++ b/extension/src/api/payment/payment.controller.js @@ -27,13 +27,13 @@ async function processRequest (request, response) { if (adyenValidator.hasErrors()) // if it's not adyen payment, ignore the payment return httpUtils.sendResponse({ response }) - const interfaceIdValidator = validatorBuilder - .validateInterfaceIdField() - if (interfaceIdValidator.hasErrors()) + const merchantReferenceValidator = validatorBuilder + .validateMerchantReferenceField() + if (merchantReferenceValidator.hasErrors()) return httpUtils.sendResponse({ response, statusCode: 400, - data: interfaceIdValidator.buildCtpErrorResponse() + data: merchantReferenceValidator.buildCtpErrorResponse() }) const paymentMethodValidator = validatorBuilder.validatePaymentMethod() if (paymentMethodValidator.hasErrors()) diff --git a/extension/src/paymentHandler/cancel-or-refund.handler.js b/extension/src/paymentHandler/cancel-or-refund.handler.js index 51be608f8..a85706002 100644 --- a/extension/src/paymentHandler/cancel-or-refund.handler.js +++ b/extension/src/paymentHandler/cancel-or-refund.handler.js @@ -34,7 +34,7 @@ async function _cancelOrRefundPayment (paymentObject) { const body = { merchantAccount: config.adyen.merchantAccount, originalReference: transaction.interactionId, - reference: paymentObject.interfaceId + reference: paymentObject.custom.fields.merchantReference } const request = { diff --git a/extension/src/paymentHandler/creditCard/credit-card-make-payment.handler.js b/extension/src/paymentHandler/creditCard/credit-card-make-payment.handler.js index d4af0b6c4..8c5149951 100644 --- a/extension/src/paymentHandler/creditCard/credit-card-make-payment.handler.js +++ b/extension/src/paymentHandler/creditCard/credit-card-make-payment.handler.js @@ -85,7 +85,7 @@ async function _makePayment (paymentObject) { currency: transaction.amount.currencyCode, value: transaction.amount.centAmount }, - reference: paymentObject.interfaceId, + reference: paymentObject.custom.fields.merchantReference, paymentMethod: { type: 'scheme', encryptedCardNumber: paymentObject.custom.fields.encryptedCardNumber, diff --git a/extension/src/paymentHandler/kcp/kcp-make-payment.handler.js b/extension/src/paymentHandler/kcp/kcp-make-payment.handler.js index 9e3823775..c74d9274d 100644 --- a/extension/src/paymentHandler/kcp/kcp-make-payment.handler.js +++ b/extension/src/paymentHandler/kcp/kcp-make-payment.handler.js @@ -56,7 +56,7 @@ async function _callAdyen (paymentObject) { currency: transaction.amount.currencyCode, value: transaction.amount.centAmount }, - reference: paymentObject.interfaceId, + reference: paymentObject.custom.fields.merchantReference, paymentMethod: { type: paymentMethodType }, diff --git a/extension/src/paymentHandler/paypal/paypal-make-payment.handler.js b/extension/src/paymentHandler/paypal/paypal-make-payment.handler.js index 3e9c41779..f4190a19d 100644 --- a/extension/src/paymentHandler/paypal/paypal-make-payment.handler.js +++ b/extension/src/paymentHandler/paypal/paypal-make-payment.handler.js @@ -56,7 +56,7 @@ async function _callAdyen (paymentObject) { currency: transaction.amount.currencyCode, value: transaction.amount.centAmount }, - reference: paymentObject.interfaceId, + reference: paymentObject.custom.fields.merchantReference, paymentMethod: { type: 'paypal' }, diff --git a/extension/src/validator/error-messages.js b/extension/src/validator/error-messages.js index 91cb4b9e8..65fb2c5ad 100644 --- a/extension/src/validator/error-messages.js +++ b/extension/src/validator/error-messages.js @@ -9,7 +9,7 @@ module.exports = { MISSING_EXPIRY_YEAR: 'Set custom.fields.encryptedExpiryYear', MISSING_SECURITY_CODE: 'Set custom.fields.encryptedSecurityCode', MISSING_RETURN_URL: 'Set custom.fields.returnUrl', - MISSING_INTERFACE_ID: 'Set interfaceId', + MISSING_MERCHANT_REFERENCE: 'Set custom merchantReference', MISSING_PAYLOAD: 'Set custom.fields.payload', MISSING_PAYMENT_DATA: 'Set custom.fields.paymentData', MISSING_PARES: 'Set custom.fields.PaRes', diff --git a/extension/src/validator/validator-builder.js b/extension/src/validator/validator-builder.js index 6d5c4d383..a761614ff 100644 --- a/extension/src/validator/validator-builder.js +++ b/extension/src/validator/validator-builder.js @@ -90,10 +90,10 @@ function withPayment (paymentObject) { errors.hasReturnUrl = errorMessages.MISSING_RETURN_URL return this }, - validateInterfaceIdField () { - const hasInterfaceId = !_.isEmpty(paymentObject.interfaceId) - if (!hasInterfaceId) - errors.hasInterfaceId = errorMessages.MISSING_INTERFACE_ID + validateMerchantReferenceField () { + const hasMerchantReference = !_.isEmpty(paymentObject.custom.fields.merchantReference) + if (!hasMerchantReference) + errors.hasMerchantReference = errorMessages.MISSING_MERCHANT_REFERENCE return this }, validatePayloadField () { diff --git a/extension/test/fixtures/ctp-payment.json b/extension/test/fixtures/ctp-payment.json index 8ae8790bd..b10c446fb 100644 --- a/extension/test/fixtures/ctp-payment.json +++ b/extension/test/fixtures/ctp-payment.json @@ -10,7 +10,6 @@ "createdBy": { "clientId": "GfjD1YgZgpe0K6wEdUEpwTmx" }, - "interfaceId": "paymentReferenceId", "amountPlanned": { "type": "centPrecision", "currencyCode": "EUR", @@ -27,6 +26,7 @@ "id": "3bb94fa2-6254-4a31-83e8-a2a882c15173" }, "fields": { + "merchantReference": "paymentReferenceId", "redirectUrl": "redirectUrl", "redirectMethod": "redirectMethod", "returnUrl": "returnUrl", diff --git a/extension/test/fixtures/payment-credit-card-3d.json b/extension/test/fixtures/payment-credit-card-3d.json index 9e943a001..8434095e5 100644 --- a/extension/test/fixtures/payment-credit-card-3d.json +++ b/extension/test/fixtures/payment-credit-card-3d.json @@ -1,5 +1,4 @@ { - "interfaceId":"paymentReferenceId", "amountPlanned": { "type": "centPrecision", "currencyCode": "EUR", @@ -16,6 +15,7 @@ "key": "ctp-adyen-integration-payment-custom-type" }, "fields": { + "merchantReference": "paymentReferenceId", "countryCode": "DE", "encryptedCardNumber":"${encryptedCardNumber}", "encryptedExpiryMonth":"${encryptedExpiryMonth}", diff --git a/extension/test/fixtures/payment-credit-card.json b/extension/test/fixtures/payment-credit-card.json index 0d33e15a3..2b81caf9d 100644 --- a/extension/test/fixtures/payment-credit-card.json +++ b/extension/test/fixtures/payment-credit-card.json @@ -1,5 +1,4 @@ { - "interfaceId":"paymentReferenceId", "amountPlanned": { "type": "centPrecision", "currencyCode": "EUR", @@ -16,6 +15,7 @@ "key": "ctp-adyen-integration-payment-custom-type" }, "fields": { + "merchantReference": "paymentReferenceId", "countryCode": "DE", "encryptedCardNumber":"${encryptedCardNumber}", "encryptedExpiryMonth":"${encryptedExpiryMonth}", diff --git a/extension/test/fixtures/payment-kcp.json b/extension/test/fixtures/payment-kcp.json index 692bcf552..1a4838726 100644 --- a/extension/test/fixtures/payment-kcp.json +++ b/extension/test/fixtures/payment-kcp.json @@ -1,5 +1,4 @@ { - "interfaceId":"paymentReferenceId", "amountPlanned": { "type": "centPrecision", "currencyCode": "EUR", @@ -16,6 +15,7 @@ "key": "ctp-adyen-integration-payment-custom-type" }, "fields": { + "merchantReference": "paymentReferenceId", "returnUrl":"https://your-company.com/checkout/" } }, diff --git a/extension/test/fixtures/payment-no-method.json b/extension/test/fixtures/payment-no-method.json index a7406fbf0..ba607c66a 100644 --- a/extension/test/fixtures/payment-no-method.json +++ b/extension/test/fixtures/payment-no-method.json @@ -1,5 +1,4 @@ { - "interfaceId": "pspReference", "amountPlanned": { "type": "centPrecision", "currencyCode": "EUR", @@ -15,6 +14,7 @@ "key": "ctp-adyen-integration-payment-custom-type" }, "fields": { + "merchantReference": "paymentReferenceId", "countryCode": "DE" } }, diff --git a/extension/test/fixtures/payment-paypal.json b/extension/test/fixtures/payment-paypal.json index 5a4c68737..64a7926e9 100644 --- a/extension/test/fixtures/payment-paypal.json +++ b/extension/test/fixtures/payment-paypal.json @@ -1,5 +1,4 @@ { - "interfaceId":"paymentReferenceId", "amountPlanned": { "type": "centPrecision", "currencyCode": "EUR", @@ -16,6 +15,7 @@ "key": "ctp-adyen-integration-payment-custom-type" }, "fields": { + "merchantReference":"paymentReferenceId", "returnUrl":"https://your-company.com/checkout/" } }, diff --git a/extension/test/integration/credit-card-make-payment.handler.spec.js b/extension/test/integration/credit-card-make-payment.handler.spec.js index e61c015b5..4969709c0 100644 --- a/extension/test/integration/credit-card-make-payment.handler.spec.js +++ b/extension/test/integration/credit-card-make-payment.handler.spec.js @@ -38,7 +38,7 @@ describe('credit card payment', () => { const adyenRequestBody = JSON.parse(adyenRequest.body) expect(adyenRequestBody.merchantAccount).to.be.equal(process.env.ADYEN_MERCHANT_ACCOUNT) - expect(adyenRequestBody.reference).to.be.equal(paymentTemplate.interfaceId) + expect(adyenRequestBody.reference).to.be.equal(paymentTemplate.custom.fields.merchantReference) expect(adyenRequestBody.returnUrl).to.be.equal(paymentTemplate.custom.fields.returnUrl) expect(adyenRequestBody.amount.currency).to.be.equal(paymentTemplate.transactions[0].amount.currencyCode) expect(adyenRequestBody.amount.value).to.be.equal(paymentTemplate.transactions[0].amount.centAmount) @@ -75,7 +75,7 @@ describe('credit card payment', () => { const adyenRequestBody = JSON.parse(adyenRequest.body) expect(adyenRequestBody.merchantAccount).to.be.equal(process.env.ADYEN_MERCHANT_ACCOUNT) - expect(adyenRequestBody.reference).to.be.equal(paymentTemplate.interfaceId) + expect(adyenRequestBody.reference).to.be.equal(paymentTemplate.custom.fields.merchantReference) expect(adyenRequestBody.returnUrl).to.be.equal(`${process.env.API_EXTENSION_BASE_URL}/test-return-url`) expect(adyenRequestBody.amount.currency).to.be.equal(paymentTemplate.transactions[0].amount.currencyCode) expect(adyenRequestBody.amount.value).to.be.equal(paymentTemplate.transactions[0].amount.centAmount) diff --git a/extension/test/unit/payment.controller.spec.js b/extension/test/unit/payment.controller.spec.js index 151bf351b..be4248ea6 100644 --- a/extension/test/unit/payment.controller.spec.js +++ b/extension/test/unit/payment.controller.spec.js @@ -26,9 +26,9 @@ describe('Payment controller', () => { await paymentController.processRequest(mockRequest) }) - it('on missing interface id should throw error', async () => { + it('on missing merchant reference should throw error', async () => { const ctpPaymentClone = _.cloneDeep(ctpPayment) - ctpPaymentClone.interfaceId = '' + ctpPaymentClone.custom.fields.merchantReference = '' utilsStub.collectRequestData = () => JSON.stringify({ resource: { obj: ctpPaymentClone } }) utilsStub.sendResponse = ({ statusCode, data }) => { @@ -36,7 +36,7 @@ describe('Payment controller', () => { expect(data).to.deep.equal({ errors: [{ code: 'InvalidField', - message: errorMessages.MISSING_INTERFACE_ID + message: errorMessages.MISSING_MERCHANT_REFERENCE }] }) } @@ -46,7 +46,7 @@ describe('Payment controller', () => { it('on wrong payment method should throw error', async () => { const ctpPaymentClone = _.cloneDeep(ctpPayment) - ctpPaymentClone.custom.fields = {} + ctpPaymentClone.custom.fields = {"merchantReference": "paymentReferenceId"} ctpPaymentClone.paymentMethodInfo.method = 'wrong method' utilsStub.collectRequestData = () => JSON.stringify({ resource: { obj: ctpPaymentClone } }) @@ -65,7 +65,7 @@ describe('Payment controller', () => { it('on missing params for make paypal payment should throw error', async () => { const ctpPaymentClone = _.cloneDeep(ctpPayment) - ctpPaymentClone.custom.fields = {} + ctpPaymentClone.custom.fields = {"merchantReference": "paymentReferenceId"} ctpPaymentClone.paymentMethodInfo.method = 'paypal' ctpPaymentClone.transactions[0].state = 'Initial' @@ -85,7 +85,7 @@ describe('Payment controller', () => { it('on missing params for complete paypal payment should throw error', async () => { const ctpPaymentClone = _.cloneDeep(ctpPayment) - ctpPaymentClone.custom.fields = {} + ctpPaymentClone.custom.fields = {"merchantReference": "paymentReferenceId"} ctpPaymentClone.paymentMethodInfo.method = 'paypal' ctpPaymentClone.transactions[0].state = 'Pending' @@ -100,7 +100,7 @@ describe('Payment controller', () => { it('on missing params for make credit card payment should throw error', async () => { const ctpPaymentClone = _.cloneDeep(ctpPayment) - ctpPaymentClone.custom.fields = {} + ctpPaymentClone.custom.fields = {"merchantReference": "paymentReferenceId"} ctpPaymentClone.paymentMethodInfo.method = 'creditCard' ctpPaymentClone.transactions[0].state = 'Initial' @@ -137,7 +137,7 @@ describe('Payment controller', () => { it('on missing params for make 3ds payment should throw error', async () => { const ctpPaymentClone = _.cloneDeep(ctpPayment) - ctpPaymentClone.custom.fields = {} + ctpPaymentClone.custom.fields = {"merchantReference": "paymentReferenceId"} ctpPaymentClone.paymentMethodInfo.method = 'creditCard_3d' ctpPaymentClone.transactions[0].state = 'Initial' @@ -174,7 +174,7 @@ describe('Payment controller', () => { it('on missing params for complete credit card payment should throw error', async () => { const ctpPaymentClone = _.cloneDeep(ctpPayment) - ctpPaymentClone.custom.fields = {} + ctpPaymentClone.custom.fields = {"merchantReference": "paymentReferenceId"} ctpPaymentClone.paymentMethodInfo.method = 'creditCard_3d' ctpPaymentClone.transactions[0].state = 'Pending' diff --git a/notification/src/handler/notification/notification.handler.js b/notification/src/handler/notification/notification.handler.js index 657f2f6ae..2d0295426 100644 --- a/notification/src/handler/notification/notification.handler.js +++ b/notification/src/handler/notification/notification.handler.js @@ -37,7 +37,7 @@ async function updatePaymentWithRepeater (payment, notification, ctpClient) { try { /* eslint-disable-next-line no-await-in-loop */ await ctpClient.update(ctpClient.builder.payments, currentPayment.id, currentVersion, updateActions) - logger.debug(`Payment with interfaceId ${currentPayment.interfaceId}` + logger.debug(`Payment with merchantReference ${currentPayment.custom.fields.merchantReference}` + 'was successfully updated') break } catch (err) { @@ -146,7 +146,7 @@ function getAddTransactionUpdateAction (type, state, amount, currency) { async function getPaymentByMerchantReference (merchantReference, ctpClient) { try { - const result = await ctpClient.fetch(ctpClient.builder.payments.where(`interfaceId="${merchantReference}"`)) + const result = await ctpClient.fetch(ctpClient.builder.payments.where(`custom(fields(merchantReference="${merchantReference}"))`)) return _.get(result, 'body.results[0]', null) } catch (err) { throw Error(`Failed to fetch a payment with merchantReference: ${merchantReference}. ` diff --git a/notification/test/integration/init/ensure-payment-custom-type.js b/notification/test/integration/init/ensure-payment-custom-type.js new file mode 100644 index 000000000..6a782c31f --- /dev/null +++ b/notification/test/integration/init/ensure-payment-custom-type.js @@ -0,0 +1,21 @@ +const paymentCustomType = require('../../resources/payment-custom-types.json') + +const utils = require('../../../src/utils/logger') + +const logger = utils.getLogger() + +async function ensurePaymentCustomType (ctpClient) { + try { + const { body } = await ctpClient.fetch(ctpClient.builder.types.where(`key="${paymentCustomType.key}"`)) + if (body.results.length === 0) { + await ctpClient.create(ctpClient.builder.types, paymentCustomType) + logger.info('Successfully created payment custom type') + } + } catch (e) { + logger.error(e, 'Error when creating payment custom type, skipping...') + } +} + +module.exports = { + ensurePaymentCustomType +} diff --git a/notification/test/integration/init/init-resources.js b/notification/test/integration/init/init-resources.js index abc32989d..81fd75838 100644 --- a/notification/test/integration/init/init-resources.js +++ b/notification/test/integration/init/init-resources.js @@ -1,10 +1,12 @@ const { ensureInterfaceInteractionCustomType } = require('../../../src/config/init/ensure-interface-interaction-custom-type') +const { ensurePaymentCustomType } = require('./ensure-payment-custom-type') const { ensurePayment } = require('./ensure-payment') async function ensureResources (ctpClient) { await ensureInterfaceInteractionCustomType(ctpClient) + await ensurePaymentCustomType(ctpClient) await ensurePayment(ctpClient) } diff --git a/notification/test/resources/payment-credit-card.json b/notification/test/resources/payment-credit-card.json index b3a698863..62e2569c9 100644 --- a/notification/test/resources/payment-credit-card.json +++ b/notification/test/resources/payment-credit-card.json @@ -1,7 +1,6 @@ { "id": "123212321", "version": "2", - "interfaceId":"paymentReferenceId", "amountPlanned": { "type": "centPrecision", "currencyCode": "EUR", @@ -19,6 +18,7 @@ }, "fields": { "countryCode": "DE", + "merchantReference":"paymentReferenceId", "encryptedCardNumber":"${encryptedCardNumber}", "encryptedExpiryMonth":"${encryptedExpiryMonth}", "encryptedExpiryYear":"${encryptedExpiryYear}", diff --git a/notification/test/resources/payment-custom-types.json b/notification/test/resources/payment-custom-types.json index 8e8b7f7e5..f8a3a8f10 100644 --- a/notification/test/resources/payment-custom-types.json +++ b/notification/test/resources/payment-custom-types.json @@ -7,6 +7,17 @@ "payment" ], "fieldDefinitions": [ + { + "type": { + "name": "String" + }, + "name": "merchantReference", + "label": { + "en": "merchantReference" + }, + "required": true, + "inputHint": "SingleLine" + }, { "type": { "name": "String" diff --git a/notification/test/resources/payment-draft.json b/notification/test/resources/payment-draft.json index ee34de69b..76258a066 100644 --- a/notification/test/resources/payment-draft.json +++ b/notification/test/resources/payment-draft.json @@ -1,12 +1,19 @@ { "key": "test-payment", - "interfaceId": "8313842560770001", "amountPlanned": { "type": "centPrecision", "currencyCode": "EUR", "centAmount": 795, "fractionDigits": 2 }, + "custom": { + "type": { + "key": "ctp-adyen-integration-payment-custom-type" + }, + "fields": { + "merchantReference": "8313842560770001" + } + }, "transactions": [ { "type": "Authorization",