diff --git a/.gitignore b/.gitignore index 3b616f1..e67b33e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ dist .DS_Store .vscode .idea +.connect build/ processor/.env processor/junit-report.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index b07a071..bb29577 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## v1.3.2 + +Added + +- A new connector setting named `CTP_TRANSACTION_CUSTOM_TYPE_KEY` for adding/updating new custom fields - the custom field key and logic would stay the same + + - IF set, new custom fields will be updated into the defined custom type key + - IF not, new custom fields will be added into a default custom type with key `sctm_transactions_custom_type` + +Updated + +- Allow to cancel an Klarna authorized payment via newly introduce endpoint `/release-authorization` instead of using `DELETE` verb + ## v1.3.1 Fixed diff --git a/application/package.json b/application/package.json index 7d2ab19..f75ff79 100644 --- a/application/package.json +++ b/application/package.json @@ -1,6 +1,6 @@ { "name": "shopmacher-mollie-application", - "version": "1.3.1", + "version": "1.3.2", "description": "Integration application between commercetools and mollie payment service provider", "private": true, "scripts": { diff --git a/application/src/constants.ts b/application/src/constants.ts index 557d351..4f5b871 100644 --- a/application/src/constants.ts +++ b/application/src/constants.ts @@ -12,8 +12,8 @@ export const EXTENSION_KEY = 'sctm-payment-create-update-extension'; export const EXTENSION_URL_PATH = '/processor'; export const APPLICATION_URL_PATH = '/application/methods'; export const USER_AGENT = { - name: 'ShopmacherMollieCommercetoolsConnector/1.3.1', - version: '1.3.1', - libraryName: 'ShopmacherMollieCommercetoolsConnector/1.3.1', + name: 'ShopmacherMollieCommercetoolsConnector/1.3.2', + version: '1.3.2', + libraryName: 'ShopmacherMollieCommercetoolsConnector/1.3.2', contactEmail: 'info@mollie.com', }; diff --git a/connect.yaml b/connect.yaml index 37c956b..cfeebc0 100644 --- a/connect.yaml +++ b/connect.yaml @@ -27,6 +27,9 @@ deployAs: description: To enable secure mode for connector requests using OAuth authentication (0 or 1) required: true default: "0" + - key: CTP_TRANSACTION_CUSTOM_TYPE_KEY + description: Custom type key for transactions in commercetools + required: false securedConfiguration: - key: MOLLIE_API_TEST_KEY description: Mollie PSP test API key diff --git a/docs/CancelPaymentRefund.md b/docs/CancelPaymentRefund.md index 1dee103..9b9fda8 100644 --- a/docs/CancelPaymentRefund.md +++ b/docs/CancelPaymentRefund.md @@ -16,6 +16,7 @@ The target Mollie endpoint will be [Cancel Payment Refund](https://docs.mollie.c In order to use this functionality, the customer must have a charged-successfully payment and a refund created which is in-progress for that payment. Technically, the CommerceTools Payment object needs to include 3 transactions: + - 1 transaction with type = `Charge`, state = `Success`. This transaction should also store the targeted Mollie Payment ID in `interactionId`. - 1 transaction with type = `Refund`, state = `Pending`. This transaction should also store the targeted Mollie Refund ID in `interactionId`. - 1 transaction with type = `CancelAuthorization`, state = `Initial`. This transaction is to point out that the customer is wanting to cancel the Refund. diff --git a/processor/.env.example b/processor/.env.example index caa579e..8c5f85b 100644 --- a/processor/.env.example +++ b/processor/.env.example @@ -30,3 +30,8 @@ MOLLIE_PROFILE_ID= ## NGROK - only required for development CONNECTOR_EXTENSION_TOKEN= + +## IF you have assigned a custom type to the transaction, you can specify it here +### So that the processor can use it to update the type adding its new fields +## If you have not assigned a custom type, you can leave this empty +CTP_TRANSACTION_CUSTOM_TYPE_KEY= \ No newline at end of file diff --git a/processor/bin/ngrok.sh b/processor/bin/ngrok.sh index 48b9057..2ac842c 100755 --- a/processor/bin/ngrok.sh +++ b/processor/bin/ngrok.sh @@ -12,7 +12,8 @@ fi # Start NGROK in background echo "⚡️ Starting ngrok" -ngrok http 8080 --authtoken ${CONNECTOR_EXTENSION_TOKEN} > /dev/null & +ngrok authtoken ${CONNECTOR_EXTENSION_TOKEN} & +ngrok http 8080 > /dev/null & # Wait for ngrok to be available while ! nc -z localhost 4040; do diff --git a/processor/package-lock.json b/processor/package-lock.json index 2354b7f..3dcd142 100644 --- a/processor/package-lock.json +++ b/processor/package-lock.json @@ -1,12 +1,12 @@ { "name": "shopmacher-mollie-processor", - "version": "1.3.1", + "version": "1.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "shopmacher-mollie-processor", - "version": "1.3.1", + "version": "1.3.2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/processor/package.json b/processor/package.json index dda3b4a..d9b9bf3 100644 --- a/processor/package.json +++ b/processor/package.json @@ -1,7 +1,7 @@ { "name": "shopmacher-mollie-processor", "description": "Integration between commercetools and mollie payment service provider", - "version": "1.3.1", + "version": "1.3.2", "main": "index.js", "private": true, "scripts": { diff --git a/processor/src/commercetools/customFields.commercetools.ts b/processor/src/commercetools/customFields.commercetools.ts index fff9118..34ff050 100644 --- a/processor/src/commercetools/customFields.commercetools.ts +++ b/processor/src/commercetools/customFields.commercetools.ts @@ -1,7 +1,6 @@ import { CustomFields } from '../utils/constant.utils'; import { createApiRoot } from '../client/create.client'; import { FieldDefinition, TypeUpdateAction } from '@commercetools/platform-sdk'; - const PAYMENT_TYPE_KEY = 'sctm-payment-custom-type'; export async function createCustomPaymentType(): Promise { @@ -237,265 +236,20 @@ export async function createCustomPaymentInterfaceInteractionType(): Promise { - const apiRoot = createApiRoot(); - - const customFieldName = CustomFields.paymentCancelReason; - - const { - body: { results: types }, - } = await createApiRoot() - .types() - .get({ - queryArgs: { - where: `key = "${customFieldName}"`, - }, - }) - .execute(); - - if (types.length <= 0) { - await apiRoot - .types() - .post({ - body: { - key: customFieldName, - name: { - en: 'SCTM - Payment cancel reason on Transaction custom fields', - de: 'SCTM - Grund für Zahlungsstornierung in benutzerdefinierten Transaktionsfeldern', - }, - description: { - en: 'Showing the reason of cancelling and identifying if the cancel action came from CommerceTools or Mollie', - de: 'Anzeige des Kündigungsgrundes und Identifizierung, ob die Kündigung von CommerceTools oder Mollie erfolgte', - }, - resourceTypeIds: ['transaction'], - fieldDefinitions: [ - { - name: 'reasonText', - label: { - en: 'The reason of cancelling the refund, include the user name', - de: 'Der Grund für die Stornierung der Rückerstattung, den Benutzernamen einschließen', - }, - required: false, - type: { - name: 'String', - }, - inputHint: 'MultiLine', - }, - { - name: 'statusText', - label: { - en: 'To differentiate between the “failure” from CommerceTools and the real status', - de: 'Um zwischen dem „Fehler“ von CommerceTools und dem tatsächlichen Status zu unterscheiden', - }, - required: false, - type: { - name: 'String', - }, - inputHint: 'MultiLine', - }, - ], - }, - }) - .execute(); - } -} - -export async function createTransactionSurchargeCustomType(): Promise { - const apiRoot = createApiRoot(); - const customFields: FieldDefinition[] = [ - { - name: CustomFields.surchargeAndCapture.fields.surchargeCode.name, - label: { - en: CustomFields.surchargeAndCapture.fields.surchargeCode.label.en, - de: CustomFields.surchargeAndCapture.fields.surchargeCode.label.en, - }, - required: false, - type: { - name: 'Number', - }, - inputHint: 'MultiLine', - }, - { - name: CustomFields.surchargeAndCapture.fields.shouldCapture.name, - label: { - en: CustomFields.surchargeAndCapture.fields.shouldCapture.label.en, - de: CustomFields.surchargeAndCapture.fields.shouldCapture.label.de, - }, - required: false, - type: { - name: 'Boolean', - }, - }, - { - name: CustomFields.surchargeAndCapture.fields.descriptionCapture.name, - label: { - en: CustomFields.surchargeAndCapture.fields.descriptionCapture.label.en, - de: CustomFields.surchargeAndCapture.fields.descriptionCapture.label.de, - }, - required: false, - type: { - name: 'String', - }, - inputHint: 'MultiLine', - }, - { - name: CustomFields.surchargeAndCapture.fields.captureErrors.name, - label: { - en: CustomFields.surchargeAndCapture.fields.captureErrors.label.en, - de: CustomFields.surchargeAndCapture.fields.captureErrors.label.de, - }, - required: false, - type: { - name: 'String', - }, - inputHint: 'MultiLine', - }, - ]; - - const { - body: { results: oldTypes }, - } = await apiRoot - .types() - .get({ - queryArgs: { - where: `key = "sctm_transaction_surcharge_cost"`, - }, - }) - .execute(); - - if (oldTypes.length > 0) { - await apiRoot - .types() - .withKey({ key: 'sctm_transaction_surcharge_cost' }) - .post({ - body: { - version: oldTypes[0].version, - actions: [ - { - action: 'changeKey', - key: CustomFields.surchargeAndCapture.typeKey, - }, - ], - }, - }) - .execute(); - } - - const { - body: { results: types }, - } = await apiRoot - .types() - .get({ - queryArgs: { - where: `key = "${CustomFields.surchargeAndCapture.typeKey}"`, - }, - }) - .execute(); +export async function createCustomTransactionType(): Promise { + const transactionCustomTypeKey = + process.env?.CTP_TRANSACTION_CUSTOM_TYPE_KEY && process.env.CTP_TRANSACTION_CUSTOM_TYPE_KEY?.length > 0 + ? process.env.CTP_TRANSACTION_CUSTOM_TYPE_KEY + : CustomFields.transactions.defaultCustomTypeKey; - if (types.length <= 0) { - await apiRoot - .types() - .post({ - body: { - key: CustomFields.surchargeAndCapture.typeKey, - name: { - en: '(SCTM) Transaction surcharge & capture control', - de: '(SCTM) Transaktionszuschlag & Erfassungskontrolle', - }, - resourceTypeIds: [CustomFields.surchargeAndCapture.resourceTypeId], - fieldDefinitions: customFields, - }, - }) - .execute(); - - return; - } else { - if (types[0].name.en !== CustomFields.surchargeAndCapture.name.en) { - await apiRoot - .types() - .withKey({ key: CustomFields.surchargeAndCapture.typeKey }) - .post({ - body: { - version: types[0].version, - actions: [ - { - action: 'changeName', - name: { - en: CustomFields.surchargeAndCapture.name.en, - de: CustomFields.surchargeAndCapture.name.de, - }, - }, - ], - }, - }) - .execute(); - } - } - - const type = types[0]; - const definitions = type.fieldDefinitions; - - if (definitions.length > 0) { - const actions: TypeUpdateAction[] = []; - const fieldKeys = customFields.map((field) => field.name); - - definitions.forEach((definition) => { - if (!fieldKeys.includes(definition.name)) { - actions.push({ - action: 'removeFieldDefinition', - fieldName: definition.name, - }); - } - }); - - customFields.forEach((field) => { - if (!definitions.find((definition) => definition.name === field.name)) { - actions.push({ - action: 'addFieldDefinition', - fieldDefinition: field, - }); - } - }); - - await apiRoot - .types() - .withKey({ key: CustomFields.surchargeAndCapture.typeKey }) - .post({ - body: { - version: type.version, - actions, - }, - }) - .execute(); - - return; - } -} - -export async function createTransactionRefundForMolliePaymentCustomType(): Promise { const apiRoot = createApiRoot(); - const customFields: FieldDefinition[] = [ - { - name: CustomFields.transactionRefundForMolliePayment, - label: { - en: 'Identify the Mollie payment which is being refunded', - de: 'Identifizieren Sie die Mollie-Zahlung, die zurückerstattet wird', - }, - required: false, - type: { - name: 'String', - }, - inputHint: 'MultiLine', - }, - ]; - const { body: { results: types }, } = await apiRoot .types() .get({ queryArgs: { - where: `key = "${CustomFields.transactionRefundForMolliePayment}"`, + where: `key = "${transactionCustomTypeKey}"`, }, }) .execute(); @@ -505,49 +259,37 @@ export async function createTransactionRefundForMolliePaymentCustomType(): Promi .types() .post({ body: { - key: CustomFields.transactionRefundForMolliePayment, + key: transactionCustomTypeKey, name: { - en: 'Identify the Mollie payment which is being refunded', - de: 'Identifizieren Sie die Mollie-Zahlung, die zurückerstattet wird', + en: CustomFields.transactions.name.en, + de: CustomFields.transactions.name.de, }, - resourceTypeIds: ['transaction'], - fieldDefinitions: customFields, + resourceTypeIds: [CustomFields.transactions.resourceTypeId], + fieldDefinitions: Object.values(CustomFields.transactions.fields) as FieldDefinition[], }, }) .execute(); - - return; } const type = types[0]; - const definitions = type.fieldDefinitions; - - if (definitions.length > 0) { - const actions: TypeUpdateAction[] = []; - definitions.forEach((definition) => { - actions.push({ - action: 'removeFieldDefinition', - fieldName: definition.name, - }); - }); - customFields.forEach((field) => { - actions.push({ - action: 'addFieldDefinition', - fieldDefinition: field, - }); - }); + const existingDefinitions = type.fieldDefinitions.map((field) => field.name); + const fieldDefinitions = Object.values(CustomFields.transactions.fields).filter( + (field) => !existingDefinitions.includes(field.name), + ) as FieldDefinition[]; + if (fieldDefinitions.length > 0) { await apiRoot .types() - .withKey({ key: CustomFields.transactionRefundForMolliePayment }) + .withKey({ key: transactionCustomTypeKey }) .post({ body: { version: type.version, - actions, + actions: fieldDefinitions.map((field) => ({ + action: 'addFieldDefinition', + fieldDefinition: field, + })), }, }) .execute(); - - return; } } diff --git a/processor/src/mollie/payment.mollie.ts b/processor/src/mollie/payment.mollie.ts index e65cec8..126addc 100644 --- a/processor/src/mollie/payment.mollie.ts +++ b/processor/src/mollie/payment.mollie.ts @@ -211,3 +211,32 @@ export const createCapturePayment = async (options: CreateParameters): Promise => { + try { + const response = await fetch(`https://api.mollie.com/v2/payments/${molliePaymentId}/release-authorization`, { + method: 'POST', + headers: HEADER, + }); + + if (response.status === 202) { + logger.debug( + `SCTM - releaseAuthorizationPayment - Authorization payment released successfully for payment ${molliePaymentId}`, + ); + } + return; + } catch (error: unknown) { + let errorMessage; + + if (error instanceof MollieApiError) { + errorMessage = `SCTM - releaseAuthorizationPayment - error: ${error.message}`; + } else { + errorMessage = `SCTM - releaseAuthorizationPayment - Failed to release authorization payment with unknown errors`; + } + + logger.error(errorMessage, { + error, + }); + throw new CustomError(400, errorMessage); + } +}; diff --git a/processor/src/service/connector.service.ts b/processor/src/service/connector.service.ts index eeb0115..80144f7 100644 --- a/processor/src/service/connector.service.ts +++ b/processor/src/service/connector.service.ts @@ -2,9 +2,7 @@ import { createPaymentExtension, deletePaymentExtension } from '../commercetools import { createCustomPaymentType, createCustomPaymentInterfaceInteractionType, - createCustomPaymentTransactionCancelReasonType, - createTransactionSurchargeCustomType, - createTransactionRefundForMolliePaymentCustomType, + createCustomTransactionType, } from '../commercetools/customFields.commercetools'; import { getAccessToken } from '../commercetools/auth.commercetools'; @@ -13,9 +11,7 @@ export const createExtensionAndCustomFields = async (extensionUrl: string): Prom await createPaymentExtension(extensionUrl, response?.access_token as string); await createCustomPaymentType(); await createCustomPaymentInterfaceInteractionType(); - await createCustomPaymentTransactionCancelReasonType(); - await createTransactionSurchargeCustomType(); - await createTransactionRefundForMolliePaymentCustomType(); + await createCustomTransactionType(); }; export const removeExtension = async (): Promise => { diff --git a/processor/src/service/payment.service.ts b/processor/src/service/payment.service.ts index b06e198..9b1ac88 100644 --- a/processor/src/service/payment.service.ts +++ b/processor/src/service/payment.service.ts @@ -23,6 +23,7 @@ import { getApplePaySession, getPaymentById, listPaymentMethods, + releaseAuthorizationPayment, } from '../mollie/payment.mollie'; import { AddTransaction, @@ -59,7 +60,7 @@ import { setTransactionCustomField, setTransactionCustomType, } from '../commercetools/action.commercetools'; -import { readConfiguration } from '../utils/config.utils'; +import { getTransactionCustomTypeKey, readConfiguration } from '../utils/config.utils'; import { getPaymentExtension } from '../commercetools/extensions.commercetools'; import { HttpDestination } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/extension'; import { cancelPaymentRefund, createPaymentRefund, getPaymentRefund } from '../mollie/refund.mollie'; @@ -195,7 +196,7 @@ const filterMethodsByPricingConstraints = ( const isCaptureFromMollie = ( manualCapture: boolean, - ctTransactions: CTTransaction[], + ctTransactions: Transaction[], molliePayment: MPayment, ): DoCapturePaymentFromMollie => { const hasSuccessAuthorizedTransaction = ctTransactions.some((transaction) => { @@ -232,8 +233,12 @@ const isCaptureFromMollie = ( transaction.interactionId === molliePayment.id, ); + const hasSurchargeCost = + pendingChargeTransaction?.custom?.fields?.[CustomFields.transactions.fields.surchargeCost.name] !== undefined; + return { answer, + hasSurchargeCost, id: pendingChargeTransaction?.id, }; }; @@ -333,7 +338,7 @@ export const handlePaymentWebhook = async (paymentId: string): Promise return false; } - const action = getPaymentStatusUpdateAction(ctPayment.transactions as CTTransaction[], molliePayment); + const action = getPaymentStatusUpdateAction(ctPayment.transactions as Transaction[], molliePayment); // If refunds are present, update their status const refunds = molliePayment._embedded?.refunds; @@ -352,7 +357,7 @@ export const handlePaymentWebhook = async (paymentId: string): Promise await updatePayment(ctPayment, action as PaymentUpdateAction[]); - if (molliePayment.status === PaymentStatus.canceled) { + if (molliePayment.status === PaymentStatus.canceled && molliePayment.method !== PaymentMethod.klarna) { await removeCartMollieCustomLineItem(ctPayment); } @@ -360,7 +365,7 @@ export const handlePaymentWebhook = async (paymentId: string): Promise }; export const getPaymentStatusUpdateAction = ( - ctTransactions: CTTransaction[], + ctTransactions: Transaction[], molliePayment: MPayment, ): UpdateAction[] => { const updateActions: UpdateAction[] = []; @@ -438,23 +443,34 @@ export const getPaymentStatusUpdateAction = ( logger.debug( `SCTM - getPaymentStatusUpdateAction - Capture payment triggered from Mollie paymentID: ${molliePaymentId}`, ); - updateActions.push( - setTransactionCustomField( - CustomFields.surchargeAndCapture.fields.shouldCapture.name, - true, - doCaptureInMollie.id as string, - ), - setTransactionCustomField( - CustomFields.surchargeAndCapture.fields.descriptionCapture.name, - 'Payment is captured from Mollie.', - doCaptureInMollie.id as string, - ), - setTransactionCustomField( - CustomFields.surchargeAndCapture.fields.captureErrors.name, - '', - doCaptureInMollie.id as string, - ), - ); + + if (doCaptureInMollie.hasSurchargeCost) { + updateActions.push( + setTransactionCustomField( + CustomFields.transactions.fields.shouldCapturePayment.name, + true, + doCaptureInMollie.id as string, + ), + setTransactionCustomField( + CustomFields.transactions.fields.capturePaymentDescription.name, + 'Payment is captured from Mollie.', + doCaptureInMollie.id as string, + ), + setTransactionCustomField( + CustomFields.transactions.fields.capturePaymentErrors.name, + '', + doCaptureInMollie.id as string, + ), + ); + } else { + updateActions.push( + setTransactionCustomType(doCaptureInMollie.id as string, getTransactionCustomTypeKey(), { + [CustomFields.transactions.fields.shouldCapturePayment.name]: true, + [CustomFields.transactions.fields.capturePaymentDescription.name]: 'Payment is captured from Mollie.', + [CustomFields.transactions.fields.capturePaymentErrors.name]: '', + }), + ); + } } return updateActions; @@ -588,7 +604,7 @@ export const getCreatePaymentUpdateAction = async ( if (surchargeAmountInCent > 0) { // Add surcharge amount to the custom field of the transaction actions.push( - setTransactionCustomType(originalTransaction.id, CustomFields.surchargeAndCapture.typeKey, { + setTransactionCustomType(originalTransaction.id, getTransactionCustomTypeKey(), { surchargeAmountInCent, }), ); @@ -609,7 +625,7 @@ export const handleCreateRefund = async (ctPayment: Payment): Promise transaction.type === CTTransactionType.Refund && transaction.state === CTTransactionState.Initial, ); - if (initialRefundTransaction?.custom?.fields[CustomFields.transactionRefundForMolliePayment]) { + if (initialRefundTransaction?.custom?.fields[CustomFields.transactions.fields.molliePaymentIdToRefund.name]) { logger.debug('SCTM - handleCreateRefund - creating a refund with specific payment id'); successChargeTransaction = ctPayment.transactions.find( @@ -617,7 +633,7 @@ export const handleCreateRefund = async (ctPayment: Payment): Promise { - const transactionCustomFieldName = CustomFields?.paymentCancelReason; + const transactionCustomFieldName = CustomFields?.transactions?.fields?.cancelPaymentReasonText.name; const newTransactionCustomFieldValue = { - reasonText: triggerTransaction?.custom?.fields?.reasonText, - statusText: CancelStatusText, + [CustomFields.transactions.fields.cancelPaymentReasonText.name]: triggerTransaction?.custom?.fields?.reasonText, + [CustomFields.transactions.fields.cancelPaymentStatusText.name]: CancelStatusText, }; // Update transaction state to failure @@ -797,7 +813,7 @@ export const getPaymentCancelActions = (targetTransaction: Transaction, triggerT // Set transaction custom field value if (transactionCustomFieldName) { actions.push( - setTransactionCustomType(targetTransaction?.id, transactionCustomFieldName, newTransactionCustomFieldValue), + setTransactionCustomType(targetTransaction?.id, getTransactionCustomTypeKey(), newTransactionCustomFieldValue), ); } @@ -873,22 +889,13 @@ export const handleCancelPayment = async (ctPayment: Payment): Promise transaction.type === CTTransactionType.Charge && (transaction.state === CTTransactionState.Pending || transaction.state === CTTransactionState.Failure) && - transaction.custom?.fields?.[CustomFields.surchargeAndCapture.fields.shouldCapture.name], + transaction.custom?.fields?.[CustomFields.transactions.fields.shouldCapturePayment.name], ); if (!pendingChargeTransaction) { @@ -963,7 +970,7 @@ export const handleCapturePayment = async (ctPayment: Payment): Promise; +export type DoCapturePaymentFromMollie = { answer: boolean; hasSurchargeCost: boolean; id?: string }; diff --git a/processor/src/types/index.types.ts b/processor/src/types/index.types.ts index 7bdf906..5aa6ba0 100644 --- a/processor/src/types/index.types.ts +++ b/processor/src/types/index.types.ts @@ -25,6 +25,7 @@ export type ConnectorEnvVars = { authMode: string; sessionAudience: string; sessionIssuer: string; + transactionCustomTypeKey: string; }; mollie: { testApiKey: string; diff --git a/processor/src/utils/config.utils.ts b/processor/src/utils/config.utils.ts index 8f0515e..d3696f5 100644 --- a/processor/src/utils/config.utils.ts +++ b/processor/src/utils/config.utils.ts @@ -2,6 +2,7 @@ import { ConnectorEnvVars } from '../types/index.types'; import CustomError from '../errors/custom.error'; import envValidators from '../validators/env.validators'; import { getValidateMessages } from '../validators/helpers.validators'; +import { CustomFields } from './constant.utils'; /** * Read the configuration env vars * (Add yours accordingly) @@ -20,6 +21,7 @@ export const readConfiguration = () => { authMode: process.env.AUTHENTICATION_MODE as string, sessionAudience: (process.env.CTP_SESSION_AUDIENCE as string) || 'https://mc.europe-west1.gcp.commercetools.com', sessionIssuer: (process.env.CTP_SESSION_ISSUER as string) || 'gcp-eu', + transactionCustomTypeKey: process.env.CTP_TRANSACTION_CUSTOM_TYPE_KEY as string, }, mollie: { testApiKey: process.env.MOLLIE_API_TEST_KEY as string, @@ -48,3 +50,11 @@ export const getApiKey = (): string => { } return process.env.MOLLIE_API_LIVE_KEY as string; }; + +export const getTransactionCustomTypeKey = (): string => { + if (process.env.CTP_TRANSACTION_CUSTOM_TYPE_KEY) { + return process.env.CTP_TRANSACTION_CUSTOM_TYPE_KEY; + } + + return CustomFields.transactions.defaultCustomTypeKey; +}; diff --git a/processor/src/utils/constant.utils.ts b/processor/src/utils/constant.utils.ts index 7d74bfb..616acb2 100644 --- a/processor/src/utils/constant.utils.ts +++ b/processor/src/utils/constant.utils.ts @@ -31,50 +31,102 @@ export const CustomFields = { }, }, }, - paymentCancelReason: 'sctm_payment_cancel_reason', applePay: { session: { request: 'sctm_apple_pay_session_request', response: 'sctm_apple_pay_session_response', }, }, - transactionSurchargeCost: 'sctm_transaction_surcharge_cost', - transactionRefundForMolliePayment: 'sctm_transaction_refund_for_mollie_payment', - surchargeAndCapture: { - typeKey: 'sctm_transaction_surcharge_and_capture', + transactions: { + defaultCustomTypeKey: 'sctm_transactions_custom_type', + resourceTypeId: 'transaction', name: { - en: '(SCTM) Transaction surcharge & capture control', - de: '(SCTM) Transaktionszuschlag & Erfassungskontrolle', + en: '(SCTM) Custom transaction type', + de: '(SCTM) Benutzerdefinierter Transaktionstyp', }, - resourceTypeId: 'transaction', fields: { - surchargeCode: { + cancelPaymentReasonText: { + name: 'reasonText', + label: { + en: 'The reason of cancelling the refund, include the user name', + de: 'Der Grund für die Stornierung der Rückerstattung, den Benutzernamen einschließen', + }, + required: false, + type: { + name: 'String', + }, + inputHint: 'MultiLine', + }, + cancelPaymentStatusText: { + name: 'statusText', + label: { + en: 'To differentiate between the “failure” from CommerceTools and the real status', + de: 'Um zwischen dem „Fehler“ von CommerceTools und dem tatsächlichen Status zu unterscheiden', + }, + required: false, + type: { + name: 'String', + }, + inputHint: 'MultiLine', + }, + molliePaymentIdToRefund: { + name: 'sctm_transaction_refund_for_mollie_payment', + label: { + en: 'Identify the Mollie payment which is being refunded by its ID', + de: 'Identifizieren Sie die Mollie-Zahlung, die zurückerstattet wird durch seine ID', + }, + required: false, + type: { + name: 'String', + }, + inputHint: 'MultiLine', + }, + surchargeCost: { name: 'surchargeAmountInCent', label: { en: 'Total surcharge amount in cent', de: 'Gesamtbetrag des Zuschlags in Cent', }, + required: false, + type: { + name: 'Number', + }, + inputHint: 'MultiLine', }, - shouldCapture: { + shouldCapturePayment: { name: 'sctm_should_capture', label: { en: 'Should capture money for this transaction', de: 'Soll das Geld für diese Transaktion eingezogen werden', }, + required: false, + type: { + name: 'Boolean', + }, }, - descriptionCapture: { + capturePaymentDescription: { name: 'sctm_capture_description', label: { en: 'Capture description', de: 'Beschreibung der Einziehung', }, + required: false, + type: { + name: 'String', + }, + inputHint: 'MultiLine', }, - captureErrors: { + capturePaymentErrors: { name: 'sctm_capture_errors', label: { en: 'Capture errors', de: 'Fehler bei der Einziehung', }, + required: false, + type: { + name: 'String', + }, + inputHint: 'MultiLine', }, }, }, diff --git a/processor/src/utils/paymentAction.utils.ts b/processor/src/utils/paymentAction.utils.ts index 12b9ff6..561cd35 100644 --- a/processor/src/utils/paymentAction.utils.ts +++ b/processor/src/utils/paymentAction.utils.ts @@ -32,7 +32,7 @@ const getTransactionGroups = (transactions: Transaction[]) => { break; case CTTransactionState.Pending: if (transaction.type === CTTransactionType.Charge) { - transaction.custom?.fields?.[CustomFields.surchargeAndCapture.fields.shouldCapture.name] + transaction.custom?.fields?.[CustomFields.transactions.fields.shouldCapturePayment.name] ? groups.pendingCapture.push(transaction) : groups.pendingCharge.push(transaction); } else if (transaction.type === CTTransactionType.Refund) { @@ -49,8 +49,8 @@ const getTransactionGroups = (transactions: Transaction[]) => { case CTTransactionState.Failure: if ( (transaction.type === CTTransactionType.Charge && - transaction.custom?.fields?.[CustomFields.surchargeAndCapture.fields.captureErrors.name]?.length >= 1) || - transaction.custom?.fields?.[CustomFields.surchargeAndCapture.fields.shouldCapture.name] + transaction.custom?.fields?.[CustomFields.transactions.fields.capturePaymentErrors.name]?.length >= 1) || + transaction.custom?.fields?.[CustomFields.transactions.fields.shouldCapturePayment.name] ) { groups.failureCapture.push(transaction); } @@ -77,7 +77,11 @@ const determineAction = (groups: ReturnType): Deter return ConnectorActions.CreatePayment; } - if (groups.successAuthorization.length === 1 && groups.initialCancelAuthorization.length === 1) { + if ( + groups.successAuthorization.length === 1 && + groups.initialCancelAuthorization.length === 1 && + groups.pendingRefund.length !== 1 + ) { return ConnectorActions.CancelPayment; } diff --git a/processor/src/validators/env.validators.ts b/processor/src/validators/env.validators.ts index 185e108..f1a31ec 100644 --- a/processor/src/validators/env.validators.ts +++ b/processor/src/validators/env.validators.ts @@ -122,6 +122,12 @@ const envValidators = [ max: 1, }, ), + + optional(standardString)(['transactionCustomTypeKey'], { + code: 'InvalidTransactionCustomTypeKey', + message: 'Transaction custom type key should be a valid string.', + referencedBy: 'environmentVariables', + }), ]; export default envValidators; diff --git a/processor/tests/commercetools/action.commercetools.spec.ts b/processor/tests/commercetools/action.commercetools.spec.ts index 5563bb1..4906ce4 100644 --- a/processor/tests/commercetools/action.commercetools.spec.ts +++ b/processor/tests/commercetools/action.commercetools.spec.ts @@ -185,7 +185,7 @@ describe('Test actions.utils.ts', () => { }); test('should be able to return the correct setTransactionCustomField action', () => { - const name = CustomFields.surchargeAndCapture.fields.surchargeCode.name; + const name = CustomFields.transactions.fields.surchargeCost.name; const surchargeInCentAmount = { surchargeInCentAmount: 12345, }; diff --git a/processor/tests/controllers/payment.controller.spec.ts b/processor/tests/controllers/payment.controller.spec.ts index b6186f0..0d9150b 100644 --- a/processor/tests/controllers/payment.controller.spec.ts +++ b/processor/tests/controllers/payment.controller.spec.ts @@ -388,7 +388,7 @@ describe('Test payment.controller.ts', () => { { action: 'setTransactionCustomField', transactionId: 'tr_123456', - name: CustomFieldName.paymentCancelReason, + name: CustomFieldName.transactions.fields.cancelPaymentReasonText.name, value: transactionCustomFieldValue, }, ], @@ -504,7 +504,7 @@ describe('Test payment.controller.ts', () => { { action: 'setTransactionCustomField', transactionId: 'tr_123456', - name: CustomFieldName.paymentCancelReason, + name: CustomFieldName.transactions.fields.cancelPaymentReasonText.name, value: transactionCustomFieldValue, }, ], diff --git a/processor/tests/mollie/payment.mollie.spec.ts b/processor/tests/mollie/payment.mollie.spec.ts index d7a5033..e32a220 100644 --- a/processor/tests/mollie/payment.mollie.spec.ts +++ b/processor/tests/mollie/payment.mollie.spec.ts @@ -6,6 +6,7 @@ import { getPaymentById, listPaymentMethods, getApplePaySession, + releaseAuthorizationPayment, } from '../../src/mollie/payment.mollie'; import { MollieApiError, PaymentCreateParams } from '@mollie/api-client'; import { logger } from '../../src/utils/logger.utils'; @@ -543,3 +544,118 @@ describe('getApplePaySession', () => { } }); }); + +describe('releaseAuthorizationPayment', () => { + afterEach(() => { + jest.clearAllMocks(); // Clear all mocks after each test + }); + + it('should call release authorized payment with molliePaymentId', async () => { + const molliePaymentId = 'tr_test'; + + const createPaymentEndpoint = `https://api.mollie.com/v2/payments/${molliePaymentId}/release-authorization`; + const headers = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${getApiKey()}`, + versionStrings: MOLLIE_VERSION_STRINGS, + }; + + (fetch as unknown as jest.Mock).mockImplementation(async () => + Promise.resolve({ + json: () => Promise.resolve({ data: [] }), + headers: new Headers(), + ok: true, + redirected: false, + status: 202, + }), + ); + + await releaseAuthorizationPayment(molliePaymentId); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith(createPaymentEndpoint, { + method: 'POST', + headers, + }); + }); + + it('should throw error when calling release authorized payment with molliePaymentId', async () => { + const molliePaymentId = 'tr_test'; + + const createPaymentEndpoint = `https://api.mollie.com/v2/payments/${molliePaymentId}/release-authorization`; + const headers = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${getApiKey()}`, + versionStrings: MOLLIE_VERSION_STRINGS, + }; + + const errorMessage = + 'SCTM - releaseAuthorizationPayment - error: The pre-authorized payment has already been reversed'; + + (fetch as unknown as jest.Mock).mockImplementation(async () => { + throw new MollieApiError('The pre-authorized payment has already been reversed', { + field: '', + statusCode: 400, + }); + }); + + try { + await releaseAuthorizationPayment(molliePaymentId); + } catch (error: unknown) { + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith(createPaymentEndpoint, { + method: 'POST', + headers, + }); + + expect(logger.error).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledWith(errorMessage, { + error: new MollieApiError('The pre-authorized payment has already been reversed', { + field: '', + statusCode: 400, + }), + }); + + expect(error).toBeInstanceOf(CustomError); + expect((error as CustomError).statusCode).toBe(400); + expect((error as CustomError).message).toBe(errorMessage); + } + }); + + it('should throw a general error when an exception is thrown somewhere in the process', async () => { + const molliePaymentId = 'tr_test'; + + const createPaymentEndpoint = `https://api.mollie.com/v2/payments/${molliePaymentId}/release-authorization`; + const headers = { + 'Content-Type': 'application/json', + Authorization: `Bearer ${getApiKey()}`, + versionStrings: MOLLIE_VERSION_STRINGS, + }; + + const errorMessage = + 'SCTM - releaseAuthorizationPayment - Failed to release authorization payment with unknown errors'; + + const generalError = new Error('General error'); + + (fetch as unknown as jest.Mock).mockImplementation(async () => { + throw generalError; + }); + + try { + await releaseAuthorizationPayment(molliePaymentId); + } catch (error: any) { + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith(createPaymentEndpoint, { + method: 'POST', + headers, + }); + + expect(logger.error).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledWith(errorMessage, { error: generalError }); + + expect(error).toBeInstanceOf(CustomError); + expect((error as CustomError).statusCode).toBe(400); + expect((error as CustomError).message).toBe(errorMessage); + } + }); +}); diff --git a/processor/tests/routes/processor.route.spec.ts b/processor/tests/routes/processor.route.spec.ts index a41e467..974e372 100644 --- a/processor/tests/routes/processor.route.spec.ts +++ b/processor/tests/routes/processor.route.spec.ts @@ -6,9 +6,7 @@ import { createPaymentExtension, deletePaymentExtension } from '../../src/commer import { createCustomPaymentType, createCustomPaymentInterfaceInteractionType, - createCustomPaymentTransactionCancelReasonType, - createTransactionSurchargeCustomType, - createTransactionRefundForMolliePaymentCustomType, + createCustomTransactionType, } from '../../src/commercetools/customFields.commercetools'; import { getAccessToken } from '../../src/commercetools/auth.commercetools'; @@ -20,9 +18,7 @@ jest.mock('../../src/commercetools/extensions.commercetools', () => ({ jest.mock('../../src/commercetools/customFields.commercetools', () => ({ createCustomPaymentType: jest.fn(), createCustomPaymentInterfaceInteractionType: jest.fn(), - createCustomPaymentTransactionCancelReasonType: jest.fn(), - createTransactionSurchargeCustomType: jest.fn(), - createTransactionRefundForMolliePaymentCustomType: jest.fn(), + createCustomTransactionType: jest.fn(), })); jest.mock('../../src/commercetools/auth.commercetools', () => ({ @@ -155,13 +151,12 @@ describe('Test src/route/processor.route.ts', () => { //@ts-expect-error handler should be always available const handler = layer.route.stack[0].handle; + expect(handler).toBeDefined(); + (createPaymentExtension as jest.Mock).mockReturnValueOnce(Promise.resolve()); (createCustomPaymentType as jest.Mock).mockReturnValueOnce(Promise.resolve()); (createCustomPaymentInterfaceInteractionType as jest.Mock).mockReturnValueOnce(Promise.resolve()); - (createCustomPaymentTransactionCancelReasonType as jest.Mock).mockReturnValueOnce(Promise.resolve()); - (createTransactionSurchargeCustomType as jest.Mock).mockReturnValueOnce(Promise.resolve()); - (createTransactionRefundForMolliePaymentCustomType as jest.Mock).mockReturnValueOnce(Promise.resolve()); - (createTransactionRefundForMolliePaymentCustomType as jest.Mock).mockReturnValueOnce(Promise.resolve()); + (createCustomTransactionType as jest.Mock).mockReturnValueOnce(Promise.resolve()); (getAccessToken as jest.Mock).mockReturnValueOnce(Promise.resolve()); diff --git a/processor/tests/service/payment.service.spec.ts b/processor/tests/service/payment.service.spec.ts index e4d98ba..c9c7443 100644 --- a/processor/tests/service/payment.service.spec.ts +++ b/processor/tests/service/payment.service.spec.ts @@ -39,6 +39,7 @@ import { getPaymentById, listPaymentMethods, createCapturePayment, + releaseAuthorizationPayment, } from '../../src/mollie/payment.mollie'; import { cancelPaymentRefund, createPaymentRefund, getPaymentRefund } from '../../src/mollie/refund.mollie'; import CustomError from '../../src/errors/custom.error'; @@ -93,6 +94,7 @@ jest.mock('../../src/mollie/payment.mollie', () => ({ getApplePaySession: jest.fn(), createPaymentWithCustomMethod: jest.fn(), createCapturePayment: jest.fn(), + releaseAuthorizationPayment: jest.fn(), })); jest.mock('../../src/mollie/refund.mollie', () => ({ @@ -1212,7 +1214,7 @@ describe('Test getCreatePaymentUpdateAction', () => { expect(actual[4]).toEqual({ action: 'setTransactionCustomType', type: { - key: 'sctm_transaction_surcharge_and_capture', + key: CustomFieldName.transactions.defaultCustomTypeKey, }, fields: { surchargeAmountInCent: 1000, @@ -1429,7 +1431,7 @@ describe('Test handleCreatePayment', () => { { action: 'setTransactionCustomType', type: { - key: 'sctm_transaction_surcharge_and_capture', + key: CustomFieldName.transactions.defaultCustomTypeKey, }, fields: { surchargeAmountInCent: 1020, @@ -1517,11 +1519,11 @@ describe('Test handleCreateRefund', () => { { action: 'setTransactionCustomType', type: { - key: CustomFieldName.transactionRefundForMolliePayment, + key: CustomFieldName.transactions.defaultCustomTypeKey, }, transactionId: 'test_refund', fields: { - [CustomFieldName.transactionRefundForMolliePayment]: 'tr_123123', + [CustomFieldName.transactions.fields.molliePaymentIdToRefund.name]: 'tr_123123', }, }, { @@ -1624,11 +1626,11 @@ describe('Test handleCreateRefund', () => { { action: 'setTransactionCustomType', type: { - key: CustomFieldName.transactionRefundForMolliePayment, + key: CustomFieldName.transactions.defaultCustomTypeKey, }, transactionId: 'test_refund', fields: { - [CustomFieldName.transactionRefundForMolliePayment]: targetedMolliePaymentId, + [CustomFieldName.transactions.fields.molliePaymentIdToRefund.name]: targetedMolliePaymentId, }, }, { @@ -1700,7 +1702,7 @@ describe('Test handleCreateRefund', () => { id: 'custom-type-id', }, fields: { - [CustomFieldName.transactionRefundForMolliePayment]: targetedMolliePaymentId, + [CustomFieldName.transactions.fields.molliePaymentIdToRefund.name]: targetedMolliePaymentId, }, }, }, @@ -1851,7 +1853,7 @@ describe('Test getPaymentCancelActions', () => { expect(actual[2]).toEqual({ action: 'setTransactionCustomType', type: { - key: CustomFieldName.paymentCancelReason, + key: CustomFieldName.transactions.defaultCustomTypeKey, }, transactionId: CTPayment.transactions[0].id, fields: { @@ -2091,7 +2093,7 @@ describe('Test handlePaymentCancelRefund', () => { id: 'custom-type', }, fields: { - [CustomFieldName.transactionRefundForMolliePayment]: 'tr_123123', + [CustomFieldName.transactions.fields.molliePaymentIdToRefund.name]: 'tr_123123', }, }, }, @@ -2236,7 +2238,7 @@ describe('Test handlePaymentCancelRefund', () => { id: 'custom-type', }, fields: { - [CustomFieldName.transactionRefundForMolliePayment]: 'tr_123123', + [CustomFieldName.transactions.fields.molliePaymentIdToRefund.name]: 'tr_123123', }, }, }, @@ -2462,7 +2464,7 @@ describe('Test handlePaymentWebhook', () => { { action: 'setTransactionCustomType', type: { - key: 'sctm_payment_cancel_reason', + key: CustomFieldName.transactions.defaultCustomTypeKey, }, fields: { reasonText: ctPayment.transactions[1].custom?.fields.reasonText, @@ -2759,6 +2761,54 @@ describe('Test handleCancelPayment', () => { actions: [], }); }); + + it('should return status code for release klarna authorization payment', async () => { + const cartService = require('../../src/service/cart.service'); + + jest.spyOn(cartService, 'removeCartMollieCustomLineItem'); + + const molliePayment: molliePayment = { + resource: 'payment', + id: 'tr_7UhSN1zuXS', + mode: 'live', + amount: { + value: '10.00', + currency: 'EUR', + }, + description: 'Order #12345', + sequenceType: 'oneoff', + redirectUrl: 'https://webshop.example.org/order/12345/', + webhookUrl: 'https://webshop.example.org/payments/webhook/', + metadata: '{"order_id":12345}', + profileId: 'pfl_QkEhN94Ba', + status: 'authorized', + method: 'klarna', + createdAt: '2024-03-20T09:13:37+00:00', + expiresAt: '2024-03-20T09:28:37+00:00', + _links: { + checkout: { + href: 'https://www.mollie.com/checkout/select-method/7UhSN1zuXS', + type: 'text/html', + }, + }, + } as molliePayment; + + (getPaymentById as jest.Mock).mockReturnValueOnce(molliePayment); + + (releaseAuthorizationPayment as jest.Mock).mockReturnValueOnce(molliePayment); + + const actual = await handleCancelPayment(CTPayment); + + expect(getPaymentById).toBeCalledTimes(1); + expect(getPaymentById).toBeCalledWith(CTPayment.transactions[0].interactionId); + expect(releaseAuthorizationPayment).toBeCalledTimes(1); + expect(releaseAuthorizationPayment).toBeCalledWith(molliePayment.id); + + expect(actual).toEqual({ + statusCode: 200, + actions: [], + }); + }); }); describe('Test handleGetApplePaySession', () => { @@ -3017,13 +3067,13 @@ describe('Test handleGetApplePaySession', () => { interactionId: 'tr_7UhSN1zuXS', custom: { fields: { - sctm_should_capture: true, + [CustomFieldName.transactions.fields.shouldCapturePayment.name]: true, }, } as unknown as CustomFields, }, { id: '5c8b0375-305a-4f19-ae8e-07806b101299', - type: 'Athorization', + type: 'Authorization', amount: { type: 'centPrecision', currencyCode: 'EUR', @@ -3193,7 +3243,7 @@ describe('Test handleGetApplePaySession', () => { interactionId: 'tr_7UhSN1zuXS', custom: { fields: { - sctm_should_capture: true, + [CustomFieldName.transactions.fields.shouldCapturePayment.name]: true, }, } as unknown as CustomFields, }, @@ -3291,7 +3341,7 @@ describe('Test handleGetApplePaySession', () => { interactionId: 'tr_7UhSN2zuXS', custom: { fields: { - sctm_should_capture: true, + [CustomFieldName.transactions.fields.shouldCapturePayment.name]: true, }, } as unknown as CustomFields, }, @@ -3379,7 +3429,7 @@ describe('Test handleGetApplePaySession', () => { interactionId: 'tr_7UhSN2zuXS', custom: { fields: { - sctm_should_capture: true, + [CustomFieldName.transactions.fields.shouldCapturePayment.name]: true, }, } as unknown as CustomFields, }, @@ -3467,7 +3517,7 @@ describe('Test handleGetApplePaySession', () => { interactionId: 'tr_7UhSN2zuXS', custom: { fields: { - sctm_should_capture: true, + [CustomFieldName.transactions.fields.shouldCapturePayment.name]: true, }, } as unknown as CustomFields, }, @@ -3532,7 +3582,7 @@ describe('Test handleGetApplePaySession', () => { actions: [ { action: 'setTransactionCustomField', - name: 'sctm_capture_errors', + name: CustomFieldName.transactions.fields.capturePaymentErrors.name, value: '{"errorMessage":"Capture failed","submitData":{"paymentId":"tr_7UhSN2zuXS","amount":{"value":"10.00","currency":"EUR"},"description":""}}', transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', diff --git a/processor/tests/utils/config.utils.spec.ts b/processor/tests/utils/config.utils.spec.ts index 0ebfe24..5ea714c 100644 --- a/processor/tests/utils/config.utils.spec.ts +++ b/processor/tests/utils/config.utils.spec.ts @@ -16,6 +16,7 @@ describe('Test src/utils/config.utils.ts', () => { authMode: process.env.AUTHENTICATION_MODE, sessionAudience: process.env.CTP_SESSION_AUDIENCE, sessionIssuer: process.env.CTP_SESSION_ISSUER, + transactionCustomTypeKey: process.env.CTP_TRANSACTION_CUSTOM_TYPE_KEY, }, mollie: { liveApiKey: process.env.MOLLIE_API_LIVE_KEY, @@ -76,4 +77,25 @@ describe('Test src/utils/config.utils.ts', () => { process.env.AUTHENTICATION_MODE = 'dummy'; expect(() => readConfiguration()).toThrow(CustomError); }); + + test('should return CTP_TRANSACTION_CUSTOM_TYPE_KEY', () => { + process.env.CTP_CLIENT_ID = '123456789012345678901234'; + process.env.CTP_CLIENT_SECRET = '12345678901234567890123456789012'; + process.env.CTP_PROJECT_KEY = 'custom_type_key'; + process.env.CTP_SCOPE = 'custom_type_key'; + process.env.CTP_REGION = 'europe-west1.gcp'; + process.env.CTP_AUTH_URL = 'custom_type_key'; + process.env.AUTHENTICATION_MODE = '0'; + process.env.CTP_SESSION_AUDIENCE = 'custom_type_key'; + process.env.CTP_SESSION_ISSUER = 'custom_type_key'; + process.env.CTP_TRANSACTION_CUSTOM_TYPE_KEY = 'custom_type_key'; + process.env.MOLLIE_API_LIVE_KEY = 'custom_type_key'; + process.env.MOLLIE_API_TEST_KEY = 'custom_type_key'; + process.env.CONNECTOR_MODE = 'test'; + process.env.DEBUG = '0'; + process.env.MOLLIE_PROFILE_ID = 'custom_type_key'; + const config = readConfiguration(); + + expect(config.commerceTools.transactionCustomTypeKey).toBe('custom_type_key'); + }); }); diff --git a/processor/tests/utils/constant.utils.spec.ts b/processor/tests/utils/constant.utils.spec.ts index a9397f2..2ecde63 100644 --- a/processor/tests/utils/constant.utils.spec.ts +++ b/processor/tests/utils/constant.utils.spec.ts @@ -37,7 +37,16 @@ describe('Test constant.utils.ts', () => { expect(CustomFields?.createPayment?.request).toBeDefined(); expect(CustomFields?.createPayment?.interfaceInteraction).toBeDefined(); - expect(CustomFields?.paymentCancelReason).toBeDefined(); + expect(CustomFields?.transactions.defaultCustomTypeKey).toBeDefined(); + expect(CustomFields?.transactions.resourceTypeId).toBeDefined(); + expect(CustomFields?.transactions.name).toBeDefined(); + expect(CustomFields?.transactions.fields.cancelPaymentReasonText.name).toBeDefined(); + expect(CustomFields?.transactions.fields.cancelPaymentStatusText.name).toBeDefined(); + expect(CustomFields?.transactions.fields.molliePaymentIdToRefund.name).toBeDefined(); + expect(CustomFields?.transactions.fields.surchargeCost.name).toBeDefined(); + expect(CustomFields?.transactions.fields.shouldCapturePayment.name).toBeDefined(); + expect(CustomFields?.transactions.fields.capturePaymentDescription.name).toBeDefined(); + expect(CustomFields?.transactions.fields.capturePaymentErrors.name).toBeDefined(); expect(CustomFields?.applePay?.session?.request).toBeDefined(); expect(CustomFields?.applePay?.session?.response).toBeDefined();