@webbers/pay-payments-medusa 2.2.50 → 2.2.52

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.
Files changed (50) hide show
  1. package/.medusa/server/package.json +1 -1
  2. package/.medusa/server/src/admin/index.js +171 -11
  3. package/.medusa/server/src/admin/index.mjs +172 -12
  4. package/.medusa/server/src/api/admin/pay/orders/[id]/direct-debit/exchange/route.d.ts +12 -0
  5. package/.medusa/server/src/api/admin/pay/orders/[id]/direct-debit/exchange/route.d.ts.map +1 -0
  6. package/.medusa/server/src/api/admin/pay/orders/[id]/direct-debit/exchange/route.js +42 -0
  7. package/.medusa/server/src/api/admin/pay/orders/[id]/direct-debit/route.d.ts +9 -0
  8. package/.medusa/server/src/api/admin/pay/orders/[id]/direct-debit/route.d.ts.map +1 -0
  9. package/.medusa/server/src/api/admin/pay/orders/[id]/direct-debit/route.js +19 -0
  10. package/.medusa/server/src/api/admin/pay/settings/route.d.ts +12 -0
  11. package/.medusa/server/src/api/admin/pay/settings/route.d.ts.map +1 -0
  12. package/.medusa/server/src/api/admin/pay/settings/route.js +24 -0
  13. package/.medusa/server/src/providers/pay/core/constants.d.ts +2 -0
  14. package/.medusa/server/src/providers/pay/core/constants.d.ts.map +1 -1
  15. package/.medusa/server/src/providers/pay/core/constants.js +3 -1
  16. package/.medusa/server/src/providers/pay/core/http-client.d.ts +9 -0
  17. package/.medusa/server/src/providers/pay/core/http-client.d.ts.map +1 -1
  18. package/.medusa/server/src/providers/pay/core/http-client.js +21 -1
  19. package/.medusa/server/src/providers/pay/core/pay-base.d.ts +6 -0
  20. package/.medusa/server/src/providers/pay/core/pay-base.d.ts.map +1 -1
  21. package/.medusa/server/src/providers/pay/core/pay-base.js +24 -1
  22. package/.medusa/server/src/providers/pay/core/pay-client.d.ts +7 -1
  23. package/.medusa/server/src/providers/pay/core/pay-client.d.ts.map +1 -1
  24. package/.medusa/server/src/providers/pay/core/pay-client.js +24 -1
  25. package/.medusa/server/src/providers/pay/services/pay-direct-debit.d.ts +13 -1
  26. package/.medusa/server/src/providers/pay/services/pay-direct-debit.d.ts.map +1 -1
  27. package/.medusa/server/src/providers/pay/services/pay-direct-debit.js +64 -13
  28. package/.medusa/server/src/providers/pay/types/index.d.ts +10 -0
  29. package/.medusa/server/src/providers/pay/types/index.d.ts.map +1 -1
  30. package/.medusa/server/src/providers/pay/types/index.js +1 -1
  31. package/.medusa/server/src/providers/pay/types/order.d.ts +22 -0
  32. package/.medusa/server/src/providers/pay/types/order.d.ts.map +1 -1
  33. package/.medusa/server/src/subscribers/direct-debit-exchange.d.ts.map +1 -1
  34. package/.medusa/server/src/subscribers/direct-debit-exchange.js +95 -24
  35. package/.medusa/server/src/subscribers/payment-failed.d.ts.map +1 -1
  36. package/.medusa/server/src/subscribers/payment-failed.js +23 -4
  37. package/.medusa/server/src/utils/directDebitExchange.d.ts +15 -0
  38. package/.medusa/server/src/utils/directDebitExchange.d.ts.map +1 -1
  39. package/.medusa/server/src/utils/directDebitExchange.js +59 -2
  40. package/.medusa/server/src/utils/directDebitSimulation.d.ts +93 -0
  41. package/.medusa/server/src/utils/directDebitSimulation.d.ts.map +1 -0
  42. package/.medusa/server/src/utils/directDebitSimulation.js +170 -0
  43. package/.medusa/server/src/utils/index.d.ts +1 -0
  44. package/.medusa/server/src/utils/index.d.ts.map +1 -1
  45. package/.medusa/server/src/utils/index.js +2 -1
  46. package/.medusa/server/src/utils/reverseCapturedPayment.d.ts +30 -0
  47. package/.medusa/server/src/utils/reverseCapturedPayment.d.ts.map +1 -0
  48. package/.medusa/server/src/utils/reverseCapturedPayment.js +72 -0
  49. package/README.md +43 -0
  50. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@webbers/pay-payments-medusa",
3
3
  "displayName": "Medusa v2 Pay. Plugin",
4
- "version": "2.2.50",
4
+ "version": "2.2.52",
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",
@@ -1,13 +1,133 @@
1
1
  "use strict";
2
2
  const jsxRuntime = require("react/jsx-runtime");
3
- const ui = require("@medusajs/ui");
4
3
  const adminSdk = require("@medusajs/admin-sdk");
