From 7d66a2accc372a5c0381a122a2b61ba9bdf7234f Mon Sep 17 00:00:00 2001 From: King-Hin Leung <35692276+leungkinghin@users.noreply.github.com> Date: Fri, 13 Jan 2023 09:19:06 +0100 Subject: [PATCH] Fix notification payment lookup (#1049) * Modify notification handler to get payment by pspReference or merchantReference * Set pspReference as payment key if it is returned after making payment * Refactor the setKey update action * Add setKey action in submit payment details handler * Fix style * Fix style * Fix unit test for make payment handler * Fix unit test for make payment handler with splits * Fix unit test for submit payment details handler * Fix integration test * Fix paypal e2e test * Fix paypal e2e test * Fix integration test for notification module * Fix unit test for notification module * Fix multitenancy test * Update extension/src/paymentHandler/make-payment.handler.js Co-authored-by: Lam Tran * Revert "Update extension/src/paymentHandler/make-payment.handler.js" This reverts commit f89ebd81eb7e82b630c68bc9d4a29dbb29e7242f. * Remove unnecessary waiting time in Paypal pop-up * Improve the boolean comparison * Fix style * Remove unnecessary merchantReference checking * Use merchant reference and original reference in notification module * Fix paypal e2e test * Remove unnecessary checking for update payment key in notification module * Revert "Use merchant reference and original reference in notification module" This reverts commit 66e60fec7021f7804302dce8c541972557fac4e6. * Remove deprecated test case * Fix loggings * Fix integration test for cancel authorized payment * Fix documentation * Split the ADR * Modify ADR * Update ADR * Modify ADR * Modify ADR * Modify ADR * Modify ADR * Modify ADR * Modify ADR * Modify ADR * Revert "Modify ADR" This reverts commit dcbc5794cd4c470d1cf5a8c23623214671bf1849. * Modify ADR * Update docs/adr/0011-matching-adyen-notification.md Co-authored-by: Lam Tran * Fix affirm payment e2e test Co-authored-by: King-Hin Leung <> Co-authored-by: Lam Tran --- docs/FAQ.md | 2 +- docs/adr/0002-matching-adyen-notification.md | 4 +- docs/adr/0011-matching-adyen-notification.md | 33 ++++ .../paymentHandler/make-payment.handler.js | 17 +- extension/src/paymentHandler/payment-utils.js | 18 ++ .../submit-payment-details.handler.js | 7 + extension/test/e2e/pageObjects/AffirmPage.js | 15 +- .../test/e2e/pageObjects/PaypalPopupPage.js | 1 + .../integration/make-payment.handler.spec.js | 12 +- .../make-payment-with-splits.handler.spec.js | 4 +- .../test/unit/make-payment.handler.spec.js | 14 +- .../submit-payment-details.handler.spec.js | 26 ++- notification/docs/IntegrationGuide.md | 2 +- .../notification/notification.handler.js | 51 ++++-- notification/src/utils/ctp.js | 9 + .../test/integration/multitenancy.spec.js | 22 ++- .../integration/notification.handler.spec.js | 168 +++++++++++++----- notification/test/test-utils.js | 15 +- notification/test/unit/ctp-client-mock.js | 4 + .../test/unit/notification.controller.spec.js | 4 +- .../test/unit/notification.handler.spec.js | 98 +++++----- 21 files changed, 374 insertions(+), 152 deletions(-) create mode 100644 docs/adr/0011-matching-adyen-notification.md diff --git a/docs/FAQ.md b/docs/FAQ.md index aacd2b4b2..1f09173d3 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -50,7 +50,7 @@ If you accidentally created a subscription you can edit it and uncheck the **Act ### How does the notification module find a matching payment? -It first find the payment by `key` where `key=${merchantReference}` and then it finds in this payment the corresponding transaction by `interactionId` where `interactionId=${pspReference}`. +It first find the payment by `key` where `key in (${merchantReference}, ${pspReference})`. If original reference exists in notification, the payment can be found by `key` where `key in (${merchantReference}, ${originalReference})`. And then it finds in this payment the corresponding transaction by `interactionId` where `interactionId=${pspReference}`. ### Will we lose a notification if it was not processed for some reason? diff --git a/docs/adr/0002-matching-adyen-notification.md b/docs/adr/0002-matching-adyen-notification.md index 2ea8653c8..201f2c2fa 100644 --- a/docs/adr/0002-matching-adyen-notification.md +++ b/docs/adr/0002-matching-adyen-notification.md @@ -4,7 +4,7 @@ Date: 2020-12-18 ## Status -[Accepted](https://github.com/commercetools/commercetools-adyen-integration/pull/395) +[Deprecated](https://github.com/commercetools/commercetools-adyen-integration/pull/395) ## Context @@ -25,3 +25,5 @@ by `interactionId` where `interactionId=${pspReference}`. - It is easier to fetch a key rather than using a custom field, also a key is an indexed field, so with a key, it's more performant. - The payment key is unique for all payments. - It's not possible to set key with my-payments endpoint. This prevents by default changing/removing the key accidentally. It is more secure than custom fields as the custom field might be changed with my-payment endpoint. Check for more details: https://docs.commercetools.com/api/projects/me-payments + +Please refer to the [0011-matching-adyen-notification](./0011-matching-adyen-notification.md) for the latest change regarding matching Notification with payment. \ No newline at end of file diff --git a/docs/adr/0011-matching-adyen-notification.md b/docs/adr/0011-matching-adyen-notification.md new file mode 100644 index 000000000..2004a1f9e --- /dev/null +++ b/docs/adr/0011-matching-adyen-notification.md @@ -0,0 +1,33 @@ +# 11. Matching Adyen Notification with commercetools payment. + +Date: 2023-01-03 + +## Status + +[Accepted](https://github.com/commercetools/commercetools-adyen-integration/pull/1049) + +## Context + +The Adyen notification needs to be matched by its commercetools payment equivalent. +We are using the payment key for the `merchantReference` and fetching the commercetools payment object with query `key="${merchantReference}"`. +Since [v9.10.0](https://github.com/commercetools/commercetools-adyen-integration/releases/tag/v9.10.0), we have introduced custom reference for refund. +Payment key could be the custom field in payment transaction defined by user, therefore payment is no longer always able to be obtained by merchant reference as key. + +The alternative for that is to use `pspReference` as payment `key` field. + +## Decision + +- We will use `originalReference` (or `pspReference` if `originalReference` does not exist) or `merchantReference` as payment key for matching payment for notification. +- For web component version 4, `pspReference` can be obtained from `makePaymentResponse` in extension module. It is used to update as payment key. Therefore we can match the payment by `pspReference` given in notification. +- For web component version 5, `pspReference` is first provided in notification with AUTHORIZATION event. It is different from web component version 4, in which `pspReference` has already been provided from Adyen API response in extension module, and used to update as payment key. Therefore we still need `merchantReference` for payment lookup in this scenario. +Once payment is found, update the payment key with `pspReference` obtained in notification. +- For events other than AUTHORIZATION, such as REFUND, CAPTURE, CANCEL, we use `originalReference` from notification, which is the original `pspReference` obtained in AUTHORIZATION notification. +- The notification will use the CTP payment key to fetch payment. It first check if `originalReference` exists. If yes, + it finds the payment by `key` where `key in (${merchantReference}, ${originalReference})`), otherwise it finds the payment by `key` where `key in (${merchantReference}, ${pspReference})`. + After that it finds the corresponding transaction in this payment by `interactionId` where `interactionId=${pspReference}`. + +For details, please refer to the [code snippet](https://github.com/commercetools/commercetools-adyen-integration/blob/dcbc5794cd4c470d1cf5a8c23623214671bf1849/notification/src/handler/notification/notification.handler.js#L52) + +## Consequences +- It is possible to lookup payment for different transaction throughout the whole payment process +- The payment key with `pspReference` (or `originalReference`) is also unique for all payments. diff --git a/extension/src/paymentHandler/make-payment.handler.js b/extension/src/paymentHandler/make-payment.handler.js index b797a5cb4..6752028c4 100644 --- a/extension/src/paymentHandler/make-payment.handler.js +++ b/extension/src/paymentHandler/make-payment.handler.js @@ -4,6 +4,7 @@ import { createSetMethodInfoMethodAction, createSetMethodInfoNameAction, createAddTransactionActionByResponse, + getPaymentKeyUpdateAction, } from './payment-utils.js' import c from '../config/constants.js' import { makePayment } from '../service/web-component-service.js' @@ -40,16 +41,12 @@ async function execute(paymentObject) { if (action) actions.push(action) } - const paymentKey = paymentObject.key - // ensure the key is a string, otherwise the error with "code": "InvalidJsonInput" will return by commercetools API. - const reference = requestBodyJson.reference?.toString() - // ensure the key and new reference is different, otherwise the error with - // "code": "InvalidOperation", "message": "'key' has no changes." will return by commercetools API. - if (reference !== paymentKey) - actions.push({ - action: 'setKey', - key: reference, - }) + const updatePaymentAction = getPaymentKeyUpdateAction( + paymentObject.key, + request, + response + ) + if (updatePaymentAction) actions.push(updatePaymentAction) const addTransactionAction = createAddTransactionActionByResponse( paymentObject.amountPlanned.centAmount, diff --git a/extension/src/paymentHandler/payment-utils.js b/extension/src/paymentHandler/payment-utils.js index 532d936da..fb6bd7d5c 100644 --- a/extension/src/paymentHandler/payment-utils.js +++ b/extension/src/paymentHandler/payment-utils.js @@ -197,6 +197,23 @@ function getIdempotencyKey(transaction) { return idempotencyKey } +function getPaymentKeyUpdateAction(paymentKey, request, response) { + const requestBodyJson = JSON.parse(request.body) + const reference = requestBodyJson.reference?.toString() + const pspReference = response.pspReference?.toString() + const newReference = pspReference || reference + let paymentKeyUpdateAction + // ensure the key and new reference is different, otherwise the error with + // "code": "InvalidOperation", "message": "'key' has no changes." will return by commercetools API. + if (newReference !== paymentKey) { + paymentKeyUpdateAction = { + action: 'setKey', + key: newReference, + } + } + return paymentKeyUpdateAction +} + export { getChargeTransactionInitial, getChargeTransactionPending, @@ -216,4 +233,5 @@ export { isValidJSON, isValidMetadata, getIdempotencyKey, + getPaymentKeyUpdateAction, } diff --git a/extension/src/paymentHandler/submit-payment-details.handler.js b/extension/src/paymentHandler/submit-payment-details.handler.js index 332cfa03c..3882f4225 100644 --- a/extension/src/paymentHandler/submit-payment-details.handler.js +++ b/extension/src/paymentHandler/submit-payment-details.handler.js @@ -4,6 +4,7 @@ import { createAddInterfaceInteractionAction, createSetCustomFieldAction, createAddTransactionActionByResponse, + getPaymentKeyUpdateAction, } from './payment-utils.js' import c from '../config/constants.js' @@ -53,6 +54,12 @@ async function execute(paymentObject) { if (addTransactionAction) actions.push(addTransactionAction) } + const updatePaymentAction = getPaymentKeyUpdateAction( + paymentObject.key, + request, + response + ) + if (updatePaymentAction) actions.push(updatePaymentAction) } return { actions, diff --git a/extension/test/e2e/pageObjects/AffirmPage.js b/extension/test/e2e/pageObjects/AffirmPage.js index 6deb79079..428d4bc53 100644 --- a/extension/test/e2e/pageObjects/AffirmPage.js +++ b/extension/test/e2e/pageObjects/AffirmPage.js @@ -6,7 +6,7 @@ export default class AffirmPage { async finishAffirmPayment() { await this.inputPhoneNumberAndClickSubmitButton() await this.inputPIN() - await this.clickTermCardAndProceed() + await this.choosePlan() await this.clickAutoPayToggleAndProceed() } @@ -22,12 +22,19 @@ export default class AffirmPage { await this.page.waitForSelector('[data-test=term-card]') } - async clickTermCardAndProceed() { - await this.page.click('[data-test="term-card"]') - await this.page.waitForSelector('#autopay-toggle') + async choosePlan() { + await this.page.waitForTimeout(1_000) + + const paymentRadioButton = await this.page.$('[data-testid="radio_button"]') + await this.page.evaluate((cb) => cb.click(), paymentRadioButton) + await this.page.waitForSelector('[data-testid="submit-button"]') + + const submitButton = await this.page.$('[data-testid="submit-button"]') + await this.page.evaluate((cb) => cb.click(), submitButton) } async clickAutoPayToggleAndProceed() { + await this.page.waitForSelector('#autopay-toggle') const autoPayToggle = await this.page.$('#autopay-toggle') await this.page.evaluate((cb) => cb.click(), autoPayToggle) await this.page.waitForTimeout(1_000) // Wait for the page refreshes after toggling the autopay diff --git a/extension/test/e2e/pageObjects/PaypalPopupPage.js b/extension/test/e2e/pageObjects/PaypalPopupPage.js index fb3915b2e..ccca42e3b 100644 --- a/extension/test/e2e/pageObjects/PaypalPopupPage.js +++ b/extension/test/e2e/pageObjects/PaypalPopupPage.js @@ -15,6 +15,7 @@ export default class PaypalPopupPage { await this.page.click('#btnLogin') await this.page.waitForSelector('#payment-submit-btn') + await this.page.waitForTimeout(1000) // Need to suspend 1 second to avoid page closed before loading data. await this.page.click('#payment-submit-btn') } } diff --git a/extension/test/integration/make-payment.handler.spec.js b/extension/test/integration/make-payment.handler.spec.js index abccb0643..4e8899cb2 100644 --- a/extension/test/integration/make-payment.handler.spec.js +++ b/extension/test/integration/make-payment.handler.spec.js @@ -85,8 +85,11 @@ describe('::make-payment with multiple adyen accounts use case::', () => { ] ) + const makePaymentResponse = + updatedPayment?.custom?.fields?.makePaymentResponse + const pspReference = JSON.parse(makePaymentResponse).pspReference expect(statusCode).to.equal(200) - expect(updatedPayment.key).to.equal(makePaymentRequestDraft.reference) + expect(updatedPayment.key).to.equal(pspReference) expect(updatedPayment.paymentMethodInfo.method).to.equal('scheme') expect(updatedPayment.paymentMethodInfo.name).to.eql({ en: 'Credit Card', @@ -147,8 +150,9 @@ describe('::make-payment with multiple adyen accounts use case::', () => { ) expect(statusCode).to.equal(201) - - expect(payment.key).to.equal(reference) + const { makePaymentResponse } = payment.custom.fields + const pspReference = JSON.parse(makePaymentResponse).pspReference + expect(payment.key).to.equal(pspReference) expect(payment.paymentMethodInfo.method).to.equal('scheme') expect(payment.paymentMethodInfo.name).to.eql({ en: 'Credit Card' }) @@ -173,8 +177,6 @@ describe('::make-payment with multiple adyen accounts use case::', () => { adyenMerchantAccount ) - const { makePaymentResponse } = payment.custom.fields - expect(makePaymentResponse).to.be.deep.equal( interfaceInteraction.fields.response ) diff --git a/extension/test/unit/make-payment-with-splits.handler.spec.js b/extension/test/unit/make-payment-with-splits.handler.spec.js index b469fe0c6..cb85e65f1 100644 --- a/extension/test/unit/make-payment-with-splits.handler.spec.js +++ b/extension/test/unit/make-payment-with-splits.handler.spec.js @@ -138,7 +138,9 @@ describe('make-payment-with-splits::execute', () => { ) const setKeyAction = response.actions.find((a) => a.action === 'setKey') - expect(setKeyAction.key).to.equal(makePaymentWithSplitsRequest.reference) + expect(setKeyAction.key).to.equal( + JSON.parse(paymentSuccessResponse).pspReference + ) const addTransaction = response.actions.find( (a) => a.action === 'addTransaction' diff --git a/extension/test/unit/make-payment.handler.spec.js b/extension/test/unit/make-payment.handler.spec.js index 274e2a01c..12ca2f6ef 100644 --- a/extension/test/unit/make-payment.handler.spec.js +++ b/extension/test/unit/make-payment.handler.spec.js @@ -124,7 +124,9 @@ describe('make-payment::execute', () => { ) const setKeyAction = response.actions.find((a) => a.action === 'setKey') - expect(setKeyAction.key).to.equal(makePaymentRequest.reference) + expect(setKeyAction.key).to.equal( + JSON.parse(paymentSuccessResponse).pspReference + ) const addTransaction = response.actions.find( (a) => a.action === 'addTransaction' @@ -184,7 +186,7 @@ describe('make-payment::execute', () => { ) const setKeyAction = response.actions.find((a) => a.action === 'setKey') - expect(setKeyAction.key).to.equal(makePaymentRequest.reference) + expect(setKeyAction.key).to.equal(makePaymentRequest.reference) // no pspReference until submitting additional details in redirect flow } ) @@ -285,7 +287,9 @@ describe('make-payment::execute', () => { ) const setKeyAction = response.actions.find((a) => a.action === 'setKey') - expect(setKeyAction.key).to.equal(makePaymentRequest.reference) + expect(setKeyAction.key).to.equal( + JSON.parse(paymentRefusedResponse).pspReference + ) const addTransaction = response.actions.find( (a) => a.action === 'addTransaction' @@ -346,7 +350,9 @@ describe('make-payment::execute', () => { ) const setKeyAction = response.actions.find((a) => a.action === 'setKey') - expect(setKeyAction.key).to.equal(makePaymentRequest.reference) + expect(setKeyAction.key).to.equal( + JSON.parse(paymentErrorResponse).pspReference + ) const addTransaction = response.actions.find( (a) => a.action === 'addTransaction' diff --git a/extension/test/unit/submit-payment-details.handler.spec.js b/extension/test/unit/submit-payment-details.handler.spec.js index 83a18ee51..ba23645e8 100644 --- a/extension/test/unit/submit-payment-details.handler.spec.js +++ b/extension/test/unit/submit-payment-details.handler.spec.js @@ -59,7 +59,7 @@ describe('submit-additional-payment-details::execute', () => { const response = await execute(ctpPaymentClone) - expect(response.actions).to.have.lengthOf(3) + expect(response.actions).to.have.lengthOf(4) const addInterfaceInteraction = response.actions.find( (a) => a.action === 'addInterfaceInteraction' ) @@ -116,6 +116,10 @@ describe('submit-additional-payment-details::execute', () => { expect(addTransaction.transaction.interactionId).to.equal( JSON.parse(submitPaymentDetailsSuccessResponse).pspReference ) + const setKeyAction = response.actions.find((a) => a.action === 'setKey') + expect(setKeyAction.key).to.equal( + JSON.parse(submitPaymentDetailsSuccessResponse).pspReference + ) } ) @@ -275,7 +279,7 @@ describe('submit-additional-payment-details::execute', () => { const response = await execute(ctpPaymentClone) - expect(response.actions).to.have.lengthOf(3) + expect(response.actions).to.have.lengthOf(4) const addInterfaceInteractionAction = response.actions.find( (a) => a.action === 'addInterfaceInteraction' ) @@ -286,7 +290,10 @@ describe('submit-additional-payment-details::execute', () => { (a) => a.action === 'setCustomField' ) expect(setCustomFieldAction.value).to.include('additionalData') - + const setKeyAction = response.actions.find((a) => a.action === 'setKey') + expect(setKeyAction.key).to.equal( + JSON.parse(submitPaymentDetailsSuccessResponse).pspReference + ) sandbox.restore() } ) @@ -321,7 +328,7 @@ describe('submit-additional-payment-details::execute', () => { const response = await execute(ctpPaymentClone) - expect(response.actions).to.have.lengthOf(3) + expect(response.actions).to.have.lengthOf(4) const addInterfaceInteractionAction = response.actions.find( (a) => a.action === 'addInterfaceInteraction' ) @@ -332,7 +339,10 @@ describe('submit-additional-payment-details::execute', () => { (a) => a.action === 'setCustomField' ) expect(setCustomFieldAction.value).to.not.include('additionalData') - + const setKeyAction = response.actions.find((a) => a.action === 'setKey') + expect(setKeyAction.key).to.equal( + JSON.parse(submitPaymentDetailsSuccessResponse).pspReference + ) sandbox.restore() } ) @@ -367,11 +377,15 @@ describe('submit-additional-payment-details::execute', () => { const response = await execute(ctpPaymentClone) - expect(response.actions).to.have.lengthOf(2) + expect(response.actions).to.have.lengthOf(3) const addTransaction = response.actions.find( (a) => a.action === 'addTransaction' ) expect(addTransaction).to.be.undefined + const setKeyAction = response.actions.find((a) => a.action === 'setKey') + expect(setKeyAction.key).to.equal( + JSON.parse(submitPaymentDetailsSuccessResponse).pspReference + ) } ) }) diff --git a/notification/docs/IntegrationGuide.md b/notification/docs/IntegrationGuide.md index 20552004e..431ed9fd6 100644 --- a/notification/docs/IntegrationGuide.md +++ b/notification/docs/IntegrationGuide.md @@ -99,7 +99,7 @@ and [transactionState](https://docs.commercetools.com/http-api-projects-payments > All mappings can be found in the [adyen-events.json](./../resources/adyen-events.json) file. After finding a mapping the notification module will find a matching payment on a commercetools project. -To find the matching payment, `merchantReference` field from the notification is used to find a payment by key +To find the matching payment, `merchantReference`, `pspReference` and `originalReference` fields from the notification are used to find a payment by key and `pspReference` field from the notification is used to find a transaction by its interactionId. If there is no transaction on the payment found, diff --git a/notification/src/handler/notification/notification.handler.js b/notification/src/handler/notification/notification.handler.js index 16b0c297c..0f622e4e4 100644 --- a/notification/src/handler/notification/notification.handler.js +++ b/notification/src/handler/notification/notification.handler.js @@ -34,18 +34,24 @@ async function processNotification( 'NotificationRequestItem.merchantReference', null ) - if (merchantReference === null) { - logger.error( - { notification: utils.getNotificationForTracking(notification) }, - "Can't extract merchantReference from the notification" - ) - return - } + + const pspReference = _.get( + notification, + 'NotificationRequestItem.pspReference', + null + ) + + const originalReference = _.get( + notification, + 'NotificationRequestItem.originalReference', + null + ) const ctpClient = await ctp.get(ctpProjectConfig) const payment = await getPaymentByMerchantReference( merchantReference, + originalReference || pspReference, ctpClient ) if (payment !== null) @@ -160,13 +166,13 @@ async function calculateUpdateActionsForPayment(payment, notification, logger) { ) if (isNotificationInInterfaceInteraction === false) updateActions.push(getAddInterfaceInteractionUpdateAction(notification)) - + const { pspReference } = notificationRequestItem const { transactionType, transactionState } = await getTransactionTypeAndStateOrNull(notificationRequestItem) if (transactionType !== null) { // if there is already a transaction with type `transactionType` then update its `transactionState` if necessary, // otherwise create a transaction with type `transactionType` and state `transactionState` - const { pspReference } = notificationRequestItem + const { eventDate } = notificationRequestItem const oldTransaction = _.find( payment.transactions, @@ -200,7 +206,17 @@ async function calculateUpdateActionsForPayment(payment, notification, logger) { ) ) } + const paymentKey = payment.key + const newPspReference = + notificationRequestItem.originalReference || pspReference + if (newPspReference && newPspReference !== paymentKey) { + updateActions.push({ + action: 'setKey', + key: newPspReference, + }) + } } + const paymentMethodFromPayment = payment.paymentMethodInfo.method const paymentMethodFromNotification = notificationRequestItem.paymentMethod if ( @@ -390,17 +406,20 @@ function getSetMethodInfoNameAction(paymentMethod) { return null } -async function getPaymentByMerchantReference(merchantReference, ctpClient) { +async function getPaymentByMerchantReference( + merchantReference, + pspReference, + ctpClient +) { try { - const result = await ctpClient.fetchByKey( - ctpClient.builder.payments, - merchantReference - ) - return result.body + const keys = [merchantReference, pspReference] + console.log(keys) + const result = await ctpClient.fetchByKeys(ctpClient.builder.payments, keys) + return result.body.results[0] } catch (err) { if (err.statusCode === 404) return null const errMsg = - `Failed to fetch a payment with merchantReference: ${merchantReference}. ` + + `Failed to fetch a payment with merchantReference ${merchantReference} and pspReference ${pspReference}. ` + `Error: ${JSON.stringify(serializeError(err))}` throw new VError(err, errMsg) } diff --git a/notification/src/utils/ctp.js b/notification/src/utils/ctp.js index 398ec7584..bc231115b 100644 --- a/notification/src/utils/ctp.js +++ b/notification/src/utils/ctp.js @@ -113,6 +113,15 @@ async function setUpClient(config) { return ctpClient.execute(this.buildRequestOptions(uri.byKey(key).build())) }, + fetchByKeys(uri, keys) { + const keyList = keys.map((key) => `"${key}"`) + const keyConditions = `key in (${keyList.join(',')})` + + return ctpClient.execute( + this.buildRequestOptions(uri.where(keyConditions).build()) + ) + }, + fetchBatches(uri, callback, opts = { accumulate: false }) { return this.process( this.buildRequestOptions(uri.build()), diff --git a/notification/test/integration/multitenancy.spec.js b/notification/test/integration/multitenancy.spec.js index bf56055b1..f5bdd948e 100644 --- a/notification/test/integration/multitenancy.spec.js +++ b/notification/test/integration/multitenancy.spec.js @@ -29,18 +29,22 @@ describe('::multitenancy::', () => { }) it('should process payment correctly when notifications are from different projects', async () => { - const payment1Key = `notificationPayment1-${new Date().getTime()}` - const payment2Key = `notificationPayment2-${new Date().getTime()}` + const merchantReference1 = `notificationPayment1-${new Date().getTime()}` + const merchantReference2 = `notificationPayment2-${new Date().getTime()}` + const pspReference1 = `pspReference1-${new Date().getTime()}` + const pspReference2 = `pspReference2-${new Date().getTime()}` await Promise.all([ ensurePayment( ctpClient1, - payment1Key, + merchantReference1, + pspReference1, commercetoolsProjectKey1, adyenMerchantAccount1 ), ensurePayment( ctpClient2, - payment2Key, + merchantReference2, + pspReference2, commercetoolsProjectKey2, adyenMerchantAccount2 ), @@ -49,13 +53,15 @@ describe('::multitenancy::', () => { const notificationPayload1 = createNotificationPayload( commercetoolsProjectKey1, adyenMerchantAccount1, - payment1Key + merchantReference1, + pspReference1 ) const notificationPayload2 = createNotificationPayload( commercetoolsProjectKey2, adyenMerchantAccount2, - payment2Key + merchantReference2, + pspReference2 ) const [response1, response2] = await Promise.all([ @@ -78,12 +84,12 @@ describe('::multitenancy::', () => { const { body: paymentAfter1 } = await ctpClient1.fetchByKey( ctpClient1.builder.payments, - payment1Key + pspReference1 // pspReference is the key of authorized payment ) const { body: paymentAfter2 } = await ctpClient2.fetchByKey( ctpClient2.builder.payments, - payment2Key + pspReference2 // pspReference is the key of authorized payment ) expect(paymentAfter1.transactions).to.have.lengthOf(1) diff --git a/notification/test/integration/notification.handler.spec.js b/notification/test/integration/notification.handler.spec.js index 76a3a16a3..176b0119f 100644 --- a/notification/test/integration/notification.handler.spec.js +++ b/notification/test/integration/notification.handler.spec.js @@ -26,10 +26,14 @@ describe('notification module', () => { 'should update the pending authorization transaction state to success state ' + 'when receives a successful AUTHORIZATION notification', async () => { - const paymentKey = `notificationPayment-${new Date().getTime()}` + const merchantReference = `notificationPayment-${new Date().getTime()}` + // pspReference cannot be static otherwise wrong payment created in the past would be obtained + const pspReference = `pspReference-${new Date().getTime()}` + const { body: paymentBefore } = await ensurePayment( ctpClient, - paymentKey, + merchantReference, + pspReference, commercetoolsProjectKey, adyenMerchantAccount ) @@ -41,7 +45,8 @@ describe('notification module', () => { const notificationPayload = createNotificationPayload( commercetoolsProjectKey, adyenMerchantAccount, - paymentKey + merchantReference, + pspReference ) // Simulating a notification from Adyen @@ -58,8 +63,9 @@ describe('notification module', () => { const { body: paymentAfter } = await ctpClient.fetchByKey( ctpClient.builder.payments, - paymentKey + pspReference // pspReference is the key of authorized payment ) + expect(paymentAfter.transactions).to.have.lengthOf(1) expect(paymentAfter.paymentMethodInfo.method).to.equal('visa') expect(paymentAfter.transactions[0].type).to.equal('Authorization') @@ -76,10 +82,14 @@ describe('notification module', () => { ) it('should add a charge transaction when receives a successful manual CAPTURE notification', async () => { - const paymentKey = `notificationPayment-${new Date().getTime()}` + const merchantReference = `notificationPayment-${new Date().getTime()}` + // pspReference cannot be static otherwise payment created in the past would be wrongly obtained + const pspReference = `pspReference-${new Date().getTime()}` + const chargeInteractionId = _generateRandomNumber() const { body: paymentBefore } = await ensurePayment( ctpClient, - paymentKey, + merchantReference, + pspReference, commercetoolsProjectKey, adyenMerchantAccount ) @@ -88,13 +98,17 @@ describe('notification module', () => { expect(paymentBefore.transactions[0].state).to.equal('Pending') expect(paymentBefore.interfaceInteractions).to.have.lengthOf(0) - // update payment transaction + // update payment transaction and set pspReference as key for authorized payment const actions = [ { action: 'changeTransactionState', state: 'Success', transactionId: paymentBefore.transactions[0].id, }, + { + action: 'setKey', + key: pspReference, + }, ] const { body: updatedPayment } = await ctpClient.update( @@ -111,9 +125,11 @@ describe('notification module', () => { const notificationPayload = createNotificationPayload( commercetoolsProjectKey, adyenMerchantAccount, - paymentKey, + merchantReference, + chargeInteractionId, 'CAPTURE', - _generateRandomNumber() + 'true', + pspReference ) // Simulating a notification from Adyen @@ -130,7 +146,7 @@ describe('notification module', () => { const { body: paymentAfter } = await ctpClient.fetchByKey( ctpClient.builder.payments, - paymentKey + pspReference // pspReference is the key of authorized payment ) expect(paymentAfter.transactions).to.have.lengthOf(2) expect(paymentAfter.transactions[0].type).to.equal('Authorization') @@ -152,10 +168,13 @@ describe('notification module', () => { 'should not update transaction when the notification event ' + 'is not mapped to any CTP payment state', async () => { - const paymentKey = `notificationPayment-${new Date().getTime()}` + const merchantReference = `notificationPayment-${new Date().getTime()}` + // pspReference cannot be static otherwise payment created in the past would be wrongly obtained + const pspReference = `pspReference-${new Date().getTime()}` const { body: paymentBefore } = await ensurePayment( ctpClient, - paymentKey, + merchantReference, + pspReference, commercetoolsProjectKey, adyenMerchantAccount ) @@ -167,7 +186,8 @@ describe('notification module', () => { const notificationPayload = createNotificationPayload( commercetoolsProjectKey, adyenMerchantAccount, - paymentKey, + merchantReference, + pspReference, 'UNKNOWN_EVENT_CODE' ) @@ -185,7 +205,7 @@ describe('notification module', () => { const { body: paymentAfter } = await ctpClient.fetchByKey( ctpClient.builder.payments, - paymentKey + merchantReference // merchantReference is the key of unauthorized payment ) expect(paymentAfter.transactions).to.have.lengthOf(1) expect(paymentAfter.transactions[0].type).to.equal('Authorization') @@ -202,10 +222,13 @@ describe('notification module', () => { ) it('should response with success when payment does not exist on the platform', async () => { - const paymentKey = `notificationPayment-${new Date().getTime()}` + const merchantReference = `notificationPayment-${new Date().getTime()}` + // pspReference cannot be static otherwise payment created in the past would be wrongly obtained + const pspReference = `pspReference-${new Date().getTime()}` const { body: paymentBefore } = await ensurePayment( ctpClient, - paymentKey, + merchantReference, + pspReference, commercetoolsProjectKey, adyenMerchantAccount ) @@ -217,7 +240,8 @@ describe('notification module', () => { const notificationPayload = createNotificationPayload( commercetoolsProjectKey, adyenMerchantAccount, - paymentKey, + merchantReference, + pspReference, 'NOT_EXISTING_PAYMENT' ) @@ -235,7 +259,7 @@ describe('notification module', () => { const { body: paymentAfter } = await ctpClient.fetchByKey( ctpClient.builder.payments, - paymentKey + merchantReference // merchantReference is the key of unauthorized payment ) expect(paymentAfter.transactions).to.have.lengthOf(1) expect(paymentAfter.transactions[0].type).to.equal('Authorization') @@ -247,10 +271,14 @@ describe('notification module', () => { 'should update the pending Refund transaction state to success state ' + 'when receives a successful REFUND notification with refund action', async () => { - const paymentKey = `notificationPayment-${new Date().getTime()}` + const merchantReference = `notificationPayment-${new Date().getTime()}` + // pspReference cannot be static otherwise payment created in the past would be wrongly obtained + const pspReference = `pspReference-${new Date().getTime()}` + const { body: paymentBefore } = await ensurePayment( ctpClient, - paymentKey, + merchantReference, + pspReference, commercetoolsProjectKey, adyenMerchantAccount ) @@ -278,6 +306,11 @@ describe('notification module', () => { interactionId: refundInteractionId, }, }, + // Change the payment key to pspReference for authorized payment + { + action: 'setKey', + key: pspReference, + }, ] const { body: updatedPayment } = await ctpClient.update( @@ -296,9 +329,11 @@ describe('notification module', () => { const notificationPayload = createNotificationPayload( commercetoolsProjectKey, adyenMerchantAccount, - paymentKey, + merchantReference, + refundInteractionId, 'REFUND', - refundInteractionId + 'true', + pspReference ) // Simulating a notification from Adyen @@ -315,7 +350,7 @@ describe('notification module', () => { const { body: paymentAfter } = await ctpClient.fetchByKey( ctpClient.builder.payments, - paymentKey + pspReference // pspReference is the key of authorized payment ) expect(paymentAfter.transactions).to.have.lengthOf(2) expect(paymentAfter.transactions[0].type).to.equal('Authorization') @@ -338,10 +373,13 @@ describe('notification module', () => { 'should update multiple pending Refund transaction states to corresponding states ' + 'when receives multiple REFUND notifications', async () => { - const paymentKey = `notificationPayment-${new Date().getTime()}` + const merchantReference = `notificationPayment-${new Date().getTime()}` + // pspReference cannot be static otherwise payment created in the past would be wrongly obtained + const pspReference = `pspReference-${new Date().getTime()}` const { body: paymentBefore } = await ensurePayment( ctpClient, - paymentKey, + merchantReference, + pspReference, commercetoolsProjectKey, adyenMerchantAccount ) @@ -385,6 +423,11 @@ describe('notification module', () => { interactionId: refundInteractionId3, }, }, + // Change the payment key to pspReference for authorized payment + { + action: 'setKey', + key: pspReference, + }, ] await ctpClient.update( ctpClient.builder.payments, @@ -396,26 +439,31 @@ describe('notification module', () => { const notificationPayload1 = createNotificationPayload( commercetoolsProjectKey, adyenMerchantAccount, - paymentKey, + merchantReference, + refundInteractionId1, 'REFUND', - refundInteractionId1 + 'true', + pspReference ) const notificationPayload2 = createNotificationPayload( commercetoolsProjectKey, adyenMerchantAccount, - paymentKey, + merchantReference, + refundInteractionId2, 'REFUND', - refundInteractionId2 + 'true', + pspReference ) const notificationPayload3 = createNotificationPayload( commercetoolsProjectKey, adyenMerchantAccount, - paymentKey, - 'REFUND', + merchantReference, refundInteractionId3, - 'false' + 'REFUND', + 'false', + pspReference ) // Simulating notifications from Adyen @@ -449,7 +497,7 @@ describe('notification module', () => { const { body: paymentAfter } = await ctpClient.fetchByKey( ctpClient.builder.payments, - paymentKey + pspReference // pspReference is the key of authorized payment ) expect(paymentAfter.transactions).to.have.lengthOf(4) expect(paymentAfter.transactions[1].type).to.equal('Refund') @@ -465,10 +513,13 @@ describe('notification module', () => { 'should update the pending CancelAuthorization transaction state to success state ' + 'when receives a successful CANCEL notification with cancel action', async () => { - const paymentKey = `notificationPayment-${new Date().getTime()}` + const merchantReference = `notificationPayment-${new Date().getTime()}` + // pspReference cannot be static otherwise payment created in the past would be wrongly obtained + const pspReference = `pspReference-${new Date().getTime()}` const { body: paymentBefore } = await ensurePayment( ctpClient, - paymentKey, + merchantReference, + pspReference, commercetoolsProjectKey, adyenMerchantAccount ) @@ -491,6 +542,17 @@ describe('notification module', () => { interactionId: cancellationInteractionId, }, }, + { + action: 'changeTransactionState', + state: 'Success', + transactionId: paymentBefore.transactions[0].id, + }, + + // Change the payment key to pspReference for authorized payment + { + action: 'setKey', + key: pspReference, + }, ] const { body: updatedPayment } = await ctpClient.update( @@ -502,7 +564,7 @@ describe('notification module', () => { expect(updatedPayment.transactions).to.have.lengthOf(2) expect(updatedPayment.transactions[0].type).to.equal('Authorization') - expect(updatedPayment.transactions[0].state).to.equal('Pending') + expect(updatedPayment.transactions[0].state).to.equal('Success') expect(updatedPayment.transactions[1].type).to.equal( 'CancelAuthorization' ) @@ -511,9 +573,11 @@ describe('notification module', () => { const notificationPayload = createNotificationPayload( commercetoolsProjectKey, adyenMerchantAccount, - paymentKey, + merchantReference, + cancellationInteractionId, 'CANCEL_OR_REFUND', - cancellationInteractionId + 'true', + pspReference ) // Simulating a notification from Adyen @@ -530,11 +594,11 @@ describe('notification module', () => { const { body: paymentAfter } = await ctpClient.fetchByKey( ctpClient.builder.payments, - paymentKey + pspReference // pspReference is the key of authorized payment ) expect(paymentAfter.transactions).to.have.lengthOf(2) expect(paymentAfter.transactions[0].type).to.equal('Authorization') - expect(paymentAfter.transactions[0].state).to.equal('Pending') + expect(paymentAfter.transactions[0].state).to.equal('Success') expect(paymentAfter.transactions[1].type).to.equal('CancelAuthorization') expect(paymentAfter.transactions[1].state).to.equal('Success') @@ -550,10 +614,13 @@ describe('notification module', () => { ) it('should not update payment when the notification is unauthorised', async () => { - const paymentKey = `notificationPayment-${new Date().getTime()}` + const merchantReference = `notificationPayment-${new Date().getTime()}` + // pspReference cannot be static otherwise payment created in the past would be wrongly obtained + const pspReference = `pspReference-${new Date().getTime()}` const { body: paymentBefore } = await ensurePayment( ctpClient, - paymentKey, + merchantReference, + pspReference, commercetoolsProjectKey, adyenMerchantAccount ) @@ -572,7 +639,8 @@ describe('notification module', () => { const notificationPayload = createNotificationPayload( commercetoolsProjectKey, adyenMerchantAccount, - paymentKey + merchantReference, + pspReference ) // Simulating a modification by a middle man during transmission @@ -592,7 +660,7 @@ describe('notification module', () => { const { body: paymentAfter } = await ctpClient.fetchByKey( ctpClient.builder.payments, - paymentKey + merchantReference // merchantReference is the key of unauthorized payment ) expect(paymentAfter.transactions).to.have.lengthOf(1) expect(paymentAfter.transactions[0].type).to.equal('Authorization') @@ -603,10 +671,13 @@ describe('notification module', () => { }) it('should use URL path as a fallback when metadata.ctProjectKey is missing', async () => { - const paymentKey = `notificationPayment-${new Date().getTime()}` + const merchantReference = `notificationPayment-${new Date().getTime()}` + // pspReference cannot be static otherwise payment created in the past would be wrongly obtained + const pspReference = `pspReference-${new Date().getTime()}` const { body: paymentBefore } = await ensurePayment( ctpClient, - paymentKey, + merchantReference, + pspReference, commercetoolsProjectKey, adyenMerchantAccount ) @@ -618,7 +689,8 @@ describe('notification module', () => { const notificationPayload = createNotificationPayload( null, adyenMerchantAccount, - paymentKey + merchantReference, + pspReference ) // Simulating a notification from Adyen @@ -639,7 +711,7 @@ describe('notification module', () => { const { body: paymentAfter } = await ctpClient.fetchByKey( ctpClient.builder.payments, - paymentKey + pspReference // pspReference is the key of authorized payment ) expect(paymentAfter.transactions).to.have.lengthOf(1) expect(paymentAfter.transactions[0].type).to.equal('Authorization') diff --git a/notification/test/test-utils.js b/notification/test/test-utils.js index 237f772aa..dc601a3fa 100644 --- a/notification/test/test-utils.js +++ b/notification/test/test-utils.js @@ -87,10 +87,11 @@ const validator = new hmacValidator() function createNotificationPayload( commercetoolsProjectKey, adyenMerchantAccount, - paymentKey, + merchantReference, + pspReference, eventCode = 'AUTHORISATION', - pspReference = 'test_AUTHORISATION_1', - success = 'true' + success = 'true', + originalReference ) { const notification = { live: 'false', @@ -107,10 +108,11 @@ function createNotificationPayload( eventCode, eventDate: '2019-01-30T18:16:22+01:00', merchantAccountCode: adyenMerchantAccount, - merchantReference: paymentKey, + merchantReference, operations: ['CANCEL', 'CAPTURE', 'REFUND'], paymentMethod: 'visa', pspReference, + originalReference, success, }, }, @@ -138,6 +140,7 @@ function createNotificationPayload( async function ensurePayment( ctpClient, paymentKey, + pspReference, commercetoolsProjectKey, adyenMerchantAccount ) { @@ -150,7 +153,9 @@ async function ensurePayment( commercetoolsProjectKey, adyenMerchantAccount, } - + if (pspReference) { + paymentDraft.transactions[0].interactionId = pspReference + } return ctpClient.create(ctpClient.builder.payments, paymentDraft) } diff --git a/notification/test/unit/ctp-client-mock.js b/notification/test/unit/ctp-client-mock.js index 4d2eec5cd..2b7d4ed2e 100644 --- a/notification/test/unit/ctp-client-mock.js +++ b/notification/test/unit/ctp-client-mock.js @@ -45,6 +45,10 @@ function setUpClient(config) { return this }, + fetchByKeys() { + return this + }, + fetchBatches() { return this }, diff --git a/notification/test/unit/notification.controller.spec.js b/notification/test/unit/notification.controller.spec.js index 330930536..65a9a7792 100644 --- a/notification/test/unit/notification.controller.spec.js +++ b/notification/test/unit/notification.controller.spec.js @@ -194,8 +194,8 @@ describe('notification controller', () => { } const ctpClient = ctpClientMock.get(ctpConfig) const modifiedPaymentMock = cloneDeep(paymentMock) - sandbox.stub(ctpClient, 'fetchByKey').callsFake(() => ({ - body: modifiedPaymentMock, + sandbox.stub(ctpClient, 'fetchByKeys').callsFake(() => ({ + body: { results: [modifiedPaymentMock] }, })) sandbox.stub(ctpClient, 'fetchById').callsFake(() => ({ body: modifiedPaymentMock, diff --git a/notification/test/unit/notification.handler.spec.js b/notification/test/unit/notification.handler.spec.js index 372f1b9b5..e5a31a496 100644 --- a/notification/test/unit/notification.handler.spec.js +++ b/notification/test/unit/notification.handler.spec.js @@ -91,8 +91,8 @@ describe('notification module', () => { state: 'Initial', }) const ctpClient = ctpClientMock.get(ctpConfig) - sandbox.stub(ctpClient, 'fetchByKey').callsFake(() => ({ - body: payment, + sandbox.stub(ctpClient, 'fetchByKeys').callsFake(() => ({ + body: { results: [payment] }, })) const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') ctp.get = () => ctpClient @@ -129,6 +129,10 @@ describe('notification module', () => { transactionId: '9ca92d05-ba63-47dc-8f83-95b08d539646', timestamp: '2021-01-01T10:00:00.000Z', }, + { + action: 'setKey', + key: 'test_AUTHORISATION_1', + }, { action: 'setMethodInfoMethod', method: 'visa', @@ -190,8 +194,8 @@ describe('notification module', () => { state: 'Pending', }) const ctpClient = ctpClientMock.get(ctpConfig) - sandbox.stub(ctpClient, 'fetchByKey').callsFake(() => ({ - body: payment, + sandbox.stub(ctpClient, 'fetchByKeys').callsFake(() => ({ + body: { results: [payment] }, })) const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') ctp.get = () => ctpClient @@ -218,6 +222,10 @@ describe('notification module', () => { notification: JSON.stringify(notifications[0]), }, }, + { + action: 'setKey', + key: 'test_AUTHORISATION_1', + }, ] // assert update actions @@ -281,9 +289,15 @@ describe('notification module', () => { createdAt: '2019-02-05T12:29:36.028Z', }, }) + const expectedUpdateActions = [ + { + action: 'setKey', + key: 'test_AUTHORISATION_1', + }, + ] const ctpClient = ctpClientMock.get(ctpConfig) - sandbox.stub(ctpClient, 'fetchByKey').callsFake(() => ({ - body: payment, + sandbox.stub(ctpClient, 'fetchByKeys').callsFake(() => ({ + body: { results: [payment] }, })) const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') ctp.get = () => ctpClient @@ -295,7 +309,13 @@ describe('notification module', () => { config ) // assert - expect(ctpClientUpdateSpy.callCount).to.equal(0) + expect(ctpClientUpdateSpy.callCount).to.equal(1) + + const actualUpdateActionsWithoutCreatedAt = ctpClientUpdateSpy.args[0][3] + + expect(actualUpdateActionsWithoutCreatedAt).to.deep.equal( + expectedUpdateActions + ) }) it(`given that ADYEN sends a "CANCELLATION is successful" notification @@ -337,8 +357,8 @@ describe('notification module', () => { state: 'Pending', }) const ctpClient = ctpClientMock.get(ctpConfig) - sandbox.stub(ctpClient, 'fetchByKey').callsFake(() => ({ - body: payment, + sandbox.stub(ctpClient, 'fetchByKeys').callsFake(() => ({ + body: { results: [payment] }, })) const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') ctp.get = () => ctpClient @@ -377,6 +397,10 @@ describe('notification module', () => { interactionId: 'test_AUTHORISATION_1', }, }, + { + action: 'setKey', + key: 'test_AUTHORISATION_1', + }, ] // assert update actions @@ -433,8 +457,8 @@ describe('notification module', () => { state: 'Success', }) const ctpClient = ctpClientMock.get(ctpConfig) - sandbox.stub(ctpClient, 'fetchByKey').callsFake(() => ({ - body: payment, + sandbox.stub(ctpClient, 'fetchByKeys').callsFake(() => ({ + body: { results: [payment] }, })) const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') ctp.get = () => ctpClient @@ -473,6 +497,10 @@ describe('notification module', () => { interactionId: 'test_AUTHORISATION_1', }, }, + { + action: 'setKey', + key: 'test_AUTHORISATION_1', + }, ] // assert update actions @@ -529,8 +557,8 @@ describe('notification module', () => { state: 'Success', }) const ctpClient = ctpClientMock.get(ctpConfig) - sandbox.stub(ctpClient, 'fetchByKey').callsFake(() => ({ - body: payment, + sandbox.stub(ctpClient, 'fetchByKeys').callsFake(() => ({ + body: { results: [payment] }, })) const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') ctp.get = () => ctpClient @@ -570,6 +598,10 @@ describe('notification module', () => { timestamp: '', }, }, + { + action: 'setKey', + key: 'test_AUTHORISATION_1', + }, ] // assert update actions @@ -635,8 +667,8 @@ describe('notification module', () => { interactionId: 'test_AUTHORISATION_1', }) const ctpClient = ctpClientMock.get(ctpConfig) - sandbox.stub(ctpClient, 'fetchByKey').callsFake(() => ({ - body: payment, + sandbox.stub(ctpClient, 'fetchByKeys').callsFake(() => ({ + body: { results: [payment] }, })) const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') ctp.get = () => ctpClient @@ -673,6 +705,10 @@ describe('notification module', () => { transactionId: '9ca92d05-ba63-47dc-8f83-95b08d539646', timestamp: '2022-04-13T05:17:29.000Z', }, + { + action: 'setKey', + key: '****', + }, ] const actualUpdateActions = ctpClientUpdateSpy.args[0][3] @@ -698,8 +734,8 @@ describe('notification module', () => { state: 'Initial', }) const ctpClient = ctpClientMock.get(ctpConfig) - sandbox.stub(ctpClient, 'fetchByKey').callsFake(() => ({ - body: modifiedPaymentMock, + sandbox.stub(ctpClient, 'fetchByKeys').callsFake(() => ({ + body: { results: [modifiedPaymentMock] }, })) sandbox.stub(ctpClient, 'fetchById').callsFake(() => ({ body: modifiedPaymentMock, @@ -729,24 +765,6 @@ describe('notification module', () => { expect(ctpClientUpdateSpy.callCount).to.equal(21) }) - it('do not make any requests when merchantReference cannot be extracted from notification', async () => { - const ctpClient = ctpClientMock.get(ctpConfig) - const ctpClientFetchByKeySpy = sandbox.spy(ctpClient, 'fetchByKey') - const ctpClientFetchByIdSpy = sandbox.spy(ctpClient, 'fetchById') - const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') - ctp.get = () => ctpClient - - // process - await notificationHandler.processNotification( - { name: 'some wrong notification' }, - false, - config - ) - - expect(ctpClientFetchByKeySpy.callCount).to.equal(0) - expect(ctpClientFetchByIdSpy.callCount).to.equal(0) - expect(ctpClientUpdateSpy.callCount).to.equal(0) - }) it( 'when "removeSensitiveData" is false, ' + 'then it should not remove sensitive data', @@ -793,8 +811,8 @@ describe('notification module', () => { ] const payment = cloneDeep(paymentMock) const ctpClient = ctpClientMock.get(ctpConfig) - sandbox.stub(ctpClient, 'fetchByKey').callsFake(() => ({ - body: payment, + sandbox.stub(ctpClient, 'fetchByKeys').callsFake(() => ({ + body: { results: [payment] }, })) const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') ctp.get = () => ctpClient @@ -861,8 +879,8 @@ describe('notification module', () => { ] const payment = cloneDeep(paymentMock) const ctpClient = ctpClientMock.get(ctpConfig) - sandbox.stub(ctpClient, 'fetchByKey').callsFake(() => ({ - body: payment, + sandbox.stub(ctpClient, 'fetchByKeys').callsFake(() => ({ + body: { results: [payment] }, })) const ctpClientUpdateSpy = sandbox.spy(ctpClient, 'update') ctp.get = () => ctpClient @@ -921,7 +939,7 @@ describe('notification module', () => { state: 'Success', }) const ctpClient = ctpClientMock.get(ctpConfig) - sandbox.stub(ctpClient, 'fetchByKey').callsFake(() => { + sandbox.stub(ctpClient, 'fetchByKeys').callsFake(() => { throw new Error('error') })