@webbers/pay-payments-medusa 2.2.51 → 2.2.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/package.json +1 -1
- package/.medusa/server/src/admin/index.js +233 -11
- package/.medusa/server/src/admin/index.mjs +234 -12
- package/.medusa/server/src/api/admin/pay/orders/[id]/direct-debit/exchange/route.d.ts +12 -0
- package/.medusa/server/src/api/admin/pay/orders/[id]/direct-debit/exchange/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/pay/orders/[id]/direct-debit/exchange/route.js +42 -0
- package/.medusa/server/src/api/admin/pay/orders/[id]/direct-debit/route.d.ts +9 -0
- package/.medusa/server/src/api/admin/pay/orders/[id]/direct-debit/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/pay/orders/[id]/direct-debit/route.js +19 -0
- package/.medusa/server/src/api/admin/pay/orders/[id]/reversal/route.d.ts +7 -0
- package/.medusa/server/src/api/admin/pay/orders/[id]/reversal/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/pay/orders/[id]/reversal/route.js +17 -0
- package/.medusa/server/src/api/admin/pay/settings/route.d.ts +12 -0
- package/.medusa/server/src/api/admin/pay/settings/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/pay/settings/route.js +24 -0
- package/.medusa/server/src/providers/pay/core/pay-base.d.ts +6 -0
- package/.medusa/server/src/providers/pay/core/pay-base.d.ts.map +1 -1
- package/.medusa/server/src/providers/pay/core/pay-base.js +24 -1
- package/.medusa/server/src/providers/pay/services/pay-direct-debit.d.ts +6 -1
- package/.medusa/server/src/providers/pay/services/pay-direct-debit.d.ts.map +1 -1
- package/.medusa/server/src/providers/pay/services/pay-direct-debit.js +34 -15
- package/.medusa/server/src/providers/pay/types/index.d.ts +6 -5
- package/.medusa/server/src/providers/pay/types/index.d.ts.map +1 -1
- package/.medusa/server/src/providers/pay/types/index.js +1 -1
- package/.medusa/server/src/subscribers/direct-debit-exchange.d.ts.map +1 -1
- package/.medusa/server/src/subscribers/direct-debit-exchange.js +96 -24
- package/.medusa/server/src/subscribers/payment-failed.d.ts.map +1 -1
- package/.medusa/server/src/subscribers/payment-failed.js +24 -4
- package/.medusa/server/src/utils/directDebitExchange.d.ts +15 -0
- package/.medusa/server/src/utils/directDebitExchange.d.ts.map +1 -1
- package/.medusa/server/src/utils/directDebitExchange.js +59 -2
- package/.medusa/server/src/utils/directDebitSimulation.d.ts +93 -0
- package/.medusa/server/src/utils/directDebitSimulation.d.ts.map +1 -0
- package/.medusa/server/src/utils/directDebitSimulation.js +170 -0
- package/.medusa/server/src/utils/index.d.ts +2 -0
- package/.medusa/server/src/utils/index.d.ts.map +1 -1
- package/.medusa/server/src/utils/index.js +3 -1
- package/.medusa/server/src/utils/paymentReversal.d.ts +24 -0
- package/.medusa/server/src/utils/paymentReversal.d.ts.map +1 -0
- package/.medusa/server/src/utils/paymentReversal.js +80 -0
- package/.medusa/server/src/utils/reverseCapturedPayment.d.ts +43 -0
- package/.medusa/server/src/utils/reverseCapturedPayment.d.ts.map +1 -0
- package/.medusa/server/src/utils/reverseCapturedPayment.js +100 -0
- package/README.md +51 -0
- package/package.json +1 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SEPA_TESTING_METADATA_KEY = exports.getPayProviderOptions = exports.isSimulatedExchangeKey = exports.SIMULATED_EXCHANGE_PRESETS = void 0;
|
|
7
|
+
exports.isSepaTestingEnabled = isSepaTestingEnabled;
|
|
8
|
+
exports.setSepaTestingEnabled = setSepaTestingEnabled;
|
|
9
|
+
exports.getDirectDebitSimulationState = getDirectDebitSimulationState;
|
|
10
|
+
exports.buildSimulatedExchangePayload = buildSimulatedExchangePayload;
|
|
11
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
12
|
+
const getPayPaymentSession_1 = __importDefault(require("./getPayPaymentSession"));
|
|
13
|
+
const directDebitExchange_1 = require("./directDebitExchange");
|
|
14
|
+
const types_1 = require("../providers/pay/types");
|
|
15
|
+
const constants_1 = require("../providers/pay/core/constants");
|
|
16
|
+
/**
|
|
17
|
+
* The exchanges a test server can simulate for a direct debit that only exists
|
|
18
|
+
* locally (see README "Testing direct debits locally"). Each preset is the body
|
|
19
|
+
* Pay. would send, minus the mandate and order reference that are filled in
|
|
20
|
+
* from the order.
|
|
21
|
+
*/
|
|
22
|
+
exports.SIMULATED_EXCHANGE_PRESETS = {
|
|
23
|
+
pending: {
|
|
24
|
+
label: "Pending",
|
|
25
|
+
description: "The debit is scheduled, the collection stays awaiting.",
|
|
26
|
+
payload: { action: "incassopending" },
|
|
27
|
+
},
|
|
28
|
+
sent: {
|
|
29
|
+
label: "Sent to bank",
|
|
30
|
+
description: "The debit was sent to the bank, the collection stays awaiting.",
|
|
31
|
+
payload: { action: "incassosend" },
|
|
32
|
+
},
|
|
33
|
+
collected: {
|
|
34
|
+
label: "Collected",
|
|
35
|
+
description: "The bank collected the money: the payment is captured.",
|
|
36
|
+
payload: { action: "incassocollected" },
|
|
37
|
+
},
|
|
38
|
+
storno: {
|
|
39
|
+
label: "Storno",
|
|
40
|
+
description: "The customer reversed the debit after collection: the payment is refunded in Medusa and the order becomes payable again.",
|
|
41
|
+
payload: { action: "incassostorno" },
|
|
42
|
+
},
|
|
43
|
+
failed: {
|
|
44
|
+
label: "Failed",
|
|
45
|
+
description: "The bank rejected the debit: the collection fails.",
|
|
46
|
+
payload: {
|
|
47
|
+
action: "incassosend",
|
|
48
|
+
status: { code: constants_1.PayDirectDebitStatusCode.FAILED },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
declined: {
|
|
52
|
+
label: "Declined",
|
|
53
|
+
description: "The debit was declined: the collection fails.",
|
|
54
|
+
payload: { action: "incassosend", declined: true },
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
const isSimulatedExchangeKey = (value) => typeof value === "string" &&
|
|
58
|
+
Object.prototype.hasOwnProperty.call(exports.SIMULATED_EXCHANGE_PRESETS, value);
|
|
59
|
+
exports.isSimulatedExchangeKey = isSimulatedExchangeKey;
|
|
60
|
+
const getPayProviderOptions = (container) => {
|
|
61
|
+
const configModule = container.resolve(utils_1.ContainerRegistrationKeys.CONFIG_MODULE);
|
|
62
|
+
const payModuleConfig = configModule.modules?.payment?.options
|
|
63
|
+
?.providers?.find((p) => p.id === "pay");
|
|
64
|
+
return payModuleConfig?.options ?? null;
|
|
65
|
+
};
|
|
66
|
+
exports.getPayProviderOptions = getPayProviderOptions;
|
|
67
|
+
/**
|
|
68
|
+
* The "SEPA testing" switch on the Pay. settings page lives in the store's
|
|
69
|
+
* metadata, so it is shared by every admin user and needs no migration. Off by
|
|
70
|
+
* default: it is meant for staging servers only.
|
|
71
|
+
*/
|
|
72
|
+
exports.SEPA_TESTING_METADATA_KEY = "pay_sepa_testing";
|
|
73
|
+
const getStore = async (container) => {
|
|
74
|
+
const storeModuleService = container.resolve(utils_1.Modules.STORE);
|
|
75
|
+
const [store] = await storeModuleService.listStores({}, { select: ["id", "metadata"], take: 1 });
|
|
76
|
+
return store ?? null;
|
|
77
|
+
};
|
|
78
|
+
async function isSepaTestingEnabled(container) {
|
|
79
|
+
const store = await getStore(container);
|
|
80
|
+
return store?.metadata?.[exports.SEPA_TESTING_METADATA_KEY] === true;
|
|
81
|
+
}
|
|
82
|
+
async function setSepaTestingEnabled(container, enabled) {
|
|
83
|
+
const store = await getStore(container);
|
|
84
|
+
if (!store) {
|
|
85
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, "No store found");
|
|
86
|
+
}
|
|
87
|
+
const storeModuleService = container.resolve(utils_1.Modules.STORE);
|
|
88
|
+
await storeModuleService.updateStores(store.id, {
|
|
89
|
+
metadata: { ...(store.metadata ?? {}), [exports.SEPA_TESTING_METADATA_KEY]: enabled },
|
|
90
|
+
});
|
|
91
|
+
return enabled;
|
|
92
|
+
}
|
|
93
|
+
const presetSummaries = () => Object.fromEntries(Object.entries(exports.SIMULATED_EXCHANGE_PRESETS).map(([key, preset]) => [
|
|
94
|
+
key,
|
|
95
|
+
{ label: preset.label, description: preset.description },
|
|
96
|
+
]));
|
|
97
|
+
async function getDirectDebitSimulationState(container, orderId) {
|
|
98
|
+
const query = container.resolve(utils_1.ContainerRegistrationKeys.QUERY);
|
|
99
|
+
const options = (0, exports.getPayProviderOptions)(container);
|
|
100
|
+
// Same default as the HTTP client: an unset testMode means test mode
|
|
101
|
+
const testMode = options?.testMode ?? true;
|
|
102
|
+
const sepaTesting = await isSepaTestingEnabled(container);
|
|
103
|
+
const { data: [order], } = await query.graph({
|
|
104
|
+
entity: "order",
|
|
105
|
+
fields: [
|
|
106
|
+
"id",
|
|
107
|
+
"display_id",
|
|
108
|
+
"payment_collections.id",
|
|
109
|
+
"payment_collections.status",
|
|
110
|
+
"payment_collections.payment_sessions.*",
|
|
111
|
+
"payment_collections.payment_sessions.payment.id",
|
|
112
|
+
"payment_collections.payment_sessions.payment.captured_at",
|
|
113
|
+
],
|
|
114
|
+
filters: { id: orderId },
|
|
115
|
+
});
|
|
116
|
+
if (!order) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const base = {
|
|
120
|
+
testMode,
|
|
121
|
+
sepaTesting,
|
|
122
|
+
orderId: order.id,
|
|
123
|
+
displayId: String(order.display_id),
|
|
124
|
+
presets: presetSummaries(),
|
|
125
|
+
};
|
|
126
|
+
const payPaymentSession = (0, getPayPaymentSession_1.default)(order);
|
|
127
|
+
if (!payPaymentSession?.provider_id.includes(types_1.PaymentProviderKeys.DIRECTDEBIT)) {
|
|
128
|
+
return {
|
|
129
|
+
...base,
|
|
130
|
+
available: false,
|
|
131
|
+
reason: "no_direct_debit",
|
|
132
|
+
mandateCode: null,
|
|
133
|
+
paymentCollectionId: null,
|
|
134
|
+
paymentCollectionStatus: null,
|
|
135
|
+
captured: false,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const sessionData = (payPaymentSession.data ?? {});
|
|
139
|
+
const mandateCode = typeof sessionData.code === "string" ? sessionData.code : null;
|
|
140
|
+
const simulated = sessionData.testMode === true && (0, directDebitExchange_1.isSimulatedDirectDebitMandate)(mandateCode);
|
|
141
|
+
const collection = (order.payment_collections ?? []).find((pc) => pc?.id === payPaymentSession.payment_collection_id);
|
|
142
|
+
const reason = !sepaTesting
|
|
143
|
+
? "sepa_testing_off"
|
|
144
|
+
: !testMode
|
|
145
|
+
? "test_mode_off"
|
|
146
|
+
: !simulated
|
|
147
|
+
? "not_simulated"
|
|
148
|
+
: undefined;
|
|
149
|
+
return {
|
|
150
|
+
...base,
|
|
151
|
+
available: !reason,
|
|
152
|
+
reason,
|
|
153
|
+
mandateCode,
|
|
154
|
+
paymentCollectionId: payPaymentSession.payment_collection_id ?? null,
|
|
155
|
+
paymentCollectionStatus: collection?.status ?? null,
|
|
156
|
+
captured: !!payPaymentSession.payment?.captured_at,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Builds the exchange body for a preset, as Pay. would send it for the order's
|
|
161
|
+
* simulated mandate.
|
|
162
|
+
*/
|
|
163
|
+
function buildSimulatedExchangePayload(key, state) {
|
|
164
|
+
return {
|
|
165
|
+
...exports.SIMULATED_EXCHANGE_PRESETS[key].payload,
|
|
166
|
+
mandateId: state.mandateCode,
|
|
167
|
+
reference: state.displayId,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlyZWN0RGViaXRTaW11bGF0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3V0aWxzL2RpcmVjdERlYml0U2ltdWxhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFzSEEsb0RBTUM7QUFFRCxzREFtQkM7QUFVRCxzRUFnRkM7QUFNRCxzRUFTQztBQTFQRCxxREFJa0M7QUFFbEMsa0ZBQXlEO0FBQ3pELCtEQUFtRTtBQUNuRSxrREFBMkU7QUFDM0UsK0RBQXdFO0FBRXhFOzs7OztHQUtHO0FBQ1UsUUFBQSwwQkFBMEIsR0FBRztJQUN4QyxPQUFPLEVBQUU7UUFDUCxLQUFLLEVBQUUsU0FBUztRQUNoQixXQUFXLEVBQUUsd0RBQXdEO1FBQ3JFLE9BQU8sRUFBRSxFQUFDLE1BQU0sRUFBRSxnQkFBZ0IsRUFBQztLQUNwQztJQUNELElBQUksRUFBRTtRQUNKLEtBQUssRUFBRSxjQUFjO1FBQ3JCLFdBQVcsRUFBRSxnRUFBZ0U7UUFDN0UsT0FBTyxFQUFFLEVBQUMsTUFBTSxFQUFFLGFBQWEsRUFBQztLQUNqQztJQUNELFNBQVMsRUFBRTtRQUNULEtBQUssRUFBRSxXQUFXO1FBQ2xCLFdBQVcsRUFBRSx3REFBd0Q7UUFDckUsT0FBTyxFQUFFLEVBQUMsTUFBTSxFQUFFLGtCQUFrQixFQUFDO0tBQ3RDO0lBQ0QsTUFBTSxFQUFFO1FBQ04sS0FBSyxFQUFFLFFBQVE7UUFDZixXQUFXLEVBQ1QsMEhBQTBIO1FBQzVILE9BQU8sRUFBRSxFQUFDLE1BQU0sRUFBRSxlQUFlLEVBQUM7S0FDbkM7SUFDRCxNQUFNLEVBQUU7UUFDTixLQUFLLEVBQUUsUUFBUTtRQUNmLFdBQVcsRUFBRSxvREFBb0Q7UUFDakUsT0FBTyxFQUFFO1lBQ1AsTUFBTSxFQUFFLGFBQWE7WUFDckIsTUFBTSxFQUFFLEVBQUMsSUFBSSxFQUFFLG9DQUF3QixDQUFDLE1BQU0sRUFBQztTQUNoRDtLQUNGO0lBQ0QsUUFBUSxFQUFFO1FB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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,0BAA0B,CAAA;AACxC,cAAc,mBAAmB,CAAA"}
|
|
@@ -15,4 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./createPayOrder"), exports);
|
|
18
|
-
|
|
18
|
+
__exportStar(require("./reverseCapturedPayment"), exports);
|
|
19
|
+
__exportStar(require("./paymentReversal"), exports);
|
|
20
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvdXRpbHMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLG1EQUFnQztBQUNoQywyREFBd0M7QUFDeEMsb0RBQWlDIn0=
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { MedusaContainer } from "@medusajs/types";
|
|
2
|
+
import { PaymentReversalKind } from "./reverseCapturedPayment";
|
|
3
|
+
export type PaymentReversal = {
|
|
4
|
+
/** Whether a Pay. chargeback / storno reversed a captured payment */
|
|
5
|
+
reversed: boolean;
|
|
6
|
+
kind: PaymentReversalKind | null;
|
|
7
|
+
statusCode: number | null;
|
|
8
|
+
/** When the (latest) reversal was recorded */
|
|
9
|
+
at: string | null;
|
|
10
|
+
/** Total amount that went back to the customer */
|
|
11
|
+
amount: number;
|
|
12
|
+
currency_code: string | null;
|
|
13
|
+
/** What the customer still owes on the order */
|
|
14
|
+
outstanding: number;
|
|
15
|
+
/** Whether a not_paid payment collection exists to collect it with */
|
|
16
|
+
payable: boolean;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Finds the reversal reverseCapturedPayment recorded on an order, recognised
|
|
20
|
+
* by the note on the refund. Used by the "payment reversed" order banner,
|
|
21
|
+
* since Medusa's own payment status can only say "refunded" for it.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getPaymentReversal(container: MedusaContainer, orderId: string): Promise<PaymentReversal | null>;
|
|
24
|
+
//# sourceMappingURL=paymentReversal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paymentReversal.d.ts","sourceRoot":"","sources":["../../../../src/utils/paymentReversal.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,eAAe,EAAC,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAEL,mBAAmB,EACpB,MAAM,0BAA0B,CAAA;AAEjC,MAAM,MAAM,eAAe,GAAG;IAC5B,qEAAqE;IACrE,QAAQ,EAAE,OAAO,CAAA;IACjB,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAAA;IAChC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,8CAA8C;IAC9C,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IACjB,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAYD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,eAAe,EAC1B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAkFjC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPaymentReversal = getPaymentReversal;
|
|
4
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
+
const reverseCapturedPayment_1 = require("./reverseCapturedPayment");
|
|
6
|
+
const toNumber = (value) => {
|
|
7
|
+
if (value === null || value === undefined || value === "") {
|
|
8
|
+
return 0;
|
|
9
|
+
}
|
|
10
|
+
const numeric = Number(value);
|
|
11
|
+
return Number.isFinite(numeric) ? numeric : 0;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Finds the reversal reverseCapturedPayment recorded on an order, recognised
|
|
15
|
+
* by the note on the refund. Used by the "payment reversed" order banner,
|
|
16
|
+
* since Medusa's own payment status can only say "refunded" for it.
|
|
17
|
+
*/
|
|
18
|
+
async function getPaymentReversal(container, orderId) {
|
|
19
|
+
const query = container.resolve(utils_1.ContainerRegistrationKeys.QUERY);
|
|
20
|
+
const { data: [order], } = await query.graph({
|
|
21
|
+
entity: "order",
|
|
22
|
+
fields: [
|
|
23
|
+
"id",
|
|
24
|
+
"currency_code",
|
|
25
|
+
"summary",
|
|
26
|
+
"payment_collections.id",
|
|
27
|
+
"payment_collections.status",
|
|
28
|
+
"payment_collections.payments.id",
|
|
29
|
+
"payment_collections.payments.refunds.id",
|
|
30
|
+
"payment_collections.payments.refunds.amount",
|
|
31
|
+
"payment_collections.payments.refunds.note",
|
|
32
|
+
"payment_collections.payments.refunds.created_at",
|
|
33
|
+
],
|
|
34
|
+
filters: { id: orderId },
|
|
35
|
+
});
|
|
36
|
+
if (!order) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const collections = (order.payment_collections ?? []);
|
|
40
|
+
const reversals = collections
|
|
41
|
+
.flatMap((collection) => collection?.payments ?? [])
|
|
42
|
+
.flatMap((payment) => payment?.refunds ?? [])
|
|
43
|
+
.map((refund) => ({
|
|
44
|
+
refund,
|
|
45
|
+
parsed: (0, reverseCapturedPayment_1.parsePayReversalNote)(refund?.note),
|
|
46
|
+
}))
|
|
47
|
+
.filter((entry) => entry.parsed !== null)
|
|
48
|
+
.sort((a, b) => new Date(b.refund.created_at ?? 0).getTime() -
|
|
49
|
+
new Date(a.refund.created_at ?? 0).getTime());
|
|
50
|
+
const summary = (order.summary ?? {});
|
|
51
|
+
const outstanding = toNumber(summary.pending_difference);
|
|
52
|
+
const payable = collections.some((collection) => collection?.status === "not_paid");
|
|
53
|
+
if (!reversals.length) {
|
|
54
|
+
return {
|
|
55
|
+
reversed: false,
|
|
56
|
+
kind: null,
|
|
57
|
+
statusCode: null,
|
|
58
|
+
at: null,
|
|
59
|
+
amount: 0,
|
|
60
|
+
currency_code: order.currency_code ?? null,
|
|
61
|
+
outstanding,
|
|
62
|
+
payable,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const latest = reversals[0];
|
|
66
|
+
const amount = reversals.reduce((total, entry) => utils_1.MathBN.add(total, entry.refund.amount ?? 0), utils_1.MathBN.convert(0));
|
|
67
|
+
return {
|
|
68
|
+
reversed: true,
|
|
69
|
+
kind: latest.parsed.kind,
|
|
70
|
+
statusCode: latest.parsed.statusCode,
|
|
71
|
+
at: latest.refund.created_at
|
|
72
|
+
? new Date(latest.refund.created_at).toISOString()
|
|
73
|
+
: null,
|
|
74
|
+
amount: toNumber(amount.toString()),
|
|
75
|
+
currency_code: order.currency_code ?? null,
|
|
76
|
+
outstanding,
|
|
77
|
+
payable,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGF5bWVudFJldmVyc2FsLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3V0aWxzL3BheW1lbnRSZXZlcnNhbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQXNDQSxnREFxRkM7QUEzSEQscURBQTJFO0FBRTNFLHFFQUdpQztBQWtCakMsTUFBTSxRQUFRLEdBQUcsQ0FBQyxLQUFjLEVBQVUsRUFBRTtJQUMxQyxJQUFJLEtBQUssS0FBSyxJQUFJLElBQUksS0FBSyxLQUFLLFNBQVMsSUFBSSxLQUFLLEtBQUssRUFBRSxFQUFFLENBQUM7UUFDMUQsT0FBTyxDQUFDLENBQUE7SUFDVixDQUFDO0lBRUQsTUFBTSxPQUFPLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFBO0lBRTdCLE9BQU8sTUFBTSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7QUFDL0MsQ0FBQyxDQUFBO0FBRUQ7Ozs7R0FJRztBQUNJLEtBQUssVUFBVSxrQkFBa0IsQ0FDdEMsU0FBMEIsRUFDMUIsT0FBZTtJQUVmLE1BQU0sS0FBSyxHQUFHLFNBQVMsQ0FBQyxPQUFPLENBQUMsaUNBQXlCLENBQUMsS0FBSyxDQUFDLENBQUE7SUFFaEUsTUFBTSxFQUNKLElBQUksRUFBRSxDQUFDLEtBQUssQ0FBQyxHQUNkLEdBQUcsTUFBTSxLQUFLLENBQUMsS0FBSyxDQUFDO1FBQ3BCLE1BQU0sRUFBRSxPQUFPO1FBQ2YsTUFBTSxFQUFFO1lBQ04sSUFBSTtZQUNKLGVBQWU7WUFDZixTQUFTO1lBQ1Qsd0JBQXdCO1lBQ3hCLDRCQUE0QjtZQUM1QixpQ0FBaUM7WUFDakMseUNBQXlDO1lBQ3pDLDZDQUE2QztZQUM3QywyQ0FBMkM7WUFDM0MsaURBQWlEO1NBQ2xEO1FBQ0QsT0FBTyxFQUFFLEVBQUMsRUFBRSxFQUFFLE9BQU8sRUFBQztLQUN2QixDQUFDLENBQUE7SUFFRixJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDWCxPQUFPLElBQUksQ0FBQTtJQUNiLENBQUM7SUFFRCxNQUFNLFdBQVcsR0FBRyxDQUFFLEtBQWEsQ0FBQyxtQkFBbUIsSUFBSSxFQUFFLENBRzFELENBQUE7SUFFSCxNQUFNLFNBQVMsR0FBRyxXQUFXO1NBQzFCLE9BQU8sQ0FBQyxDQUFDLFVBQVUsRUFBRSxFQUFFLENBQUMsVUFBVSxFQUFFLFFBQVEsSUFBSSxFQUFFLENBQUM7U0FDbkQsT0FBTyxDQUFDLENBQUMsT0FBNEIsRUFBRSxFQUFFLENBQUMsT0FBTyxFQUFFLE9BQU8sSUFBSSxFQUFFLENBQUM7U0FDakUsR0FBRyxDQUFDLENBQUMsTUFBMkIsRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNyQyxNQUFNO1FBQ04sTUFBTSxFQUFFLElBQUEsNkNBQW9CLEVBQUMsTUFBTSxFQUFFLElBQUksQ0FBQztLQUMzQyxDQUFDLENBQUM7U0FDRixNQUFNLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLEtBQUssQ0FBQyxNQUFNLEtBQUssSUFBSSxDQUFDO1NBQ3hDLElBQUksQ0FDSCxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUNQLElBQUksSUFBSSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsVUFBVSxJQUFJLENBQUMsQ0FBQyxDQUFDLE9BQU8sRUFBRTtRQUM1QyxJQUFJLElBQUksQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLFVBQVUsSUFBSSxDQUFDLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FDL0MsQ0FBQTtJQUVILE1BQU0sT0FBTyxHQUFHLENBQUUsS0FBYSxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQXdCLENBQUE7SUFDckUsTUFBTSxXQUFXLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQyxDQUFBO0lBQ3hELE1BQU0sT0FBTyxHQUFHLFdBQVcsQ0FBQyxJQUFJLENBQzlCLENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FBQyxVQUFVLEVBQUUsTUFBTSxLQUFLLFVBQVUsQ0FDbEQsQ0FBQTtJQUVELElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDdEIsT0FBTztZQUNMLFFBQVEsRUFBRSxLQUFLO1lBQ2YsSUFBSSxFQUFFLElBQUk7WUFDVixVQUFVLEVBQUUsSUFBSTtZQUNoQixFQUFFLEVBQUUsSUFBSTtZQUNSLE1BQU0sRUFBRSxDQUFDO1lBQ1QsYUFBYSxFQUFHLEtBQWEsQ0FBQyxhQUFhLElBQUksSUFBSTtZQUNuRCxXQUFXO1lBQ1gsT0FBTztTQUNSLENBQUE7SUFDSCxDQUFDO0lBRUQsTUFBTSxNQUFNLEdBQUcsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFBO0lBQzNCLE1BQU0sTUFBTSxHQUFHLFNBQVMsQ0FBQyxNQUFNLENBQzdCLENBQUMsS0FBSyxFQUFFLEtBQUssRUFBRSxFQUFFLENBQUMsY0FBTSxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsS0FBSyxDQUFDLE1BQU0sQ0FBQyxNQUFNLElBQUksQ0FBQyxDQUFDLEVBQzdELGNBQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQ2xCLENBQUE7SUFFRCxPQUFPO1FBQ0wsUUFBUSxFQUFFLElBQUk7UUFDZCxJQUFJLEVBQUUsTUFBTSxDQUFDLE1BQU8sQ0FBQyxJQUFJO1FBQ3pCLFVBQVUsRUFBRSxNQUFNLENBQUMsTUFBTyxDQUFDLFVBQVU7UUFDckMsRUFBRSxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsVUFBVTtZQUMxQixDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxVQUFVLENBQUMsQ0FBQyxXQUFXLEVBQUU7WUFDbEQsQ0FBQyxDQUFDLElBQUk7UUFDUixNQUFNLEVBQUUsUUFBUSxDQUFDLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNuQyxhQUFhLEVBQUcsS0FBYSxDQUFDLGFBQWEsSUFBSSxJQUFJO1FBQ25ELFdBQVc7UUFDWCxPQUFPO0tBQ1IsQ0FBQTtBQUNILENBQUMifQ==
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { MedusaContainer } from "@medusajs/types";
|
|
2
|
+
export type PaymentReversalKind = "chargeback" | "storno" | "failure";
|
|
3
|
+
export type ReverseCapturedPaymentInput = {
|
|
4
|
+
orderId: string;
|
|
5
|
+
paymentId: string;
|
|
6
|
+
paymentCollectionId: string;
|
|
7
|
+
/** What reversed the payment, becomes the refund note */
|
|
8
|
+
kind: PaymentReversalKind;
|
|
9
|
+
/** The Pay. status code that reported it, when known */
|
|
10
|
+
statusCode?: number | string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The refund note that marks a reversal, e.g. "Pay. chargeback (status -71)"
|
|
14
|
+
* or "Pay. direct debit storno (status 127)". Medusa 2.11 refunds carry no
|
|
15
|
+
* metadata, so the note is what the admin banner recognises a reversal by.
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatPayReversalNote(kind: PaymentReversalKind, statusCode?: number | string): string;
|
|
18
|
+
export declare function parsePayReversalNote(note: unknown): {
|
|
19
|
+
kind: PaymentReversalKind;
|
|
20
|
+
statusCode: number | null;
|
|
21
|
+
} | null;
|
|
22
|
+
/**
|
|
23
|
+
* A chargeback or direct debit storno reverses a payment Medusa already
|
|
24
|
+
* captured. Reflect that on the order so it shows as unpaid and can be paid
|
|
25
|
+
* again:
|
|
26
|
+
*
|
|
27
|
+
* 1. record a refund for the still-captured amount on the payment. The Pay.
|
|
28
|
+
* provider recognises a reversed payment and records it without asking Pay.
|
|
29
|
+
* for a refund, the money is already back with the customer;
|
|
30
|
+
* 2. add the matching negative order transaction, so the order summary shows
|
|
31
|
+
* the amount as outstanding again (refundPaymentWorkflow is not used
|
|
32
|
+
* because it would also credit the order for the refund);
|
|
33
|
+
* 3. mark the payment collection failed;
|
|
34
|
+
* 4. create a fresh not_paid payment collection for the outstanding amount,
|
|
35
|
+
* which is what the admin's "Copy payment link" / "Mark as paid" buttons and
|
|
36
|
+
* the storefront payment-collection route work with.
|
|
37
|
+
*
|
|
38
|
+
* Safe to run more than once: a payment that is already fully reversed only
|
|
39
|
+
* gets whatever step is still missing.
|
|
40
|
+
*/
|
|
41
|
+
export declare function reverseCapturedPayment(container: MedusaContainer, input: ReverseCapturedPaymentInput): Promise<void>;
|
|
42
|
+
export default reverseCapturedPayment;
|
|
43
|
+
//# sourceMappingURL=reverseCapturedPayment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reverseCapturedPayment.d.ts","sourceRoot":"","sources":["../../../../src/utils/reverseCapturedPayment.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,eAAe,EAChB,MAAM,iBAAiB,CAAA;AAExB,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,QAAQ,GAAG,SAAS,CAAA;AAErE,MAAM,MAAM,2BAA2B,GAAG;IACxC,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,yDAAyD;IACzD,IAAI,EAAE,mBAAmB,CAAA;IACzB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC7B,CAAA;AAKD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,EACzB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAC3B,MAAM,CAQR;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,OAAO,GACZ;IAAC,IAAI,EAAE,mBAAmB,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,GAAG,IAAI,CAe/D;AAQD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,2BAA2B,GACjC,OAAO,CAAC,IAAI,CAAC,CAiFf;AAED,eAAe,sBAAsB,CAAA"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatPayReversalNote = formatPayReversalNote;
|
|
4
|
+
exports.parsePayReversalNote = parsePayReversalNote;
|
|
5
|
+
exports.reverseCapturedPayment = reverseCapturedPayment;
|
|
6
|
+
const core_flows_1 = require("@medusajs/core-flows");
|
|
7
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
8
|
+
const REVERSAL_NOTE_PATTERN = /^Pay\. (chargeback|direct debit (?:storno|failure)) \(status (-?\d+|unknown)\)$/;
|
|
9
|
+
/**
|
|
10
|
+
* The refund note that marks a reversal, e.g. "Pay. chargeback (status -71)"
|
|
11
|
+
* or "Pay. direct debit storno (status 127)". Medusa 2.11 refunds carry no
|
|
12
|
+
* metadata, so the note is what the admin banner recognises a reversal by.
|
|
13
|
+
*/
|
|
14
|
+
function formatPayReversalNote(kind, statusCode) {
|
|
15
|
+
const what = kind === "chargeback" ? "chargeback" : `direct debit ${kind}`;
|
|
16
|
+
const status = statusCode === undefined || statusCode === null || statusCode === ""
|
|
17
|
+
? "unknown"
|
|
18
|
+
: String(statusCode);
|
|
19
|
+
return `Pay. ${what} (status ${status})`;
|
|
20
|
+
}
|
|
21
|
+
function parsePayReversalNote(note) {
|
|
22
|
+
if (typeof note !== "string") {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const match = note.trim().match(REVERSAL_NOTE_PATTERN);
|
|
26
|
+
if (!match) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const kind = match[1].replace("direct debit ", "");
|
|
30
|
+
const statusCode = match[2] === "unknown" ? null : Number(match[2]);
|
|
31
|
+
return { kind, statusCode };
|
|
32
|
+
}
|
|
33
|
+
const sumAmounts = (items) => (items ?? []).reduce((total, item) => utils_1.MathBN.add(total, item.amount), utils_1.MathBN.convert(0));
|
|
34
|
+
/**
|
|
35
|
+
* A chargeback or direct debit storno reverses a payment Medusa already
|
|
36
|
+
* captured. Reflect that on the order so it shows as unpaid and can be paid
|
|
37
|
+
* again:
|
|
38
|
+
*
|
|
39
|
+
* 1. record a refund for the still-captured amount on the payment. The Pay.
|
|
40
|
+
* provider recognises a reversed payment and records it without asking Pay.
|
|
41
|
+
* for a refund, the money is already back with the customer;
|
|
42
|
+
* 2. add the matching negative order transaction, so the order summary shows
|
|
43
|
+
* the amount as outstanding again (refundPaymentWorkflow is not used
|
|
44
|
+
* because it would also credit the order for the refund);
|
|
45
|
+
* 3. mark the payment collection failed;
|
|
46
|
+
* 4. create a fresh not_paid payment collection for the outstanding amount,
|
|
47
|
+
* which is what the admin's "Copy payment link" / "Mark as paid" buttons and
|
|
48
|
+
* the storefront payment-collection route work with.
|
|
49
|
+
*
|
|
50
|
+
* Safe to run more than once: a payment that is already fully reversed only
|
|
51
|
+
* gets whatever step is still missing.
|
|
52
|
+
*/
|
|
53
|
+
async function reverseCapturedPayment(container, input) {
|
|
54
|
+
const logger = container.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
|
|
55
|
+
const paymentModuleService = container.resolve(utils_1.Modules.PAYMENT);
|
|
56
|
+
const orderModuleService = container.resolve(utils_1.Modules.ORDER);
|
|
57
|
+
let payment = await paymentModuleService.retrievePayment(input.paymentId, {
|
|
58
|
+
relations: ["captures", "refunds"],
|
|
59
|
+
});
|
|
60
|
+
const outstanding = utils_1.MathBN.sub(sumAmounts(payment.captures), sumAmounts(payment.refunds));
|
|
61
|
+
const note = formatPayReversalNote(input.kind, input.statusCode);
|
|
62
|
+
if (utils_1.MathBN.gt(outstanding, 0)) {
|
|
63
|
+
const amount = new utils_1.BigNumber(outstanding).numeric;
|
|
64
|
+
payment = await paymentModuleService.refundPayment({
|
|
65
|
+
payment_id: payment.id,
|
|
66
|
+
amount,
|
|
67
|
+
note,
|
|
68
|
+
});
|
|
69
|
+
logger.info(`Pay. - Recorded reversal of ${payment.currency_code} ${amount} on payment ${payment.id} for order ${input.orderId}: ${note}`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
logger.info(`Pay. - Payment ${payment.id} for order ${input.orderId} is already fully reversed`);
|
|
73
|
+
}
|
|
74
|
+
// Same bookkeeping as refundPaymentWorkflow: one negative transaction per
|
|
75
|
+
// refund. Refunds made earlier through the admin already have theirs.
|
|
76
|
+
const existingTransactions = await orderModuleService.listOrderTransactions({ order_id: input.orderId }, { select: ["reference", "reference_id"] });
|
|
77
|
+
const knownRefundIds = new Set(existingTransactions
|
|
78
|
+
.filter((transaction) => transaction.reference === "refund")
|
|
79
|
+
.map((transaction) => transaction.reference_id));
|
|
80
|
+
const missingTransactions = (payment.refunds ?? [])
|
|
81
|
+
.filter((refund) => !knownRefundIds.has(refund.id))
|
|
82
|
+
.map((refund) => ({
|
|
83
|
+
order_id: input.orderId,
|
|
84
|
+
amount: new utils_1.BigNumber(utils_1.MathBN.mult(refund.amount, -1)).numeric,
|
|
85
|
+
currency_code: payment.currency_code,
|
|
86
|
+
reference: "refund",
|
|
87
|
+
reference_id: refund.id,
|
|
88
|
+
}));
|
|
89
|
+
if (missingTransactions.length) {
|
|
90
|
+
await orderModuleService.addOrderTransactions(missingTransactions);
|
|
91
|
+
}
|
|
92
|
+
// Recording the refund recomputes the collection status from the captured
|
|
93
|
+
// amount (back to completed), so the failure is applied afterwards
|
|
94
|
+
await paymentModuleService.updatePaymentCollections(input.paymentCollectionId, { status: utils_1.PaymentCollectionStatus.FAILED });
|
|
95
|
+
const { result } = await (0, core_flows_1.createOrUpdateOrderPaymentCollectionWorkflow)(container).run({ input: { order_id: input.orderId } });
|
|
96
|
+
const collections = Array.isArray(result) ? result : result ? [result] : [];
|
|
97
|
+
logger.info(`Pay. - Order ${input.orderId} is payable again through payment collection ${collections.map((collection) => collection.id).join(", ") || "(none)"}`);
|
|
98
|
+
}
|
|
99
|
+
exports.default = reverseCapturedPayment;
|
|
100
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV2ZXJzZUNhcHR1cmVkUGF5bWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy91dGlscy9yZXZlcnNlQ2FwdHVyZWRQYXltZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBb0NBLHNEQVdDO0FBRUQsb0RBaUJDO0FBMkJELHdEQW9GQztBQWpMRCxxREFBaUY7QUFDakYscURBTWtDO0FBcUJsQyxNQUFNLHFCQUFxQixHQUN6QixpRkFBaUYsQ0FBQTtBQUVuRjs7OztHQUlHO0FBQ0gsU0FBZ0IscUJBQXFCLENBQ25DLElBQXlCLEVBQ3pCLFVBQTRCO0lBRTVCLE1BQU0sSUFBSSxHQUFHLElBQUksS0FBSyxZQUFZLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLElBQUksRUFBRSxDQUFBO0lBQzFFLE1BQU0sTUFBTSxHQUNWLFVBQVUsS0FBSyxTQUFTLElBQUksVUFBVSxLQUFLLElBQUksSUFBSSxVQUFVLEtBQUssRUFBRTtRQUNsRSxDQUFDLENBQUMsU0FBUztRQUNYLENBQUMsQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUE7SUFFeEIsT0FBTyxRQUFRLElBQUksWUFBWSxNQUFNLEdBQUcsQ0FBQTtBQUMxQyxDQUFDO0FBRUQsU0FBZ0Isb0JBQW9CLENBQ2xDLElBQWE7SUFFYixJQUFJLE9BQU8sSUFBSSxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQzdCLE9BQU8sSUFBSSxDQUFBO0lBQ2IsQ0FBQztJQUVELE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxLQUFLLENBQUMscUJBQXFCLENBQUMsQ0FBQTtJQUV0RCxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDWCxPQUFPLElBQUksQ0FBQTtJQUNiLENBQUM7SUFFRCxNQUFNLElBQUksR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLGVBQWUsRUFBRSxFQUFFLENBQXdCLENB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
|
package/README.md
CHANGED
|
@@ -242,6 +242,57 @@ dropdown.
|
|
|
242
242
|
|
|
243
243
|
Make sure that the selected payment methods are enabled in your Pay origanization settings as well.
|
|
244
244
|
|
|
245
|
+
### Testing direct debits locally
|
|
246
|
+
|
|
247
|
+
Pay.'s test environment does not process SEPA direct debits. With `testMode` enabled the plugin therefore does not
|
|
248
|
+
create a mandate at Pay. when a direct debit order is placed. It stores a simulated mandate on the payment session
|
|
249
|
+
instead (code `TEST-<payment session id>`, logged as `Pay. direct debit test mode: storing simulated mandate ...`), and
|
|
250
|
+
the collection flow can be driven by posting the exchanges Pay. would normally send to the webhook route yourself.
|
|
251
|
+
Exchanges for a simulated mandate are not re-fetched from Pay., the body is taken as the direct debit state. This only
|
|
252
|
+
happens while `testMode` is on and only for mandates that were created in test mode.
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# 1. Place an order with SEPA Direct Debit, the payment collection is now "awaiting".
|
|
256
|
+
# Copy the simulated mandate code from the server log.
|
|
257
|
+
MANDATE="TEST-payses_01JXXXXXXXXXXXXXXXXXXXXXXX"
|
|
258
|
+
|
|
259
|
+
# 2. The bank collected the money: the payment is captured and the order is paid.
|
|
260
|
+
curl -X POST http://localhost:9000/hooks/pay/pay-direct-debit_pay \
|
|
261
|
+
-H "Content-Type: application/json" \
|
|
262
|
+
-d "{\"action\":\"incassocollected\",\"mandateId\":\"$MANDATE\"}"
|
|
263
|
+
|
|
264
|
+
# 3. The customer reversed the debit (storno): the payment is refunded in Medusa only, the order
|
|
265
|
+
# shows the outstanding amount again and the admin offers "Copy payment link" / "Mark as paid".
|
|
266
|
+
curl -X POST http://localhost:9000/hooks/pay/pay-direct-debit_pay \
|
|
267
|
+
-H "Content-Type: application/json" \
|
|
268
|
+
-d "{\"action\":\"incassostorno\",\"mandateId\":\"$MANDATE\"}"
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Other legacy actions (`incassopending`, `incassosend`) and an explicit `status: {"code": 106}` (failed) or
|
|
272
|
+
`declined: true` work the same way. Add `"reference": "<order display id>"` to the body when the order can not be
|
|
273
|
+
resolved from the mandate code. The webhook route processes exchanges asynchronously (5 seconds by default), so give
|
|
274
|
+
the server a moment before checking the order. The payment link opens the regular Pay. hosted checkout, so the
|
|
275
|
+
outstanding amount can be paid with any test payment method.
|
|
276
|
+
|
|
277
|
+
#### Reversed payments in the admin
|
|
278
|
+
|
|
279
|
+
When a chargeback or storno reverses a captured payment, the plugin refunds the payment in Medusa (without contacting
|
|
280
|
+
Pay.), books the amount as outstanding again and opens a new payment collection, so the order offers "Copy payment
|
|
281
|
+
link" and "Mark as paid". Medusa's own payment status can only call that "Refunded", so the plugin adds a
|
|
282
|
+
"Payment reversed, action required" banner on top of the order page that names the cause (chargeback, storno, failed
|
|
283
|
+
collection), the amount that went back to the customer and what is still outstanding.
|
|
284
|
+
|
|
285
|
+
#### Simulator widget in the admin
|
|
286
|
+
|
|
287
|
+
The same exchanges can be triggered from the order detail page. Switch on **SEPA testing** in Settings > Pay (off by
|
|
288
|
+
default, meant for staging servers) and a "Pay. direct debit simulator" panel appears in the sidebar of orders that hold
|
|
289
|
+
a simulated mandate, with a button per exchange (pending, sent, collected, storno, failed, declined). The exchanges are
|
|
290
|
+
processed synchronously, so the order refreshes right away. The switch is stored in the store's metadata
|
|
291
|
+
(`pay_sepa_testing`), so it applies to every admin user of that server. The panel is backed by
|
|
292
|
+
`GET /admin/pay/orders/:id/direct-debit` and `POST /admin/pay/orders/:id/direct-debit/exchange` (`{"action": "storno"}`),
|
|
293
|
+
which only work while the switch and `testMode` are on; the switch itself is read and written through
|
|
294
|
+
`GET`/`POST /admin/pay/settings` (`{"sepaTesting": true}`).
|
|
295
|
+
|
|
245
296
|
## Supported Payment Methods
|
|
246
297
|
|
|
247
298
|
The plugin currently supports the following Pay payment methods:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webbers/pay-payments-medusa",
|
|
3
3
|
"displayName": "Medusa v2 Pay. Plugin",
|
|
4
|
-
"version": "2.2.
|
|
4
|
+
"version": "2.2.53",
|
|
5
5
|
"description": "The Pay.nl payment provider plugin for Medusa v2",
|
|
6
6
|
"author": "Webbers B.V. <development@webbers.com>",
|
|
7
7
|
"license": "MIT",
|