5
- const Medusa = require("@medusajs/js-sdk");
4
+ const ui = require("@medusajs/ui");
6
5
  const reactQuery = require("@tanstack/react-query");
6
+ const Medusa = require("@medusajs/js-sdk");
7
7
  const react = require("react");
8
8
  require("@medusajs/admin-shared");
9
9
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
10
10
  const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
11
+ const sdk = new Medusa__default.default({
12
+ baseUrl: "/",
13
+ debug: false,
14
+ auth: {
15
+ type: "session"
16
+ }
17
+ });
18
+ const PRESET_ORDER = [
19
+ "pending",
20
+ "sent",
21
+ "collected",
22
+ "storno",
23
+ "failed",
24
+ "declined"
25
+ ];
26
+ const statusColor = (status) => {
27
+ switch (status) {
28
+ case "completed":
29
+ case "authorized":
30
+ return "green";
31
+ case "failed":
32
+ case "canceled":
33
+ return "red";
34
+ case "awaiting":
35
+ return "orange";
36
+ default:
37
+ return "grey";
38
+ }
39
+ };
40
+ const queryKey = (orderId) => ["pay-direct-debit-simulation", orderId];
41
+ const PayDirectDebitSimulatorWidget = ({
42
+ data: order
43
+ }) => {
44
+ const queryClient = reactQuery.useQueryClient();
45
+ const { data: state, isLoading } = reactQuery.useQuery({
46
+ queryKey: queryKey(order.id),
47
+ queryFn: () => sdk.client.fetch(`/admin/pay/orders/${order.id}/direct-debit`)
48
+ });
49
+ const simulate = reactQuery.useMutation({
50
+ mutationFn: (action) => sdk.client.fetch(
51
+ `/admin/pay/orders/${order.id}/direct-debit/exchange`,
52
+ { method: "POST", body: { action } }
53
+ ),
54
+ onSuccess: async (_result, action) => {
55
+ var _a;
56
+ await Promise.all([
57
+ queryClient.invalidateQueries({ queryKey: queryKey(order.id) }),
58
+ // The dashboard's order detail, summary and payments
59
+ queryClient.invalidateQueries({ queryKey: ["orders"] }),
60
+ queryClient.invalidateQueries({ queryKey: ["payment_collections"] })
61
+ ]);
62
+ ui.toast.success(`Simulated "${((_a = state == null ? void 0 : state.presets) == null ? void 0 : _a[action].label) ?? action}"`);
63
+ },
64
+ onError: (error) => {
65
+ ui.toast.error(error.message);
66
+ }
67
+ });
68
+ if (isLoading || !(state == null ? void 0 : state.visible)) {
69
+ return null;
70
+ }
71
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
72
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-x-4 px-6 py-4", children: [
73
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
74
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", weight: "plus", children: "Pay. direct debit simulator" }),
75
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: "Test mode. This mandate only exists on this server, pick the exchange Pay. would send for it." })
76
+ ] }),
77
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: statusColor(state.paymentCollectionStatus), children: state.paymentCollectionStatus ?? "unknown" })
78
+ ] }),
79
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3 px-6 py-4", children: [
80
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
81
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: "Mandate" }),
82
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", className: "truncate font-mono", children: state.mandateCode })
83
+ ] }),
84
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
85
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: "Payment" }),
86
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: state.captured ? "Captured" : "Not captured" })
87
+ ] })
88
+ ] }),
89
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-y-2 px-6 py-4", children: PRESET_ORDER.map((key) => {
90
+ var _a;
91
+ const preset = (_a = state.presets) == null ? void 0 : _a[key];
92
+ if (!preset) {
93
+ return null;
94
+ }
95
+ return /* @__PURE__ */ jsxRuntime.jsxs(
96
+ "div",
97
+ {
98
+ className: "flex items-center justify-between gap-x-4",
99
+ children: [
100
+ /* @__PURE__ */ jsxRuntime.jsx(
101
+ ui.Text,
102
+ {
103
+ size: "small",
104
+ leading: "compact",
105
+ className: "text-ui-fg-subtle",
106
+ children: preset.description
107
+ }
108
+ ),
109
+ /* @__PURE__ */ jsxRuntime.jsx(
110
+ ui.Button,
111
+ {
112
+ size: "small",
113
+ variant: "secondary",
114
+ className: "shrink-0",
115
+ disabled: simulate.isPending,
116
+ isLoading: simulate.isPending && simulate.variables === key,
117
+ onClick: () => simulate.mutate(key),
118
+ children: preset.label
119
+ }
120
+ )
121
+ ]
122
+ },
123
+ key
124
+ );
125
+ }) })
126
+ ] });
127
+ };
128
+ adminSdk.defineWidgetConfig({
129
+ zone: "order.details.side.after"
130
+ });
11
131
  const PayLogo = ({}) => {
12
132
  return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "64", height: "40", viewBox: "0 0 64 40", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
13
133
  /* @__PURE__ */ jsxRuntime.jsxs("g", { clipPath: "url(#clip0_596_748)", children: [
@@ -50,15 +170,8 @@ const PayLogo = ({}) => {
50
170
  /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsx("clipPath", { id: "clip0_596_748", children: /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "63.0137", height: "40", fill: "white" }) }) })
51
171
  ] });
