medusa-payment-sumup 0.1.0
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/src/admin/index.js +22 -0
- package/.medusa/server/src/admin/index.mjs +23 -0
- package/.medusa/server/src/providers/sumup/core/sumup-client.js +148 -0
- package/.medusa/server/src/providers/sumup/core/sumup-provider.js +489 -0
- package/.medusa/server/src/providers/sumup/index.js +9 -0
- package/.medusa/server/src/providers/sumup/services/index.js +9 -0
- package/.medusa/server/src/providers/sumup/types/index.js +18 -0
- package/README.md +142 -0
- package/package.json +51 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const widgetModule = { widgets: [] };
|
|
3
|
+
const routeModule = {
|
|
4
|
+
routes: []
|
|
5
|
+
};
|
|
6
|
+
const menuItemModule = {
|
|
7
|
+
menuItems: []
|
|
8
|
+
};
|
|
9
|
+
const formModule = { customFields: {} };
|
|
10
|
+
const displayModule = {
|
|
11
|
+
displays: {}
|
|
12
|
+
};
|
|
13
|
+
const i18nModule = { resources: {} };
|
|
14
|
+
const plugin = {
|
|
15
|
+
widgetModule,
|
|
16
|
+
routeModule,
|
|
17
|
+
menuItemModule,
|
|
18
|
+
formModule,
|
|
19
|
+
displayModule,
|
|
20
|
+
i18nModule
|
|
21
|
+
};
|
|
22
|
+
module.exports = plugin;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const widgetModule = { widgets: [] };
|
|
2
|
+
const routeModule = {
|
|
3
|
+
routes: []
|
|
4
|
+
};
|
|
5
|
+
const menuItemModule = {
|
|
6
|
+
menuItems: []
|
|
7
|
+
};
|
|
8
|
+
const formModule = { customFields: {} };
|
|
9
|
+
const displayModule = {
|
|
10
|
+
displays: {}
|
|
11
|
+
};
|
|
12
|
+
const i18nModule = { resources: {} };
|
|
13
|
+
const plugin = {
|
|
14
|
+
widgetModule,
|
|
15
|
+
routeModule,
|
|
16
|
+
menuItemModule,
|
|
17
|
+
formModule,
|
|
18
|
+
displayModule,
|
|
19
|
+
i18nModule
|
|
20
|
+
};
|
|
21
|
+
export {
|
|
22
|
+
plugin as default
|
|
23
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SumUpClient = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
const SUMUP_API_BASE = "https://api.sumup.com";
|
|
6
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
7
|
+
const MAX_RETRIES = 3;
|
|
8
|
+
// Use zero-delay backoff in tests so the suite stays fast.
|
|
9
|
+
const BASE_BACKOFF_MS = process.env.NODE_ENV === "test" ? 0 : 300;
|
|
10
|
+
// Fields that must never appear in logged/thrown error messages.
|
|
11
|
+
const SENSITIVE_FIELDS = new Set([
|
|
12
|
+
"card",
|
|
13
|
+
"cvv",
|
|
14
|
+
"pan",
|
|
15
|
+
"pan_fingerprint",
|
|
16
|
+
"token",
|
|
17
|
+
"number",
|
|
18
|
+
"secret",
|
|
19
|
+
]);
|
|
20
|
+
/** Strips sensitive keys from a JSON error body before it appears in error messages. */
|
|
21
|
+
function redactErrorBody(text) {
|
|
22
|
+
try {
|
|
23
|
+
const obj = JSON.parse(text);
|
|
24
|
+
for (const key of SENSITIVE_FIELDS) {
|
|
25
|
+
delete obj[key];
|
|
26
|
+
}
|
|
27
|
+
return JSON.stringify(obj);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Non-JSON body (e.g. HTML gateway error) — truncate to avoid noise.
|
|
31
|
+
return text.length > 500 ? `${text.slice(0, 500)}…` : text;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Classify an HTTP status code into a SumUpErrorType. */
|
|
35
|
+
function classifyStatus(status) {
|
|
36
|
+
if (status === 400 || status === 422)
|
|
37
|
+
return "validation";
|
|
38
|
+
if (status === 401 || status === 403)
|
|
39
|
+
return "auth";
|
|
40
|
+
if (status === 404)
|
|
41
|
+
return "not_found";
|
|
42
|
+
if (status === 409)
|
|
43
|
+
return "conflict";
|
|
44
|
+
if (status === 429)
|
|
45
|
+
return "rate_limit";
|
|
46
|
+
return "server"; // 5xx and any other non-OK code
|
|
47
|
+
}
|
|
48
|
+
/** Async sleep. Resolves immediately when ms ≤ 0. */
|
|
49
|
+
function sleep(ms) {
|
|
50
|
+
if (ms <= 0)
|
|
51
|
+
return Promise.resolve();
|
|
52
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Compute how long to wait before the next retry.
|
|
56
|
+
* Respects the `Retry-After` header (seconds) when provided by the server,
|
|
57
|
+
* otherwise uses bounded exponential back-off with ±30 % jitter.
|
|
58
|
+
*/
|
|
59
|
+
function backoffMs(attempt, retryAfterSec) {
|
|
60
|
+
if (retryAfterSec !== undefined && !Number.isNaN(retryAfterSec)) {
|
|
61
|
+
return Math.min(retryAfterSec * 1_000, 60_000);
|
|
62
|
+
}
|
|
63
|
+
const base = BASE_BACKOFF_MS * 2 ** attempt;
|
|
64
|
+
const jitter = Math.random() * base * 0.3;
|
|
65
|
+
return Math.min(base + jitter, 10_000);
|
|
66
|
+
}
|
|
67
|
+
class SumUpClient {
|
|
68
|
+
constructor(options) {
|
|
69
|
+
this.headers = {
|
|
70
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
71
|
+
"Content-Type": "application/json",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Central request dispatcher. Handles timeouts, error classification,
|
|
76
|
+
* and retries (with back-off) for transient failures (5xx, rate-limit,
|
|
77
|
+
* network errors, and timeouts).
|
|
78
|
+
*/
|
|
79
|
+
async request(url, init, attempt = 0) {
|
|
80
|
+
let res;
|
|
81
|
+
// ── Network / timeout ──────────────────────────────────────────
|
|
82
|
+
try {
|
|
83
|
+
res = await fetch(url, {
|
|
84
|
+
...init,
|
|
85
|
+
headers: this.headers,
|
|
86
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
const isTimeout = err?.name === "TimeoutError" || err?.name === "AbortError";
|
|
91
|
+
const type = isTimeout ? "timeout" : "network";
|
|
92
|
+
if (attempt < MAX_RETRIES) {
|
|
93
|
+
await sleep(backoffMs(attempt));
|
|
94
|
+
return this.request(url, init, attempt + 1);
|
|
95
|
+
}
|
|
96
|
+
throw new types_1.SumUpClientError(isTimeout
|
|
97
|
+
? `SumUp request timed out after ${REQUEST_TIMEOUT_MS}ms`
|
|
98
|
+
: `SumUp network error: ${err?.message ?? "unknown"}`, type);
|
|
99
|
+
}
|
|
100
|
+
// ── Success ────────────────────────────────────────────────────
|
|
101
|
+
if (res.ok) {
|
|
102
|
+
if (res.status === 204)
|
|
103
|
+
return undefined;
|
|
104
|
+
return (await res.json());
|
|
105
|
+
}
|
|
106
|
+
// ── Error response ─────────────────────────────────────────────
|
|
107
|
+
const bodyText = await res.text().catch(() => "");
|
|
108
|
+
const type = classifyStatus(res.status);
|
|
109
|
+
const isRetryable = type === "rate_limit" || type === "server";
|
|
110
|
+
if (isRetryable && attempt < MAX_RETRIES) {
|
|
111
|
+
const retryAfterHeader = res.headers.get("Retry-After");
|
|
112
|
+
const retryAfterSec = retryAfterHeader != null
|
|
113
|
+
? parseInt(retryAfterHeader, 10)
|
|
114
|
+
: undefined;
|
|
115
|
+
await sleep(backoffMs(attempt, retryAfterSec));
|
|
116
|
+
return this.request(url, init, attempt + 1);
|
|
117
|
+
}
|
|
118
|
+
throw new types_1.SumUpClientError(`SumUp API error (${res.status}): ${redactErrorBody(bodyText)}`, type, res.status);
|
|
119
|
+
}
|
|
120
|
+
async createCheckout(params) {
|
|
121
|
+
return this.request(`${SUMUP_API_BASE}/v0.1/checkouts`, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
body: JSON.stringify(params),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async getCheckout(checkoutId) {
|
|
127
|
+
return this.request(`${SUMUP_API_BASE}/v0.1/checkouts/${encodeURIComponent(checkoutId)}`, { method: "GET" });
|
|
128
|
+
}
|
|
129
|
+
async deactivateCheckout(checkoutId) {
|
|
130
|
+
try {
|
|
131
|
+
await this.request(`${SUMUP_API_BASE}/v0.1/checkouts/${encodeURIComponent(checkoutId)}`, { method: "DELETE" });
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
// A 404 means the checkout is already gone — treat as success.
|
|
135
|
+
if (err instanceof types_1.SumUpClientError && err.type === "not_found")
|
|
136
|
+
return;
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async refundTransaction(transactionId, amount) {
|
|
141
|
+
const body = {};
|
|
142
|
+
if (amount !== undefined)
|
|
143
|
+
body.amount = amount;
|
|
144
|
+
return this.request(`${SUMUP_API_BASE}/v0.1/me/refund/${encodeURIComponent(transactionId)}`, { method: "POST", body: JSON.stringify(body) });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.SumUpClient = SumUpClient;
|
|
148
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3VtdXAtY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL3Byb3ZpZGVycy9zdW11cC9jb3JlL3N1bXVwLWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFNQSxvQ0FBNEM7QUFFNUMsTUFBTSxjQUFjLEdBQUcsdUJBQXVCLENBQUM7QUFDL0MsTUFBTSxrQkFBa0IsR0FBRyxNQUFNLENBQUM7QUFDbEMsTUFBTSxXQUFXLEdBQUcsQ0FBQyxDQUFDO0FBRXRCLDJEQUEyRDtBQUMzRCxNQUFNLGVBQWUsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsS0FBSyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDO0FBRWxFLGlFQUFpRTtBQUNqRSxNQUFNLGdCQUFnQixHQUFHLElBQUksR0FBRyxDQUFDO0lBQy9CLE1BQU07SUFDTixLQUFLO0lBQ0wsS0FBSztJQUNMLGlCQUFpQjtJQUNqQixPQUFPO0lBQ1AsUUFBUTtJQUNSLFFBQVE7Q0FDVCxDQUFDLENBQUM7QUFFSCx3RkFBd0Y7QUFDeEYsU0FBUyxlQUFlLENBQUMsSUFBWTtJQUNuQyxJQUFJLENBQUM7UUFDSCxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBNEIsQ0FBQztRQUN4RCxLQUFLLE1BQU0sR0FBRyxJQUFJLGdCQUFnQixFQUFFLENBQUM7WUFDbkMsT0FBTyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDbEIsQ0FBQztRQUNELE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUM3QixDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AscUVBQXFFO1FBQ3JFLE9BQU8sSUFBSSxDQUFDLE1BQU0sR0FBRyxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLEdBQUcs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
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
4
|
+
const sumup_client_1 = require("./sumup-client");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
// ISO-4217: 3 uppercase alpha characters.
|
|
7
|
+
const CURRENCY_RE = /^[A-Z]{3}$/;
|
|
8
|
+
const SUMUP_STATUS_MAP = {
|
|
9
|
+
PENDING: utils_1.PaymentSessionStatus.PENDING,
|
|
10
|
+
PAID: utils_1.PaymentSessionStatus.CAPTURED,
|
|
11
|
+
FAILED: utils_1.PaymentSessionStatus.ERROR,
|
|
12
|
+
EXPIRED: utils_1.PaymentSessionStatus.CANCELED,
|
|
13
|
+
};
|
|
14
|
+
class SumUpProviderService extends utils_1.AbstractPaymentProvider {
|
|
15
|
+
static validateOptions(options) {
|
|
16
|
+
if (!options.apiKey || typeof options.apiKey !== "string") {
|
|
17
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "SumUp API key (apiKey) is required.");
|
|
18
|
+
}
|
|
19
|
+
if (!options.merchantCode || typeof options.merchantCode !== "string") {
|
|
20
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "SumUp merchant code (merchantCode) is required.");
|
|
21
|
+
}
|
|
22
|
+
if (!options.medusaUrl || typeof options.medusaUrl !== "string") {
|
|
23
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Medusa backend URL (medusaUrl) is required.");
|
|
24
|
+
}
|
|
25
|
+
if (!options.redirectUrl || typeof options.redirectUrl !== "string") {
|
|
26
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Redirect URL (redirectUrl) is required.");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
constructor(container, options) {
|
|
30
|
+
super(container, options);
|
|
31
|
+
this.logger_ = container.logger;
|
|
32
|
+
this.options_ = options;
|
|
33
|
+
this.debug_ =
|
|
34
|
+
options.debug ||
|
|
35
|
+
process.env.NODE_ENV === "development" ||
|
|
36
|
+
process.env.NODE_ENV === "test" ||
|
|
37
|
+
false;
|
|
38
|
+
this.client_ = new sumup_client_1.SumUpClient(options);
|
|
39
|
+
}
|
|
40
|
+
// ── Input validators ──────────────────────────────────────────────────────
|
|
41
|
+
/**
|
|
42
|
+
* Asserts that `amount` is a positive finite number and returns it as a
|
|
43
|
+
* parsed float. Throws INVALID_DATA otherwise.
|
|
44
|
+
*/
|
|
45
|
+
static assertAmount(amount, ctx) {
|
|
46
|
+
const n = parseFloat(String(amount));
|
|
47
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
48
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `${ctx}: amount must be a positive finite number, got "${amount}"`);
|
|
49
|
+
}
|
|
50
|
+
return n;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Asserts that `code` is a valid ISO-4217 format (3 alpha chars), normalises
|
|
54
|
+
* to uppercase, and returns it. Throws INVALID_DATA otherwise.
|
|
55
|
+
*/
|
|
56
|
+
static assertCurrency(code, ctx) {
|
|
57
|
+
const upper = typeof code === "string" ? code.toUpperCase().trim() : "";
|
|
58
|
+
if (!CURRENCY_RE.test(upper)) {
|
|
59
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `${ctx}: currency_code must be a 3-letter ISO-4217 code, got "${code}"`);
|
|
60
|
+
}
|
|
61
|
+
return upper;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Asserts that `id` is a non-empty string and returns it trimmed.
|
|
65
|
+
* Throws INVALID_DATA otherwise.
|
|
66
|
+
*/
|
|
67
|
+
static assertExternalId(id, ctx) {
|
|
68
|
+
if (typeof id !== "string" || id.trim() === "") {
|
|
69
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `${ctx}: checkout ID is required`);
|
|
70
|
+
}
|
|
71
|
+
return id.trim();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Translates a SumUpClientError into the most appropriate MedusaError type
|
|
75
|
+
* so callers receive structured, actionable errors rather than raw HTTP
|
|
76
|
+
* status text. Always throws.
|
|
77
|
+
*/
|
|
78
|
+
rethrowAsMedusaError(error, ctx) {
|
|
79
|
+
if (error instanceof types_1.SumUpClientError) {
|
|
80
|
+
switch (error.type) {
|
|
81
|
+
case "auth":
|
|
82
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNAUTHORIZED, `SumUp authentication failed in ${ctx}. Verify your API key.`);
|
|
83
|
+
case "not_found":
|
|
84
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, `SumUp resource not found in ${ctx}.`);
|
|
85
|
+
case "validation":
|
|
86
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `SumUp rejected the request in ${ctx}: ${error.message}`);
|
|
87
|
+
case "conflict":
|
|
88
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.CONFLICT, `SumUp conflict in ${ctx}: ${error.message}`);
|
|
89
|
+
case "rate_limit":
|
|
90
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, `SumUp rate limit exceeded in ${ctx}. Please retry later.`);
|
|
91
|
+
// network / timeout / server: re-throw as-is with a clear prefix
|
|
92
|
+
default: {
|
|
93
|
+
const wrapped = new Error(`SumUp ${error.type} error in ${ctx}: ${error.message}`);
|
|
94
|
+
throw wrapped;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Creates a new SumUp checkout for the payment session.
|
|
102
|
+
*/
|
|
103
|
+
async initiatePayment(input) {
|
|
104
|
+
const ctx = "initiatePayment";
|
|
105
|
+
// ── Input validation ────────────────────────────────────────────
|
|
106
|
+
const amount = SumUpProviderService.assertAmount(input.amount, ctx);
|
|
107
|
+
const currency = SumUpProviderService.assertCurrency(input.currency_code, ctx);
|
|
108
|
+
const { context } = input;
|
|
109
|
+
try {
|
|
110
|
+
const checkoutReference = input.data?.session_id ?? crypto.randomUUID();
|
|
111
|
+
if (!input.data?.session_id) {
|
|
112
|
+
this.logger_.warn("SumUp: data.session_id is missing — using random UUID as checkout_reference. " +
|
|
113
|
+
"Webhook-based status updates may not work correctly.");
|
|
114
|
+
}
|
|
115
|
+
const webhookUrl = this.options_.medusaUrl + "/hooks/payment/sumup_sumup";
|
|
116
|
+
const customer = context?.customer;
|
|
117
|
+
// Merge guest data (from cart shipping address) with logged-in customer
|
|
118
|
+
// context; context.customer takes precedence when present.
|
|
119
|
+
const pdFirst = customer?.first_name ?? input.data?.first_name;
|
|
120
|
+
const pdLast = customer?.last_name ?? input.data?.last_name;
|
|
121
|
+
const pdEmail = customer?.email ?? input.data?.email;
|
|
122
|
+
const personalDetails = pdFirst || pdLast || pdEmail
|
|
123
|
+
? {
|
|
124
|
+
...(pdFirst && { first_name: pdFirst }),
|
|
125
|
+
...(pdLast && { last_name: pdLast }),
|
|
126
|
+
...(pdEmail && { email: pdEmail }),
|
|
127
|
+
}
|
|
128
|
+
: undefined;
|
|
129
|
+
const checkout = await this.client_.createCheckout({
|
|
130
|
+
checkout_reference: checkoutReference,
|
|
131
|
+
amount,
|
|
132
|
+
currency,
|
|
133
|
+
merchant_code: this.options_.merchantCode,
|
|
134
|
+
description: input.data?.description || "SumUp payment via Medusa",
|
|
135
|
+
return_url: webhookUrl,
|
|
136
|
+
redirect_url: this.options_.redirectUrl,
|
|
137
|
+
...(personalDetails && { personal_details: personalDetails }),
|
|
138
|
+
});
|
|
139
|
+
this.debug_ &&
|
|
140
|
+
this.logger_.info(`SumUp checkout ${checkout.id} created with amount ${amount} ${currency}`);
|
|
141
|
+
return {
|
|
142
|
+
id: checkout.id,
|
|
143
|
+
data: {
|
|
144
|
+
id: checkout.id,
|
|
145
|
+
checkout_reference: checkoutReference,
|
|
146
|
+
idempotency_key: context?.idempotency_key,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
this.logger_.error(`Error in ${ctx}: ${error.message}`);
|
|
152
|
+
this.rethrowAsMedusaError(error, ctx);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Checks the SumUp checkout status to authorize the payment.
|
|
157
|
+
* SumUp auto-captures — when the checkout is PAID we return AUTHORIZED
|
|
158
|
+
* so that Medusa can proceed to capture (which is a no-op verification).
|
|
159
|
+
*/
|
|
160
|
+
async authorizePayment(input) {
|
|
161
|
+
const ctx = "authorizePayment";
|
|
162
|
+
const externalId = SumUpProviderService.assertExternalId(input.data?.id, ctx);
|
|
163
|
+
try {
|
|
164
|
+
const checkout = await this.client_.getCheckout(externalId);
|
|
165
|
+
const sumupStatus = checkout.status;
|
|
166
|
+
const transaction = checkout.transactions?.[0];
|
|
167
|
+
switch (sumupStatus) {
|
|
168
|
+
case "PAID":
|
|
169
|
+
// SumUp auto-captures. Return AUTHORIZED so Medusa's
|
|
170
|
+
// authorize → capture flow proceeds naturally.
|
|
171
|
+
this.debug_ &&
|
|
172
|
+
this.logger_.info(`SumUp payment ${externalId} is PAID — returning AUTHORIZED`);
|
|
173
|
+
return {
|
|
174
|
+
data: {
|
|
175
|
+
...input.data,
|
|
176
|
+
transaction_id: transaction?.id,
|
|
177
|
+
transaction_code: transaction?.transaction_code,
|
|
178
|
+
},
|
|
179
|
+
status: utils_1.PaymentSessionStatus.AUTHORIZED,
|
|
180
|
+
};
|
|
181
|
+
case "PENDING":
|
|
182
|
+
this.debug_ &&
|
|
183
|
+
this.logger_.info(`SumUp payment ${externalId} is still PENDING`);
|
|
184
|
+
return {
|
|
185
|
+
data: input.data,
|
|
186
|
+
status: utils_1.PaymentSessionStatus.PENDING,
|
|
187
|
+
};
|
|
188
|
+
case "FAILED":
|
|
189
|
+
return {
|
|
190
|
+
data: input.data,
|
|
191
|
+
status: utils_1.PaymentSessionStatus.ERROR,
|
|
192
|
+
};
|
|
193
|
+
case "EXPIRED":
|
|
194
|
+
return {
|
|
195
|
+
data: input.data,
|
|
196
|
+
status: utils_1.PaymentSessionStatus.CANCELED,
|
|
197
|
+
};
|
|
198
|
+
default:
|
|
199
|
+
this.logger_.warn(`SumUp payment ${externalId} returned unknown status "${sumupStatus}" — treating as PENDING`);
|
|
200
|
+
return {
|
|
201
|
+
data: input.data,
|
|
202
|
+
status: utils_1.PaymentSessionStatus.PENDING,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
this.logger_.error(`Error in ${ctx} for ${externalId}: ${error.message}`);
|
|
208
|
+
this.rethrowAsMedusaError(error, ctx);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* SumUp auto-captures payments. This verifies the checkout is PAID.
|
|
213
|
+
*/
|
|
214
|
+
async capturePayment(input) {
|
|
215
|
+
const ctx = "capturePayment";
|
|
216
|
+
const externalId = SumUpProviderService.assertExternalId(input.data?.id, ctx);
|
|
217
|
+
try {
|
|
218
|
+
const checkout = await this.client_.getCheckout(externalId);
|
|
219
|
+
if (checkout.status !== "PAID") {
|
|
220
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR, `Cannot capture: SumUp checkout status is ${checkout.status}, not PAID.`);
|
|
221
|
+
}
|
|
222
|
+
const transactions = checkout.transactions ?? [];
|
|
223
|
+
if (transactions.length === 0) {
|
|
224
|
+
// PAID but no transactions array — log a warning and proceed;
|
|
225
|
+
// SumUp sometimes omits this on freshly completed checkouts.
|
|
226
|
+
this.logger_.warn(`SumUp checkout ${externalId} is PAID but has no transactions array. ` +
|
|
227
|
+
"transaction_id will not be available.");
|
|
228
|
+
return { data: { ...input.data } };
|
|
229
|
+
}
|
|
230
|
+
const transaction = transactions[0];
|
|
231
|
+
this.debug_ &&
|
|
232
|
+
this.logger_.info(`SumUp payment ${externalId} capture confirmed`);
|
|
233
|
+
return {
|
|
234
|
+
data: {
|
|
235
|
+
...input.data,
|
|
236
|
+
transaction_id: transaction.id,
|
|
237
|
+
transaction_code: transaction.transaction_code,
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
this.logger_.error(`Error in ${ctx} for ${externalId}: ${error.message}`);
|
|
243
|
+
this.rethrowAsMedusaError(error, ctx);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Refunds a captured SumUp payment.
|
|
248
|
+
*/
|
|
249
|
+
async refundPayment(input) {
|
|
250
|
+
const ctx = "refundPayment";
|
|
251
|
+
// Validate refund amount first — before any API call.
|
|
252
|
+
const refundAmount = SumUpProviderService.assertAmount(input.amount, ctx);
|
|
253
|
+
const transactionId = input.data?.transaction_id;
|
|
254
|
+
if (!transactionId) {
|
|
255
|
+
// If we don't have transaction_id yet, try to get it from the checkout
|
|
256
|
+
const externalId = input.data?.id;
|
|
257
|
+
if (externalId) {
|
|
258
|
+
let checkout;
|
|
259
|
+
try {
|
|
260
|
+
checkout = await this.client_.getCheckout(externalId);
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
this.logger_.error(`Error in ${ctx} looking up checkout ${externalId}: ${error.message}`);
|
|
264
|
+
this.rethrowAsMedusaError(error, ctx);
|
|
265
|
+
}
|
|
266
|
+
const transactions = checkout.transactions ?? [];
|
|
267
|
+
if (transactions.length === 0) {
|
|
268
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Cannot refund: checkout ${externalId} has no completed transactions.`);
|
|
269
|
+
}
|
|
270
|
+
const txn = transactions[0];
|
|
271
|
+
// Guard against refunding more than was originally captured.
|
|
272
|
+
if (typeof txn.amount === "number" &&
|
|
273
|
+
refundAmount > txn.amount) {
|
|
274
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Cannot refund ${refundAmount}: exceeds captured transaction amount of ${txn.amount}.`);
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
await this.client_.refundTransaction(txn.id, refundAmount);
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
this.logger_.error(`Error in ${ctx} refunding transaction ${txn.id}: ${error.message}`);
|
|
281
|
+
this.rethrowAsMedusaError(error, ctx);
|
|
282
|
+
}
|
|
283
|
+
this.debug_ &&
|
|
284
|
+
this.logger_.info(`SumUp refund for payment ${externalId} created with amount ${refundAmount}`);
|
|
285
|
+
return {
|
|
286
|
+
data: {
|
|
287
|
+
...input.data,
|
|
288
|
+
transaction_id: txn.id,
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Cannot refund: no transaction_id found in payment data.");
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
await this.client_.refundTransaction(transactionId, refundAmount);
|
|
296
|
+
this.debug_ &&
|
|
297
|
+
this.logger_.info(`SumUp refund for transaction ${transactionId} created with amount ${refundAmount}`);
|
|
298
|
+
return {
|
|
299
|
+
data: input.data,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
this.logger_.error(`Error in ${ctx} for transaction ${transactionId}: ${error.message}`);
|
|
304
|
+
this.rethrowAsMedusaError(error, ctx);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Cancels a pending SumUp checkout by deactivating it.
|
|
309
|
+
*/
|
|
310
|
+
async cancelPayment(input) {
|
|
311
|
+
const externalId = input.data?.id;
|
|
312
|
+
try {
|
|
313
|
+
if (externalId) {
|
|
314
|
+
const checkout = await this.client_.getCheckout(externalId);
|
|
315
|
+
if (checkout.status === "EXPIRED") {
|
|
316
|
+
this.debug_ &&
|
|
317
|
+
this.logger_.info(`SumUp checkout ${externalId} is already expired, no need to cancel`);
|
|
318
|
+
return { data: input.data };
|
|
319
|
+
}
|
|
320
|
+
await this.client_.deactivateCheckout(externalId);
|
|
321
|
+
}
|
|
322
|
+
this.debug_ &&
|
|
323
|
+
this.logger_.info(`SumUp payment ${externalId} cancelled`);
|
|
324
|
+
return {
|
|
325
|
+
data: input.data,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
this.logger_.warn(`Could not cancel SumUp checkout ${externalId}: ${error.message}`);
|
|
330
|
+
return { data: input.data };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Deletes a payment session. Equivalent to cancellation for SumUp.
|
|
335
|
+
*/
|
|
336
|
+
async deletePayment(input) {
|
|
337
|
+
return this.cancelPayment(input);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Retrieves the full checkout data from SumUp.
|
|
341
|
+
*/
|
|
342
|
+
async retrievePayment(input) {
|
|
343
|
+
const ctx = "retrievePayment";
|
|
344
|
+
const externalId = SumUpProviderService.assertExternalId(input.data?.id, ctx);
|
|
345
|
+
try {
|
|
346
|
+
const data = await this.client_.getCheckout(externalId);
|
|
347
|
+
return { data: data };
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
this.logger_.error(`Error in ${ctx} for ${externalId}: ${error.message}`);
|
|
351
|
+
this.rethrowAsMedusaError(error, ctx);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Maps SumUp checkout status to Medusa PaymentSessionStatus.
|
|
356
|
+
*/
|
|
357
|
+
async getPaymentStatus(input) {
|
|
358
|
+
const ctx = "getPaymentStatus";
|
|
359
|
+
const externalId = SumUpProviderService.assertExternalId(input.data?.id, ctx);
|
|
360
|
+
try {
|
|
361
|
+
const checkout = await this.client_.getCheckout(externalId);
|
|
362
|
+
const status = checkout.status;
|
|
363
|
+
const mappedStatus = SUMUP_STATUS_MAP[status] ?? utils_1.PaymentSessionStatus.PENDING;
|
|
364
|
+
if (!SUMUP_STATUS_MAP[status]) {
|
|
365
|
+
this.logger_.warn(`SumUp checkout ${externalId} returned unknown status "${status}" — defaulting to PENDING`);
|
|
366
|
+
}
|
|
367
|
+
this.debug_ &&
|
|
368
|
+
this.logger_.debug(`SumUp checkout ${externalId} status: ${status} (mapped to: ${mappedStatus})`);
|
|
369
|
+
return { status: mappedStatus };
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
this.logger_.error(`Error in ${ctx} for ${externalId}: ${error.message}`);
|
|
373
|
+
this.rethrowAsMedusaError(error, ctx);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* SumUp does not support modifying a checkout after creation.
|
|
378
|
+
* We cancel the old checkout and create a new one with the updated amount.
|
|
379
|
+
*/
|
|
380
|
+
async updatePayment(input) {
|
|
381
|
+
const ctx = "updatePayment";
|
|
382
|
+
// ── Input validation ────────────────────────────────────────────
|
|
383
|
+
const amount = SumUpProviderService.assertAmount(input.amount, ctx);
|
|
384
|
+
const currency = SumUpProviderService.assertCurrency(input.currency_code, ctx);
|
|
385
|
+
const externalId = input.data?.id;
|
|
386
|
+
try {
|
|
387
|
+
// Cancel the existing checkout (best-effort; may already be expired)
|
|
388
|
+
if (externalId) {
|
|
389
|
+
try {
|
|
390
|
+
await this.client_.deactivateCheckout(externalId);
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
// Ignore — checkout may already be in a terminal state
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Create a new checkout with the updated amount
|
|
397
|
+
const checkoutReference = input.data?.session_id ??
|
|
398
|
+
input.data?.checkout_reference ??
|
|
399
|
+
crypto.randomUUID();
|
|
400
|
+
const webhookUrl = this.options_.medusaUrl + "/hooks/payment/sumup_sumup";
|
|
401
|
+
const checkout = await this.client_.createCheckout({
|
|
402
|
+
checkout_reference: checkoutReference,
|
|
403
|
+
amount,
|
|
404
|
+
currency,
|
|
405
|
+
merchant_code: this.options_.merchantCode,
|
|
406
|
+
description: input.data?.description ||
|
|
407
|
+
"SumUp payment via Medusa",
|
|
408
|
+
return_url: webhookUrl,
|
|
409
|
+
redirect_url: this.options_.redirectUrl,
|
|
410
|
+
});
|
|
411
|
+
this.debug_ &&
|
|
412
|
+
this.logger_.info(`SumUp checkout updated: old=${externalId} → new=${checkout.id} (amount: ${amount} ${currency})`);
|
|
413
|
+
return {
|
|
414
|
+
data: {
|
|
415
|
+
id: checkout.id,
|
|
416
|
+
checkout_reference: checkoutReference,
|
|
417
|
+
idempotency_key: input.context?.idempotency_key,
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
this.logger_.error(`Error in ${ctx} for ${externalId}: ${error.message}`);
|
|
423
|
+
this.rethrowAsMedusaError(error, ctx);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Processes SumUp webhook events.
|
|
428
|
+
* SumUp does NOT sign webhooks — we always verify by calling the API.
|
|
429
|
+
*/
|
|
430
|
+
async getWebhookActionAndData(payload) {
|
|
431
|
+
const ctx = "getWebhookActionAndData";
|
|
432
|
+
const { data } = payload;
|
|
433
|
+
// Validate that the payload carries a checkout ID before hitting the API.
|
|
434
|
+
const checkoutId = data?.id;
|
|
435
|
+
if (!checkoutId || typeof checkoutId !== "string") {
|
|
436
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "SumUp webhook payload is missing a checkout ID.");
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
// Always re-verify via the SumUp API — never trust the webhook body alone.
|
|
440
|
+
const checkout = await this.client_.getCheckout(checkoutId);
|
|
441
|
+
const session_id = checkout.checkout_reference;
|
|
442
|
+
const amount = new utils_1.BigNumber(checkout.amount);
|
|
443
|
+
const transaction = checkout.transactions?.[0];
|
|
444
|
+
const baseData = {
|
|
445
|
+
session_id,
|
|
446
|
+
amount,
|
|
447
|
+
transaction_id: transaction?.id,
|
|
448
|
+
transaction_code: transaction?.transaction_code,
|
|
449
|
+
};
|
|
450
|
+
switch (checkout.status) {
|
|
451
|
+
case "PAID":
|
|
452
|
+
return {
|
|
453
|
+
action: utils_1.PaymentActions.SUCCESSFUL,
|
|
454
|
+
data: baseData,
|
|
455
|
+
};
|
|
456
|
+
case "FAILED":
|
|
457
|
+
return {
|
|
458
|
+
action: utils_1.PaymentActions.FAILED,
|
|
459
|
+
data: baseData,
|
|
460
|
+
};
|
|
461
|
+
case "EXPIRED":
|
|
462
|
+
return {
|
|
463
|
+
action: utils_1.PaymentActions.CANCELED,
|
|
464
|
+
data: baseData,
|
|
465
|
+
};
|
|
466
|
+
case "PENDING":
|
|
467
|
+
return {
|
|
468
|
+
action: utils_1.PaymentActions.PENDING,
|
|
469
|
+
data: baseData,
|
|
470
|
+
};
|
|
471
|
+
default:
|
|
472
|
+
this.logger_.warn(`SumUp webhook: checkout ${checkoutId} has unknown status "${checkout.status}"`);
|
|
473
|
+
return {
|
|
474
|
+
action: utils_1.PaymentActions.NOT_SUPPORTED,
|
|
475
|
+
data: baseData,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
this.logger_.error(`Error in ${ctx} for checkout ${checkoutId}: ${error.message}`);
|
|
481
|
+
// Attempt graceful degradation: if we can classify the error (e.g. auth
|
|
482
|
+
// failure), surface it. Otherwise re-throw so Medusa can retry.
|
|
483
|
+
this.rethrowAsMedusaError(error, ctx);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
SumUpProviderService.identifier = "sumup";
|
|
488
|
+
exports.default = SumUpProviderService;
|
|
489
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3VtdXAtcHJvdmlkZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvcHJvdmlkZXJzL3N1bXVwL2NvcmUvc3VtdXAtcHJvdmlkZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFLQSxxREFNbUM7QUFxQm5DLGlEQUE2QztBQUM3QyxvQ0FBNEM7QUFPNUMsMENBQTBDO0FBQzFDLE1BQU0sV0FBVyxHQUFHLFlBQVksQ0FBQztBQUVqQyxNQUFNLGdCQUFnQixHQUFzRDtJQUMxRSxPQUFPLEVBQUUsNEJBQW9CLENBQUMsT0FBTztJQUNyQyxJQUFJLEVBQUUsNEJBQW9CLENBQUMsUUFBUTtJQUNuQyxNQUFNLEVBQUUsNEJBQW9CLENBQUMsS0FBSztJQUNsQyxPQUFPLEVBQUUsNEJBQW9CLENBQUMsUUFBUTtDQUN2QyxDQUFDO0FBRUYsTUFBTSxvQkFBcUIsU0FBUSwrQkFBdUI7SUFReEQsTUFBTSxDQUFDLGVBQWUsQ0FBQyxPQUFnQztRQUNyRCxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sSUFBSSxPQUFPLE9BQU8sQ0FBQyxNQUFNLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDMUQsTUFBTSxJQUFJLG1CQUFXLENBQ25CLG1CQUFXLENBQUMsS0FBSyxDQUFDLFlBQVksRUFDOUIscUNBQXFDLENBQ3RDLENBQUM7UUFDSixDQUFDO1FBQ0QsSUFBSSxDQUFDLE9BQU8sQ0FBQyxZQUFZLElBQUksT0FBTyxPQUFPLENBQUMsWUFBWSxLQUFLLFFBQVEsRUFBRSxDQUFDO1lBQ3RFLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLGlEQUFpRCxDQUNsRCxDQUFDO1FBQ0osQ0FBQztRQUNELElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxJQUFJLE9BQU8sT0FBTyxDQUFDLFNBQVMsS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUNoRSxNQUFNLElBQUksbUJBQVcsQ0FDbkIsbUJB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
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
4
|
+
const services_1 = require("./services");
|
|
5
|
+
const services = [services_1.SumUpProviderService];
|
|
6
|
+
exports.default = (0, utils_1.ModuleProvider)(utils_1.Modules.PAYMENT, {
|
|
7
|
+
services,
|
|
8
|
+
});
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvcHJvdmlkZXJzL3N1bXVwL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEscURBQW9FO0FBQ3BFLHlDQUFrRDtBQUVsRCxNQUFNLFFBQVEsR0FBRyxDQUFDLCtCQUFvQixDQUFDLENBQUM7QUFFeEMsa0JBQWUsSUFBQSxzQkFBYyxFQUFDLGVBQU8sQ0FBQyxPQUFPLEVBQUU7SUFDN0MsUUFBUTtDQUNULENBQUMsQ0FBQyJ9
|
|
@@ -0,0 +1,9 @@
|
|
|
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.SumUpProviderService = void 0;
|
|
7
|
+
var sumup_provider_1 = require("../core/sumup-provider");
|
|
8
|
+
Object.defineProperty(exports, "SumUpProviderService", { enumerable: true, get: function () { return __importDefault(sumup_provider_1).default; } });
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvcHJvdmlkZXJzL3N1bXVwL3NlcnZpY2VzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUFBLHlEQUF5RTtBQUFoRSx1SUFBQSxPQUFPLE9BQXdCIn0=
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SumUpClientError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Typed error thrown by SumUpClient for all non-OK HTTP responses and
|
|
6
|
+
* network-level failures. Callers can inspect `.type` to decide how to
|
|
7
|
+
* handle (surface to user, retry, fall through, etc.).
|
|
8
|
+
*/
|
|
9
|
+
class SumUpClientError extends Error {
|
|
10
|
+
constructor(message, type, statusCode) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "SumUpClientError";
|
|
13
|
+
this.type = type;
|
|
14
|
+
this.statusCode = statusCode;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.SumUpClientError = SumUpClientError;
|
|
18
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvcHJvdmlkZXJzL3N1bXVwL3R5cGVzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQTJHQTs7OztHQUlHO0FBQ0gsTUFBYSxnQkFBaUIsU0FBUSxLQUFLO0lBSXpDLFlBQ0UsT0FBZSxFQUNmLElBQW9CLEVBQ3BCLFVBQW1CO1FBRW5CLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNmLElBQUksQ0FBQyxJQUFJLEdBQUcsa0JBQWtCLENBQUM7UUFDL0IsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7UUFDakIsSUFBSSxDQUFDLFVBQVUsR0FBRyxVQUFVLENBQUM7SUFDL0IsQ0FBQztDQUNGO0FBZEQsNENBY0MifQ==
|
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# SumUp Payments for Medusa
|
|
2
|
+
|
|
3
|
+
A SumUp payment provider plugin for [Medusa](https://medusajs.com/) V2.
|
|
4
|
+
Inspired by https://github.com/VariableVic/mollie-payments-medusa and mostly implemented by Claude.
|
|
5
|
+
|
|
6
|
+
> [!WARNING]
|
|
7
|
+
> This plugin has not been tested on a live store. Please conduct thorough testing before using it in a production environment. I am not responsible for any missed or failed payments resulting from the use of this plugin. If you encounter any issues, please report them [here](https://github.com/meister-eder/medusa-payment-sumup/issues).
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Features](#features)
|
|
12
|
+
- [Prerequisites](#prerequisites)
|
|
13
|
+
- [Installation](#installation)
|
|
14
|
+
- [Configuration](#configuration)
|
|
15
|
+
- [Configuration Options](#configuration-options)
|
|
16
|
+
- [Environment Variables](#environment-variables)
|
|
17
|
+
- [Usage](#usage)
|
|
18
|
+
- [Client-Side Integration](#client-side-integration)
|
|
19
|
+
- [License](#license)
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **SumUp Checkout**: Creates a hosted SumUp checkout session that customers complete via the SumUp Card Widget or a custom card form.
|
|
24
|
+
- **Webhook Support**: Receives real-time checkout status updates via SumUp webhooks.
|
|
25
|
+
- **Refunds**: Supports full and partial refunds via the SumUp Transactions API.
|
|
26
|
+
- **No extra dependencies**: Uses the native Node.js `fetch` API — no additional HTTP client needed.
|
|
27
|
+
|
|
28
|
+
## Prerequisites
|
|
29
|
+
|
|
30
|
+
- Medusa server v2.3.0 or later
|
|
31
|
+
- Node.js v20 or later
|
|
32
|
+
- A [SumUp](https://sumup.com/) merchant account with a valid API key and merchant code
|
|
33
|
+
|
|
34
|
+
> [!NOTE]
|
|
35
|
+
> You can find your API key and merchant code in your SumUp Dashboard under **Settings → Developer → API Keys**.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install medusa-payment-sumup
|
|
41
|
+
# or
|
|
42
|
+
yarn add medusa-payment-sumup
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
Add the provider to the `@medusajs/payment` module in your `medusa-config.ts` file:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
modules: [
|
|
51
|
+
// ... other modules
|
|
52
|
+
{
|
|
53
|
+
resolve: "@medusajs/payment",
|
|
54
|
+
options: {
|
|
55
|
+
providers: [
|
|
56
|
+
// ... other providers
|
|
57
|
+
{
|
|
58
|
+
resolve: "medusa-payment-sumup/providers/sumup",
|
|
59
|
+
id: "sumup",
|
|
60
|
+
options: {
|
|
61
|
+
apiKey: process.env.SUMUP_API_KEY,
|
|
62
|
+
merchantCode: process.env.SUMUP_MERCHANT_CODE,
|
|
63
|
+
medusaUrl: process.env.MEDUSA_BACKEND_URL,
|
|
64
|
+
redirectUrl: process.env.SUMUP_REDIRECT_URL,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Configuration Options
|
|
74
|
+
|
|
75
|
+
| Option | Description | Required | Default |
|
|
76
|
+
|---|---|---|---|
|
|
77
|
+
| `apiKey` | Your SumUp API key (e.g. `sup_sk_...`) | ✅ | — |
|
|
78
|
+
| `merchantCode` | Your SumUp merchant code (e.g. `MH4H92C7`) | ✅ | — |
|
|
79
|
+
| `medusaUrl` | The public URL of your Medusa backend | ✅ | — |
|
|
80
|
+
| `redirectUrl` | The storefront URL to redirect the customer to after payment | ✅ | — |
|
|
81
|
+
| `debug` | Enable verbose debug logging | ❌ | `false` |
|
|
82
|
+
|
|
83
|
+
### Environment Variables
|
|
84
|
+
|
|
85
|
+
Add the following to your `.env` file:
|
|
86
|
+
|
|
87
|
+
```env
|
|
88
|
+
SUMUP_API_KEY=sup_sk_your_api_key
|
|
89
|
+
SUMUP_MERCHANT_CODE=MH4H92C7
|
|
90
|
+
MEDUSA_BACKEND_URL=https://your-medusa-server.com
|
|
91
|
+
SUMUP_REDIRECT_URL=https://your-store.com/checkout/payment
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Usage
|
|
95
|
+
|
|
96
|
+
Once installed and configured, the SumUp payment method will be available in your Medusa Admin. To enable it:
|
|
97
|
+
|
|
98
|
+
1. Log in to your Medusa Admin
|
|
99
|
+
2. Go to **Settings → Regions**
|
|
100
|
+
3. Add or edit a region and select **SumUp** from the payment providers dropdown
|
|
101
|
+
|
|
102
|
+
The provider ID for the SumUp checkout is:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
pp_sumup_sumup
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Client-Side Integration
|
|
109
|
+
|
|
110
|
+
SumUp checkouts are created server-side by Medusa. After a payment session is initiated, the session data will contain a SumUp checkout `id` that you pass to the [SumUp Card Widget](https://developer.sumup.com/online-payments/tools/card-widget/) on your storefront to render the payment form.
|
|
111
|
+
|
|
112
|
+
Basic flow:
|
|
113
|
+
|
|
114
|
+
1. Customer reaches the payment step — Medusa creates a SumUp checkout via `initiatePayment`
|
|
115
|
+
2. Your storefront receives the checkout `id` from the payment session data
|
|
116
|
+
3. Render the SumUp Card Widget with the checkout ID
|
|
117
|
+
4. SumUp processes the payment and calls the Medusa webhook (`/hooks/payment/sumup_sumup`)
|
|
118
|
+
5. Medusa authorizes and captures the payment automatically
|
|
119
|
+
|
|
120
|
+
Example Card Widget integration:
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<script src="https://gateway.sumup.com/gateway/ecom/card/v2/sdk.js"></script>
|
|
124
|
+
<div id="sumup-card"></div>
|
|
125
|
+
|
|
126
|
+
<script>
|
|
127
|
+
SumUpCard.mount({
|
|
128
|
+
checkoutId: "<checkout-id-from-session-data>",
|
|
129
|
+
onResponse: function (type, body) {
|
|
130
|
+
if (type === "success") {
|
|
131
|
+
// Redirect or poll for order completion
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
</script>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
For APMs (iDEAL, Bancontact, etc.), the `redirectUrl` option handles the post-payment redirect automatically.
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "medusa-payment-sumup",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SumUp payment provider for Medusa v2",
|
|
5
|
+
"author": "Tim Eder <timeder92@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"files": [
|
|
8
|
+
".medusa/server"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
"./package.json": "./package.json",
|
|
12
|
+
"./providers/*": "./.medusa/server/src/providers/*/index.js",
|
|
13
|
+
"./*": "./.medusa/server/src/*.js"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"medusa-plugin-integration",
|
|
20
|
+
"medusa-v2",
|
|
21
|
+
"medusa-plugin-payment",
|
|
22
|
+
"sumup",
|
|
23
|
+
"payment",
|
|
24
|
+
"payment-provider",
|
|
25
|
+
"ecommerce"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/meister-eder/medusa-payment-sumup.git"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "medusa plugin:build",
|
|
33
|
+
"dev": "medusa plugin:develop",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest",
|
|
36
|
+
"prepublishOnly": "medusa plugin:build"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@medusajs/cli": "^2.5.0",
|
|
41
|
+
"@medusajs/framework": "^2.5.0",
|
|
42
|
+
"@medusajs/medusa": "^2.5.0",
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"typescript": "^5.6.0",
|
|
45
|
+
"vitest": "^3.0.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@medusajs/framework": "^2.5.0",
|
|
49
|
+
"@medusajs/medusa": "^2.5.0"
|
|
50
|
+
}
|
|
51
|
+
}
|