diff --git a/CHANGELOG.md b/CHANGELOG.md index fbb6cdf..128c500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,24 @@ 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.2.3 + +Added + +- Capture payment feature + +## v1.2.2 + +Fixed + +- Bugs fixing for custom application unit tests + +## v1.2.1 + +Updated + +- Move setting of credit card component visibility and bank transfer payment method due date into the custom application + ## v1.2.0 Added @@ -45,7 +63,7 @@ Added - New custom field for transaction: `sctm_transaction_refund_for_mollie_payment` which would store the Mollie Payment ID that need to be refunded -Fixes +Fixed [Create Refund](./docs/CreateRefund.md) - Handling the Refund Creation for the case that the Payment has more than one Success Charge transaction @@ -251,7 +269,7 @@ Added ## v1.1.1 -Fixes +Fixed - Type converting issue in payment method listing endpoint @@ -278,7 +296,7 @@ Added ## v1.0.2 -Fixes +Fixed - Fix the issue that the payment method is not correctly set in some cases diff --git a/application/src/components/method-details/availability/details-form.tsx b/application/src/components/method-details/availability/details-form.tsx index 0d1d341..d032172 100644 --- a/application/src/components/method-details/availability/details-form.tsx +++ b/application/src/components/method-details/availability/details-form.tsx @@ -67,6 +67,7 @@ const AvailabilityDetailsForm = (props: TAvailabilityDetailsFormProps) => { const [selectedCurrency, setSelectedCurrency] = useState( projectCurrencies[0] as TCurrencyCode ); + const [countryOptions, setCountryOptions] = useState< { value: string; label: string }[] >( diff --git a/application/src/components/method-details/availability/details.tsx b/application/src/components/method-details/availability/details.tsx index 59acffc..6f028c6 100644 --- a/application/src/components/method-details/availability/details.tsx +++ b/application/src/components/method-details/availability/details.tsx @@ -86,7 +86,7 @@ const AvailabilityDetails = (props: TAvailabilityDetailFormProps) => { maxAmount: maxAmount?.toString(), surchargeCost: { percentageAmount: surchargeCost?.percentageAmount ?? 0, - fixedAmount: surchargeCost?.fixedAmount.toString() ?? '0', + fixedAmount: surchargeCost?.fixedAmount?.toString() ?? '0', }, }; diff --git a/application/src/components/method-details/availability/list.spec.tsx b/application/src/components/method-details/availability/list.spec.tsx index 8b1c0c7..b21b13e 100644 --- a/application/src/components/method-details/availability/list.spec.tsx +++ b/application/src/components/method-details/availability/list.spec.tsx @@ -1,39 +1,62 @@ import { - screen, render, + screen, } from '@commercetools-frontend/application-shell/test-utils'; import { Suspense } from 'react'; import { MemoryRouter } from 'react-router'; import { IntlProvider } from 'react-intl'; import AvailabilityList from './list'; import { TFetchCustomObjectDetailsQuery } from '../../../types/generated/ctp'; +import { useApplicationContext } from '@commercetools-frontend/application-shell-connectors'; +import { useMcMutation } from '@commercetools-frontend/application-shell'; +import { useShowNotification } from '@commercetools-frontend/actions-global'; + +jest.mock('@commercetools-frontend/application-shell-connectors', () => ({ + useApplicationContext: jest.fn(), +})); +jest.mock('@commercetools-frontend/application-shell', () => ({ + useMcMutation: jest.fn(), +})); +jest.mock('@commercetools-frontend/actions-global', () => ({ + useShowNotification: jest.fn(), +})); describe('test MethodDetails.tsx', () => { it('test render', async () => { const pricingConstraints = [ { id: 1, - currency: 'GBP1', - country: 'UK1', + currencyCode: 'GBP1', + countryCode: 'UK1', minAmount: 100, maxAmount: 2000, - surchargeCost: '2%', + surchargeCost: { + percentageAmount: 2, + renderedText: '2%', + }, }, { id: 2, - currency: 'GBP2', - country: 'DE2', + currencyCode: 'GBP2', + countryCode: 'DE2', minAmount: 1000, maxAmount: 30000, - surchargeCost: '4%', + surchargeCost: { + percentageAmount: 4, + renderedText: '4%', + }, }, { id: 3, - currency: 'EUR3', - country: 'DE3', + currencyCode: 'EUR3', + countryCode: 'DE3', minAmount: 500, maxAmount: 10000, - surchargeCost: '2 % + € 0,35', + surchargeCost: { + percentageAmount: 2, + fixedAmount: 100, + renderedText: '2% + 100EUR3', + }, }, ]; @@ -62,7 +85,23 @@ describe('test MethodDetails.tsx', () => { } as unknown as string, }; - render( + (useApplicationContext as jest.Mock).mockReturnValue({ + dataLocale: 'de', + projectLanguages: ['de'], + projectCurrencies: ['GBP1', 'GBP2', 'EUR3'], + projectCountries: ['UK1', 'DE2', 'DE3'], + }); + + (useMcMutation as jest.Mock).mockReturnValue([ + jest.fn().mockResolvedValue({}), + { + loading: false, + }, + ]); + + (useShowNotification as jest.Mock).mockReturnValue(jest.fn()); + + const result = render( Loading...}> { ); pricingConstraints.forEach( - ({ currency, country, minAmount, maxAmount, surchargeCost }) => { - expect(screen.getByText(currency)).not.toBeNull(); - expect(screen.getByText(country)).not.toBeNull(); + ({ currencyCode, countryCode, minAmount, maxAmount, surchargeCost }) => { + expect(screen.getByText(currencyCode)).not.toBeNull(); + expect(screen.getByText(countryCode)).not.toBeNull(); expect(screen.getByText(minAmount)).not.toBeNull(); expect(screen.getByText(maxAmount)).not.toBeNull(); - expect(screen.getByText(surchargeCost)).not.toBeNull(); + expect(screen.getByText(surchargeCost.renderedText)).not.toBeNull(); } ); }); diff --git a/application/src/components/method-details/method-details.spec.tsx b/application/src/components/method-details/method-details.spec.tsx index b3bc91a..119cfb6 100644 --- a/application/src/components/method-details/method-details.spec.tsx +++ b/application/src/components/method-details/method-details.spec.tsx @@ -81,6 +81,8 @@ describe('Test method-details.tsx', () => { (useApplicationContext as jest.Mock).mockReturnValue({ dataLocale: 'de', projectLanguages: ['de'], + projectCurrencies: ['DE'], + projectCountries: ['EUR'], }); (useShowNotification as jest.Mock).mockReturnValue(jest.fn()); diff --git a/docs/CapturePayment.md b/docs/CapturePayment.md new file mode 100644 index 0000000..ebc94bb --- /dev/null +++ b/docs/CapturePayment.md @@ -0,0 +1,159 @@ +# Capture Payment + +* [Overview](#overview) +* [Conditions](#conditions) +* [Transactions' custom fields](#transactions-custom-fields) +* [Example payload](#example-payload) +* [Example response](#example-response) + +## Overview + +This feature is to capture the money of an authorized Mollie payment and update transactions with correct status and information. + +It calls Mollie [Capture API](https://docs.mollie.com/reference/captures-api) eventually. + +To trigger the capture for a certain payment, simply update the target pending transaction of type `Charge` with our predefined custom type named `sctm_capture_payment_request` + +| **Custom fields** | **Required** | **Description** | +| --- | --- | --- | +| `sctm_should_capture` | false | WHEN its value set to `true`, the Processor will start to capture with associated payment information | +| `sctm_capture_description` | false | Hold the description of the capture | + +## Conditions + +1. A valid authorized CT payment must contain the two follow transactions: + * One with type of `authorization` and state `success` + * One with type of `charge` and state `pending` | `failure` with (in case previous capture attempt failed and we want to retry) with custom field `sctm_should_capture` = `true` +2. The ref Mollie payment must have `captureMode` equal to `manual` and `state` equal to `authorized` + +## Transactions' custom fields + +| Fields | Data type | Required | Usage | +|-----------------------------------------------------------------------------------------------------------|----------------------------------------------|----------|------------------| +| `sctm_should_capture` | `boolean` | NO | If `true`, trigger the capture process | +| `sctm_capture_description` | `string` | NO | Contain the description for the capture | + +## Example payload + +```json +ENDPOINT: `https://api.europe-west1.gcp.commercetools.com/{{your_project_key}}/payments/{{payment_id_for_capture}}` +{ + "version": {{capture_payment_version}}, + "actions": [ + { + "action" : "setTransactionCustomType", + "type" : { + "id" : "{{capture_type_id}}", + "typeId" : "type" + }, + "fields" : { + "sctm_should_capture" : true, + "sctm_capture_description": "Capture description" + }, + "transactionId" : "{{capture_transaction_id}}" + } + ] +} +``` + +## Example response + +```json +{ + "id": "ba410236-3135-4037-b61e-10aa82a1ae99", + "version": 26, + "versionModifiedAt": "2025-02-17T08:12:45.109Z", + "lastMessageSequenceNumber": 6, + "createdAt": "2025-02-17T08:09:06.565Z", + "lastModifiedAt": "2025-02-17T08:12:45.109Z", + "lastModifiedBy": { + "clientId": "8a_rNd-HokSybRmRRqdEuh82", + "isPlatformClient": false + }, + "createdBy": { + "clientId": "8a_rNd-HokSybRmRRqdEuh82", + "isPlatformClient": false + }, + "amountPlanned": { + "type": "centPrecision", + "currencyCode": "EUR", + "centAmount": 11999, + "fractionDigits": 2 + }, + "paymentMethodInfo": { + "paymentInterface": "mollie", + "method": "creditcard", + "name": { + "en": "creditcard", + "de": "creditcard" + } + }, + "custom": { + "type": { + "typeId": "type", + "id": "53cdd626-7884-4421-9226-d81ba7038424" + }, + "fields": { + "sctm_payment_methods_request": "{\"locale\":\"de_DE\",\"billingCountry\":\"DE\",\"includeWallets\":\"applepay\"}", + "sctm_mollie_profile_id": "pfl_SPkYGiEQjf", + "sctm_payment_methods_response": "{\"count\":3,\"methods\":[{\"id\":\"creditcard\",\"name\":{\"en-GB\":\"Card\",\"de-DE\":\"Card\",\"en-US\":\"Card\"},\"description\":{\"en-GB\":\"\",\"de-DE\":\"\",\"en-US\":\"\"},\"image\":\"https://www.mollie.com/external/icons/payment-methods/creditcard.svg\",\"order\":20},{\"id\":\"applepay\",\"name\":{\"en-GB\":\"Apple Pay\",\"de-DE\":\"Apple Pay\",\"en-US\":\"Apple Pay\"},\"description\":{\"en-GB\":\"\",\"de-DE\":\"\",\"en-US\":\"\"},\"image\":\"https://www.mollie.com/external/icons/payment-methods/applepay.svg\",\"order\":0},{\"id\":\"banktransfer\",\"name\":{\"en-GB\":\"Bank transfer\",\"de-DE\":\"Bank transfer\",\"en-US\":\"Bank transfer\"},\"description\":{\"en-GB\":\"\",\"de-DE\":\"\",\"en-US\":\"\"},\"image\":\"https://www.mollie.com/external/icons/payment-methods/banktransfer.svg\",\"order\":0}]}", + "sctm_create_payment_request": "{\"description\":\"Testing creating Mollie payment\",\"redirectUrl\":\"http://localhost:3000/thank-you?orderId=ae22-e03f-aab1\",\"billingAddress\":{\"givenName\":\"thach\",\"familyName\":\"dang\",\"streetAndNumber\":\"Am campus 5\",\"postalCode\":\"48721\",\"city\":\"Gescher\",\"country\":\"DE\",\"phone\":\"49254287030\",\"email\":\"t.dang@shopmacher.de\"},\"shippingAddress\":{\"givenName\":\"thach\",\"familyName\":\"dang\",\"streetAndNumber\":\"Am campus 5\",\"postalCode\":\"48721\",\"city\":\"Gescher\",\"country\":\"DE\",\"phone\":\"49254287030\",\"email\":\"t.dang@shopmacher.de\"},\"billingEmail\":\"t.dang@shopmacher.de\",\"cardToken\":\"tkn_h3mrzMtest\",\"lines\":[{\"description\":\"Geometrischer Kissenbezug\",\"quantity\":1,\"quantityUnit\":\"pcs\",\"unitPrice\":{\"currency\":\"EUR\",\"value\":\"19.99\"},\"totalAmount\":{\"currency\":\"EUR\",\"value\":\"19.99\"}}],\"captureMode\":\"manual\"}" + } + }, + "paymentStatus": { + "interfaceText": "initial" + }, + "transactions": [ + { + "id": "0c80239c-ade7-4556-a871-f4317759be10", + "timestamp": "2025-02-17T08:09:36.000Z", + "type": "Charge", + "amount": { + "type": "centPrecision", + "currencyCode": "EUR", + "centAmount": 11999, + "fractionDigits": 2 + }, + "interactionId": "tr_mDmhmxTzkX", + "state": "Success", + "custom": { + "type": { + "typeId": "type", + "id": "418c568c-15a5-4418-9698-a81edcffc471" + }, + "fields": { + "sctm_should_capture": true, + "sctm_capture_description": "Capture on 2025-02-17T08:12:42.171Z" + } + } + }, + { + "id": "d39ed5fc-f8ae-405b-ae99-3f713d243da0", + "type": "Authorization", + "amount": { + "type": "centPrecision", + "currencyCode": "EUR", + "centAmount": 11999, + "fractionDigits": 2 + }, + "interactionId": "tr_mDmhmxTzkX", + "state": "Success" + } + ], + "interfaceInteractions": [ + { + "type": { + "typeId": "type", + "id": "9d9b436c-58cc-4f2e-a393-0fbd05ba0193" + }, + "fields": { + "sctm_id": "6e134208-fdfe-453f-928e-8ab44d9b10ec", + "sctm_action_type": "createPayment", + "sctm_created_at": "2025-02-17T08:09:36+00:00", + "sctm_request": "{\"transactionId\":\"0c80239c-ade7-4556-a871-f4317759be10\",\"paymentMethod\":\"creditcard\"}", + "sctm_response": "{\"molliePaymentId\":\"tr_mDmhmxTzkX\",\"checkoutUrl\":\"https://www.mollie.com/checkout/test-mode?method=creditcard&token=6.8hhrnm\",\"transactionId\":\"0c80239c-ade7-4556-a871-f4317759be10\"}" + } + } + ] +} +``` diff --git a/docs/postman/sctm_connector_v101.postman_collection.json b/docs/postman/sctm_connector_v123.postman_collection.json similarity index 62% rename from docs/postman/sctm_connector_v101.postman_collection.json rename to docs/postman/sctm_connector_v123.postman_collection.json index 68f72a5..4a00f79 100644 --- a/docs/postman/sctm_connector_v101.postman_collection.json +++ b/docs/postman/sctm_connector_v123.postman_collection.json @@ -1,10 +1,10 @@ { "info": { "_postman_id": "8fb1a9c2-1d84-49cb-8871-5c80b3d92e15", - "name": "SCTM Collection v1.0.1", + "name": "SCTM Collection v1.2.3", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_exporter_id": "14200932", - "_collection_link": "https://adventure-1591.postman.co/workspace/Adventure-Workspace~471ca30a-668a-4dde-88b2-8fa80e5dae88/collection/14200932-8fb1a9c2-1d84-49cb-8871-5c80b3d92e15?action=share&source=collection_link&creator=14200932" + "_collection_link": "https://mol-1591.postman.co/workspace/MOL~471ca30a-668a-4dde-88b2-8fa80e5dae88/collection/14200932-8fb1a9c2-1d84-49cb-8871-5c80b3d92e15?action=share&source=collection_link&creator=14200932" }, "item": [ { @@ -16,7 +16,6 @@ { "listen": "test", "script": { - "type": "text/javascript", "exec": [ "tests[\"Status code is 200\"] = responseCode.code === 200;", "var data = JSON.parse(responseBody);", @@ -36,7 +35,9 @@ " }", " }", "}" - ] + ], + "type": "text/javascript", + "packages": {} } } ], @@ -59,8 +60,8 @@ "method": "POST", "header": [], "body": { - "mode": "raw", - "raw": "" + "mode": "urlencoded", + "urlencoded": [] }, "url": { "raw": "{{auth_url}}/oauth/token?grant_type=client_credentials", @@ -144,7 +145,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"destination\" : {\n \"type\" : \"HTTP\",\n \"url\" : \"https://efd6-115-74-115-119.ngrok-free.app/processor\"\n },\n \"triggers\" : [ {\n \"resourceTypeId\" : \"payment\",\n \"actions\" : [ \"Create\", \"Update\" ]\n } ],\n \"key\" : \"sctm-payment-create-update-extension\"\n}" + "raw": "{\n \"destination\": {\n \"type\": \"HTTP\",\n \"url\": \"https://efd6-115-74-115-119.ngrok-free.app/processor\",\n \"authorization\": {\n \"type\": \"AuthorizationHeader\",\n \"headerValue\": \"_token_\"\n }\n },\n \"triggers\": [\n {\n \"resourceTypeId\": \"payment\",\n \"actions\": [\n \"Create\",\n \"Update\"\n ]\n }\n ],\n \"key\": \"sctm-payment-create-update-extension\"\n}" }, "url": { "raw": "{{host}}/{{project-key}}/extensions", @@ -414,9 +415,170 @@ "description": "null" }, "response": [] + }, + { + "name": "Get Extension by key", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "tests[\"Status code \" + responseCode.code] = responseCode.code === 200 || responseCode.code === 201;", + "var data = JSON.parse(responseBody);", + "if(data.results && data.results[0] && data.results[0].id && data.results[0].version){", + " pm.environment.set(\"extension-id\", data.results[0].id); ", + " pm.environment.set(\"extension-version\", data.results[0].version);", + "}", + "if(data.results && data.results[0] && data.results[0].key){", + " pm.environment.set(\"extension-key\", data.results[0].key); ", + "}", + "if(data.version){", + " pm.environment.set(\"extension-version\", data.version);", + "}", + "if(data.id){", + " pm.environment.set(\"extension-id\", data.id); ", + "}", + "if(data.key){", + " pm.environment.set(\"extension-key\", data.key);", + "}", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "oauth2", + "oauth2": [ + { + "key": "accessToken", + "value": "{{ctp_access_token}}", + "type": "string" + }, + { + "key": "addTokenTo", + "value": "header", + "type": "string" + }, + { + "key": "tokenType", + "value": "Bearer", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{host}}/{{project-key}}/extensions/key=sctm-payment-create-update-extension", + "host": [ + "{{host}}" + ], + "path": [ + "{{project-key}}", + "extensions", + "key=sctm-payment-create-update-extension" + ] + }, + "description": "null" + }, + "response": [] + }, + { + "name": "Update Extension Destination", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "tests[\"Status code \" + responseCode.code] = responseCode.code === 200 || responseCode.code === 201;", + "var data = JSON.parse(responseBody);", + "if(data.results && data.results[0] && data.results[0].id && data.results[0].version){", + " pm.environment.set(\"extension-id\", data.results[0].id); ", + " pm.environment.set(\"extension-version\", data.results[0].version);", + "}", + "if(data.results && data.results[0] && data.results[0].key){", + " pm.environment.set(\"extension-key\", data.results[0].key); ", + "}", + "if(data.version){", + " pm.environment.set(\"extension-version\", data.version);", + "}", + "if(data.id){", + " pm.environment.set(\"extension-id\", data.id); ", + "}", + "if(data.key){", + " pm.environment.set(\"extension-key\", data.key);", + "}", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "oauth2", + "oauth2": [ + { + "key": "accessToken", + "value": "{{ctp_access_token}}", + "type": "string" + }, + { + "key": "addTokenTo", + "value": "header", + "type": "string" + }, + { + "key": "tokenType", + "value": "Bearer", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"version\": {{YOUR_EXTENSION_VERSION}},\n \"actions\": [\n {\n \"action\": \"changeDestination\",\n \"destination\": {\n \"type\": \"HTTP\",\n \"url\": \"{{_YOUR_EXTENSION_URL}}\",\n \"authentication\": {\n \"type\": \"AuthorizationHeader\",\n \"headerValue\": \"Bearer {{_YOUR_TOKEN_}}\"\n }\n }\n }\n ]\n}" + }, + "url": { + "raw": "{{host}}/{{project-key}}/extensions/key=sctm-payment-create-update-extension", + "host": [ + "{{host}}" + ], + "path": [ + "{{project-key}}", + "extensions", + "key=sctm-payment-create-update-extension" + ] + }, + "description": "null" + }, + "response": [] } ], - "description": "To be able to use the connector as well as making the API calls in the next sections to trigger the connector to do its job, you must follow these points below:\n\n1. Install the connector to your merchant center:\n \n 1. Installing the connector to your merchant center will trigger creating an API extension to listen the changes on `Create` and `Update` a CommerceTools Payment\n \n 2. It will also create some specific connector's custom fields, here is the list (will be updated continously):\n \n 1. `sctm-payment-custom-type` on **Payment**, it includes the fields: `sctm_mollie_profile_id`, `sctm_payment_methods_request`, `sctm_payment_methods_response`, `sctm_create_payment_request`\n \n 2. `sctm_interface_interaction_type` on **Interface Interaction of Payment**, it includes the fields: `sctmId`, `sctmActionType`, `sctmCreatedAt`, `sctmRequest`, `sctmResponse`\n \n 3. `sctm_payment_cancel_refund` on **Transaction of Payment**, it includes the fields: `reasonText`, `statusText`\n \n 4. In case you want to have a look at the details of creating process or want to re-run it manually when got trouble, we have put all the requests you need in this folder, please have a look if necessary.\n \n2. Update the environments in Postman collection to adapt with your CommerceTools and Mollie credentials\n \n3. To able to make API calls to CommerceTools endpoints, you absolutely need to obtain the access token from your account, we provide you a request to do that in this folder, just make sure you have updated all your credentials and then you are good to go, whenever a request failed because of invalid token, just need to call it again." + "description": "To be able to use the connector as well as making the API calls in the next sections to trigger the connector to do its job, you must follow these points below:\n\n1. Install the connector to your merchant center:\n \n 1. Installing the connector to your merchant center will trigger creating an API extension to listen the changes on `Create` and `Update` a CommerceTools Payment\n \n 2. It will also create some specific connector's custom fields, here is the list (will be updated continously):\n \n 1. `sctm-payment-custom-type` on **Payment**, it includes the fields: `sctm_mollie_profile_id`, `sctm_payment_methods_request`, `sctm_payment_methods_response`, `sctm_create_payment_request`\n \n 2. `sctm_interface_interaction_type` on **Interface Interaction of Payment**, it includes the fields: `sctm_id`, `sctm_action_type`, `sctm_created_at`, `sctm_request`, `sctm_response`\n \n 3. `sctm_payment_cancel_refund` on **Transaction of Payment**, it includes the fields: `reasonText`, `statusText`\n \n 4. In case you want to have a look at the details of creating process or want to re-run it manually when got trouble, we have put all the requests you need in this folder, please have a look if necessary.\n \n2. Update the environments in Postman collection to adapt with your CommerceTools and Mollie credentials\n \n3. To able to make API calls to CommerceTools endpoints, you absolutely need to obtain the access token from your account, we provide you a request to do that in this folder, just make sure you have updated all your credentials and then you are good to go, whenever a request failed because of invalid token, just need to call it again. \n **Notice:** The processor endpoint" }, { "name": "II. Connector Requests", @@ -457,26 +619,6 @@ } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -491,7 +633,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"amountPlanned\" : {\n \"currencyCode\" : \"EUR\",\n \"centAmount\" : 1000,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\" : {\n \"paymentInterface\" : \"Mollie\" // required to be verify with our connector\n },\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"key\": \"sctm-payment-custom-type\"\n },\n \"fields\": {\n // hold all the filter options\n \"sctm_payment_methods_request\": \"{\\\"locale\\\":\\\"de_DE\\\",\\\"billingCountry\\\":\\\"DE\\\",\\\"includeWallets\\\":\\\"applepay\\\",\\\"sequenceType\\\":\\\"oneoff\\\",\\\"orderLineCategories\\\":\\\"\\\",\\\"include\\\":\\\"\\\"}\",\n \"sctm_payment_methods_response\": \"\" // hold the payment list from Mollie response\n }\n }\n}" + "raw": "{\n \"amountPlanned\" : {\n \"currencyCode\" : \"EUR\",\n \"centAmount\" : 1000,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\" : {\n \"paymentInterface\" : \"Mollie\" // required to be verify with our connector\n },\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"key\": \"sctm-payment-custom-type\"\n },\n \"fields\": {\n // hold all the filter options\n \"sctm_payment_methods_request\": \"{\\\"locale\\\":\\\"de_DE\\\",\\\"billingCountry\\\":\\\"PL\\\",\\\"includeWallets\\\":\\\"applepay\\\",\\\"sequenceType\\\":\\\"oneoff\\\",\\\"orderLineCategories\\\":\\\"\\\",\\\"include\\\":\\\"\\\"}\",\n \"sctm_payment_methods_response\": \"\" // hold the payment list from Mollie response\n }\n }\n}" }, "url": { "raw": "{{host}}/{{project-key}}/payments", @@ -547,26 +689,6 @@ } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -601,7 +723,7 @@ "response": [] } ], - "description": "## GET PAYMENT METHODS\n\n**Integration steps**\n\nNo matter when payment methods is retrieved, the `payment` object has to be **create/update** for the connector procedure to be triggered\n\n1. A `payment` object must be instantiated/modified via CT extension actions\n \n2. The object has several parameters as described in the table below\n \n3. The connector will return the corresponding update actions to this `payment` object\n \n4. Then, these update actions will be consumed and update the `payment` object with its Mollie responses\n \n\n**Parameters**\n\n| **Name** | **Required** | **Note** |\n| --- | --- | --- |\n| amountPlanned | √ | To detemine Mollie payment amount |\n| paymentMethodInfo.paymentInterface | √ | To be verified by the connector |\n| custom.fields.sctm_payment_methods_response | √ | To trigger the listing payment method action - **its value should be empty** |\n| custom.fields.sctm_payment_methods_request | √ | To hold all the listing option for Mollie |\n| custom.fields.sctm_payment_methods_request.sequenceType | | To hold Mollie `sequenceType` option |\n| custom.fields.sctm_payment_methods_request.locale | | To hold Mollie `locale` option |\n| custom.fields.sctm_payment_methods_request.resource | | To hold Mollie `resource` option |\n| custom.fields.sctm_payment_methods_request.billingCountry | | To hold Mollie `billingCountry` option |\n| custom.fields.sctm_payment_methods_request.includeWallets | | To hold Mollie `includeWallets` option |\n| custom.fields.sctm_payment_methods_request.orderLineCategories | | To hold Mollie `orderLineCategories` option |\n| custom.fields.sctm_payment_methods_request.include | | To hold Mollie `include` option |\n\n_Example payload_\n\n``` json\n{\n \"action\": \"Create\",\n \"resource\": {\n \"typeId\": \"payment\",\n \"id\": \"PM-112555\",\n \"obj\": {\n \"id\": \"PID-123456\",\n \"amountPlanned\": {\n \"type\": \"centPrecision\",\n \"currencyCode\": \"EUR\",\n \"centAmount\": 1000,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\": {\n \"paymentInterface\": \"Mollie\"\n },\n \"custom\": {\n \"fields\": {\n \"sctm_payment_methods_request\": {\n \"sequenceType\": \"oneoff\",\n \"locale\": \"de_DE\",\n \"resource\": \"payments\",\n \"billingCountry\": \"DE\",\n \"includeWallets\": \"applepay\"\n },\n \"sctm_payment_methods_response: {}\n }\n }\n }\n }\n}\n\n ```\n\n**Response**\n\n_Status:_ 200\n\n_Body:_\n\n``` json\n{\n \"actions\": [\n {\n \"action\": \"setCustomField\",\n \"name\": \"sctm_payment_methods_response\",\n \"value\": \"{\\\"count\\\":11,\\\"methods\\\":[{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"applepay\\\",\\\"description\\\":\\\"Apple Pay\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"10000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/applepay.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/applepay2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/applepay.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/applepay\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"creditcard\\\",\\\"description\\\":\\\"Karte\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"10000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/creditcard.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/creditcard2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/creditcard.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/creditcard\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"paypal\\\",\\\"description\\\":\\\"PayPal\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":null,\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/paypal.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/paypal2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/paypal.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/paypal\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"banktransfer\\\",\\\"description\\\":\\\"Überweisung\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"1000000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/banktransfer.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/banktransfer2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/banktransfer.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/banktransfer\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"ideal\\\",\\\"description\\\":\\\"iDEAL\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"50000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/ideal.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/ideal2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/ideal.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/ideal\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"bancontact\\\",\\\"description\\\":\\\"Bancontact\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.02\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"50000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/bancontact.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/bancontact2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/bancontact.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/bancontact\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"eps\\\",\\\"description\\\":\\\"eps\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"1.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"50000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/eps.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/eps2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/eps.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/eps\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"przelewy24\\\",\\\"description\\\":\\\"Przelewy24\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"12815.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/przelewy24.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/przelewy242x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/przelewy24.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/przelewy24\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"kbc\\\",\\\"description\\\":\\\"KBC/CBC Zahlungsbutton\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"50000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/kbc.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/kbc2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/kbc.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/kbc\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"belfius\\\",\\\"description\\\":\\\"Belfius Pay Button\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"50000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/belfius.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/belfius2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/belfius.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/belfius\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"bancomatpay\\\",\\\"description\\\":\\\"Bancomat Pay\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"50000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/bancomatpay.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/bancomatpay2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/bancomatpay.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/bancomatpay\\\",\\\"type\\\":\\\"application/hal+json\\\"}}}]}\"\n },\n {\n \"action\": \"setCustomField\",\n \"name\": \"sctm_mollie_profile_id\",\n \"value\": \"pfl_SPkYGiEQjf\"\n }\n ]\n}\n\n ```" + "description": "## GET PAYMENT METHODS\n\n**Integration steps**\n\nNo matter when payment methods is retrieved, the `payment` object has to be **create/update** for the connector procedure to be triggered\n\n1. A `payment` object must be instantiated/modified via CT extension actions\n \n2. The object has several parameters as described in the table below\n \n3. The connector will return the corresponding update actions to this `payment` object\n \n4. Then, these update actions will be consumed and update the `payment` object with its Mollie responses\n \n\n**Parameters**\n\n| **Name** | **Required** | **Note** |\n| --- | --- | --- |\n| amountPlanned | √ | To detemine Mollie payment amount |\n| paymentMethodInfo.paymentInterface | √ | To be verified by the connector |\n| custom.fields.sctm_payment_methods_response | √ | To trigger the listing payment method action - **its value should be empty** |\n| custom.fields.sctm_payment_methods_request | √ | To hold all the listing option for Mollie |\n| custom.fields.sctm_payment_methods_request.sequenceType | | To hold Mollie `sequenceType` option |\n| custom.fields.sctm_payment_methods_request.locale | | To hold Mollie `locale` option |\n| custom.fields.sctm_payment_methods_request.resource | | To hold Mollie `resource` option |\n| custom.fields.sctm_payment_methods_request.billingCountry | √ | To hold Mollie `billingCountry` option |\n| custom.fields.sctm_payment_methods_request.includeWallets | | To hold Mollie `includeWallets` option |\n| custom.fields.sctm_payment_methods_request.orderLineCategories | | To hold Mollie `orderLineCategories` option |\n| custom.fields.sctm_payment_methods_request.include | | To hold Mollie `include` option |\n\n_Example payload_\n\n``` json\n{\n \"action\": \"Create\",\n \"resource\": {\n \"typeId\": \"payment\",\n \"id\": \"PM-112555\",\n \"obj\": {\n \"id\": \"PID-123456\",\n \"amountPlanned\": {\n \"type\": \"centPrecision\",\n \"currencyCode\": \"EUR\",\n \"centAmount\": 1000,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\": {\n \"paymentInterface\": \"Mollie\"\n },\n \"custom\": {\n \"fields\": {\n \"sctm_payment_methods_request\": {\n \"sequenceType\": \"oneoff\",\n \"locale\": \"de_DE\",\n \"resource\": \"payments\",\n \"billingCountry\": \"DE\",\n \"includeWallets\": \"applepay\"\n },\n \"sctm_payment_methods_response: {}\n }\n }\n }\n }\n}\n\n ```\n\n**Response**\n\n_Status:_ 200\n\n_Body:_\n\n``` json\n{\n \"actions\": [\n {\n \"action\": \"setCustomField\",\n \"name\": \"sctm_payment_methods_response\",\n \"value\": \"{\\\"count\\\":11,\\\"methods\\\":[{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"applepay\\\",\\\"description\\\":\\\"Apple Pay\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"10000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/applepay.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/applepay2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/applepay.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/applepay\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"creditcard\\\",\\\"description\\\":\\\"Karte\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"10000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/creditcard.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/creditcard2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/creditcard.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/creditcard\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"paypal\\\",\\\"description\\\":\\\"PayPal\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":null,\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/paypal.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/paypal2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/paypal.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/paypal\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"banktransfer\\\",\\\"description\\\":\\\"Überweisung\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"1000000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/banktransfer.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/banktransfer2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/banktransfer.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/banktransfer\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"ideal\\\",\\\"description\\\":\\\"iDEAL\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"50000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/ideal.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/ideal2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/ideal.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/ideal\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"bancontact\\\",\\\"description\\\":\\\"Bancontact\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.02\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"50000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/bancontact.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/bancontact2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/bancontact.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/bancontact\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"eps\\\",\\\"description\\\":\\\"eps\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"1.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"50000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/eps.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/eps2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/eps.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/eps\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"przelewy24\\\",\\\"description\\\":\\\"Przelewy24\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"12815.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/przelewy24.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/przelewy242x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/przelewy24.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/przelewy24\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"kbc\\\",\\\"description\\\":\\\"KBC/CBC Zahlungsbutton\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"50000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/kbc.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/kbc2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/kbc.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/kbc\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"belfius\\\",\\\"description\\\":\\\"Belfius Pay Button\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"50000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/belfius.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/belfius2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/belfius.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/belfius\\\",\\\"type\\\":\\\"application/hal+json\\\"}}},{\\\"resource\\\":\\\"method\\\",\\\"id\\\":\\\"bancomatpay\\\",\\\"description\\\":\\\"Bancomat Pay\\\",\\\"minimumAmount\\\":{\\\"value\\\":\\\"0.01\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"maximumAmount\\\":{\\\"value\\\":\\\"50000.00\\\",\\\"currency\\\":\\\"EUR\\\"},\\\"image\\\":{\\\"size1x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/bancomatpay.png\\\",\\\"size2x\\\":\\\"https://www.mollie.com/external/icons/payment-methods/bancomatpay2x.png\\\",\\\"svg\\\":\\\"https://www.mollie.com/external/icons/payment-methods/bancomatpay.svg\\\"},\\\"status\\\":\\\"activated\\\",\\\"_links\\\":{\\\"self\\\":{\\\"href\\\":\\\"https://api.mollie.com/v2/methods/bancomatpay\\\",\\\"type\\\":\\\"application/hal+json\\\"}}}]}\"\n },\n {\n \"action\": \"setCustomField\",\n \"name\": \"sctm_mollie_profile_id\",\n \"value\": \"pfl_SPkYGiEQjf\"\n }\n ]\n}\n\n ```" }, { "name": "Create Payment", @@ -616,7 +738,7 @@ "listen": "test", "script": { "exec": [ - "tests[\"Status code \" + responseCode.code] = responseCode.code === 200 || responseCode.code === 201;", + "pm.test[\"Status code \" + pm.response.code] = pm.response.code === 200 || pm.response.code === 201;", "var data = JSON.parse(responseBody);", "if(data.results && data.results[0] && data.results[0].id && data.results[0].version){", " pm.environment.set(\"payment-id\", data.results[0].id); ", @@ -639,29 +761,16 @@ "type": "text/javascript", "packages": {} } + }, + { + "listen": "prerequest", + "script": { + "packages": {}, + "type": "text/javascript" + } } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -671,7 +780,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"amountPlanned\" : {\n \"currencyCode\" : \"EUR\",\n \"centAmount\" : 1000,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\" : {\n \"paymentInterface\" : \"Mollie\",\n \"method\" : \"banktransfer\",\n \"name\" : {\n \"en\" : \"Bank Transfer\"\n }\n },\n \"transactions\" : [ {\n \"timestamp\" : \"2015-10-20T08:54:24.000Z\",\n \"type\" : \"Charge\",\n \"amount\" : {\n \"currencyCode\" : \"USD\",\n \"centAmount\" : 1000\n },\n \"state\" : \"Initial\"\n } ],\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"key\": \"sctm-payment-custom-type\"\n },\n \"fields\": {\n \"sctm_create_payment_request\": \"{\\\"description\\\":\\\"Test payment\\\",\\\"locale\\\":\\\"en_GB\\\",\\\"redirectUrl\\\":\\\"https://www.google.com/\\\",\\\"billingEmail\\\":\\\"n.tran@shopmacher.de\\\",\\\"dueDate\\\":\\\"2024-09-10\\\"}\"\n }\n }\n}" + "raw": "{\n \"amountPlanned\" : {\n \"currencyCode\" : \"EUR\",\n \"centAmount\" : 1000,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\" : {\n \"paymentInterface\" : \"Mollie\",\n \"method\" : \"banktransfer\",\n \"name\" : {\n \"en\" : \"Bank Transfer\"\n }\n },\n \"transactions\" : [ {\n \"timestamp\" : \"2015-10-20T08:54:24.000Z\",\n \"type\" : \"Charge\",\n \"amount\" : {\n \"currencyCode\" : \"USD\",\n \"centAmount\" : 1000\n },\n \"state\" : \"Initial\"\n } ],\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"key\": \"sctm-payment-custom-type\"\n },\n \"fields\": {\n \"sctm_create_payment_request\": \"{\\\"description\\\":\\\"Test payment\\\",\\\"locale\\\":\\\"en_GB\\\",\\\"redirectUrl\\\":\\\"https://www.google.com/\\\",\\\"dueDate\\\":\\\"2024-09-10\\\",\\\"billingAddress\\\":{\\\"email\\\":\\\"n.tran@shopmacher.de\\\"}}\"\n }\n }\n}" }, "url": { "raw": "{{host}}/{{project-key}}/payments", @@ -698,10 +807,10 @@ "description": "Play around with the Postman request:\n\n- Here I have described an example to make a payment with method banktransfer. From the payload, please update the `billingEmail` and/or `dueDate` custom.fields.sctm_create_payment_request\n \n\n\n\n- Next, pressing on Send button, after response returned successfully, you will see the `checkoutUrl` in `interfaceInteractions.fields`, please access it via the browser and process the checkout.\n \n- After finishing, you will receive an email to your specified email address which is to notify that the payment has been made. Which means that the billingEmail and the dueDate has been sent along with our request to the targeted Mollie endpoint when creating the payment" }, { - "name": "With Method Blik", + "name": "Create Payment With Line Items", "item": [ { - "name": "Create Payment", + "name": "Create Payment With Line Items", "event": [ { "listen": "test", @@ -733,26 +842,6 @@ } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -762,7 +851,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"amountPlanned\": {\n \"currencyCode\": \"EUR\",\n \"centAmount\": 2000,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\": {\n \"paymentInterface\": \"Mollie\",\n \"method\": \"creditcard\",\n \"name\": {\n \"en\": \"creditcard\"\n }\n },\n \"transactions\": [\n {\n \"timestamp\": \"2015-10-20T08:54:24.000Z\",\n \"type\": \"Charge\",\n \"amount\": {\n \"currencyCode\": \"EUR\",\n \"centAmount\": 2000\n },\n \"state\": \"Initial\"\n }\n ],\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"key\": \"sctm-payment-custom-type\"\n },\n \"fields\": {\n \"sctm_create_payment_request\": \"{\\\"description\\\":\\\"Test payment method blik\\\",\\\"locale\\\":\\\"en_GB\\\",\\\"redirectUrl\\\":\\\"https://www.google.com/\\\",\\\"billingEmail\\\":\\\"n.tran+w@shopmacher.de\\\"}\"\n }\n }\n}" + "raw": "{\n \"amountPlanned\" : {\n \"currencyCode\" : \"EUR\",\n \"centAmount\" : 1000,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\" : {\n \"paymentInterface\" : \"Mollie\",\n \"method\" : \"paypal\",\n \"name\" : {\n \"en\" : \"PayPal\"\n }\n },\n \"transactions\" : [ {\n \"timestamp\" : \"2015-10-20T08:54:24.000Z\",\n \"type\" : \"Charge\",\n \"amount\" : {\n \"currencyCode\" : \"USD\",\n \"centAmount\" : 1000\n },\n \"state\" : \"Initial\"\n } ],\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"key\": \"sctm-payment-custom-type\"\n },\n \"fields\": {\n \"sctm_create_payment_request\": \"{\\\"description\\\":\\\"Test payment with line items\\\",\\\"locale\\\":\\\"en_GB\\\",\\\"redirectUrl\\\":\\\"https://www.google.com/\\\",\\\"billingAddress\\\":{\\\"email\\\":\\\"example@test.de\\\"},\\\"lines\\\":[{\\\"description\\\":\\\"Item 1\\\",\\\"quantity\\\":1,\\\"quantityUnit\\\":\\\"pcs\\\",\\\"unitPrice\\\":{\\\"currency\\\":\\\"EUR\\\",\\\"value\\\":\\\"5.00\\\"},\\\"totalAmount\\\":{\\\"currency\\\":\\\"EUR\\\",\\\"value\\\":\\\"5.00\\\"},\\\"sku\\\":\\\"TEST1\\\",\\\"imageUrl\\\":\\\"https://example.com/image1.jpg\\\",\\\"productUrl\\\":\\\"https://example.com/product1\\\"},{\\\"description\\\":\\\"Item 2\\\",\\\"quantity\\\":1,\\\"quantityUnit\\\":\\\"pcs\\\",\\\"unitPrice\\\":{\\\"currency\\\":\\\"EUR\\\",\\\"value\\\":\\\"5.00\\\"},\\\"totalAmount\\\":{\\\"currency\\\":\\\"EUR\\\",\\\"value\\\":\\\"5.00\\\"},\\\"sku\\\":\\\"TEST2\\\",\\\"imageUrl\\\":\\\"https://example.com/image2.jpg\\\",\\\"productUrl\\\":\\\"https://example.com/product2\\\"}]}\"\n }\n }\n}" }, "url": { "raw": "{{host}}/{{project-key}}/payments", @@ -786,10 +875,10 @@ "response": [] } ], - "description": "Play around with Postman request:\n\n- Make a call with the `Create Payment` request in this folder. Note that the Blik method is only available with the currency code: PLN and you also have to specify the `billingEmail` as well\n \n- After the response returned successfully, you will see the `checkoutUrl` and `molliePaymentId` in `interfaceInteractions.fields`, please access the checkout URL and process the payment and select the status as you want to, then go to the Mollie Dashboard and use the value of molliePaymentId to search for the payment, it should indicate the status that you selected." + "description": "Play around with the Postman request:\n\n- Here I have described an example to make a payment with PayPal including the information of line items\n \n\n\n\n- Next, pressing on Send button, after response returned successfully, you will see the `checkoutUrl` in `interfaceInteractions.fields`, please access it via the browser and process the checkout.\n \n- After finishing, head over to the Mollie payment dashboard and navigate to the corresponding payment checking whether the line items data is transferred or not.\n \n\n" }, { - "name": "With Method Bank Transfer", + "name": "With Method Blik", "item": [ { "name": "Create Payment", @@ -824,26 +913,6 @@ } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -853,7 +922,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"amountPlanned\": {\n \"currencyCode\": \"EUR\",\n \"centAmount\": 2000,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\": {\n \"paymentInterface\": \"Mollie\",\n \"method\": \"banktransfer\",\n \"name\": {\n \"en\": \"banktransfer\"\n }\n },\n \"transactions\": [\n {\n \"timestamp\": \"2015-10-20T08:54:24.000Z\",\n \"type\": \"Charge\",\n \"amount\": {\n \"currencyCode\": \"EUR\",\n \"centAmount\": 2000\n },\n \"state\": \"Initial\"\n }\n ],\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"key\": \"sctm-payment-custom-type\"\n },\n \"fields\": {\n \"sctm_create_payment_request\": \"{\\\"description\\\":\\\"Test payment method banktransfer with duedate was set by configuration\\\",\\\"locale\\\":\\\"en_GB\\\",\\\"redirectUrl\\\":\\\"https://www.google.com/\\\",\\\"billingAddress\\\":{\\\"email\\\":\\\"n.tran@shopmacher.de\\\"}}\"\n }\n }\n}" + "raw": "{\n \"amountPlanned\": {\n \"currencyCode\": \"PLN\",\n \"centAmount\": 2000,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\": {\n \"paymentInterface\": \"Mollie\",\n \"method\": \"blik\",\n \"name\": {\n \"en\": \"blik\"\n }\n },\n \"transactions\": [\n {\n \"timestamp\": \"2015-10-20T08:54:24.000Z\",\n \"type\": \"Charge\",\n \"amount\": {\n \"currencyCode\": \"PLN\",\n \"centAmount\": 2000\n },\n \"state\": \"Initial\"\n }\n ],\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"key\": \"sctm-payment-custom-type\"\n },\n \"fields\": {\n \"sctm_create_payment_request\": \"{\\\"description\\\":\\\"Test payment method blik\\\",\\\"locale\\\":\\\"en_GB\\\",\\\"redirectUrl\\\":\\\"https://www.google.com/\\\",\\\"billingEmail\\\":\\\"n.tran+w@shopmacher.de\\\"}\"\n\n }\n }\n}" }, "url": { "raw": "{{host}}/{{project-key}}/payments", @@ -877,10 +946,10 @@ "response": [] } ], - "description": "Play around with Postman request:\n\n- First, go to the connector settings, you should able to see a configuration name MOLLIE_BANK_TRANSFER_DUE_DATE, it is set by default to be 14d which mean the payment will be expired after 14 days from the day payment created. You can update it to be in the range from 1d to 100d, just make sure to redeploy the connector again after changing it.\n \n\n\"\"\n\n- Make a call with the `Create Payment` request in this folder, please update the email in `custom.fields.sctm_create_payment_request` of the request body before doing that.\n \n\n\n\n- After the response returned successfully, you will see the `checkoutUrl` and `molliePaymentId` in `interfaceInteractions.fields`, please access the checkout URL and process the payment and select the status as you want to, then go to the Mollie Dashboard and use the value of molliePaymentId to search for the payment, it should indicate the status that you selected.\n \n- You should have receive an email which will include the due date, it should be calculated correctly ( = today + due date set on connector config)" + "description": "Play around with Postman request:\n\n- Make a call with the `Create Payment` request in this folder. Note that the Blik method is only available with the currency code: PLN and you also have to specify the `billingEmail` as well\n \n- After the response returned successfully, you will see the `checkoutUrl` and `molliePaymentId` in `interfaceInteractions.fields`, please access the checkout URL and process the payment and select the status as you want to, then go to the Mollie Dashboard and use the value of molliePaymentId to search for the payment, it should indicate the status that you selected." }, { - "name": "With Method ApplePay", + "name": "With Method Bank Transfer", "item": [ { "name": "Create Payment", @@ -915,26 +984,6 @@ } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -944,7 +993,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"amountPlanned\" : {\n \"currencyCode\" : \"GBP\",\n \"centAmount\" : 2000,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\" : {\n \"paymentInterface\" : \"Mollie\",\n \"method\" : \"applepay\",\n \"name\" : {\n \"en\" : \"Apple Pay\"\n }\n },\n \"transactions\" : [ {\n \"timestamp\" : \"2015-10-20T08:54:24.000Z\",\n \"type\" : \"Charge\",\n \"amount\" : {\n \"currencyCode\" : \"GBP\",\n \"centAmount\" : 2000\n },\n \"state\" : \"Initial\"\n } ],\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"key\": \"sctm-payment-custom-type\"\n },\n \"fields\": {\n // replace applePayPaymentToken with your token 0\n \"sctm_create_payment_request\": \"{\\\"description\\\":\\\"Test payment method applepay\\\",\\\"locale\\\":\\\"en_GB\\\",\\\"redirectUrl\\\":\\\"https://www.google.com/\\\",\\\"billingEmail\\\":\\\"demo@shopmacher.de\\\",\\\"applePayPaymentToken\\\":\\\"{\\\\\\\"your\\\\\\\":\\\\\\\"token\\\\\\\"}\\\"}\"\n }\n }\n}" + "raw": "{\n \"amountPlanned\": {\n \"currencyCode\": \"EUR\",\n \"centAmount\": 2000,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\": {\n \"paymentInterface\": \"Mollie\",\n \"method\": \"banktransfer\",\n \"name\": {\n \"en\": \"banktransfer\"\n }\n },\n \"transactions\": [\n {\n \"timestamp\": \"2015-10-20T08:54:24.000Z\",\n \"type\": \"Charge\",\n \"amount\": {\n \"currencyCode\": \"EUR\",\n \"centAmount\": 2000\n },\n \"state\": \"Initial\"\n }\n ],\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"key\": \"sctm-payment-custom-type\"\n },\n \"fields\": {\n \"sctm_create_payment_request\": \"{\\\"description\\\":\\\"Test payment method banktransfer with duedate was set by configuration\\\",\\\"locale\\\":\\\"en_GB\\\",\\\"redirectUrl\\\":\\\"https://www.google.com/\\\",\\\"billingAddress\\\":{\\\"email\\\":\\\"n.tran@shopmacher.de\\\"}}\"\n }\n }\n}" }, "url": { "raw": "{{host}}/{{project-key}}/payments", @@ -967,62 +1016,113 @@ }, "response": [] } - ] + ], + "description": "Play around with Postman request:\n\n- First, go to the custom application, you should able to see a configuration name Banktransfer due date under the Banktransfer payment method setting, it is set by default to be 14d which mean the payment will be expired after 14 days from the day payment created. You can update it to be in the range from 1d to 100d, just make sure to redeploy the connector again after changing it.\n \n\n\"\"\n\n- Make a call with the `Create Payment` request in this folder, please update the email in `custom.fields.sctm_create_payment_request` of the request body before doing that.\n \n\n\n\n- After the response returned successfully, you will see the `checkoutUrl` and `molliePaymentId` in `interfaceInteractions.fields`, please access the checkout URL and process the payment and select the status as you want to, then go to the Mollie Dashboard and use the value of molliePaymentId to search for the payment, it should indicate the status that you selected.\n \n- You should have receive an email which will include the due date, it should be calculated correctly ( = today + due date set on connector config)" }, { - "name": "Create Payment with one transaction", - "event": [ + "name": "With Method ApplePay", + "item": [ { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code \" + responseCode.code] = responseCode.code === 200 || responseCode.code === 201;", - "var data = JSON.parse(responseBody);", - "if(data.results && data.results[0] && data.results[0].id && data.results[0].version){", - " pm.environment.set(\"payment-id\", data.results[0].id); ", - " pm.environment.set(\"payment-version\", data.results[0].version);", - "}", - "if(data.results && data.results[0] && data.results[0].key){", - " pm.environment.set(\"payment-key\", data.results[0].key); ", - "}", - "if(data.version){", - " pm.environment.set(\"payment-version\", data.version);", - "}", - "if(data.id){", - " pm.environment.set(\"payment-id\", data.id); ", - "}", - "if(data.key){", - " pm.environment.set(\"payment-key\", data.key);", - "}", - "" - ], - "type": "text/javascript", - "packages": {} - } - } - ], - "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, + "name": "Create Payment", + "event": [ { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, - "method": "POST", + "listen": "test", + "script": { + "exec": [ + "tests[\"Status code \" + responseCode.code] = responseCode.code === 200 || responseCode.code === 201;", + "var data = JSON.parse(responseBody);", + "if(data.results && data.results[0] && data.results[0].id && data.results[0].version){", + " pm.environment.set(\"payment-id\", data.results[0].id); ", + " pm.environment.set(\"payment-version\", data.results[0].version);", + "}", + "if(data.results && data.results[0] && data.results[0].key){", + " pm.environment.set(\"payment-key\", data.results[0].key); ", + "}", + "if(data.version){", + " pm.environment.set(\"payment-version\", data.version);", + "}", + "if(data.id){", + " pm.environment.set(\"payment-id\", data.id); ", + "}", + "if(data.key){", + " pm.environment.set(\"payment-key\", data.key);", + "}", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"amountPlanned\" : {\n \"currencyCode\" : \"GBP\",\n \"centAmount\" : 2000,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\" : {\n \"paymentInterface\" : \"Mollie\",\n \"method\" : \"applepay\",\n \"name\" : {\n \"en\" : \"Apple Pay\"\n }\n },\n \"transactions\" : [ {\n \"timestamp\" : \"2015-10-20T08:54:24.000Z\",\n \"type\" : \"Charge\",\n \"amount\" : {\n \"currencyCode\" : \"GBP\",\n \"centAmount\" : 2000\n },\n \"state\" : \"Initial\"\n } ],\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"key\": \"sctm-payment-custom-type\"\n },\n \"fields\": {\n // replace applePayPaymentToken with your token 0\n \"sctm_create_payment_request\": \"{\\\"description\\\":\\\"Test payment method applepay\\\",\\\"locale\\\":\\\"en_GB\\\",\\\"redirectUrl\\\":\\\"https://www.google.com/\\\",\\\"billingEmail\\\":\\\"demo@shopmacher.de\\\",\\\"applePayPaymentToken\\\":\\\"{\\\\\\\"your\\\\\\\":\\\\\\\"token\\\\\\\"}\\\"}\"\n }\n }\n}" + }, + "url": { + "raw": "{{host}}/{{project-key}}/payments", + "host": [ + "{{host}}" + ], + "path": [ + "{{project-key}}", + "payments" + ], + "query": [ + { + "key": "expand", + "value": "", + "disabled": true + } + ] + }, + "description": "Creating a Payment produces the [PaymentCreated](ctp:api:type:PaymentCreatedMessage) Message.\n" + }, + "response": [] + } + ] + }, + { + "name": "Create Payment with one transaction", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "tests[\"Status code \" + responseCode.code] = responseCode.code === 200 || responseCode.code === 201;", + "var data = JSON.parse(responseBody);", + "if(data.results && data.results[0] && data.results[0].id && data.results[0].version){", + " pm.environment.set(\"payment-id\", data.results[0].id); ", + " pm.environment.set(\"payment-version\", data.results[0].version);", + "}", + "if(data.results && data.results[0] && data.results[0].key){", + " pm.environment.set(\"payment-key\", data.results[0].key); ", + "}", + "if(data.version){", + " pm.environment.set(\"payment-version\", data.version);", + "}", + "if(data.id){", + " pm.environment.set(\"payment-id\", data.id); ", + "}", + "if(data.key){", + " pm.environment.set(\"payment-key\", data.key);", + "}", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", "header": [ { "key": "Content-Type", @@ -1087,26 +1187,6 @@ } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -1140,7 +1220,7 @@ "response": [] } ], - "description": "## Overview\n\nThis functionality is used to create a new Payment on Mollie:\n\nThis calls Mollie's [create payment](https://docs.mollie.com/reference/create-payment) endpoint.\n\n## Conditions\n\nAdding a new transaction to an existing order with type `Charge` and state `Initial` triggers create order payment.\n\nThere should be only one transaction with type `Charge` and state `Initial` in your CommerceTools Payment object.\n\nAnd also, the targeted Payment object should not have any transactions with type `Charge` and state is `Pending` or `Success`.\n\n## Parameters map\n\n| CT `Payment` object | Parameter (Mollie Payment) | Required |\n| --- | --- | --- |\n| `paymentMethodInfo.method: \"applepay\"` | `method: applepay` | YES |\n| `amountPlanned.currencyCode: \"EUR\"` | `amount.currency: EUR` | YES |\n| `amountPlanned.centAmount: \"1000\"` and `amountPlanned.fractionDigits: \"2\"` | `amount.value: \"10.00\"` | YES |\n| `custom.fields.sctm_create_payment_request.redirectUrl: \"https://webshop.example.org/order/12345/\"` | `redirectUrl: \"https://webshop.example.org/order/12345/\"` | YES |\n| `custom.fields.sctm_create_payment_request.description: \"Testing creating Mollie Payment\"` | `description: \"Testing creating Mollie Payment\"` | YES |\n\nThe others params which listed [here](https://docs.mollie.com/reference/create-payment) can be passed through the custom field of the Payment object name **sctm_create_payment_request** with exactly the same format like the field `redirectUrl` above\n\n## Representation: CommerceTools Payment\n\nExample CommerceTools Payment object triggering creating a Mollie Payment\n\n## Creating CommerceTools actions from Mollie's response\n\nWhen create order payment is successfully added on Mollie, we update CommerceTools payment with following actions\n\n| Action name (CT) | Value |\n| --- | --- |\n| `changeTransactionState` | `transactionId: , state: 'Pending'` |\n| `changeTransactionTimestamp` | `transactionId: , timestamp:` |\n| `changeTransactionInteractionId` | `transactionId: , interactionId:` |\n| `addInterfaceInteraction` | `actionType: \"CreatePayment\", id: , timestamp: , requestValue: {, responseValue:` |\n\n\\* Actions will always use first `Initial` transaction. There should only be one per payment. Transaction id will be the ID of the transaction which triggered the create payment.\n\n## **Demo with Postman**\n\nIn here we will just focus on the change on CommerceTools Payment object which will trigger our connector to create a Mollie Payment.\n\nIn the real project, it would be more complex when we will have a cart, then attach/create the Payment object for it then update the Payment object. But in general, the trigger point is that creating/updating the Payment object which satisfies the conditions above.\n\n**Steps to trigger the connector to create a Mollie Payment:**\n\n1\\. Obtain the access token:\n\n- Open the `Obtain access token` request, press on the send button to send request. After sending the request succesfully, it will automatically store the access_token in the response for later use\n \n\n2.\n\na. Create Payment with one transaction\n\n- Represent for the case that the user want to make a payment right after shopping.\n \n- Response expectation:\n \n - Transaction state will be updated to `Pending`\n \n - Transaction timestamp will be updated\n \n - Transaction `interactionId` will have a value like tr_XXXXXX, this is the Mollie Payment ID\n \n - interfaceInteractions now will have a custom field, that custom fieldset will include:\n \n - id: Unique string generated by the connector\n \n - actionType: will be `createPayment` in this case\n \n - requestValue: JSON string contain CommerceTools transaction ID and payment method\n \n - resoponeValue: JSON string contain Mollie Payment ID, Checkout URL and the CommerceTools transaction ID\n \n\nb. Create Payment with more than one transaction\n\n- Represent the case that the user could made a payment before but failed or something like that, therefor he wants to make a new payment again, therefor our CommerceTools Payment object will have at least 2 transactions: the first one is the failed one (called transaction A) and the second one represent for the current creating payment process (call transaction B)\n \n- Response expectation: really the same with the 2.a, except that the only target transaction (the one which will be updated) is the transaction B above" + "description": "## Overview\n\nThis functionality is used to create a new Payment on Mollie:\n\nThis calls Mollie's [create payment](https://docs.mollie.com/reference/create-payment) endpoint.\n\n## Conditions\n\nAdding a new transaction to an existing order with type `Charge` and state `Initial` triggers create order payment.\n\nThere should be only one transaction with type `Charge` and state `Initial` in your CommerceTools Payment object.\n\nAnd also, the targeted Payment object should not have any transactions with type `Charge` and state is `Pending` or `Success`.\n\n## Parameters map\n\n| CT `Payment` object | Parameter (Mollie Payment) | Required |\n| --- | --- | --- |\n| `paymentMethodInfo.method: \"applepay\"` | `method: applepay` | YES |\n| `amountPlanned.currencyCode: \"EUR\"` | `amount.currency: EUR` | YES |\n| `amountPlanned.centAmount: \"1000\"` and `amountPlanned.fractionDigits: \"2\"` | `amount.value: \"10.00\"` | YES |\n| `custom.fields.sctm_create_payment_request.redirectUrl: \"https://webshop.example.org/order/12345/\"` | `redirectUrl: \"https://webshop.example.org/order/12345/\"` | YES |\n| `custom.fields.sctm_create_payment_request.description: \"Testing creating Mollie Payment\"` | `description: \"Testing creating Mollie Payment\"` | YES |\n| `custom.fields.sctm_create_payment_request.lines: [ { description: 'Item 1', quantity: 1, quantityUnit: 'pcs', unitPrice: { currency: 'EUR', value: '10.00' }, totalAmount: { currency: 'EUR', value: '10.00' }, sku: 'TEST1', imageUrl: '`[https://example.com/image1.jpg'](https://example.com/image1.jpg')`, productUrl: '`[https://example.com/product1'](https://example.com/product1')`, }, { description: 'Item 2', quantity: 1, quantityUnit: 'pcs', unitPrice: { currency: 'EUR', value: '10.00' }, totalAmount: { currency: 'EUR', value: '10.00' }, sku: 'TEST2', imageUrl: '`[https://example.com/image2.jpg'](https://example.com/image2.jpg')`, productUrl: '`[https://example.com/product2'](https://example.com/product2')`, }, ]` | `lines: [ { description: 'Item 1', quantity: 1, quantityUnit: 'pcs', unitPrice: { currency: 'EUR', value: '10.00' }, totalAmount: { currency: 'EUR', value: '10.00' }, sku: 'TEST1', imageUrl: '`[https://example.com/image1.jpg'](https://example.com/image1.jpg')`, productUrl: '`[https://example.com/product1'](https://example.com/product1')`, }, { description: 'Item 2', quantity: 1, quantityUnit: 'pcs', unitPrice: { currency: 'EUR', value: '10.00' }, totalAmount: { currency: 'EUR', value: '10.00' }, sku: 'TEST2', imageUrl: '`[https://example.com/image2.jpg'](https://example.com/image2.jpg')`, productUrl: '`[https://example.com/product2'](https://example.com/product2')`, }, ]` | NO |\n\nThe others params which listed [here](https://docs.mollie.com/reference/create-payment) can be passed through the custom field of the Payment object name **sctm_create_payment_request** with exactly the same format like the field `redirectUrl` above\n\n## Representation: CommerceTools Payment\n\nExample CommerceTools Payment object triggering creating a Mollie Payment\n\n## Creating CommerceTools actions from Mollie's response\n\nWhen create order payment is successfully added on Mollie, we update CommerceTools payment with following actions\n\n| Action name (CT) | Value |\n| --- | --- |\n| `changeTransactionState` | `transactionId: , state: 'Pending'` |\n| `changeTransactionTimestamp` | `transactionId: , timestamp:` |\n| `changeTransactionInteractionId` | `transactionId: , interactionId:` |\n| `addInterfaceInteraction` | `actionType: \"CreatePayment\", id: , timestamp: , requestValue: {, responseValue:` |\n\n\\* Actions will always use first `Initial` transaction. There should only be one per payment. Transaction id will be the ID of the transaction which triggered the create payment.\n\n## **Demo with Postman**\n\nIn here we will just focus on the change on CommerceTools Payment object which will trigger our connector to create a Mollie Payment.\n\nIn the real project, it would be more complex when we will have a cart, then attach/create the Payment object for it then update the Payment object. But in general, the trigger point is that creating/updating the Payment object which satisfies the conditions above.\n\n**Steps to trigger the connector to create a Mollie Payment:**\n\n1\\. Obtain the access token:\n\n- Open the `Obtain access token` request, press on the send button to send request. After sending the request succesfully, it will automatically store the access_token in the response for later use\n \n\n2.\n\na. Create Payment with one transaction\n\n- Represent for the case that the user want to make a payment right after shopping.\n \n- Response expectation:\n \n - Transaction state will be updated to `Pending`\n \n - Transaction timestamp will be updated\n \n - Transaction `interactionId` will have a value like tr_XXXXXX, this is the Mollie Payment ID\n \n - interfaceInteractions now will have a custom field, that custom fieldset will include:\n \n - id: Unique string generated by the connector\n \n - actionType: will be `createPayment` in this case\n \n - requestValue: JSON string contain CommerceTools transaction ID and payment method\n \n - resoponeValue: JSON string contain Mollie Payment ID, Checkout URL and the CommerceTools transaction ID\n \n\nb. Create Payment with more than one transaction\n\n- Represent the case that the user could made a payment before but failed or something like that, therefor he wants to make a new payment again, therefor our CommerceTools Payment object will have at least 2 transactions: the first one is the failed one (called transaction A) and the second one represent for the current creating payment process (call transaction B)\n \n- Response expectation: really the same with the 2.a, except that the only target transaction (the one which will be updated) is the transaction B above" }, { "name": "Cancel Payment", @@ -1181,26 +1261,6 @@ } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -1268,26 +1328,6 @@ } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -1297,7 +1337,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"version\" : {{payment-targeted-version}},\n \"actions\" : [ {\n \"action\" : \"addTransaction\",\n \"transaction\": {\n \"type\": \"CancelAuthorization\",\n \"amount\": {\n \"currencyCode\" : \"EUR\",\n \"centAmount\" : 1100\n },\n \"state\": \"Initial\",\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"key\": \"sctm_payment_cancel_reason\"\n },\n \"fields\": {\n \"reasonText\": \"Testing cancel payment\"\n }\n }\n }\n } ]\n}" + "raw": "{\n \"version\" : {{payment-version}},\n \"actions\" : [ {\n \"action\" : \"addTransaction\",\n \"transaction\": {\n \"type\": \"CancelAuthorization\",\n \"amount\": {\n \"currencyCode\" : \"EUR\",\n \"centAmount\" : 5000\n },\n \"state\": \"Initial\",\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"key\": \"sctm_payment_cancel_reason\"\n },\n \"fields\": {\n \"reasonText\": \"Testing cancel payment\"\n }\n }\n }\n } ]\n}" }, "url": { "raw": "{{host}}/{{project-key}}/payments/{{payment-id}}", @@ -1327,7 +1367,6 @@ { "listen": "test", "script": { - "type": "text/javascript", "exec": [ "tests[\"Status code \" + responseCode.code] = responseCode.code === 200 || responseCode.code === 201;", "var data = JSON.parse(responseBody);", @@ -1348,7 +1387,9 @@ " pm.environment.set(\"payment-key\", data.key);", "}", "" - ] + ], + "type": "text/javascript", + "packages": {} } } ], @@ -1356,26 +1397,6 @@ "disableBodyPruning": true }, "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "GET", "header": [ { @@ -1448,26 +1469,6 @@ } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -1535,26 +1536,6 @@ } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -1594,7 +1575,6 @@ { "listen": "test", "script": { - "type": "text/javascript", "exec": [ "tests[\"Status code \" + responseCode.code] = responseCode.code === 200 || responseCode.code === 201;", "var data = JSON.parse(responseBody);", @@ -1615,7 +1595,9 @@ " pm.environment.set(\"payment-key\", data.key);", "}", "" - ] + ], + "type": "text/javascript", + "packages": {} } } ], @@ -1623,26 +1605,6 @@ "disableBodyPruning": true }, "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "GET", "header": [ { @@ -1715,26 +1677,6 @@ } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -1802,26 +1744,6 @@ } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -1886,26 +1808,6 @@ } ], "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -1945,7 +1847,6 @@ { "listen": "test", "script": { - "type": "text/javascript", "exec": [ "tests[\"Status code \" + responseCode.code] = responseCode.code === 200 || responseCode.code === 201;", "var data = JSON.parse(responseBody);", @@ -1966,7 +1867,9 @@ " pm.environment.set(\"payment-key\", data.key);", "}", "" - ] + ], + "type": "text/javascript", + "packages": {} } } ], @@ -1974,26 +1877,6 @@ "disableBodyPruning": true }, "request": { - "auth": { - "type": "oauth2", - "oauth2": [ - { - "key": "accessToken", - "value": "{{ctp_access_token}}", - "type": "string" - }, - { - "key": "addTokenTo", - "value": "header", - "type": "string" - }, - { - "key": "tokenType", - "value": "Bearer", - "type": "string" - } - ] - }, "method": "GET", "header": [ { @@ -2030,6 +1913,132 @@ ], "description": "Play around with the Postman requests:\n\n- As usual, you need to create a payment object first, you will do that by sending a request via `Create Payment` in this folder.\n \n- It will return the `checkoutUrl` and the `molliePaymentId` inside `interfaceInteractions`, please access the `checkoutUrl` via your browser and process checkout, select Paid as the status.\n \n- After that, you can access our Mollie Dashboard, use the value from `molliePaymentId` to check the details of the payment. Its status here should be Paid.\n \n- Next, make a new request with the `Create Refund` request in this folder. After the response returned successfully, please have a look at the Mollie Payment details page again, the History on the right side of the page should be updated, it should say about a Refund created.\n \n- Next, make a new request with the `Cancel Refund` request in this folder. Similar with the above point, after the response returned successfully, the History in Mollie Payment details page should be updated, information about the Refund Cancellation should be added.\n \n- Finally, you can have a look at the CommerceTools final state if you want to by making a request with `Get Payment by id` in this folder, it is expected to have 3 transactions: 1 Success Charge, 1 Failure Refund and 1 Success CancelAuthorization." }, + { + "name": "Capture Payment", + "item": [ + { + "name": "1. Get capture custom type ID", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.environment.set('capture_type_id', JSON.parse(pm.response.text()).id);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{host}}/{{project-key}}/types/key=sctm_capture_payment_request", + "host": [ + "{{host}}" + ], + "path": [ + "{{project-key}}", + "types", + "key=sctm_capture_payment_request" + ] + } + }, + "response": [] + }, + { + "name": "2. Get the payment for capturing", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const pendingTransaction = JSON.parse(pm.response.text()).transactions.find((transaction) => transaction.state === 'Pending') || {};", + "", + "if (pendingTransaction) {", + " pm.environment.set('capture_payment_id', JSON.parse(pm.response.text()).id);", + " pm.environment.set('capture_payment_version', JSON.parse(pm.response.text()).version);", + " pm.environment.set('capture_transaction_id', pendingTransaction.id);", + "}" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{host}}/{{project-key}}/payments/{{payment-id}}", + "host": [ + "{{host}}" + ], + "path": [ + "{{project-key}}", + "payments", + "{{payment-id}}" + ] + } + }, + "response": [] + }, + { + "name": "3. Capture payment", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "pm.environment.set('current_datetime', new Date().toISOString())", + "pm.environment.set('sctm_should_capture', 'sctm_should_capture')", + "pm.environment.set('sctm_capture_description', 'sctm_capture_description')" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"version\": {{capture_payment_version}},\n \"actions\": [\n {\n \"action\" : \"setTransactionCustomType\",\n \"type\" : {\n \"id\" : \"{{capture_type_id}}\",\n \"typeId\" : \"type\"\n },\n \"fields\" : {\n \"{{sctm_should_capture}}\" : true,\n \"{{sctm_capture_description}}\": \"Capture on {{current_datetime}}\"\n },\n \"transactionId\" : \"{{capture_transaction_id}}\"\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{host}}/{{project-key}}/payments/{{capture_payment_id}}", + "host": [ + "{{host}}" + ], + "path": [ + "{{project-key}}", + "payments", + "{{capture_payment_id}}" + ] + } + }, + "response": [] + } + ], + "description": "# Capture Payment\n\n* [Overview](#overview)\n* [Conditions](#conditions)\n* [Transactions' custom fields](#transactions-custom-fields)\n* [Example payload](#example-payload)\n* [Example response](#example-response)\n\n## Overview\n\nThis feature is to capture the money of an authorized Mollie payment and update transactions with correct status and information.\n\nIt calls Mollie [Capture API](https://docs.mollie.com/reference/captures-api) eventually.\n\nTo trigger the capture for a certain payment, simply update the target pending transaction of type `Charge` with our predefined custom type named `sctm_capture_payment_request`\n\n| **Custom fields** | **Required** | **Description** |\n| --- | --- | --- |\n| `sctm_should_capture` | false | WHEN its value set to `true`, the Processor will start to capture with associated payment information |\n| `sctm_capture_description` | false | Hold the description of the capture |\n\n## Conditions\n\n1. A valid authorized CT payment must contain the two follow transactions:\n * One with type of `authorization` and state `success`\n * One with type of `charge` and state `pending`\n2. The ref Mollie payment must have `captureMode` equal to `manual` and `state` equal to `authorized`\n\n## Transactions' custom fields\n\n| Fields | Data type | Required | Usage |\n|-----------------------------------------------------------------------------------------------------------|----------------------------------------------|----------|------------------|\n| `sctm_should_capture` | `boolean` | NO | If `true`, trigger the capture process |\n| `sctm_capture_description` | `string` | NO | Contain the description for the capture |\n\n## Example payload\n\n```json\nENDPOINT: `https://api.europe-west1.gcp.commercetools.com/{{your_project_key}}/payments/{{payment_id_for_capture}}`\n{\n \"version\": {{capture_payment_version}},\n \"actions\": [\n {\n \"action\" : \"setTransactionCustomType\",\n \"type\" : {\n \"id\" : \"{{capture_type_id}}\",\n \"typeId\" : \"type\"\n },\n \"fields\" : {\n \"sctm_should_capture\" : true,\n \"sctm_capture_description\": \"Capture description\"\n },\n \"transactionId\" : \"{{capture_transaction_id}}\"\n }\n ]\n}\n```\n\n## Example response\n\n```json\n{\n \"id\": \"ba410236-3135-4037-b61e-10aa82a1ae99\",\n \"version\": 26,\n \"versionModifiedAt\": \"2025-02-17T08:12:45.109Z\",\n \"lastMessageSequenceNumber\": 6,\n \"createdAt\": \"2025-02-17T08:09:06.565Z\",\n \"lastModifiedAt\": \"2025-02-17T08:12:45.109Z\",\n \"lastModifiedBy\": {\n \"clientId\": \"8a_rNd-HokSybRmRRqdEuh82\",\n \"isPlatformClient\": false\n },\n \"createdBy\": {\n \"clientId\": \"8a_rNd-HokSybRmRRqdEuh82\",\n \"isPlatformClient\": false\n },\n \"amountPlanned\": {\n \"type\": \"centPrecision\",\n \"currencyCode\": \"EUR\",\n \"centAmount\": 11999,\n \"fractionDigits\": 2\n },\n \"paymentMethodInfo\": {\n \"paymentInterface\": \"mollie\",\n \"method\": \"creditcard\",\n \"name\": {\n \"en\": \"creditcard\",\n \"de\": \"creditcard\"\n }\n },\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"id\": \"53cdd626-7884-4421-9226-d81ba7038424\"\n },\n \"fields\": {\n \"sctm_payment_methods_request\": \"{\\\"locale\\\":\\\"de_DE\\\",\\\"billingCountry\\\":\\\"DE\\\",\\\"includeWallets\\\":\\\"applepay\\\"}\",\n \"sctm_mollie_profile_id\": \"pfl_SPkYGiEQjf\",\n \"sctm_payment_methods_response\": \"{\\\"count\\\":3,\\\"methods\\\":[{\\\"id\\\":\\\"creditcard\\\",\\\"name\\\":{\\\"en-GB\\\":\\\"Card\\\",\\\"de-DE\\\":\\\"Card\\\",\\\"en-US\\\":\\\"Card\\\"},\\\"description\\\":{\\\"en-GB\\\":\\\"\\\",\\\"de-DE\\\":\\\"\\\",\\\"en-US\\\":\\\"\\\"},\\\"image\\\":\\\"https://www.mollie.com/external/icons/payment-methods/creditcard.svg\\\",\\\"order\\\":20},{\\\"id\\\":\\\"applepay\\\",\\\"name\\\":{\\\"en-GB\\\":\\\"Apple Pay\\\",\\\"de-DE\\\":\\\"Apple Pay\\\",\\\"en-US\\\":\\\"Apple Pay\\\"},\\\"description\\\":{\\\"en-GB\\\":\\\"\\\",\\\"de-DE\\\":\\\"\\\",\\\"en-US\\\":\\\"\\\"},\\\"image\\\":\\\"https://www.mollie.com/external/icons/payment-methods/applepay.svg\\\",\\\"order\\\":0},{\\\"id\\\":\\\"banktransfer\\\",\\\"name\\\":{\\\"en-GB\\\":\\\"Bank transfer\\\",\\\"de-DE\\\":\\\"Bank transfer\\\",\\\"en-US\\\":\\\"Bank transfer\\\"},\\\"description\\\":{\\\"en-GB\\\":\\\"\\\",\\\"de-DE\\\":\\\"\\\",\\\"en-US\\\":\\\"\\\"},\\\"image\\\":\\\"https://www.mollie.com/external/icons/payment-methods/banktransfer.svg\\\",\\\"order\\\":0}]}\",\n \"sctm_create_payment_request\": \"{\\\"description\\\":\\\"Testing creating Mollie payment\\\",\\\"redirectUrl\\\":\\\"http://localhost:3000/thank-you?orderId=ae22-e03f-aab1\\\",\\\"billingAddress\\\":{\\\"givenName\\\":\\\"thach\\\",\\\"familyName\\\":\\\"dang\\\",\\\"streetAndNumber\\\":\\\"Am campus 5\\\",\\\"postalCode\\\":\\\"48721\\\",\\\"city\\\":\\\"Gescher\\\",\\\"country\\\":\\\"DE\\\",\\\"phone\\\":\\\"49254287030\\\",\\\"email\\\":\\\"t.dang@shopmacher.de\\\"},\\\"shippingAddress\\\":{\\\"givenName\\\":\\\"thach\\\",\\\"familyName\\\":\\\"dang\\\",\\\"streetAndNumber\\\":\\\"Am campus 5\\\",\\\"postalCode\\\":\\\"48721\\\",\\\"city\\\":\\\"Gescher\\\",\\\"country\\\":\\\"DE\\\",\\\"phone\\\":\\\"49254287030\\\",\\\"email\\\":\\\"t.dang@shopmacher.de\\\"},\\\"billingEmail\\\":\\\"t.dang@shopmacher.de\\\",\\\"cardToken\\\":\\\"tkn_h3mrzMtest\\\",\\\"lines\\\":[{\\\"description\\\":\\\"Geometrischer Kissenbezug\\\",\\\"quantity\\\":1,\\\"quantityUnit\\\":\\\"pcs\\\",\\\"unitPrice\\\":{\\\"currency\\\":\\\"EUR\\\",\\\"value\\\":\\\"19.99\\\"},\\\"totalAmount\\\":{\\\"currency\\\":\\\"EUR\\\",\\\"value\\\":\\\"19.99\\\"}}],\\\"captureMode\\\":\\\"manual\\\"}\"\n }\n },\n \"paymentStatus\": {\n \"interfaceText\": \"initial\"\n },\n \"transactions\": [\n {\n \"id\": \"0c80239c-ade7-4556-a871-f4317759be10\",\n \"timestamp\": \"2025-02-17T08:09:36.000Z\",\n \"type\": \"Charge\",\n \"amount\": {\n \"type\": \"centPrecision\",\n \"currencyCode\": \"EUR\",\n \"centAmount\": 11999,\n \"fractionDigits\": 2\n },\n \"interactionId\": \"tr_mDmhmxTzkX\",\n \"state\": \"Success\",\n \"custom\": {\n \"type\": {\n \"typeId\": \"type\",\n \"id\": \"418c568c-15a5-4418-9698-a81edcffc471\"\n },\n \"fields\": {\n \"sctm_should_capture\": true,\n \"sctm_capture_description\": \"Capture on 2025-02-17T08:12:42.171Z\"\n }\n }\n },\n {\n \"id\": \"d39ed5fc-f8ae-405b-ae99-3f713d243da0\",\n \"type\": \"Authorization\",\n \"amount\": {\n \"type\": \"centPrecision\",\n \"currencyCode\": \"EUR\",\n \"centAmount\": 11999,\n \"fractionDigits\": 2\n },\n \"interactionId\": \"tr_mDmhmxTzkX\",\n \"state\": \"Success\"\n }\n ],\n \"interfaceInteractions\": [\n {\n \"type\": {\n \"typeId\": \"type\",\n \"id\": \"9d9b436c-58cc-4f2e-a393-0fbd05ba0193\"\n },\n \"fields\": {\n \"sctm_id\": \"6e134208-fdfe-453f-928e-8ab44d9b10ec\",\n \"sctm_action_type\": \"createPayment\",\n \"sctm_created_at\": \"2025-02-17T08:09:36+00:00\",\n \"sctm_request\": \"{\\\"transactionId\\\":\\\"0c80239c-ade7-4556-a871-f4317759be10\\\",\\\"paymentMethod\\\":\\\"creditcard\\\"}\",\n \"sctm_response\": \"{\\\"molliePaymentId\\\":\\\"tr_mDmhmxTzkX\\\",\\\"checkoutUrl\\\":\\\"https://www.mollie.com/checkout/test-mode?method=creditcard&token=6.8hhrnm\\\",\\\"transactionId\\\":\\\"0c80239c-ade7-4556-a871-f4317759be10\\\"}\"\n }\n }\n ]\n}\n```\n" + }, { "name": "Health check", "request": { @@ -2054,7 +2063,6 @@ { "listen": "test", "script": { - "type": "text/javascript", "exec": [ "tests[\"Status code is 200\"] = responseCode.code === 200;", "var data = JSON.parse(responseBody);", @@ -2074,26 +2082,13 @@ " }", " }", "}" - ] + ], + "type": "text/javascript", + "packages": {} } } ], "request": { - "auth": { - "type": "basic", - "basic": [ - { - "key": "username", - "value": "{{client_id}}", - "type": "string" - }, - { - "key": "password", - "value": "{{client_secret}}", - "type": "string" - } - ] - }, "method": "POST", "header": [], "body": { @@ -2121,6 +2116,198 @@ "response": [] } ] + }, + { + "name": "III. Custom Application Requests", + "item": [ + { + "name": "Get Method Objects", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "tests[\"Status code \" + responseCode.code] = responseCode.code === 200 || responseCode.code === 201;", + "var data = JSON.parse(responseBody);", + "if(data.results && data.results[0] && data.results[0].id && data.results[0].version){", + " pm.environment.set(\"custom-object-id\", data.results[0].id); ", + " pm.environment.set(\"custom-object-version\", data.results[0].version);", + "}", + "if(data.results && data.results[0] && data.results[0].key){", + " pm.environment.set(\"custom-object-key\", data.results[0].key); ", + "}", + "if(data.version){", + " pm.environment.set(\"custom-object-version\", data.version);", + "}", + "if(data.id){", + " pm.environment.set(\"custom-object-id\", data.id); ", + "}", + "if(data.key){", + " pm.environment.set(\"custom-object-key\", data.key);", + "}", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{host}}/{{project-key}}/custom-objects/{{container}}", + "host": [ + "{{host}}" + ], + "path": [ + "{{project-key}}", + "custom-objects", + "{{container}}" + ], + "query": [ + { + "key": "sort", + "value": "", + "disabled": true + }, + { + "key": "where", + "value": "", + "disabled": true + }, + { + "key": "expand", + "value": "", + "disabled": true + }, + { + "key": "/^var[.][a-zA-Z0-9]+$/", + "value": "", + "disabled": true + }, + { + "key": "limit", + "value": "", + "disabled": true + }, + { + "key": "offset", + "value": "", + "disabled": true + }, + { + "key": "withTotal", + "value": "", + "disabled": true + } + ] + }, + "description": "null" + }, + "response": [] + } + ] + } + ], + "auth": { + "type": "oauth2", + "oauth2": [ + { + "key": "tokenType", + "value": "Bearer", + "type": "string" + }, + { + "key": "accessToken", + "value": "{{ctp_access_token}}", + "type": "string" + }, + { + "key": "refreshTokenUrl", + "value": "{{auth_url}}/oauth/token?grant_type=client_credentials", + "type": "string" + }, + { + "key": "tokenName", + "value": "ctp_access_token", + "type": "string" + }, + { + "key": "accessTokenUrl", + "value": "{{auth_url}}/oauth/token?grant_type=client_credentials", + "type": "string" + }, + { + "key": "clientSecret", + "value": "{{client_secret}}", + "type": "string" + }, + { + "key": "clientId", + "value": "{{client_id}}", + "type": "string" + }, + { + "key": "grant_type", + "value": "client_credentials", + "type": "string" + }, + { + "key": "authUrl", + "value": "{{auth_url}}/oauth/token?grant_type=client_credentials", + "type": "string" + }, + { + "key": "redirect_uri", + "value": "", + "type": "string" + }, + { + "key": "addTokenTo", + "value": "header", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "currentDateTime", + "value": "" } ] } \ No newline at end of file diff --git a/docs/postman/sctm_connector_v101.postman_environment.json b/docs/postman/sctm_connector_v123.postman_environment.json similarity index 100% rename from docs/postman/sctm_connector_v101.postman_environment.json rename to docs/postman/sctm_connector_v123.postman_environment.json diff --git a/processor/.env.jest b/processor/.env.jest index a947354..db58273 100644 --- a/processor/.env.jest +++ b/processor/.env.jest @@ -5,8 +5,11 @@ CTP_CLIENT_SECRET=12345678901234567890123456789012 CTP_PROJECT_KEY=TEST CTP_SCOPE=TEST CTP_REGION=europe-west1.gcp -CTP_AUTH_URL=https://auth.europe-west1.gcp.commercetools.com AUTHENTICATION_MODE=0 + +CTP_AUTH_URL=https://auth.europe-west1.gcp.commercetools.com +CTP_API_URL=https://api.europe-west1.gcp.commercetools.com + CTP_SESSION_AUDIENCE=https://mc.europe-west1.gcp.commercetools.com CTP_SESSION_ISSUER=gcp-eu diff --git a/processor/jest.config.cjs b/processor/jest.config.cjs index 108d1c2..c6143f6 100644 --- a/processor/jest.config.cjs +++ b/processor/jest.config.cjs @@ -7,5 +7,5 @@ module.exports = { setupFiles: ['/src/jest.setup.ts'], setupFilesAfterEnv: ['/src/jest.setupAfterEnv.ts'], modulePathIgnorePatterns: ['/src/jest.setup.ts'], - reporters: ['default', 'jest-junit'] + reporters: ['default', 'jest-junit'], }; diff --git a/processor/package-lock.json b/processor/package-lock.json index 9459397..bc77f0d 100644 --- a/processor/package-lock.json +++ b/processor/package-lock.json @@ -1,12 +1,12 @@ { "name": "shopmacher-mollie-processor", - "version": "1.2.0-build14.01.25.1420", + "version": "1.2.1-build17.02.25.1541", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "shopmacher-mollie-processor", - "version": "1.2.0-build14.01.25.1420", + "version": "1.2.1-build17.02.25.1541", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -15,7 +15,7 @@ "@commercetools/connect-payments-sdk": "^0.10.0", "@commercetools/platform-sdk": "^4.11.0", "@commercetools/sdk-client-v2": "^2.5.0", - "@mollie/api-client": "^3.7.0", + "@mollie/api-client": "^4.0", "@types/uuid": "^10.0.0", "body-parser": "^1.20.2", "dotenv": "^16.4.5", @@ -377,6 +377,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -460,6 +461,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/ruply": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ruply/-/ruply-1.0.1.tgz", + "integrity": "sha512-p39LnaaJyuucPGlgaB0KiyifpcuOkn24+Hq5y0ejAD/LlH+mRAbkHn2tckCLgHir+S+nis1WYG+TYEC4zHX0WQ==", + "license": "MIT" + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -2069,7 +2076,8 @@ "node_modules/color/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" }, "node_modules/jest-validate": { "version": "29.7.0", @@ -2132,6 +2140,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -2269,6 +2278,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -2762,6 +2772,7 @@ "version": "4.15.9", "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -3098,6 +3109,7 @@ "version": "1.16.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", + "license": "MIT", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -3809,7 +3821,8 @@ "node_modules/@commercetools-backend/express/node_modules/serve-static/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/levn": { "version": "0.4.1", @@ -4013,15 +4026,6 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "node_modules/@mollie/api-client/node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", - "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, "node_modules/is-shared-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", @@ -4457,6 +4461,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -4756,7 +4761,8 @@ "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/define-properties": { "version": "1.2.1", @@ -5077,6 +5083,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -6303,7 +6310,6 @@ "version": "2.6.11", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", - "dev": true, "dependencies": { "@types/node": "*", "form-data": "^4.0.0" @@ -6817,6 +6823,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -7645,6 +7652,7 @@ "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -7801,25 +7809,6 @@ "node": ">=8" } }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -8105,7 +8094,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/supports-color": { "version": "8.1.1", @@ -8355,14 +8345,17 @@ } }, "node_modules/@mollie/api-client": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@mollie/api-client/-/api-client-3.7.0.tgz", - "integrity": "sha512-8pYq08xwv7VJIQvOJU+3nThZbXuNLhtD8iJQQ4UYW67kxHw4eC6cVE9SNVtUuHRfxTwcKLv5wrZd9jFb3BFqiA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@mollie/api-client/-/api-client-4.1.0.tgz", + "integrity": "sha512-cSPigdR7cK9k6YgW2QuX0Jk++TLNxZwi5QR1Nmuq2wrSBcQOswZHaGmCP1nSZgdIJtzfXpZxg4qptzGe1VkYZw==", + "license": "BSD-3-Clause", "dependencies": { - "axios": "^0.27.2" + "@types/node-fetch": "^2.6.11", + "node-fetch": "^2.7.0", + "ruply": "^1.0.1" }, "engines": { - "node": ">=6.14" + "node": ">=8" } }, "node_modules/negotiator": { diff --git a/processor/package.json b/processor/package.json index 534f07c..21d35af 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.2.0-build14.01.25.1420", + "version": "1.2.1-build17.02.25.1541", "main": "index.js", "private": true, "scripts": { @@ -13,8 +13,8 @@ "lint": "eslint . --ext .ts", "prettier:check": "prettier --check '**/*.{js,ts}'", "fix": "eslint . --ext .ts --fix && prettier --write '**/*.{js,ts}'", - "test": "jest --detectOpenHandles --clearMocks --colors --config jest.config.cjs --ci --ci --reporters=default --reporters=jest-junit --coverage", - "test:watch": "jest --watch --clearMocks --detectOpenHandles --colors --config jest.config.cjs", + "test": "jest --detectOpenHandles --clearMocks --colors --forceExit --config jest.config.cjs --ci --ci --reporters=default --reporters=jest-junit --coverage", + "test:watch": "jest --watch --clearMocks --detectOpenHandles --colors --config jest.config.cjs", "preinstall": "npx npm-force-resolutions", "connector:post-deploy": "node dist/connector/post-deploy.js", "connector:pre-undeploy": "node dist/connector/pre-undeploy.js" @@ -65,7 +65,7 @@ "@commercetools/connect-payments-sdk": "^0.10.0", "@commercetools/platform-sdk": "^4.11.0", "@commercetools/sdk-client-v2": "^2.5.0", - "@mollie/api-client": "^3.7.0", + "@mollie/api-client": "^4.0", "@types/uuid": "^10.0.0", "body-parser": "^1.20.2", "dotenv": "^16.4.5", diff --git a/processor/src/commercetools/action.commercetools.ts b/processor/src/commercetools/action.commercetools.ts index eaff2d2..24fa39a 100644 --- a/processor/src/commercetools/action.commercetools.ts +++ b/processor/src/commercetools/action.commercetools.ts @@ -140,7 +140,7 @@ export const addCustomLineItem = ( }; }; -export const setTransactionCustomField = (name: string, value: string, transactionId: string) => { +export const setTransactionCustomField = (name: string, value: string | boolean, transactionId: string) => { return { action: 'setTransactionCustomField', name, diff --git a/processor/src/commercetools/customFields.commercetools.ts b/processor/src/commercetools/customFields.commercetools.ts index 0d1c507..275a34f 100644 --- a/processor/src/commercetools/customFields.commercetools.ts +++ b/processor/src/commercetools/customFields.commercetools.ts @@ -459,3 +459,106 @@ export async function createTransactionRefundForMolliePaymentCustomType(): Promi return; } } + +export async function createTransactionCaptureForMolliePaymentCustomType(): Promise { + const apiRoot = createApiRoot(); + const customFields: FieldDefinition[] = [ + { + name: CustomFields.capturePayment.fields.shouldCapture.name, + label: { + en: CustomFields.capturePayment.fields.shouldCapture.label.en, + de: CustomFields.capturePayment.fields.shouldCapture.label.de, + }, + required: false, + type: { + name: 'Boolean', + }, + }, + { + name: CustomFields.capturePayment.fields.descriptionCapture.name, + label: { + en: CustomFields.capturePayment.fields.descriptionCapture.label.en, + de: CustomFields.capturePayment.fields.descriptionCapture.label.de, + }, + required: false, + type: { + name: 'String', + }, + inputHint: 'MultiLine', + }, + { + name: CustomFields.capturePayment.fields.captureErrors.name, + label: { + en: CustomFields.capturePayment.fields.captureErrors.label.en, + de: CustomFields.capturePayment.fields.captureErrors.label.de, + }, + required: false, + type: { + name: 'String', + }, + inputHint: 'MultiLine', + }, + ]; + + const { + body: { results: types }, + } = await apiRoot + .types() + .get({ + queryArgs: { + where: `key = "${CustomFields.capturePayment.typeKey}"`, + }, + }) + .execute(); + + if (types.length <= 0) { + await apiRoot + .types() + .post({ + body: { + key: CustomFields.capturePayment.typeKey, + name: { + en: CustomFields.capturePayment.name.en, + de: CustomFields.capturePayment.name.de, + }, + resourceTypeIds: [CustomFields.capturePayment.resourceTypeId], + fieldDefinitions: customFields, + }, + }) + .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, + }); + }); + + await apiRoot + .types() + .withKey({ key: CustomFields.capturePayment.typeKey }) + .post({ + body: { + version: type.version, + actions, + }, + }) + .execute(); + + return; + } +} diff --git a/processor/src/controllers/application.controller.ts b/processor/src/controllers/application.controller.ts index be47f9a..de85480 100644 --- a/processor/src/controllers/application.controller.ts +++ b/processor/src/controllers/application.controller.ts @@ -2,13 +2,13 @@ import { Request, Response } from 'express'; import { apiError } from '../api/error.api'; import { formatErrorResponse } from '../errors/mollie.error'; import { getAllPaymentMethods } from '../mollie/payment.mollie'; -import { List, Locale, Method, MethodInclude } from '@mollie/api-client'; +import { Locale, Method, MethodInclude } from '@mollie/api-client'; import { logger } from '../utils/logger.utils'; export const getMethods = async (request: Request, response: Response) => { try { logger.debug('getMethods - Prepare payment methods for the custom application.'); - const data: List = await getAllPaymentMethods({ + const data: Method[] = await getAllPaymentMethods({ locale: Locale.en_US, include: MethodInclude.pricing, }); diff --git a/processor/src/controllers/payment.controller.ts b/processor/src/controllers/payment.controller.ts index 189d9aa..0c923e9 100644 --- a/processor/src/controllers/payment.controller.ts +++ b/processor/src/controllers/payment.controller.ts @@ -7,6 +7,7 @@ import { handleGetApplePaySession, handleListPaymentMethodsByPayment, handlePaymentCancelRefund, + handleCapturePayment, } from '../service/payment.service'; import { PaymentReference, Payment } from '@commercetools/platform-sdk'; import { ConnectorActions } from '../utils/constant.utils'; @@ -60,6 +61,9 @@ export const paymentController = async ( case ConnectorActions.GetApplePaySession: logger.debug('SCTM - payment processing - paymentController - getApplePaySession'); return await handleGetApplePaySession(ctPayment); + case ConnectorActions.CapturePayment: + logger.debug('SCTM - payment processing - paymentController - capturePayment'); + return await handleCapturePayment(ctPayment); default: logger.debug('SCTM - payment processing - paymentController - No payment actions matched'); throw new SkipError('No payment actions matched'); diff --git a/processor/src/mollie/payment.mollie.ts b/processor/src/mollie/payment.mollie.ts index 10289a4..e65cec8 100644 --- a/processor/src/mollie/payment.mollie.ts +++ b/processor/src/mollie/payment.mollie.ts @@ -1,5 +1,5 @@ import { - List, + Capture, Method, MethodsListParams, MollieApiError, @@ -11,10 +11,11 @@ import { initMollieClient, initMollieClientForApplePaySession } from '../client/ import CustomError from '../errors/custom.error'; import { logger } from '../utils/logger.utils'; import { ApplePaySessionRequest, CustomPayment } from '../types/mollie.types'; -import ApplePaySession from '@mollie/api-client/dist/types/src/data/applePaySession/ApplePaySession'; import { getApiKey } from '../utils/config.utils'; import { MOLLIE_VERSION_STRINGS } from '../utils/constant.utils'; import fetch from 'node-fetch'; +import ApplePaySession from '@mollie/api-client/dist/types/data/applePaySession/ApplePaySession'; +import { CreateParameters } from '@mollie/api-client/dist/types/binders/payments/captures/parameters'; const HEADER = { 'Content-Type': 'application/json', @@ -62,9 +63,9 @@ export const getPaymentById = async (paymentId: string): Promise => { * Retrieves a list of payment methods using the provided options. * * @param {MethodsListParams} options - The parameters for listing payment methods. - * @return {Promise>} A promise that resolves to the list of payment methods. + * @return {Promise} A promise that resolves to the list of payment methods. */ -export const listPaymentMethods = async (options: MethodsListParams): Promise> => { +export const listPaymentMethods = async (options: MethodsListParams): Promise => { try { return await initMollieClient().methods.list(options); } catch (error: unknown) { @@ -149,7 +150,7 @@ export const createPaymentWithCustomMethod = async (paymentParams: PaymentCreate } }; -export const getAllPaymentMethods = async (options: MethodsListParams): Promise> => { +export const getAllPaymentMethods = async (options: MethodsListParams): Promise => { let errorMessage; try { @@ -192,3 +193,21 @@ export const getApplePaySession = async (options: ApplePaySessionRequest): Promi throw new CustomError(400, errorMessage); } }; + +export const createCapturePayment = async (options: CreateParameters): Promise => { + try { + return await initMollieClient().paymentCaptures.create(options); + } catch (error: unknown) { + let errorMessage; + if (error instanceof MollieApiError) { + errorMessage = `SCTM - createCapturePayment - error: ${error.message}, field: ${error.field}`; + } else { + errorMessage = `SCTM - createCapturePayment - Failed to create capture payment with unknown errors`; + } + + logger.error(errorMessage, { + error, + }); + return new CustomError(400, errorMessage); + } +}; diff --git a/processor/src/mollie/refund.mollie.ts b/processor/src/mollie/refund.mollie.ts index 35da049..b6b0629 100644 --- a/processor/src/mollie/refund.mollie.ts +++ b/processor/src/mollie/refund.mollie.ts @@ -1,13 +1,12 @@ +import { initMollieClient } from '../client/mollie.client'; +import { MollieApiError, Refund } from '@mollie/api-client'; +import { logger } from '../utils/logger.utils'; +import CustomError from '../errors/custom.error'; import { CancelParameters, CreateParameters, GetParameters, -} from '@mollie/api-client/dist/types/src/binders/payments/refunds/parameters'; -import { initMollieClient } from '../client/mollie.client'; -import { MollieApiError } from '@mollie/api-client'; -import { logger } from '../utils/logger.utils'; -import CustomError from '../errors/custom.error'; -import Refund from '@mollie/api-client/dist/types/src/data/refunds/Refund'; +} from '@mollie/api-client/dist/types/binders/payments/refunds/parameters'; export const createPaymentRefund = async (params: CreateParameters): Promise => { try { diff --git a/processor/src/service/connector.service.ts b/processor/src/service/connector.service.ts index eeb0115..ef96313 100644 --- a/processor/src/service/connector.service.ts +++ b/processor/src/service/connector.service.ts @@ -5,6 +5,7 @@ import { createCustomPaymentTransactionCancelReasonType, createTransactionSurchargeCustomType, createTransactionRefundForMolliePaymentCustomType, + createTransactionCaptureForMolliePaymentCustomType, } from '../commercetools/customFields.commercetools'; import { getAccessToken } from '../commercetools/auth.commercetools'; @@ -16,6 +17,7 @@ export const createExtensionAndCustomFields = async (extensionUrl: string): Prom await createCustomPaymentTransactionCancelReasonType(); await createTransactionSurchargeCustomType(); await createTransactionRefundForMolliePaymentCustomType(); + await createTransactionCaptureForMolliePaymentCustomType(); }; export const removeExtension = async (): Promise => { diff --git a/processor/src/service/payment.service.ts b/processor/src/service/payment.service.ts index 9d09478..3a56ed5 100644 --- a/processor/src/service/payment.service.ts +++ b/processor/src/service/payment.service.ts @@ -1,7 +1,6 @@ import { ControllerResponseType } from '../types/controller.types'; import { CancelStatusText, ConnectorActions, CustomFields, PAY_LATER_ENUMS } from '../utils/constant.utils'; -import { List, Method, Payment as MPayment, PaymentMethod, PaymentStatus, Refund } from '@mollie/api-client'; -import { logger } from '../utils/logger.utils'; +import { Capture, Method, Payment as MPayment, PaymentMethod, PaymentStatus, Refund } from '@mollie/api-client'; import { createCartUpdateActions, createMollieCreatePaymentParams, @@ -18,6 +17,7 @@ import { import CustomError from '../errors/custom.error'; import { cancelPayment, + createCapturePayment, createMolliePayment, createPaymentWithCustomMethod, getApplePaySession, @@ -26,6 +26,7 @@ import { } from '../mollie/payment.mollie'; import { AddTransaction, + CaptureModes, ChangeTransactionState, CTTransaction, CTTransactionState, @@ -35,6 +36,7 @@ import { mollieRefundToCTStatusMap, PricingConstraintItem, UpdateActionKey, + DoCapturePaymentFromMollie, } from '../types/commercetools.types'; import { makeCTMoney, @@ -54,13 +56,10 @@ import { changeTransactionState, changeTransactionTimestamp, setCustomFields, + setTransactionCustomField, setTransactionCustomType, } from '../commercetools/action.commercetools'; import { readConfiguration } from '../utils/config.utils'; -import { - CancelParameters, - CreateParameters, -} from '@mollie/api-client/dist/types/src/binders/payments/refunds/parameters'; 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'; @@ -72,10 +71,13 @@ import { roundSurchargeAmountToCent, sortTransactionsByLatestCreationTime, } from '../utils/app.utils'; -import ApplePaySession from '@mollie/api-client/dist/types/src/data/applePaySession/ApplePaySession'; import { getMethodConfigObjects, getSingleMethodConfigObject } from '../commercetools/customObjects.commercetools'; import { getCartFromPayment, updateCart } from '../commercetools/cart.commercetools'; import { removeCartMollieCustomLineItem } from './cart.service'; +import { CancelParameters, CreateParameters } from '@mollie/api-client/dist/types/binders/payments/refunds/parameters'; +import ApplePaySession from '@mollie/api-client/dist/types/data/applePaySession/ApplePaySession'; +import { CreateParameters as CreateCaptureParameters } from '@mollie/api-client/dist/types/binders/payments/captures/parameters'; +import { logger } from '../utils/logger.utils'; /** * Validates and sorts the payment methods. @@ -191,6 +193,51 @@ const filterMethodsByPricingConstraints = ( }); }; +const isCaptureFromMollie = ( + manualCapture: boolean, + ctTransactions: CTTransaction[], + molliePayment: MPayment, +): DoCapturePaymentFromMollie => { + const hasSuccessAuthorizedTransaction = ctTransactions.some((transaction) => { + return ( + transaction.type === CTTransactionType.Authorization && + transaction.state === CTTransactionState.Success && + transaction.interactionId === molliePayment.id + ); + }); + const hasPendingChargeTransaction = ctTransactions.some((transaction) => { + return ( + transaction.type === CTTransactionType.Charge && + transaction.state === CTTransactionState.Pending && + transaction.interactionId === molliePayment.id + ); + }); + + const hasFailureCaptureTransaction = ctTransactions.some((transaction) => { + return ( + transaction.type === CTTransactionType.Charge && + transaction.state === CTTransactionState.Failure && + transaction.interactionId === molliePayment.id && + transaction.custom?.fields + ); + }); + + const answer = + manualCapture && hasSuccessAuthorizedTransaction && (hasPendingChargeTransaction || hasFailureCaptureTransaction); + + const pendingChargeTransaction = ctTransactions.find( + (transaction) => + transaction.type === CTTransactionType.Charge && + (transaction.state === CTTransactionState.Pending || transaction.state === CTTransactionState.Failure) && + transaction.interactionId === molliePayment.id, + ); + + return { + answer, + id: pendingChargeTransaction?.id, + }; +}; + /** * Handles listing payment methods by payment. * @@ -201,7 +248,7 @@ export const handleListPaymentMethodsByPayment = async (ctPayment: Payment): Pro logger.debug(`SCTM - listPaymentMethodsByPayment - ctPaymentId:${JSON.stringify(ctPayment?.id)}`); try { const mollieOptions = await mapCommercetoolsPaymentCustomFieldsToMollieListParams(ctPayment); - const methods: List = await listPaymentMethods(mollieOptions); + const methods: Method[] = await listPaymentMethods(mollieOptions); const configObjects: CustomObject[] = await getMethodConfigObjects(); const billingCountry = getBillingCountry(ctPayment); @@ -322,12 +369,18 @@ export const getPaymentStatusUpdateAction = ( ctTransactions: CTTransaction[], molliePayment: MPayment, ): UpdateAction[] => { + const updateActions: UpdateAction[] = []; const { id: molliePaymentId, status: molliePaymentStatus, method: paymentMethod } = molliePayment; // Determine if paynow or paylater method const manualCapture = PAY_LATER_ENUMS.includes(paymentMethod as PaymentMethod) || ('captureMode' in molliePayment && molliePayment.captureMode === 'manual'); const matchingTransaction = ctTransactions.find((transaction) => transaction.interactionId === molliePaymentId); + const doCaptureInMollie: DoCapturePaymentFromMollie = isCaptureFromMollie( + manualCapture, + ctTransactions, + molliePayment, + ); // Handle for cancel payment case if (molliePayment.status === PaymentStatus.canceled) { @@ -347,7 +400,7 @@ export const getPaymentStatusUpdateAction = ( ); } - if (manualCapture) { + if (manualCapture && molliePayment.status !== PaymentStatus.paid) { return [ { action: UpdateActionKey.AddTransaction, @@ -379,15 +432,38 @@ export const getPaymentStatusUpdateAction = ( // Corresponding transaction, update it const shouldUpdate = shouldPaymentStatusUpdate(molliePaymentStatus, matchingTransaction.state as CTTransactionState); if (shouldUpdate) { - return [ + updateActions.push( changeTransactionState( matchingTransaction.id as string, molliePaymentToCTStatusMap[molliePaymentStatus], ) as ChangeTransactionState, - ]; + ); + } + + if (doCaptureInMollie.answer) { + logger.debug( + `SCTM - getPaymentStatusUpdateAction - Capture payment triggered from Mollie paymentID: ${molliePaymentId}`, + ); + updateActions.push( + setTransactionCustomField( + CustomFields.capturePayment.fields.shouldCapture.name, + true, + doCaptureInMollie.id as string, + ), + setTransactionCustomField( + CustomFields.capturePayment.fields.descriptionCapture.name, + 'Payment is captured from Mollie.', + doCaptureInMollie.id as string, + ), + setTransactionCustomField( + CustomFields.capturePayment.fields.captureErrors.name, + '', + doCaptureInMollie.id as string, + ), + ); } - return []; + return updateActions; }; /** @@ -845,3 +921,87 @@ export const handleGetApplePaySession = async (ctPayment: Payment): Promise => { + const ctActions: UpdateAction[] = []; + + const pendingChargeTransaction = ctPayment.transactions.find( + (transaction) => + transaction.type === CTTransactionType.Charge && + (transaction.state === CTTransactionState.Pending || transaction.state === CTTransactionState.Failure) && + transaction.custom?.fields?.[CustomFields.capturePayment.fields.shouldCapture.name], + ); + + if (!pendingChargeTransaction) { + logger.error( + `SCTM - handleCapturePayment - Pending charge transaction is not found, CommerceTools Payment ID: ${ctPayment.id}`, + ); + + throw new CustomError(400, 'SCTM - handleCapturePayment - Pending charge transaction is not found.'); + } + + const molliePayment: MPayment = await getPaymentById(pendingChargeTransaction.interactionId as string); + + if (molliePayment?.status === PaymentStatus.paid) { + logger.error(`SCTM - handleCapturePayment - Payment is already paid, Mollie Payment ID: ${molliePayment.id}`); + + return { + statusCode: 200, + actions: [changeTransactionState(pendingChargeTransaction.id, CTTransactionState.Success)], + }; + } + + if ( + molliePayment?.captureMode?.toString() !== CaptureModes.Manual || + molliePayment?.status !== PaymentStatus.authorized + ) { + logger.error( + `SCTM - handleCapturePayment - Payment is not authorized or capture mode is not manual, Mollie Payment ID: ${molliePayment.id}`, + ); + + throw new CustomError( + 400, + `SCTM - handleCapturePayment - Payment is not authorized or capture mode is not manual, Mollie Payment ID: ${molliePayment.id}`, + ); + } + + const createParams: CreateCaptureParameters = { + paymentId: molliePayment.id, + amount: molliePayment.amount, + description: + pendingChargeTransaction?.custom?.fields?.[CustomFields.capturePayment.fields.descriptionCapture.name] || '', + }; + + const captureResponse: Capture | CustomError = await createCapturePayment(createParams); + + // if errors, change transaction type to Failure & save error message to custom field sctm_capture_errors + if (captureResponse instanceof CustomError) { + ctActions.push( + setTransactionCustomField( + CustomFields.capturePayment.fields.captureErrors.name, + JSON.stringify({ + errorMessage: captureResponse.message, + submitData: createParams, + }), + pendingChargeTransaction.id, + ), + ); + + if (pendingChargeTransaction.state !== CTTransactionState.Failure) { + ctActions.push(changeTransactionState(pendingChargeTransaction.id, CTTransactionState.Failure)); + } + + return { + statusCode: 400, + actions: ctActions, + }; + } + + logger.debug(`SCTM - handleCapturePayment - Capture is successful, Mollie Payment ID: ${molliePayment.id}`); + ctActions.push(changeTransactionState(pendingChargeTransaction.id, CTTransactionState.Success)); + + return { + statusCode: 200, + actions: ctActions, + }; +}; diff --git a/processor/src/types/commercetools.types.ts b/processor/src/types/commercetools.types.ts index f49b0a8..aa427f0 100644 --- a/processor/src/types/commercetools.types.ts +++ b/processor/src/types/commercetools.types.ts @@ -175,3 +175,10 @@ export type SurchargeCost = { percentageAmount: number; fixedAmount: number; }; + +export enum CaptureModes { + Manual = 'manual', + Automatic = 'automatic', +} + +export type DoCapturePaymentFromMollie = { answer: boolean } & Pick; diff --git a/processor/src/types/controller.types.ts b/processor/src/types/controller.types.ts index e830708..c5e654b 100644 --- a/processor/src/types/controller.types.ts +++ b/processor/src/types/controller.types.ts @@ -12,4 +12,5 @@ export type DeterminePaymentActionType = | 'createRefund' | 'cancelRefund' | 'noAction' - | 'getApplePaySession'; + | 'getApplePaySession' + | 'capturePayment'; diff --git a/processor/src/types/mollie.types.ts b/processor/src/types/mollie.types.ts index f91d4b4..ccaa341 100644 --- a/processor/src/types/mollie.types.ts +++ b/processor/src/types/mollie.types.ts @@ -1,4 +1,4 @@ -import { PaymentData } from '@mollie/api-client/dist/types/src/data/payments/data'; +import { PaymentData } from '@mollie/api-client/dist/types/data/payments/data'; export type ParsedMethodsRequestType = { locale?: string; diff --git a/processor/src/utils/constant.utils.ts b/processor/src/utils/constant.utils.ts index d060fdf..2a2d2f4 100644 --- a/processor/src/utils/constant.utils.ts +++ b/processor/src/utils/constant.utils.ts @@ -40,6 +40,37 @@ export const CustomFields = { }, transactionSurchargeCost: 'sctm_transaction_surcharge_cost', transactionRefundForMolliePayment: 'sctm_transaction_refund_for_mollie_payment', + capturePayment: { + typeKey: 'sctm_capture_payment_request', + name: { + en: 'Capture payment', + de: 'Zahlung einziehen', + }, + resourceTypeId: 'transaction', + fields: { + shouldCapture: { + name: 'sctm_should_capture', + label: { + en: 'Should capture money for this transaction', + de: 'Soll das Geld für diese Transaktion eingezogen werden', + }, + }, + descriptionCapture: { + name: 'sctm_capture_description', + label: { + en: 'Capture description', + de: 'Beschreibung der Einziehung', + }, + }, + captureErrors: { + name: 'sctm_capture_errors', + label: { + en: 'Capture errors', + de: 'Fehler bei der Einziehung', + }, + }, + }, + }, }; export enum ConnectorActions { @@ -50,6 +81,7 @@ export enum ConnectorActions { CancelRefund = 'cancelRefund', NoAction = 'noAction', GetApplePaySession = 'getApplePaySession', + CapturePayment = 'capturePayment', } export const ErrorMessages = { diff --git a/processor/src/utils/mollie.utils.ts b/processor/src/utils/mollie.utils.ts index afcc058..068ec2e 100644 --- a/processor/src/utils/mollie.utils.ts +++ b/processor/src/utils/mollie.utils.ts @@ -1,12 +1,12 @@ import { CentPrecisionMoney } from '@commercetools/platform-sdk'; -import { Amount } from '@mollie/api-client/dist/types/src/data/global'; import { CTMoney, CTTransactionState } from '../types/commercetools.types'; import { PaymentStatus, RefundStatus } from '@mollie/api-client'; import { DEFAULT_DUE_DATE, DUE_DATE_PATTERN } from './constant.utils'; import { logger } from './logger.utils'; import CustomError from '../errors/custom.error'; +import { Amount } from '@mollie/api-client/dist/types/data/global'; -const convertCTToMollieAmountValue = (ctValue: number, fractionDigits = 2): string => { +export const convertCTToMollieAmountValue = (ctValue: number, fractionDigits = 2): string => { const divider = Math.pow(10, fractionDigits); return (ctValue / divider).toFixed(fractionDigits); }; diff --git a/processor/src/utils/paymentAction.utils.ts b/processor/src/utils/paymentAction.utils.ts index 03bbadc..9fb498a 100644 --- a/processor/src/utils/paymentAction.utils.ts +++ b/processor/src/utils/paymentAction.utils.ts @@ -15,6 +15,8 @@ const getTransactionGroups = (transactions: Transaction[]) => { pendingRefund: [] as Transaction[], initialCancelAuthorization: [] as Transaction[], successAuthorization: [] as Transaction[], + failureCapture: [] as Transaction[], + pendingCapture: [] as Transaction[], }; transactions?.forEach((transaction) => { @@ -30,7 +32,9 @@ const getTransactionGroups = (transactions: Transaction[]) => { break; case CTTransactionState.Pending: if (transaction.type === CTTransactionType.Charge) { - groups.pendingCharge.push(transaction); + transaction.custom?.fields?.[CustomFields.capturePayment.fields.shouldCapture.name] + ? groups.pendingCapture.push(transaction) + : groups.pendingCharge.push(transaction); } else if (transaction.type === CTTransactionType.Refund) { groups.pendingRefund.push(transaction); } @@ -42,6 +46,15 @@ const getTransactionGroups = (transactions: Transaction[]) => { groups.successAuthorization.push(transaction); } break; + case CTTransactionState.Failure: + if ( + (transaction.type === CTTransactionType.Charge && + transaction.custom?.fields?.[CustomFields.capturePayment.fields.captureErrors.name]?.length >= 1) || + transaction.custom?.fields?.[CustomFields.capturePayment.fields.shouldCapture.name] + ) { + groups.failureCapture.push(transaction); + } + break; } }); @@ -80,6 +93,13 @@ const determineAction = (groups: ReturnType): Deter return ConnectorActions.CancelRefund; } + if ( + (groups.failureCapture.length >= 1 || groups.pendingCapture.length >= 1) && + groups.successAuthorization.length >= 1 + ) { + return ConnectorActions.CapturePayment; + } + logger.warn('SCTM - No payment actions matched'); return ConnectorActions.NoAction; }; diff --git a/processor/tests/commercetools/auth.commercetools.spec.ts b/processor/tests/commercetools/auth.commercetools.spec.ts index abaf9a3..9d55f68 100644 --- a/processor/tests/commercetools/auth.commercetools.spec.ts +++ b/processor/tests/commercetools/auth.commercetools.spec.ts @@ -1,16 +1,20 @@ import { getAccessToken } from './../../src/commercetools/auth.commercetools'; -import { afterEach, describe, expect, jest, it } from '@jest/globals'; +import { afterEach, describe, expect, jest, test } from '@jest/globals'; import fetch from 'node-fetch'; // @ts-expect-error: Mock fetch globally fetch = jest.fn() as jest.Mock; +jest.mock('./../../src/commercetools/auth.commercetools', () => ({ + getAccessToken: jest.fn(), +})); + describe('test getAccessToken', () => { afterEach(() => { jest.clearAllMocks(); // Clear all mocks after each test }); - it('should call fetch with the correct parameters', async () => { + test('should call fetch with the correct parameters', () => { const expectedHeaders = { Authorization: `Basic ${btoa(`${process.env.CTP_CLIENT_ID}:${process.env.CTP_CLIENT_SECRET}`)}`, 'Content-Type': 'application/json', @@ -18,21 +22,17 @@ describe('test getAccessToken', () => { const expectedUrl = `${process.env.CTP_AUTH_URL}/oauth/token?grant_type=client_credentials`; - (fetch as unknown as jest.Mock).mockImplementation(async () => - Promise.resolve({ - json: () => Promise.resolve({ data: [] }), - headers: new Headers(), - ok: true, - redirected: false, - status: 201, - statusText: 'OK', - url: '', - }), - ); - - await getAccessToken(); - - expect(fetch).toHaveBeenCalledTimes(1); + (getAccessToken as jest.Mock).mockImplementation(async () => { + return await fetch(expectedUrl, { + method: 'POST', + headers: expectedHeaders, + redirect: 'follow', + }); + }); + + getAccessToken(); + + expect(fetch).toBeCalledTimes(1); expect(fetch).toHaveBeenCalledWith(expectedUrl, { method: 'POST', headers: expectedHeaders, diff --git a/processor/tests/controllers/payment.controller.spec.ts b/processor/tests/controllers/payment.controller.spec.ts index 4227826..b6186f0 100644 --- a/processor/tests/controllers/payment.controller.spec.ts +++ b/processor/tests/controllers/payment.controller.spec.ts @@ -10,6 +10,7 @@ import { handleCreateRefund, handleGetApplePaySession, handleCancelPayment, + handleCapturePayment, } from '../../src/service/payment.service'; import { CancelStatusText, ConnectorActions, CustomFields as CustomFieldName } from '../../src/utils/constant.utils'; import { validateCommerceToolsPaymentPayload } from '../../src/validators/payment.validators'; @@ -23,6 +24,7 @@ jest.mock('../../src/service/payment.service', () => ({ handleCreateRefund: jest.fn(), handleGetApplePaySession: jest.fn(), handleCancelPayment: jest.fn(), + handleCapturePayment: jest.fn(), })); jest.mock('../../src/validators/payment.validators.ts', () => ({ @@ -525,4 +527,79 @@ describe('Test payment.controller.ts', () => { expect(handleCancelPayment).toBeCalledTimes(1); expect(handleCancelPayment).toReturnWith(handleCancelPaymentResponse); }); + + test('able to call and retrieve the result from handleCapturePayment', async () => { + mockAction = 'Update' as string; + mockResource = { + typeId: 'payment', + obj: { + id: '5c8b0375-305a-3f19-ae8e-07806b101999', + version: 1, + createdAt: '2024-07-04T14:07:35.625Z', + lastModifiedAt: '2024-07-04T14:07:35.625Z', + amountPlanned: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + paymentStatus: {}, + transactions: [ + { + id: '5c8b0375-305a-4f19-ae8e-07806b101999', + type: 'Charge', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Pending', + interactionId: 'tr_7UhSN2zuXS', + customs: { + fields: { + scmt_should_capture: true, + sctm_capture_description: 'Capture the payment', + }, + }, + }, + { + id: '5c8b0375-305a-4f19-ae8e-07806b101299', + type: 'Authorization', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Success', + interactionId: 'tr_7UhSN2zuXS', + }, + ], + interfaceInteractions: [], + paymentMethodInfo: { + method: 'creditcard', + }, + } as unknown as Payment, + } as PaymentReference; + + const ctActions = { + statusCode: 200, + actions: [ + { + action: 'changeTransactionState', + transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', + state: 'Success', + }, + ], + }; + + (determinePaymentAction as jest.Mock).mockReturnValue(ConnectorActions.CapturePayment); + + (handleCapturePayment as jest.Mock).mockReturnValue(ctActions); + + const response = await paymentController(mockAction, mockResource); + + expect(response).toBeDefined(); + }); }); diff --git a/processor/tests/mollie/refund.mollie.spec.ts b/processor/tests/mollie/refund.mollie.spec.ts index 62a674a..ea244e8 100644 --- a/processor/tests/mollie/refund.mollie.spec.ts +++ b/processor/tests/mollie/refund.mollie.spec.ts @@ -1,13 +1,13 @@ import { afterEach, describe, expect, it, jest } from '@jest/globals'; -import { - CancelParameters, - CreateParameters, - GetParameters, -} from '@mollie/api-client/dist/types/src/binders/payments/refunds/parameters'; import { cancelPaymentRefund, createPaymentRefund, getPaymentRefund } from '../../src/mollie/refund.mollie'; import { MollieApiError } from '@mollie/api-client'; import CustomError from '../../src/errors/custom.error'; import { logger } from '../../src/utils/logger.utils'; +import { + CancelParameters, + CreateParameters, + GetParameters, +} from '@mollie/api-client/dist/types/binders/payments/refunds/parameters'; const mockPaymentRefundGet = jest.fn(); const mockPaymentRefundCreate = jest.fn(); diff --git a/processor/tests/routes/processor.route.spec.ts b/processor/tests/routes/processor.route.spec.ts index 2e69811..90f2aee 100644 --- a/processor/tests/routes/processor.route.spec.ts +++ b/processor/tests/routes/processor.route.spec.ts @@ -9,7 +9,9 @@ import { createCustomPaymentTransactionCancelReasonType, createTransactionSurchargeCustomType, createTransactionRefundForMolliePaymentCustomType, + createTransactionCaptureForMolliePaymentCustomType, } from '../../src/commercetools/customFields.commercetools'; +import { getAccessToken } from '../../src/commercetools/auth.commercetools'; jest.mock('../../src/commercetools/extensions.commercetools', () => ({ deletePaymentExtension: jest.fn(), @@ -22,6 +24,11 @@ jest.mock('../../src/commercetools/customFields.commercetools', () => ({ createCustomPaymentTransactionCancelReasonType: jest.fn(), createTransactionSurchargeCustomType: jest.fn(), createTransactionRefundForMolliePaymentCustomType: jest.fn(), + createTransactionCaptureForMolliePaymentCustomType: jest.fn(), +})); + +jest.mock('../../src/commercetools/auth.commercetools', () => ({ + getAccessToken: jest.fn(), })); describe('Test src/route/processor.route.ts', () => { @@ -86,7 +93,48 @@ describe('Test src/route/processor.route.ts', () => { action: 'Update', resource: { typeId: 'payment', - obj: {}, + obj: { + id: '5c8b0375-305a-4f19-ae8e-07806b101999', + version: 1, + createdAt: '2024-07-04T14:07:35.625Z', + lastModifiedAt: '2024-07-04T14:07:35.625Z', + amountPlanned: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + paymentStatus: {}, + transactions: [ + { + id: '199292919285', + type: 'Charge', + interactionId: 'tr_123123', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Success', + }, + { + id: 'test_refund', + type: 'Refund', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Initial', + }, + ], + interfaceInteractions: [], + paymentMethodInfo: { + method: 'creditcard', + }, + }, }, }, }; @@ -115,6 +163,10 @@ describe('Test src/route/processor.route.ts', () => { (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()); + (createTransactionCaptureForMolliePaymentCustomType as jest.Mock).mockReturnValueOnce(Promise.resolve()); + + (getAccessToken as jest.Mock).mockReturnValueOnce(Promise.resolve()); req = { hostname: 'test.com', @@ -125,6 +177,7 @@ describe('Test src/route/processor.route.ts', () => { await handler(req as Request, res as Response, next); expect(res.status).toHaveBeenCalledWith(200); + expect(res.send).not.toHaveBeenCalled; }); it('should return 400 if hostname is not provided', async () => { diff --git a/processor/tests/service/payment.service.spec.ts b/processor/tests/service/payment.service.spec.ts index c6e2e61..021db43 100644 --- a/processor/tests/service/payment.service.spec.ts +++ b/processor/tests/service/payment.service.spec.ts @@ -1,10 +1,5 @@ import { - getMethodConfigObjects, - getSingleMethodConfigObject, -} from './../../src/commercetools/customObjects.commercetools'; -import { afterEach, beforeEach, describe, expect, it, jest, test } from '@jest/globals'; -import { Cart, CustomFields, Payment } from '@commercetools/platform-sdk'; -import { + handleCapturePayment, getCreatePaymentUpdateAction, getPaymentCancelActions, getRefundStatusUpdateActions, @@ -15,7 +10,13 @@ import { handleListPaymentMethodsByPayment, handlePaymentCancelRefund, handlePaymentWebhook, -} from '../../src/service/payment.service'; +} from './../../src/service/payment.service'; +import { + getMethodConfigObjects, + getSingleMethodConfigObject, +} from './../../src/commercetools/customObjects.commercetools'; +import { afterEach, beforeEach, describe, expect, it, jest, test } from '@jest/globals'; +import { Cart, CustomFields, Payment } from '@commercetools/platform-sdk'; import { ControllerResponseType } from '../../src/types/controller.types'; import { CancelStatusText, @@ -37,21 +38,21 @@ import { createMolliePayment, getPaymentById, listPaymentMethods, - createPaymentWithCustomMethod, + createCapturePayment, } from '../../src/mollie/payment.mollie'; import { cancelPaymentRefund, createPaymentRefund, getPaymentRefund } from '../../src/mollie/refund.mollie'; import CustomError from '../../src/errors/custom.error'; import { logger } from '../../src/utils/logger.utils'; import { getPaymentByMolliePaymentId, updatePayment } from '../../src/commercetools/payment.commercetools'; -import { CreateParameters } from '@mollie/api-client/dist/types/src/binders/payments/refunds/parameters'; import { getPaymentExtension } from '../../src/commercetools/extensions.commercetools'; import { createCartUpdateActions, createMollieCreatePaymentParams } from '../../src/utils/map.utils'; -import { CustomPayment } from '../../src/types/mollie.types'; import { changeTransactionState } from '../../src/commercetools/action.commercetools'; import { makeCTMoney, shouldRefundStatusUpdate } from '../../src/utils/mollie.utils'; import { getCartFromPayment, updateCart } from '../../src/commercetools/cart.commercetools'; import { calculateTotalSurchargeAmount } from '../../src/utils/app.utils'; import { removeCartMollieCustomLineItem } from '../../src/service/cart.service'; +import { CreateParameters } from '@mollie/api-client/dist/types/binders/payments/refunds/parameters'; +import { CreateParameters as CreateCaptureParameters } from '@mollie/api-client/dist/types/binders/payments/captures/parameters'; const uuid = '5c8b0375-305a-4f19-ae8e-07806b101999'; jest.mock('uuid', () => ({ @@ -91,6 +92,7 @@ jest.mock('../../src/mollie/payment.mollie', () => ({ cancelPayment: jest.fn(), getApplePaySession: jest.fn(), createPaymentWithCustomMethod: jest.fn(), + createCapturePayment: jest.fn(), })); jest.mock('../../src/mollie/refund.mollie', () => ({ @@ -1441,179 +1443,6 @@ describe('Test handleCreatePayment', () => { actions: ctActions, }); }); - - it('should able to call the createPaymentWithCustomMethod when the payment method is not defined in Mollie PaymentMethod enum', async () => { - const molliePayment: CustomPayment = { - resource: 'payment', - id: 'tr_7UhSN1zuXS', - amount: { - value: '10.00', - currency: 'EUR', - }, - description: 'Order #12345', - redirectUrl: 'https://webshop.example.org/order/12345/', - webhookUrl: 'https://webshop.example.org/payments/webhook/', - metadata: '{"order_id":12345}', - profileId: 'pfl_QkEhN94Ba', - method: 'blik', - status: PaymentStatus.open, - isCancelable: false, - createdAt: '2024-03-20T09:13:37+00:00', - expiresAt: '2024-03-20T09:28:37+00:00', - _links: { - self: { - href: '...', - type: 'application/hal+json', - }, - checkout: { - href: 'https://www.mollie.com/checkout/select-method/7UhSN1zuXS', - type: 'text/html', - }, - documentation: { - href: '...', - type: 'text/html', - }, - }, - } as CustomPayment; - - const customLineItem = { - id: 'custom-line', - key: MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, - }; - - const cart = { - customLineItems: [customLineItem], - } as Cart; - - const methodConfig = { - value: { - pricingConstraints: [ - { - currencyCode: CTPayment.amountPlanned.currencyCode, - countryCode: JSON.parse(CTPayment.custom?.fields?.sctm_payment_methods_request).billingCountry, - surchargeCost: { - percentageAmount: 2, - fixedAmount: 10, - }, - }, - ], - }, - }; - - const appUtils = require('../../src/utils/app.utils'); - - jest.spyOn(appUtils, 'calculateTotalSurchargeAmount'); - - const mapUtils = require('../../src/utils/map.utils'); - - jest.spyOn(mapUtils, 'createCartUpdateActions'); - - (getCartFromPayment as jest.Mock).mockReturnValueOnce(cart); - (getSingleMethodConfigObject as jest.Mock).mockReturnValueOnce(methodConfig); - - (createPaymentWithCustomMethod as jest.Mock).mockReturnValueOnce(molliePayment); - (getPaymentExtension as jest.Mock).mockReturnValueOnce({ - destination: { - url: 'https://example.com', - }, - }); - - (createMollieCreatePaymentParams as jest.Mock).mockReturnValueOnce({ - method: 'blik', - }); - - (changeTransactionState as jest.Mock).mockReturnValueOnce({ - action: 'changeTransactionState', - state: 'Pending', - transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', - }); - - (updateCart as jest.Mock).mockReturnValueOnce(cart); - - const totalSurchargeAmount = 1020; - - const actual = await handleCreatePayment(CTPayment); - - const expectedCartUpdateActions = [ - { - action: 'removeCustomLineItem', - customLineItemId: customLineItem.id, - }, - { - action: 'addCustomLineItem', - name: { - de: MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, - en: MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, - }, - quantity: 1, - money: { - centAmount: totalSurchargeAmount, - currencyCode: CTPayment.amountPlanned.currencyCode, - }, - slug: MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, - }, - ]; - - expect(calculateTotalSurchargeAmount).toHaveBeenCalledTimes(1); - expect(calculateTotalSurchargeAmount).toHaveBeenCalledWith( - CTPayment, - methodConfig.value.pricingConstraints[0].surchargeCost, - ); - expect(calculateTotalSurchargeAmount).toHaveReturnedWith( - totalSurchargeAmount / Math.pow(10, CTPayment.amountPlanned.fractionDigits), - ); - - expect(createCartUpdateActions).toHaveBeenCalledTimes(1); - expect(createCartUpdateActions).toHaveBeenCalledWith(cart, CTPayment, totalSurchargeAmount); - expect(createCartUpdateActions).toHaveReturnedWith(expectedCartUpdateActions); - - const ctActions = [ - { - action: 'addInterfaceInteraction', - type: { key: 'sctm_interface_interaction_type' }, - fields: { - sctm_id: '5c8b0375-305a-4f19-ae8e-07806b101999', - sctm_action_type: 'createPayment', - sctm_created_at: '2024-03-20T09:13:37+00:00', - sctm_request: '{"transactionId":"5c8b0375-305a-4f19-ae8e-07806b101999","paymentMethod":"creditcard"}', - sctm_response: - '{"molliePaymentId":"tr_7UhSN1zuXS","checkoutUrl":"https://www.mollie.com/checkout/select-method/7UhSN1zuXS","transactionId":"5c8b0375-305a-4f19-ae8e-07806b101999"}', - }, - }, - { - action: 'changeTransactionInteractionId', - transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', - interactionId: 'tr_7UhSN1zuXS', - }, - { - action: 'changeTransactionTimestamp', - transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', - timestamp: '2024-03-20T09:13:37+00:00', - }, - { - action: 'changeTransactionState', - transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', - state: 'Pending', - }, - { - action: 'setTransactionCustomType', - type: { - key: 'sctm_transaction_surcharge_cost', - }, - fields: { - surchargeAmountInCent: totalSurchargeAmount, - }, - transactionId: CTPayment.transactions[0].id, - }, - ]; - - expect(createPaymentWithCustomMethod).toBeCalledTimes(1); - - expect(actual).toEqual({ - statusCode: 201, - actions: ctActions, - }); - }); }); describe('Test handleCreateRefund', () => { @@ -3155,4 +2984,566 @@ describe('Test handleGetApplePaySession', () => { expect(mockMakeCTMoney).toHaveBeenCalledWith({ currency: 'EUR', value: '20.00' }); }); }); + + describe('Test handleCapturePayment', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should return status code and array of actions', async () => { + const CTPayment: Payment = { + id: '5c8b0375-305a-4f19-ae8e-07806b101999', + version: 1, + createdAt: '2024-07-04T14:07:35.625Z', + lastModifiedAt: '2024-07-04T14:07:35.625Z', + amountPlanned: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + paymentStatus: {}, + transactions: [ + { + id: '5c8b0375-305a-4f19-ae8e-07806b101999', + type: 'Charge', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Pending', + interactionId: 'tr_7UhSN1zuXS', + custom: { + fields: { + sctm_should_capture: true, + }, + } as unknown as CustomFields, + }, + { + id: '5c8b0375-305a-4f19-ae8e-07806b101299', + type: 'Athorization', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Success', + interactionId: 'tr_7UhSN1zuXS', + }, + ], + interfaceInteractions: [], + paymentMethodInfo: { + method: 'creditcard', + }, + }; + + 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', + captureMode: 'manual', + status: 'authorized', + isCancelable: true, + 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; + + const createCaptureParams: CreateCaptureParameters = { + paymentId: molliePayment.id, + amount: molliePayment.amount, + description: '', + }; + + const ctActions = [ + { + action: 'changeTransactionState', + state: 'Success', + transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', + }, + ]; + + (getPaymentById as jest.Mock).mockReturnValueOnce(molliePayment); + + (createCapturePayment as jest.Mock).mockReturnValueOnce({ + resource: 'capture', + id: 'cpt_EfcGLXjfSJLFcKJPwhg4J', + mode: 'test', + amount: { + value: '116.99', + currency: 'EUR', + }, + status: 'pending', + createdAt: '2025-02-17T04:50:18+00:00', + description: 'Test capture api', + paymentId: 'tr_hveLpm4TrJ', + _links: { + self: { + href: 'https://api.mollie.com/v2/payments/tr_hveLpm4TrJ/captures/cpt_EfcGLXjfSJLFcKJPwhg4J', + type: 'application/hal+json', + }, + payment: { + href: 'https://api.mollie.com/v2/payments/tr_hveLpm4TrJ', + type: 'application/hal+json', + }, + documentation: { + href: 'https://docs.mollie.com/reference/v2/captures-api/create-capture', + type: 'text/html', + }, + }, + }); + + (changeTransactionState as jest.Mock).mockReturnValueOnce(ctActions[0]); + + const actual = await handleCapturePayment(CTPayment); + + expect(getPaymentById).toBeCalledTimes(1); + expect(getPaymentById).toBeCalledWith(CTPayment.transactions[0].interactionId); + expect(createCapturePayment).toBeCalledTimes(1); + expect(createCapturePayment).toBeCalledWith(createCaptureParams); + + expect(actual).toEqual({ + statusCode: 200, + actions: ctActions, + }); + expect(logger.debug).toBeCalledTimes(1); + }); + + test('should throw error when no pending charge transaction found ', async () => { + const CTPayment: Payment = { + id: '5c8b0375-305a-4f19-ae8e-07806b101999', + version: 1, + createdAt: '2024-07-04T14:07:35.625Z', + lastModifiedAt: '2024-07-04T14:07:35.625Z', + amountPlanned: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + paymentStatus: {}, + transactions: [ + { + id: '5c8b0375-305a-4f19-ae8e-07806b101299', + type: 'Athorization', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Success', + interactionId: 'tr_7UhSN1zuXS', + }, + ], + interfaceInteractions: [], + paymentMethodInfo: { + method: 'creditcard', + }, + }; + + await expect(handleCapturePayment(CTPayment)).rejects.toThrow( + new CustomError(400, 'SCTM - handleCapturePayment - Pending charge transaction is not found.'), + ); + expect(logger.error).toBeCalledTimes(1); + }); + + test('should update transaction status to success when mollie payment already paid ', async () => { + const CTPayment: Payment = { + id: '5c8b0375-305a-4f19-ae8e-07806b101999', + version: 1, + createdAt: '2024-07-04T14:07:35.625Z', + lastModifiedAt: '2024-07-04T14:07:35.625Z', + amountPlanned: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + paymentStatus: {}, + transactions: [ + { + id: '5c8b0375-305a-4f19-ae8e-07806b101999', + type: 'Charge', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Pending', + interactionId: 'tr_7UhSN1zuXS', + custom: { + fields: { + sctm_should_capture: true, + }, + } as unknown as CustomFields, + }, + { + id: '5c8b0375-305a-4f19-ae8e-07806b101299', + type: 'Athorization', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Success', + interactionId: 'tr_7UhSN1zuXS', + }, + ], + interfaceInteractions: [], + paymentMethodInfo: { + method: 'creditcard', + }, + }; + + 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', + captureMode: 'manual', + status: 'paid', + isCancelable: true, + 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); + + (changeTransactionState as jest.Mock).mockReturnValueOnce({ + action: 'changeTransactionState', + state: 'Success', + transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', + }); + + await expect(handleCapturePayment(CTPayment)).resolves.toEqual({ + statusCode: 200, + actions: [ + { + action: 'changeTransactionState', + state: 'Success', + transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', + }, + ], + }); + expect(logger.error).toBeCalledTimes(1); + }); + + test('should throw error when mollie payment is not valid (captureMode != manual or state != authorized)', async () => { + const CTPayment: Payment = { + id: '5c8b0375-305a-3f19-ae8e-07806b101999', + version: 1, + createdAt: '2024-07-04T14:07:35.625Z', + lastModifiedAt: '2024-07-04T14:07:35.625Z', + amountPlanned: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + paymentStatus: {}, + transactions: [ + { + id: '5c8b0375-305a-4f19-ae8e-07806b101999', + type: 'Charge', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Pending', + interactionId: 'tr_7UhSN2zuXS', + custom: { + fields: { + sctm_should_capture: true, + }, + } as unknown as CustomFields, + }, + { + id: '5c8b0375-305a-4f19-ae8e-07806b101299', + type: 'Athorization', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Success', + interactionId: 'tr_7UhSN2zuXS', + }, + ], + interfaceInteractions: [], + paymentMethodInfo: { + method: 'creditcard', + }, + }; + + const molliePayment: molliePayment = { + resource: 'payment', + id: 'tr_7UhSN2zuXS', + 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', + captureMode: 'automatic', + status: 'pending', + isCancelable: true, + 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); + + await expect(handleCapturePayment(CTPayment)).rejects.toThrow( + new CustomError( + 400, + `SCTM - handleCapturePayment - Payment is not authorized or capture mode is not manual, Mollie Payment ID: ${molliePayment.id}`, + ), + ); + expect(logger.error).toBeCalledTimes(1); + }); + + test('should throw error when mollie payment is not valid (captureMode != manual or state != authorized)', async () => { + const CTPayment: Payment = { + id: '5c8b0375-305a-3f19-ae8e-07806b101999', + version: 1, + createdAt: '2024-07-04T14:07:35.625Z', + lastModifiedAt: '2024-07-04T14:07:35.625Z', + amountPlanned: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + paymentStatus: {}, + transactions: [ + { + id: '5c8b0375-305a-4f19-ae8e-07806b101999', + type: 'Charge', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Pending', + interactionId: 'tr_7UhSN2zuXS', + custom: { + fields: { + sctm_should_capture: true, + }, + } as unknown as CustomFields, + }, + { + id: '5c8b0375-305a-4f19-ae8e-07806b101299', + type: 'Athorization', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Success', + interactionId: 'tr_7UhSN2zuXS', + }, + ], + interfaceInteractions: [], + paymentMethodInfo: { + method: 'creditcard', + }, + }; + + const molliePayment: molliePayment = { + resource: 'payment', + id: 'tr_7UhSN2zuXS', + 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', + captureMode: 'automatic', + status: 'pending', + isCancelable: true, + 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); + + await expect(handleCapturePayment(CTPayment)).rejects.toThrow( + new CustomError( + 400, + `SCTM - handleCapturePayment - Payment is not authorized or capture mode is not manual, Mollie Payment ID: ${molliePayment.id}`, + ), + ); + expect(logger.error).toBeCalledTimes(1); + }); + + test('should record errors when capturing failed', async () => { + const CTPayment: Payment = { + id: '5c8b0375-305a-3f19-ae8e-07806b101999', + version: 1, + createdAt: '2024-07-04T14:07:35.625Z', + lastModifiedAt: '2024-07-04T14:07:35.625Z', + amountPlanned: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + paymentStatus: {}, + transactions: [ + { + id: '5c8b0375-305a-4f19-ae8e-07806b101999', + type: 'Charge', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Pending', + interactionId: 'tr_7UhSN2zuXS', + custom: { + fields: { + sctm_should_capture: true, + }, + } as unknown as CustomFields, + }, + { + id: '5c8b0375-305a-4f19-ae8e-07806b101299', + type: 'Athorization', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Success', + interactionId: 'tr_7UhSN2zuXS', + }, + ], + interfaceInteractions: [], + paymentMethodInfo: { + method: 'creditcard', + }, + }; + + const molliePayment: molliePayment = { + resource: 'payment', + id: 'tr_7UhSN2zuXS', + 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', + captureMode: 'manual', + status: 'authorized', + isCancelable: true, + 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); + + (createCapturePayment as jest.Mock).mockReturnValueOnce(new CustomError(400, 'Capture failed')); + + (changeTransactionState as jest.Mock).mockReturnValueOnce({ + action: 'changeTransactionState', + state: 'Failure', + transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', + }); + + await expect(handleCapturePayment(CTPayment)).resolves.toEqual({ + statusCode: 400, + actions: [ + { + action: 'setTransactionCustomField', + name: 'sctm_capture_errors', + value: + '{"errorMessage":"Capture failed","submitData":{"paymentId":"tr_7UhSN2zuXS","amount":{"value":"10.00","currency":"EUR"},"description":""}}', + transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', + }, + { + action: 'changeTransactionState', + state: 'Failure', + transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', + }, + ], + }); + }); + }); }); diff --git a/processor/tests/utils/mollie.utils.spec.ts b/processor/tests/utils/mollie.utils.spec.ts index c016a39..8f35f55 100644 --- a/processor/tests/utils/mollie.utils.spec.ts +++ b/processor/tests/utils/mollie.utils.spec.ts @@ -9,10 +9,10 @@ import { shouldRefundStatusUpdate, calculateDueDate, } from '../../src/utils/mollie.utils'; -import { Amount } from '@mollie/api-client/dist/types/src/data/global'; import { expect, describe, it, test, jest } from '@jest/globals'; import { logger } from '../../src/utils/logger.utils'; import CustomError from '../../src/errors/custom.error'; +import { Amount } from '@mollie/api-client/dist/types/data/global'; describe('Test mollie.utils.ts', () => { describe('convertCTToMollieAmountValue', () => {