52
172
  };
53
- const sdk = new Medusa__default.default({
54
- baseUrl: "/",
55
- debug: false,
56
- auth: {
57
- type: "session"
58
- }
59
- });
60
173
  const displayName = "Medusa v2 Pay. Plugin";
61
- const version = "2.2.50";
174
+ const version = "2.2.52";
62
175
  function getSortedPaymentMethods(data) {
63
176
  const { checkoutOptions, checkoutSequence } = data;
64
177
  const { primary } = checkoutSequence.default;
@@ -77,6 +190,7 @@ const formatter = new Intl.NumberFormat([], {
77
190
  minimumFractionDigits: 0,
78
191
  maximumFractionDigits: 2
79
192
  });
193
+ const SETTINGS_QUERY_KEY = ["pay-settings"];
80
194
  const columnHelper = ui.createDataTableColumnHelper();
81
195
  const columns = [
82
196
  columnHelper.accessor("id", {
@@ -97,6 +211,26 @@ const columns = [
97
211
  })
98
212
  ];
99
213
  const PaySettingPage = () => {
214
+ const queryClient = reactQuery.useQueryClient();
215
+ const { data: settings, isLoading: isLoadingSettings } = reactQuery.useQuery({
216
+ queryFn: () => sdk.client.fetch("/admin/pay/settings"),
217
+ queryKey: SETTINGS_QUERY_KEY
218
+ });
219
+ const updateSepaTesting = reactQuery.useMutation({
220
+ mutationFn: (sepaTesting) => sdk.client.fetch("/admin/pay/settings", {
221
+ method: "POST",
222
+ body: { sepaTesting }
223
+ }),
224
+ onSuccess: (updated) => {
225
+ queryClient.setQueryData(SETTINGS_QUERY_KEY, updated);
226
+ ui.toast.success("Success", {
227
+ description: updated.sepaTesting ? "SEPA testing enabled, the simulator is shown on direct debit orders" : "SEPA testing disabled"
228
+ });
229
+ },
230
+ onError: (error) => {
231
+ ui.toast.error("Error", { description: error.message });
232
+ }
233
+ });
100
234
  const { data: config2, refetch: refetchConfig } = reactQuery.useQuery({
101
235
  queryFn: () => sdk.client.fetch("/admin/pay/payment-methods"),
102
236
  queryKey: ["pay-payment-methods"]
@@ -170,6 +304,27 @@ const PaySettingPage = () => {
170
304
  children: "Clear Cache"
171
305
  }
172
306
  ) })
307
+ ] }),
308
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-ui-fg-subtle grid grid-cols-2 items-center px-6 py-4", children: [
309
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
310
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", weight: "plus", children: "SEPA testing" }),
311
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", leading: "compact", children: [
312
+ "Pay. cannot process direct debits in its test environment. When enabled, direct debit orders get a simulator that walks the payment through the exchanges Pay. would send (collected, storno, ...). Meant for staging servers only.",
313
+ settings && !settings.testMode ? " The provider is not in test mode, so the simulator stays hidden." : ""
314
+ ] })
315
+ ] }),
316
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-3", children: [
317
+ /* @__PURE__ */ jsxRuntime.jsx(
318
+ ui.Switch,
319
+ {
320
+ id: "pay-sepa-testing",
321
+ checked: (settings == null ? void 0 : settings.sepaTesting) ?? false,
322
+ disabled: isLoadingSettings || updateSepaTesting.isPending,
323
+ onCheckedChange: (checked) => updateSepaTesting.mutate(checked)
324
+ }
325
+ ),
326
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: (settings == null ? void 0 : settings.sepaTesting) ? "Enabled" : "Disabled" })
327
+ ] })
173
328
  ] })
174
329
  ] }),
