Skip to content

Commit

Permalink
Handle non-JSON response (#1059)
Browse files Browse the repository at this point in the history
* Fix the non-JSON handling in API call and refund unit test

* Fix format in refund unit test

* Throw error instead of returning non-JSON response

* Avoid hardcording the legacy api version

* Fix style

* Fix URL for disabling stored payment

* Remove printout

Co-authored-by: King-Hin Leung <>
  • Loading branch information
leungkinghin-ct authored Jan 19, 2023
1 parent 4f55b67 commit ea66823
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 16 deletions.
6 changes: 6 additions & 0 deletions extension/src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,10 @@ export default {
CTP_INTERACTION_TYPE_AMOUNT_UPDATES: 'amountUpdates',
CTP_INTERACTION_TYPE_DISABLE_STORED_PAYMENT: 'disableStoredPayment',
CTP_DISABLE_STORED_PAYMENT_RESPONSE: 'disableStoredPaymentResponse',
ADYEN_LEGACY_API_VERSION: {
MANUAL_CAPTURE: 'v64',
CANCEL: 'v64',
REFUND: 'v64',
DISABLED_STORED_PAYMENT: 'v68',
},
}
42 changes: 32 additions & 10 deletions extension/src/service/web-component-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fetch from 'node-fetch'
import { serializeError } from 'serialize-error'
import config from '../config/config.js'
import utils from '../utils.js'
import constants from '../config/constants.js'

async function getPaymentMethods(merchantAccount, getPaymentMethodsRequestObj) {
const adyenCredentials = config.getAdyenConfig(merchantAccount)
Expand Down Expand Up @@ -63,7 +64,7 @@ function manualCapture(
const adyenCredentials = config.getAdyenConfig(merchantAccount)
extendRequestObjWithMetadata(manualCaptureRequestObj, commercetoolsProjectKey)
return callAdyen(
`${adyenCredentials.legacyApiBaseUrl}/Payment/v64/capture`,
`${adyenCredentials.legacyApiBaseUrl}/Payment/${constants.ADYEN_LEGACY_API_VERSION.MANUAL_CAPTURE}/capture`,
merchantAccount,
adyenCredentials.apiKey,
manualCaptureRequestObj,
Expand All @@ -79,7 +80,7 @@ function cancelPayment(
const adyenCredentials = config.getAdyenConfig(merchantAccount)
extendRequestObjWithMetadata(cancelPaymentRequestObj)
return callAdyen(
`${adyenCredentials.legacyApiBaseUrl}/Payment/v64/cancel`,
`${adyenCredentials.legacyApiBaseUrl}/Payment/${constants.ADYEN_LEGACY_API_VERSION.CANCEL}/cancel`,
merchantAccount,
adyenCredentials.apiKey,
cancelPaymentRequestObj
Expand All @@ -95,7 +96,7 @@ function refund(
const adyenCredentials = config.getAdyenConfig(merchantAccount)
extendRequestObjWithMetadata(refundRequestObj, commercetoolsProjectKey)
return callAdyen(
`${adyenCredentials.legacyApiBaseUrl}/Payment/v64/refund`,
`${adyenCredentials.legacyApiBaseUrl}/Payment/${constants.ADYEN_LEGACY_API_VERSION.REFUND}/refund`,
merchantAccount,
adyenCredentials.apiKey,
refundRequestObj,
Expand Down Expand Up @@ -131,8 +132,11 @@ function updateAmount(

function disableStoredPayment(merchantAccount, disableStoredPaymentRequestObj) {
const adyenCredentials = config.getAdyenConfig(merchantAccount)
const url =
`${adyenCredentials.legacyApiBaseUrl}/Recurring/` +
`${constants.ADYEN_LEGACY_API_VERSION.DISABLED_STORED_PAYMENT}/disable`
return callAdyen(
`${adyenCredentials.legacyApiBaseUrl}/Recurring/v68/disable`,
url,
merchantAccount,
adyenCredentials.apiKey,
disableStoredPaymentRequestObj
Expand Down Expand Up @@ -192,6 +196,7 @@ async function callAdyen(
returnedRequest = { body: JSON.stringify(requestArg) }
returnedResponse = serializeError(err)
}

return { request: returnedRequest, response: returnedResponse }
}

Expand All @@ -206,18 +211,35 @@ async function fetchAsync(
const removeSensitiveData =
requestObj.removeSensitiveData ?? moduleConfig.removeSensitiveData
delete requestObj.removeSensitiveData
let response
let responseBody
let responseBodyInText
const request = buildRequest(
adyenMerchantAccount,
adyenApiKey,
requestObj,
headers
)
const response = await fetch(url, request)
const responseBody = await response.json()
// strip away sensitive data from the adyen response.
request.headers['X-Api-Key'] = '***'
if (removeSensitiveData) {
delete responseBody.additionalData

try {
response = await fetch(url, request)
responseBodyInText = await response.text()

responseBody = JSON.parse(responseBodyInText)
} catch (err) {
if (response)
// Handle non-JSON format response
throw new Error(
`Unable to receive non-JSON format resposne from Adyen API : ${responseBodyInText}`
)
// Error in fetching URL
else throw err
} finally {
// strip away sensitive data from the adyen response.
request.headers['X-Api-Key'] = '***'
if (removeSensitiveData && responseBody) {
delete responseBody.additionalData
}
}
return { response: responseBody, request }
}
Expand Down
40 changes: 34 additions & 6 deletions extension/test/unit/refund-payment.handler.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { expect } from 'chai'
import config from '../../src/config/config.js'
import refundPaymentHandler from '../../src/paymentHandler/refund-payment.handler.js'
import utils from '../../src/utils.js'
import paymentSuccessResponse from './fixtures/adyen-make-payment-success-response.js'
import constants from '../../src/config/constants.js'
import { overrideGenerateIdempotencyKeyConfig } from '../test-utils.js'

const { execute } = refundPaymentHandler
Expand Down Expand Up @@ -44,16 +44,16 @@ describe('refund-payment::execute', () => {

beforeEach(() => {
const adyenConfig = config.getAdyenConfig(adyenMerchantAccount)
scope = nock(`${adyenConfig.apiBaseUrl}`)
scope = nock(
`${adyenConfig.legacyApiBaseUrl}/Payment/${constants.ADYEN_LEGACY_API_VERSION.REFUND}`
)
})

afterEach(() => {
nock.cleanAll()
})

it('when refund payment request contains reference, then it should send this reference to Adyen', async () => {
scope.post('/payments').reply(200, paymentSuccessResponse)

const ctpPaymentClone = _.cloneDeep(ctpPayment)

ctpPaymentClone.transactions.push(refundPaymentTransaction)
Expand All @@ -79,8 +79,6 @@ describe('refund-payment::execute', () => {
async () => {
overrideGenerateIdempotencyKeyConfig(true)

scope.post('/payments').reply(200, paymentSuccessResponse)

const ctpPaymentClone = _.cloneDeep(ctpPayment)

ctpPaymentClone.transactions.push(refundPaymentTransaction)
Expand All @@ -97,4 +95,34 @@ describe('refund-payment::execute', () => {
)
}
)

it(
'when refund payment response contains non-JSON format, ' +
'then it should return the response in plain text inside interfaceInteraction',
async () => {
scope.post('/refund').reply(200, 'non-json-response')

const ctpPaymentClone = _.cloneDeep(ctpPayment)

ctpPaymentClone.transactions.push(refundPaymentTransaction)
ctpPaymentClone.custom.fields.adyenMerchantAccount = adyenMerchantAccount

const response = await execute(ctpPaymentClone)

const adyenRequest = response.actions.find(
(action) => action.action === 'addInterfaceInteraction'
).fields.request
const adyenResponse = response.actions.find(
(action) => action.action === 'addInterfaceInteraction'
).fields.response

const adyenRequestJson = JSON.parse(adyenRequest)
const requestBody = JSON.parse(adyenRequestJson.body)

expect(requestBody.reference).to.equal(
refundPaymentTransaction.custom.fields.reference
)
expect(adyenResponse).to.contains('non-json-response')
}
)
})

0 comments on commit ea66823

Please sign in to comment.