175
330
  /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable, { instance: table, children: [
@@ -197,7 +352,12 @@ const PaySettingPage = () => {
197
352
  const config = adminSdk.defineRouteConfig({
198
353
  label: "Pay"
199
354
  });
200
- const widgetModule = { widgets: [] };
355
+ const widgetModule = { widgets: [
356
+ {
357
+ Component: PayDirectDebitSimulatorWidget,
358
+ zone: ["order.details.side.after"]
359
+ }
360
+ ] };
201
361
  const routeModule = {
202
362
  routes: [
203
363
  {
@@ -1,10 +1,130 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import { createDataTableColumnHelper, useDataTable, Container, Heading, Text, Button, DataTable, toast } from "@medusajs/ui";
3
- import { defineRouteConfig } from "@medusajs/admin-sdk";
2
+ import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
3
+ import { toast, Container, Text, Badge, Button, createDataTableColumnHelper, useDataTable, Heading, Switch, DataTable } from "@medusajs/ui";
4
+ import { useQueryClient, useQuery, useMutation } from "@tanstack/react-query";
4
5
  import Medusa from "@medusajs/js-sdk";
5
- import { useQuery } from "@tanstack/react-query";
6
6
  import { useMemo } from "react";
7
7
  import "@medusajs/admin-shared";
8
+ const sdk = new Medusa({
9
+ baseUrl: "/",
10
+ debug: false,
11
+ auth: {
12
+ type: "session"
13
+ }
14
+ });
15
+ const PRESET_ORDER = [
16
+ "pending",
17
+ "sent",
18
+ "collected",
19
+ "storno",
20
+ "failed",
21
+ "declined"
22
+ ];
23
+ const statusColor = (status) => {
24
+ switch (status) {
25
+ case "completed":
26
+ case "authorized":
27
+ return "green";
28
+ case "failed":
29
+ case "canceled":
30
+ return "red";
31
+ case "awaiting":
32
+ return "orange";
33
+ default:
34
+ return "grey";
35
+ }
36
+ };
37
+ const queryKey = (orderId) => ["pay-direct-debit-simulation", orderId];
38
+ const PayDirectDebitSimulatorWidget = ({
39
+ data: order
40
+ }) => {
41
+ const queryClient = useQueryClient();
42
+ const { data: state, isLoading } = useQuery({
43
+ queryKey: queryKey(order.id),
44
+ queryFn: () => sdk.client.fetch(`/admin/pay/orders/${order.id}/direct-debit`)
45
+ });
46
+ const simulate = useMutation({
47
+ mutationFn: (action) => sdk.client.fetch(
48
+ `/admin/pay/orders/${order.id}/direct-debit/exchange`,
49
+ { method: "POST", body: { action } }
50
+ ),
51
+ onSuccess: async (_result, action) => {
52
+ var _a;
53
+ await Promise.all([
54
+ queryClient.invalidateQueries({ queryKey: queryKey(order.id) }),
55
+ // The dashboard's order detail, summary and payments
56
+ queryClient.invalidateQueries({ queryKey: ["orders"] }),
57
+ queryClient.invalidateQueries({ queryKey: ["payment_collections"] })
58
+ ]);
59
+ toast.success(`Simulated "${((_a = state == null ? void 0 : state.presets) == null ? void 0 : _a[action].label) ?? action}"`);
60
+ },
61
+ onError: (error) => {
62
+ toast.error(error.message);
63
+ }
64
+ });
65
+ if (isLoading || !(state == null ? void 0 : state.visible)) {
66
+ return null;
67
+ }
68
+ return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
69
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-x-4 px-6 py-4", children: [
70
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-1", children: [
71
+ /* @__PURE__ */ jsx(Text, { size: "small", leading: "compact", weight: "plus", children: "Pay. direct debit simulator" }),
72
+ /* @__PURE__ */ jsx(Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: "Test mode. This mandate only exists on this server, pick the exchange Pay. would send for it." })
73
+ ] }),
74
+ /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: statusColor(state.paymentCollectionStatus), children: state.paymentCollectionStatus ?? "unknown" })
75
+ ] }),
76
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-3 px-6 py-4", children: [
77
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
78
+ /* @__PURE__ */ jsx(Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: "Mandate" }),
79
+ /* @__PURE__ */ jsx(Text, { size: "small", leading: "compact", className: "truncate font-mono", children: state.mandateCode })
80
+ ] }),
81
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
82
+ /* @__PURE__ */ jsx(Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: "Payment" }),
83
+ /* @__PURE__ */ jsx(Text, { size: "small", leading: "compact", children: state.captured ? "Captured" : "Not captured" })
84
+ ] })
85
+ ] }),
86
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-y-2 px-6 py-4", children: PRESET_ORDER.map((key) => {
87
+ var _a;
88
+ const preset = (_a = state.presets) == null ? void 0 : _a[key];
89
+ if (!preset) {
90
+ return null;
91
+ }
92
+ return /* @__PURE__ */ jsxs(
93
+ "div",
94
+ {
95
+ className: "flex items-center justify-between gap-x-4",
96
+ children: [
97
+ /* @__PURE__ */ jsx(
98
+ Text,
99
+ {
100
+ size: "small",
101
+ leading: "compact",
102
+ className: "text-ui-fg-subtle",
103
+ children: preset.description
104
+ }
105
+ ),
106
+ /* @__PURE__ */ jsx(
107
+ Button,
108
+ {
109
+ size: "small",
110
+ variant: "secondary",
111
+ className: "shrink-0",
112
+ disabled: simulate.isPending,
113
+ isLoading: simulate.isPending && simulate.variables === key,
114
+ onClick: () => simulate.mutate(key),
115
+ children: preset.label
116
+ }
117
+ )
118
+ ]
119
+ },
120
+ key
121
+ );
122
+ }) })
123
+ ] });
124
+ };
125
+ defineWidgetConfig({
126
+ zone: "order.details.side.after"
127
+ });
8
128
  const PayLogo = ({}) => {
9
129
  return /* @__PURE__ */ jsxs("svg", { width: "64", height: "40", viewBox: "0 0 64 40", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
10
130
  /* @__PURE__ */ jsxs("g", { clipPath: "url(#clip0_596_748)", children: [
@@ -47,15 +167,8 @@ const PayLogo = ({}) => {
47
167
  /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: "clip0_596_748", children: /* @__PURE__ */ jsx("rect", { width: "63.0137", height: "40", fill: "white" }) }) })
48
168
  ] });
49
169
  };
50
- const sdk = new Medusa({
51
- baseUrl: "/",
52
- debug: false,
53
- auth: {
54
- type: "session"
55
- }
56
- });
57
170
  const displayName = "Medusa v2 Pay. Plugin";
58
- const version = "2.2.50";
171
+ const version = "2.2.52";
59
172
  function getSortedPaymentMethods(data) {
60
173
  const { checkoutOptions, checkoutSequence } = data;
61
174
  const { primary } = checkoutSequence.default;
@@ -74,6 +187,7 @@ const formatter = new Intl.NumberFormat([], {
74
187
  minimumFractionDigits: 0,
75
188
  maximumFractionDigits: 2
76
189
  });
190
+ const SETTINGS_QUERY_KEY = ["pay-settings"];
77
191
  const columnHelper = createDataTableColumnHelper();
78
192
  const columns = [
79
193
  columnHelper.accessor("id", {
@@ -94,6 +208,26 @@ const columns = [
94
208
  })
95
209
  ];
96
210
  const PaySettingPage = () => {
211
+ const queryClient = useQueryClient();
212
+ const { data: settings, isLoading: isLoadingSettings } = useQuery({
213
+ queryFn: () => sdk.client.fetch("/admin/pay/settings"),
214
+ queryKey: SETTINGS_QUERY_KEY
215
+ });
216
+ const updateSepaTesting = useMutation({
217
+ mutationFn: (sepaTesting) => sdk.client.fetch("/admin/pay/settings", {
218
+ method: "POST",
219
+ body: { sepaTesting }
220
+ }),
221
+ onSuccess: (updated) => {
222
+ queryClient.setQueryData(SETTINGS_QUERY_KEY, updated);
223
+ toast.success("Success", {
224
+ description: updated.sepaTesting ? "SEPA testing enabled, the simulator is shown on direct debit orders" : "SEPA testing disabled"
225
+ });
226
+ },
227
+ onError: (error) => {
228
+ toast.error("Error", { description: error.message });
229
+ }
230
+ });
97
231
  const { data: config2, refetch: refetchConfig } = useQuery({
98
232
  queryFn: () => sdk.client.fetch("/admin/pay/payment-methods"),
99
233
  queryKey: ["pay-payment-methods"]
@@ -167,6 +301,27 @@ const PaySettingPage = () => {
167
301
  children: "Clear Cache"
168
302
  }
169
303
  ) })
304
+ ] }),
305
+ /* @__PURE__ */ jsxs("div", { className: "text-ui-fg-subtle grid grid-cols-2 items-center px-6 py-4", children: [
306
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-1", children: [
307
+ /* @__PURE__ */ jsx(Text, { size: "small", leading: "compact", weight: "plus", children: "SEPA testing" }),
308
+ /* @__PURE__ */ jsxs(Text, { size: "small", leading: "compact", children: [
309
+ "Pay. cannot process direct debits in its test environment. When enabled, direct debit orders get a simulator that walks the payment through the exchanges Pay. would send (collected, storno, ...). Meant for staging servers only.",
310
+ settings && !settings.testMode ? " The provider is not in test mode, so the simulator stays hidden." : ""
311
+ ] })
312
+ ] }),
313
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-3", children: [
314
+ /* @__PURE__ */ jsx(
315
+ Switch,
316
+ {
317
+ id: "pay-sepa-testing",
318
+ checked: (settings == null ? void 0 : settings.sepaTesting) ?? false,
319
+ disabled: isLoadingSettings || updateSepaTesting.isPending,
320
+ onCheckedChange: (checked) => updateSepaTesting.mutate(checked)
321
+ }
322
+ ),
323
+ /* @__PURE__ */ jsx(Text, { size: "small", leading: "compact", children: (settings == null ? void 0 : settings.sepaTesting) ? "Enabled" : "Disabled" })
324
+ ] })
170
325
  ] })
171
326
  ] }),
172
327
  /* @__PURE__ */ jsx(Container, { className: "divide-y p-0 overflow-hidden", children: /* @__PURE__ */ jsxs(DataTable, { instance: table, children: [
@@ -194,7 +349,12 @@ const PaySettingPage = () => {
194
349
  const config = defineRouteConfig({
195
350
  label: "Pay"
196
351
  });
197
- const widgetModule = { widgets: [] };
352
+ const widgetModule = { widgets: [
353
+ {
354
+ Component: PayDirectDebitSimulatorWidget,
355
+ zone: ["order.details.side.after"]
356
+ }
357
+ ] };
198
358
  const routeModule = {
199
359
  routes: [
200
360
  {
@@ -0,0 +1,12 @@
1
+ import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework/http";
2
+ /**
3
+ * Simulates the exchange Pay. would send for the order's test mode direct
4
+ * debit. Only allowed while "SEPA testing" is switched on in the Pay. settings
5
+ * (see getDirectDebitSimulationState). The exchange is processed synchronously
6
+ * through the same subscriber that handles real exchanges, so the response
7
+ * carries the resulting state.
8
+ */
9
+ export declare const POST: (req: AuthenticatedMedusaRequest<{
10
+ action?: unknown;
11
+ }>, res: MedusaResponse) => Promise<MedusaResponse>;
12
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/api/admin/pay/orders/[id]/direct-debit/exchange/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,0BAA0B,EAAE,cAAc,EAAC,MAAM,0BAA0B,CAAA;AAUnF;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GACf,KAAK,0BAA0B,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAC,CAAC,EACnD,KAAK,cAAc,4BAoCpB,CAAA"}
@@ -0,0 +1,42 @@
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.POST = void 0;
7
+ const utils_1 = require("@medusajs/framework/utils");
8
+ const directDebitSimulation_1 = require("../../../../../../../utils/directDebitSimulation");
9
+ const directDebitExchange_1 = require("../../../../../../../utils/directDebitExchange");
10
+ const direct_debit_exchange_1 = __importDefault(require("../../../../../../../subscribers/direct-debit-exchange"));
11
+ /**
12
+ * Simulates the exchange Pay. would send for the order's test mode direct
13
+ * debit. Only allowed while "SEPA testing" is switched on in the Pay. settings
14
+ * (see getDirectDebitSimulationState). The exchange is processed synchronously
15
+ * through the same subscriber that handles real exchanges, so the response
16
+ * carries the resulting state.
17
+ */
18
+ const POST = async (req, res) => {
19
+ const action = req.body?.action;
20
+ if (!(0, directDebitSimulation_1.isSimulatedExchangeKey)(action)) {
21
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Unknown exchange "${String(action)}"`);
22
+ }
23
+ const state = await (0, directDebitSimulation_1.getDirectDebitSimulationState)(req.scope, req.params.id);
24
+ if (!state) {
25
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, "Order not found");
26
+ }
27
+ if (!state.available) {
28
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, `Direct debit exchanges cannot be simulated for this order (${state.reason})`);
29
+ }
30
+ await (0, direct_debit_exchange_1.default)({
31
+ event: {
32
+ name: directDebitExchange_1.PAY_DIRECT_DEBIT_EXCHANGE_EVENT,
33
+ data: { payload: (0, directDebitSimulation_1.buildSimulatedExchangePayload)(action, state) },
34
+ },
35
+ container: req.scope,
36
+ pluginOptions: {},
37
+ });
38
+ const updated = await (0, directDebitSimulation_1.getDirectDebitSimulationState)(req.scope, req.params.id);
39
+ return res.json({ visible: updated?.available ?? false, ...updated });
40
+ };
41
+ exports.POST = POST;
42
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3BheS9vcmRlcnMvW2lkXS9kaXJlY3QtZGViaXQvZXhjaGFuZ2Uvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQ0EscURBQXFEO0FBQ3JELDRGQUl5RDtBQUN6RCx3RkFBOEY7QUFDOUYsbUhBQWtHO0FBRWxHOzs7Ozs7R0FNRztBQUNJLE1BQU0sSUFBSSxHQUFHLEtBQUssRUFDdkIsR0FBbUQsRUFDbkQsR0FBbUIsRUFDbkIsRUFBRTtJQUNGLE1BQU0sTUFBTSxHQUFJLEdBQUcsQ0FBQyxJQUF1QyxFQUFFLE1BQU0sQ0FBQTtJQUVuRSxJQUFJLENBQUMsSUFBQSw4Q0FBc0IsRUFBQyxNQUFNLENBQUMsRUFBRSxDQUFDO1FBQ3BDLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLHFCQUFxQixNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FDdkMsQ0FBQTtJQUNILENBQUM7SUFFRCxNQUFNLEtBQUssR0FBRyxNQUFNLElBQUEscURBQTZCLEVBQUMsR0FBRyxDQUFDLEtBQUssRUFBRSxHQUFHLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxDQUFBO0lBRTNFLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNYLE1BQU0sSUFBSSxtQkFBVyxDQUFDLG1CQUFXLENBQUMsS0FBSyxDQUFDLFNBQVMsRUFBRSxpQkFBaUIsQ0FBQyxDQUFBO0lBQ3ZFLENBQUM7SUFFRCxJQUFJLENBQUMsS0FBSyxDQUFDLFNBQVMsRUFBRSxDQUFDO1FBQ3JCLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxXQUFXLEVBQzdCLDhEQUE4RCxLQUFLLENBQUMsTUFBTSxHQUFHLENBQzlFLENBQUE7SUFDSCxDQUFDO0lBRUQsTUFBTSxJQUFBLCtCQUE2QixFQUFDO1FBQ2xDLEtBQUssRUFBRTtZQUNMLElBQUksRUFBRSxxREFBK0I7WUFDckMsSUFBSSxFQUFFLEVBQUMsT0FBTyxFQUFFLElBQUEscURBQTZCLEVBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxFQUFDO1NBQzlEO1FBQ0QsU0FBUyxFQUFFLEdBQUcsQ0FBQyxLQUFLO1FBQ3BCLGFBQWEsRUFBRSxFQUFFO0tBQ2xCLENBQUMsQ0FBQTtJQUVGLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBQSxxREFBNkIsRUFBQyxHQUFHLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLENBQUE7SUFFN0UsT0FBTyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUMsT0FBTyxFQUFFLE9BQU8sRUFBRSxTQUFTLElBQUksS0FBSyxFQUFFLEdBQUcsT0FBTyxFQUFDLENBQUMsQ0FBQTtBQUNyRSxDQUFDLENBQUE7QUF0Q1ksUUFBQSxJQUFJLFFBc0NoQiJ9
@@ -0,0 +1,9 @@
1
+ import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework/http";
2
+ /**
3
+ * The direct debit simulation state of an order, for the order detail widget.
4
+ * `visible` is only true while "SEPA testing" is switched on in the Pay.
5
+ * settings, the server runs in test mode and the order holds a simulated
6
+ * mandate, so the widget stays hidden everywhere else.
7
+ */
8
+ export declare const GET: (req: AuthenticatedMedusaRequest, res: MedusaResponse) => Promise<MedusaResponse>;
9
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/api/admin/pay/orders/[id]/direct-debit/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,0BAA0B,EAAE,cAAc,EAAC,MAAM,0BAA0B,CAAA;AAGnF;;;;;GAKG;AACH,eAAO,MAAM,GAAG,GACd,KAAK,0BAA0B,EAC/B,KAAK,cAAc,4BASpB,CAAA"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = void 0;
4
+ const directDebitSimulation_1 = require("../../../../../../utils/directDebitSimulation");
5
+ /**
6
+ * The direct debit simulation state of an order, for the order detail widget.
7
+ * `visible` is only true while "SEPA testing" is switched on in the Pay.
8
+ * settings, the server runs in test mode and the order holds a simulated
9
+ * mandate, so the widget stays hidden everywhere else.
10
+ */
11
+ const GET = async (req, res) => {
12
+ const state = await (0, directDebitSimulation_1.getDirectDebitSimulationState)(req.scope, req.params.id);
13
+ if (!state) {
14
+ return res.status(404).json({ message: "Order not found" });
15
+ }
16
+ return res.json({ visible: state.available, ...state });
17
+ };
18
+ exports.GET = GET;
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3BheS9vcmRlcnMvW2lkXS9kaXJlY3QtZGViaXQvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EseUZBQTJGO0FBRTNGOzs7OztHQUtHO0FBQ0ksTUFBTSxHQUFHLEdBQUcsS0FBSyxFQUN0QixHQUErQixFQUMvQixHQUFtQixFQUNuQixFQUFFO0lBQ0YsTUFBTSxLQUFLLEdBQUcsTUFBTSxJQUFBLHFEQUE2QixFQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQTtJQUUzRSxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDWCxPQUFPLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUMsT0FBTyxFQUFFLGlCQUFpQixFQUFDLENBQUMsQ0FBQTtJQUMzRCxDQUFDO0lBRUQsT0FBTyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxTQUFTLEVBQUUsR0FBRyxLQUFLLEVBQUMsQ0FBQyxDQUFBO0FBQ3ZELENBQUMsQ0FBQTtBQVhZLFFBQUEsR0FBRyxPQVdmIn0=
@@ -0,0 +1,12 @@
1
+ import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework/http";
2
+ export type PayAdminSettings = {
3
+ /** Whether the provider runs against Pay.'s test environment */
4
+ testMode: boolean;
5
+ /** The "SEPA testing" switch: allows simulating direct debit exchanges */
6
+ sepaTesting: boolean;
7
+ };
8
+ export declare const GET: (req: AuthenticatedMedusaRequest, res: MedusaResponse) => Promise<void>;
9
+ export declare const POST: (req: AuthenticatedMedusaRequest<{
10
+ sepaTesting?: unknown;
11
+ }>, res: MedusaResponse) => Promise<void>;
12
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/api/admin/pay/settings/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,0BAA0B,EAAE,cAAc,EAAC,MAAM,0BAA0B,CAAA;AAQnF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gEAAgE;IAChE,QAAQ,EAAE,OAAO,CAAA;IACjB,0EAA0E;IAC1E,WAAW,EAAE,OAAO,CAAA;CACrB,CAAA;AASD,eAAO,MAAM,GAAG,GACd,KAAK,0BAA0B,EAC/B,KAAK,cAAc,kBAGpB,CAAA;AAED,eAAO,MAAM,IAAI,GACf,KAAK,0BAA0B,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAC,CAAC,EACxD,KAAK,cAAc,kBAepB,CAAA"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = exports.GET = void 0;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const directDebitSimulation_1 = require("../../../../utils/directDebitSimulation");
6
+ const readSettings = async (req) => ({
7
+ testMode: (0, directDebitSimulation_1.getPayProviderOptions)(req.scope)?.testMode ?? true,
8
+ sepaTesting: await (0, directDebitSimulation_1.isSepaTestingEnabled)(req.scope),
9
+ });
10
+ const GET = async (req, res) => {
11
+ res.json(await readSettings(req));
12
+ };
13
+ exports.GET = GET;
14
+ const POST = async (req, res) => {
15
+ const sepaTesting = req.body
16
+ ?.sepaTesting;
17
+ if (typeof sepaTesting !== "boolean") {
18
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "sepaTesting must be a boolean");
19
+ }
20
+ await (0, directDebitSimulation_1.setSepaTestingEnabled)(req.scope, sepaTesting);
21
+ res.json(await readSettings(req));
22
+ };
23
+ exports.POST = POST;
24
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3BheS9zZXR0aW5ncy9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFDQSxxREFBcUQ7QUFDckQsbUZBSWdEO0FBU2hELE1BQU0sWUFBWSxHQUFHLEtBQUssRUFDeEIsR0FBK0IsRUFDSixFQUFFLENBQUMsQ0FBQztJQUMvQixRQUFRLEVBQUUsSUFBQSw2Q0FBcUIsRUFBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEVBQUUsUUFBUSxJQUFJLElBQUk7SUFDNUQsV0FBVyxFQUFFLE1BQU0sSUFBQSw0Q0FBb0IsRUFBQyxHQUFHLENBQUMsS0FBSyxDQUFDO0NBQ25ELENBQUMsQ0FBQTtBQUVLLE1BQU0sR0FBRyxHQUFHLEtBQUssRUFDdEIsR0FBK0IsRUFDL0IsR0FBbUIsRUFDbkIsRUFBRTtJQUNGLEdBQUcsQ0FBQyxJQUFJLENBQUMsTUFBTSxZQUFZLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQTtBQUNuQyxDQUFDLENBQUE7QUFMWSxRQUFBLEdBQUcsT0FLZjtBQUVNLE1BQU0sSUFBSSxHQUFHLEtBQUssRUFDdkIsR0FBd0QsRUFDeEQsR0FBbUIsRUFDbkIsRUFBRTtJQUNGLE1BQU0sV0FBVyxHQUFJLEdBQUcsQ0FBQyxJQUE0QztRQUNuRSxFQUFFLFdBQVcsQ0FBQTtJQUVmLElBQUksT0FBTyxXQUFXLEtBQUssU0FBUyxFQUFFLENBQUM7UUFDckMsTUFBTSxJQUFJLG1CQUFXLENBQ25CLG1CQUFXLENBQUMsS0FBSyxDQUFDLFlBQVksRUFDOUIsK0JBQStCLENBQ2hDLENBQUE7SUFDSCxDQUFDO0lBRUQsTUFBTSxJQUFBLDZDQUFxQixFQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsV0FBVyxDQUFDLENBQUE7SUFFbkQsR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFBO0FBQ25DLENBQUMsQ0FBQTtBQWpCWSxRQUFBLElBQUksUUFpQmhCIn0=
@@ -5,6 +5,7 @@ export declare const PAY_CACHE_KEY: {
5
5
  };
6
6
  export declare const PayEnvironmentPaths: {
7
7
  REST_API: string;
8
+ REST_API_v3: string;
8
9
  TGU_API: string;
9
10
  };
10
11
  export declare const PayApiPath: {
@@ -17,6 +18,7 @@ export declare const PayApiPath: {
17
18
  GET_TRANSACTION: string;
18
19
  TRANSACTION_REFUND: string;
19
20
  DIRECT_DEBIT: string;
21
+ DIRECT_DEBIT_V3: string;
20
22
  DIRECT_DEBIT_MANDATE: string;
21
23
  DIRECT_DEBIT_INFO: string;
22
24
  DIRECT_DEBIT_INFO_BY_MANDATE: string;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../../../src/providers/pay/core/constants.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAC/B,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAA;AAEpD,eAAO,MAAM,aAAa;;;CAGhB,CAAA;AAEV,eAAO,MAAM,mBAAmB;;;CAG/B,CAAA;AAED,eAAO,MAAM,UAAU;;;;;;;;;;;;;CAatB,CAAA;AAGD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;CAyBnB,CAAA;AAIV,eAAO,MAAM,wBAAwB;;;;;;;CAO3B,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../../../src/providers/pay/core/constants.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAC/B,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAA;AAEpD,eAAO,MAAM,aAAa;;;CAGhB,CAAA;AAEV,eAAO,MAAM,mBAAmB;;;;CAI/B,CAAA;AAED,eAAO,MAAM,UAAU;;;;;;;;;;;;;;CActB,CAAA;AAGD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;CAyBnB,CAAA;AAIV,eAAO,MAAM,wBAAwB;;;;;;;CAO3B,CAAA"}