@crediblemark/buayar 0.8.6 â 0.8.7
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/README.md +11 -7
- package/dist/cli/index.js +7 -3
- package/dist/index.d.mts +97 -3
- package/dist/index.d.ts +97 -3
- package/dist/index.js +409 -6
- package/dist/index.mjs +402 -6
- package/docs/guide.md +6 -2
- package/docs/sumopod.md +359 -0
- package/package.json +5 -4
package/dist/index.mjs
CHANGED
|
@@ -291,6 +291,8 @@ var CANONICAL_TO_OY = {
|
|
|
291
291
|
var CANONICAL_TO_STRIPE = {
|
|
292
292
|
credit_card: "card",
|
|
293
293
|
qris: "qris",
|
|
294
|
+
QRIS_SUMOPOD: "qris",
|
|
295
|
+
qris_sumopod: "qris",
|
|
294
296
|
gopay_qris: "qris",
|
|
295
297
|
shopeepay_qris: "qris",
|
|
296
298
|
bca_va: "customer_balance",
|
|
@@ -397,6 +399,25 @@ function toStripePaymentMethod(code) {
|
|
|
397
399
|
}
|
|
398
400
|
return lower;
|
|
399
401
|
}
|
|
402
|
+
var CANONICAL_TO_SUMOPOD = {
|
|
403
|
+
qris: "QRIS",
|
|
404
|
+
gopay_qris: "QRIS",
|
|
405
|
+
shopeepay_qris: "QRIS",
|
|
406
|
+
nobu_qris: "QRIS",
|
|
407
|
+
qris_sumopod: "QRIS"
|
|
408
|
+
};
|
|
409
|
+
var SUMOPOD_TO_CANONICAL = {
|
|
410
|
+
QRIS: "qris",
|
|
411
|
+
qris: "qris"
|
|
412
|
+
};
|
|
413
|
+
function toSumopodPaymentMethod(code) {
|
|
414
|
+
if (!code) return void 0;
|
|
415
|
+
const lower = code.toLowerCase().trim();
|
|
416
|
+
if (CANONICAL_TO_SUMOPOD[lower]) {
|
|
417
|
+
return CANONICAL_TO_SUMOPOD[lower];
|
|
418
|
+
}
|
|
419
|
+
return code;
|
|
420
|
+
}
|
|
400
421
|
function toCanonicalPaymentMethod(provider, code) {
|
|
401
422
|
if (!code) return "";
|
|
402
423
|
const upper = code.toUpperCase().trim();
|
|
@@ -404,7 +425,10 @@ function toCanonicalPaymentMethod(provider, code) {
|
|
|
404
425
|
if (provider.toLowerCase() === "duitku" && DUITKU_TO_CANONICAL[upper]) {
|
|
405
426
|
return DUITKU_TO_CANONICAL[upper];
|
|
406
427
|
}
|
|
407
|
-
if (
|
|
428
|
+
if (provider.toLowerCase() === "sumopod" && SUMOPOD_TO_CANONICAL[upper]) {
|
|
429
|
+
return SUMOPOD_TO_CANONICAL[upper];
|
|
430
|
+
}
|
|
431
|
+
if (CANONICAL_TO_MIDTRANS[lower] || CANONICAL_TO_DUITKU[lower] || CANONICAL_TO_IPAYMU[lower] || CANONICAL_TO_XENDIT[lower] || CANONICAL_TO_DOKU[lower] || CANONICAL_TO_PRISMALINK[lower] || CANONICAL_TO_FASPAY[lower] || CANONICAL_TO_FINPAY[lower] || CANONICAL_TO_NICEPAY[lower] || CANONICAL_TO_OY[lower] || CANONICAL_TO_STRIPE[lower] || CANONICAL_TO_SUMOPOD[lower]) {
|
|
408
432
|
return lower;
|
|
409
433
|
}
|
|
410
434
|
return lower;
|
|
@@ -8017,6 +8041,285 @@ var TwoCheckoutProvider = class extends BasePaymentProvider {
|
|
|
8017
8041
|
}
|
|
8018
8042
|
};
|
|
8019
8043
|
|
|
8044
|
+
// src/providers/sumopod/signature.ts
|
|
8045
|
+
import crypto6 from "crypto";
|
|
8046
|
+
function verifySumopodSvixSignature(secret, svixId, svixTimestamp, svixSignature, rawBody, toleranceSeconds = 300) {
|
|
8047
|
+
if (!secret || !svixId || !svixTimestamp || !svixSignature || rawBody === void 0 || rawBody === null) {
|
|
8048
|
+
return false;
|
|
8049
|
+
}
|
|
8050
|
+
if (toleranceSeconds > 0) {
|
|
8051
|
+
const timestampSec = Number(svixTimestamp);
|
|
8052
|
+
if (isNaN(timestampSec)) {
|
|
8053
|
+
return false;
|
|
8054
|
+
}
|
|
8055
|
+
const currentSec = Math.floor(Date.now() / 1e3);
|
|
8056
|
+
if (Math.abs(currentSec - timestampSec) > toleranceSeconds) {
|
|
8057
|
+
return false;
|
|
8058
|
+
}
|
|
8059
|
+
}
|
|
8060
|
+
try {
|
|
8061
|
+
const cleanSecret = secret.startsWith("whsec_") ? secret.slice(6) : secret;
|
|
8062
|
+
const secretBytes = Buffer.from(cleanSecret, "base64");
|
|
8063
|
+
const signedContent = `${svixId}.${svixTimestamp}.${rawBody}`;
|
|
8064
|
+
const expectedSignature = crypto6.createHmac("sha256", secretBytes).update(signedContent).digest("base64");
|
|
8065
|
+
const signatures = svixSignature.split(" ").map((s) => s.includes(",") ? s.split(",")[1] : s).filter(Boolean);
|
|
8066
|
+
return signatures.some((sig) => safeCompare(sig, expectedSignature));
|
|
8067
|
+
} catch {
|
|
8068
|
+
return false;
|
|
8069
|
+
}
|
|
8070
|
+
}
|
|
8071
|
+
function verifySumopodToken(expectedToken, receivedToken) {
|
|
8072
|
+
if (!expectedToken || !receivedToken) return false;
|
|
8073
|
+
return safeCompare(expectedToken.trim(), receivedToken.trim());
|
|
8074
|
+
}
|
|
8075
|
+
|
|
8076
|
+
// src/providers/sumopod/provider.ts
|
|
8077
|
+
var SumopodProvider = class extends BasePaymentProvider {
|
|
8078
|
+
name = "sumopod";
|
|
8079
|
+
getBaseUrl(config) {
|
|
8080
|
+
if (config?.customBaseUrl) return config.customBaseUrl;
|
|
8081
|
+
if (config?.sandbox) return "https://api-pay-sandbox.sumopod.com";
|
|
8082
|
+
return "https://api-pay.sumopod.com";
|
|
8083
|
+
}
|
|
8084
|
+
async createInvoice(params, config) {
|
|
8085
|
+
const { orderId, amount, returnUrl } = params;
|
|
8086
|
+
const apiKey = config.apiKey || config.serverKey || config.secretKey || "";
|
|
8087
|
+
const baseUrl = this.getBaseUrl(config);
|
|
8088
|
+
const integerAmount = Math.round(amount);
|
|
8089
|
+
const payload = {
|
|
8090
|
+
order_id: orderId,
|
|
8091
|
+
amount: integerAmount,
|
|
8092
|
+
currency: (params.currency || "IDR").toUpperCase(),
|
|
8093
|
+
expires_in_hours: params.providerParams?.expires_in_hours ?? 24,
|
|
8094
|
+
...returnUrl || config.returnUrl ? { success_return_url: returnUrl || config.returnUrl } : {},
|
|
8095
|
+
...params.providerParams?.cancel_return_url || config.returnUrl || returnUrl ? { cancel_return_url: params.providerParams?.cancel_return_url || config.returnUrl || returnUrl } : {},
|
|
8096
|
+
payment_method_type_code: "QRIS",
|
|
8097
|
+
...params.providerParams
|
|
8098
|
+
};
|
|
8099
|
+
try {
|
|
8100
|
+
const response = await fetch(`${baseUrl}/api/v1/payments`, {
|
|
8101
|
+
method: "POST",
|
|
8102
|
+
headers: {
|
|
8103
|
+
"Content-Type": "application/json",
|
|
8104
|
+
"Accept": "application/json",
|
|
8105
|
+
"X-Api-Key": apiKey
|
|
8106
|
+
},
|
|
8107
|
+
body: JSON.stringify(payload)
|
|
8108
|
+
});
|
|
8109
|
+
const text = await response.text();
|
|
8110
|
+
let data = null;
|
|
8111
|
+
try {
|
|
8112
|
+
data = JSON.parse(text);
|
|
8113
|
+
} catch {
|
|
8114
|
+
}
|
|
8115
|
+
if (!response.ok || !data || data.error) {
|
|
8116
|
+
return {
|
|
8117
|
+
success: false,
|
|
8118
|
+
provider: "sumopod",
|
|
8119
|
+
orderId,
|
|
8120
|
+
amount: integerAmount,
|
|
8121
|
+
rawResponse: data,
|
|
8122
|
+
error: data?.message || data?.error || `HTTP error! Status: ${response.status} - ${text}`
|
|
8123
|
+
};
|
|
8124
|
+
}
|
|
8125
|
+
const isQris = data.payment_channel_used?.toLowerCase()?.includes("qris") || data.payment_code_type === "QRIS" || data.payment_code_type === "QR_TEXT" || !data.payment_code_type;
|
|
8126
|
+
const isVa = data.payment_code_type === "ACCOUNT_NUMBER";
|
|
8127
|
+
return {
|
|
8128
|
+
success: true,
|
|
8129
|
+
provider: "sumopod",
|
|
8130
|
+
orderId: data.order_id || orderId,
|
|
8131
|
+
amount: data.amount ? Number(data.amount) : integerAmount,
|
|
8132
|
+
reference: data.payment_id,
|
|
8133
|
+
paymentUrl: data.payment_link_url,
|
|
8134
|
+
paymentCode: data.payment_code,
|
|
8135
|
+
qrString: isQris ? data.payment_code || data.payment_link_url : void 0,
|
|
8136
|
+
qrCodeUrl: data.payment_code_type === "QR_IMAGE" ? data.payment_code : void 0,
|
|
8137
|
+
vaNumber: isVa ? data.payment_code : void 0,
|
|
8138
|
+
vaBank: isVa ? data.payment_channel_used?.split(".")[0]?.toLowerCase() : void 0,
|
|
8139
|
+
expiresAt: data.expires_at ? new Date(data.expires_at) : void 0,
|
|
8140
|
+
mode: isQris ? "qris" : isVa ? "va" : "checkout",
|
|
8141
|
+
rawResponse: data
|
|
8142
|
+
};
|
|
8143
|
+
} catch (e) {
|
|
8144
|
+
return {
|
|
8145
|
+
success: false,
|
|
8146
|
+
provider: "sumopod",
|
|
8147
|
+
orderId,
|
|
8148
|
+
amount: integerAmount,
|
|
8149
|
+
rawResponse: null,
|
|
8150
|
+
error: e.message || "Failed to make request to SumoPod API"
|
|
8151
|
+
};
|
|
8152
|
+
}
|
|
8153
|
+
}
|
|
8154
|
+
async verifyCallback(body, config) {
|
|
8155
|
+
const rawBody = typeof body === "string" ? body : JSON.stringify(body);
|
|
8156
|
+
let parsedBody;
|
|
8157
|
+
try {
|
|
8158
|
+
parsedBody = typeof body === "string" ? JSON.parse(body) : body;
|
|
8159
|
+
} catch {
|
|
8160
|
+
parsedBody = {};
|
|
8161
|
+
}
|
|
8162
|
+
const headers = config.extra?.headers || {};
|
|
8163
|
+
const getHeader = (name) => {
|
|
8164
|
+
const lower = name.toLowerCase();
|
|
8165
|
+
for (const k of Object.keys(headers)) {
|
|
8166
|
+
if (k.toLowerCase() === lower) {
|
|
8167
|
+
const val = headers[k];
|
|
8168
|
+
return Array.isArray(val) ? val[0] : val;
|
|
8169
|
+
}
|
|
8170
|
+
}
|
|
8171
|
+
return void 0;
|
|
8172
|
+
};
|
|
8173
|
+
const svixId = getHeader("svix-id") || config.extra?.svixId;
|
|
8174
|
+
const svixTimestamp = getHeader("svix-timestamp") || config.extra?.svixTimestamp;
|
|
8175
|
+
const svixSignature = getHeader("svix-signature") || config.extra?.svixSignature || config.extra?.signatureHeader;
|
|
8176
|
+
const receivedToken = getHeader("x-webhook-token") || config.extra?.webhookTokenHeader;
|
|
8177
|
+
const webhookSecret = config.extra?.webhookSecret || config.secretKey || "";
|
|
8178
|
+
const webhookToken = config.extra?.webhookToken || config.extra?.callbackToken || "";
|
|
8179
|
+
let isValid = false;
|
|
8180
|
+
if (svixId && svixTimestamp && svixSignature && webhookSecret) {
|
|
8181
|
+
isValid = verifySumopodSvixSignature(webhookSecret, svixId, svixTimestamp, svixSignature, rawBody);
|
|
8182
|
+
} else if (receivedToken && webhookToken) {
|
|
8183
|
+
isValid = verifySumopodToken(webhookToken, receivedToken);
|
|
8184
|
+
} else if (!webhookSecret && !webhookToken) {
|
|
8185
|
+
isValid = true;
|
|
8186
|
+
}
|
|
8187
|
+
const eventType = parsedBody?.event_type || "";
|
|
8188
|
+
const data = parsedBody?.data || parsedBody;
|
|
8189
|
+
const orderId = String(data?.order_id || "");
|
|
8190
|
+
const amount = Number(data?.amount || 0);
|
|
8191
|
+
const statusRaw = (data?.status || "").toLowerCase();
|
|
8192
|
+
const isPaid = eventType === "payment.completed" || statusRaw === "completed";
|
|
8193
|
+
const isFailed = eventType === "payment.failed" || statusRaw === "failed";
|
|
8194
|
+
const isExpired = eventType === "payment.expired" || statusRaw === "expired";
|
|
8195
|
+
const isPending = !isPaid && !isFailed && !isExpired;
|
|
8196
|
+
const status = isPaid ? "paid" : isExpired ? "expired" : isFailed ? "failed" : "pending";
|
|
8197
|
+
return {
|
|
8198
|
+
isValid,
|
|
8199
|
+
provider: "sumopod",
|
|
8200
|
+
orderId,
|
|
8201
|
+
amount,
|
|
8202
|
+
status,
|
|
8203
|
+
isPaid,
|
|
8204
|
+
isPending,
|
|
8205
|
+
isFailed,
|
|
8206
|
+
isExpired,
|
|
8207
|
+
statusCode: eventType || statusRaw,
|
|
8208
|
+
transactionTime: data?.completed_at ? new Date(data.completed_at) : void 0,
|
|
8209
|
+
rawPayload: parsedBody
|
|
8210
|
+
};
|
|
8211
|
+
}
|
|
8212
|
+
async getPaymentMethods(params, config) {
|
|
8213
|
+
const amount = params?.amount || 1e4;
|
|
8214
|
+
const percentFee = Math.round(amount * 7e-3);
|
|
8215
|
+
const flatFee = 300;
|
|
8216
|
+
const totalFeeNum = percentFee + flatFee;
|
|
8217
|
+
const methods = [
|
|
8218
|
+
{
|
|
8219
|
+
paymentMethod: "qris",
|
|
8220
|
+
code: "QRIS",
|
|
8221
|
+
paymentName: "QRIS",
|
|
8222
|
+
paymentImage: "https://upload.wikimedia.org/wikipedia/commons/a/a2/Logo_QRIS.svg",
|
|
8223
|
+
totalFee: "0.7% + Rp 300",
|
|
8224
|
+
category: "QRIS",
|
|
8225
|
+
feeDetail: {
|
|
8226
|
+
percent: 0.7,
|
|
8227
|
+
flat: 300,
|
|
8228
|
+
totalFee: totalFeeNum
|
|
8229
|
+
}
|
|
8230
|
+
}
|
|
8231
|
+
];
|
|
8232
|
+
const categories = {
|
|
8233
|
+
QRIS: methods
|
|
8234
|
+
};
|
|
8235
|
+
return {
|
|
8236
|
+
success: true,
|
|
8237
|
+
provider: "sumopod",
|
|
8238
|
+
methods,
|
|
8239
|
+
categories,
|
|
8240
|
+
rawResponse: methods
|
|
8241
|
+
};
|
|
8242
|
+
}
|
|
8243
|
+
async checkTransaction(params, config) {
|
|
8244
|
+
const { merchantOrderId } = params;
|
|
8245
|
+
const apiKey = config.apiKey || config.serverKey || config.secretKey || "";
|
|
8246
|
+
const baseUrl = this.getBaseUrl(config);
|
|
8247
|
+
try {
|
|
8248
|
+
const response = await fetch(`${baseUrl}/api/v1/payments/${encodeURIComponent(merchantOrderId)}`, {
|
|
8249
|
+
method: "GET",
|
|
8250
|
+
headers: {
|
|
8251
|
+
"Accept": "application/json",
|
|
8252
|
+
"X-Api-Key": apiKey
|
|
8253
|
+
}
|
|
8254
|
+
});
|
|
8255
|
+
const text = await response.text();
|
|
8256
|
+
let data = null;
|
|
8257
|
+
try {
|
|
8258
|
+
data = JSON.parse(text);
|
|
8259
|
+
} catch {
|
|
8260
|
+
}
|
|
8261
|
+
if (!response.ok || !data || data.error) {
|
|
8262
|
+
return {
|
|
8263
|
+
success: false,
|
|
8264
|
+
provider: "sumopod",
|
|
8265
|
+
orderId: merchantOrderId,
|
|
8266
|
+
reference: merchantOrderId,
|
|
8267
|
+
amount: 0,
|
|
8268
|
+
statusCode: response.status.toString(),
|
|
8269
|
+
status: "failed",
|
|
8270
|
+
isPaid: false,
|
|
8271
|
+
isPending: false,
|
|
8272
|
+
isFailed: true,
|
|
8273
|
+
isExpired: false,
|
|
8274
|
+
statusMessage: data?.message || data?.error || `HTTP error! Status: ${response.status}`,
|
|
8275
|
+
error: data?.message || data?.error,
|
|
8276
|
+
rawResponse: data
|
|
8277
|
+
};
|
|
8278
|
+
}
|
|
8279
|
+
const statusRaw = (data.status || "").toLowerCase();
|
|
8280
|
+
const isPaid = statusRaw === "completed" || statusRaw === "paid";
|
|
8281
|
+
const isFailed = statusRaw === "failed";
|
|
8282
|
+
const isExpired = statusRaw === "expired";
|
|
8283
|
+
const isPending = !isPaid && !isFailed && !isExpired;
|
|
8284
|
+
const status = isPaid ? "paid" : isExpired ? "expired" : isFailed ? "failed" : "pending";
|
|
8285
|
+
return {
|
|
8286
|
+
success: true,
|
|
8287
|
+
provider: "sumopod",
|
|
8288
|
+
orderId: data.order_id || merchantOrderId,
|
|
8289
|
+
reference: data.payment_id || merchantOrderId,
|
|
8290
|
+
amount: Number(data.amount || 0),
|
|
8291
|
+
statusCode: statusRaw,
|
|
8292
|
+
status,
|
|
8293
|
+
isPaid,
|
|
8294
|
+
isPending,
|
|
8295
|
+
isFailed,
|
|
8296
|
+
isExpired,
|
|
8297
|
+
statusMessage: statusRaw,
|
|
8298
|
+
paymentType: data.payment_method || "qris",
|
|
8299
|
+
transactionTime: data.completed_at ? new Date(data.completed_at) : void 0,
|
|
8300
|
+
rawResponse: data
|
|
8301
|
+
};
|
|
8302
|
+
} catch (e) {
|
|
8303
|
+
return {
|
|
8304
|
+
success: false,
|
|
8305
|
+
provider: "sumopod",
|
|
8306
|
+
orderId: merchantOrderId,
|
|
8307
|
+
reference: merchantOrderId,
|
|
8308
|
+
amount: 0,
|
|
8309
|
+
statusCode: "ERROR",
|
|
8310
|
+
status: "failed",
|
|
8311
|
+
isPaid: false,
|
|
8312
|
+
isPending: false,
|
|
8313
|
+
isFailed: true,
|
|
8314
|
+
isExpired: false,
|
|
8315
|
+
statusMessage: e.message,
|
|
8316
|
+
error: e.message,
|
|
8317
|
+
rawResponse: null
|
|
8318
|
+
};
|
|
8319
|
+
}
|
|
8320
|
+
}
|
|
8321
|
+
};
|
|
8322
|
+
|
|
8020
8323
|
// src/clients/duitku.ts
|
|
8021
8324
|
var DuitkuClient = class {
|
|
8022
8325
|
merchantCode;
|
|
@@ -9440,6 +9743,63 @@ var TwoCheckoutClient = class {
|
|
|
9440
9743
|
}
|
|
9441
9744
|
};
|
|
9442
9745
|
|
|
9746
|
+
// src/clients/sumopod.ts
|
|
9747
|
+
var SumopodClient = class {
|
|
9748
|
+
apiKey;
|
|
9749
|
+
baseUrl;
|
|
9750
|
+
constructor(config) {
|
|
9751
|
+
this.apiKey = config.apiKey || config.secretKey || config.serverKey || "";
|
|
9752
|
+
this.baseUrl = config.customBaseUrl || (config.sandbox ? "https://api-pay-sandbox.sumopod.com" : "https://api-pay.sumopod.com");
|
|
9753
|
+
}
|
|
9754
|
+
/**
|
|
9755
|
+
* HTTP request helper dengan header autentikasi X-Api-Key SumoPod
|
|
9756
|
+
*/
|
|
9757
|
+
async request(method, endpoint, body) {
|
|
9758
|
+
const url = endpoint.startsWith("http") ? endpoint : `${this.baseUrl}${endpoint}`;
|
|
9759
|
+
const headers = {
|
|
9760
|
+
"Content-Type": "application/json",
|
|
9761
|
+
"Accept": "application/json",
|
|
9762
|
+
"X-Api-Key": this.apiKey
|
|
9763
|
+
};
|
|
9764
|
+
const fetchOptions = {
|
|
9765
|
+
method,
|
|
9766
|
+
headers
|
|
9767
|
+
};
|
|
9768
|
+
if (method === "POST" && body) {
|
|
9769
|
+
fetchOptions.body = JSON.stringify(body);
|
|
9770
|
+
}
|
|
9771
|
+
const response = await fetch(url, fetchOptions);
|
|
9772
|
+
const text = await response.text();
|
|
9773
|
+
let data = null;
|
|
9774
|
+
try {
|
|
9775
|
+
data = JSON.parse(text);
|
|
9776
|
+
} catch {
|
|
9777
|
+
}
|
|
9778
|
+
if (!response.ok) {
|
|
9779
|
+
throw new Error(
|
|
9780
|
+
data?.message || data?.error || `HTTP error! Status: ${response.status} - ${text}`
|
|
9781
|
+
);
|
|
9782
|
+
}
|
|
9783
|
+
return data;
|
|
9784
|
+
}
|
|
9785
|
+
/**
|
|
9786
|
+
* Buat payment link / sesi pembayaran baru di SumoPod
|
|
9787
|
+
*/
|
|
9788
|
+
async createPayment(params) {
|
|
9789
|
+
return this.request("POST", "/api/v1/payments", {
|
|
9790
|
+
currency: "IDR",
|
|
9791
|
+
payment_method_type_code: "QRIS",
|
|
9792
|
+
...params
|
|
9793
|
+
});
|
|
9794
|
+
}
|
|
9795
|
+
/**
|
|
9796
|
+
* Ambil detail status pembayaran berdasarkan ID pembayaran
|
|
9797
|
+
*/
|
|
9798
|
+
async getPayment(paymentId) {
|
|
9799
|
+
return this.request("GET", `/api/v1/payments/${encodeURIComponent(paymentId)}`);
|
|
9800
|
+
}
|
|
9801
|
+
};
|
|
9802
|
+
|
|
9443
9803
|
// src/core/manager.ts
|
|
9444
9804
|
var PaymentManager = class {
|
|
9445
9805
|
providers = /* @__PURE__ */ new Map();
|
|
@@ -9463,6 +9823,7 @@ var PaymentManager = class {
|
|
|
9463
9823
|
this.registerProvider(new PayuProvider());
|
|
9464
9824
|
this.registerProvider(new BraintreeProvider());
|
|
9465
9825
|
this.registerProvider(new TwoCheckoutProvider());
|
|
9826
|
+
this.registerProvider(new SumopodProvider());
|
|
9466
9827
|
}
|
|
9467
9828
|
registerProvider(provider) {
|
|
9468
9829
|
this.providers.set(provider.name.toLowerCase(), provider);
|
|
@@ -9590,6 +9951,12 @@ var PaymentManager = class {
|
|
|
9590
9951
|
getTwoCheckoutClient(config) {
|
|
9591
9952
|
return new TwoCheckoutClient(config);
|
|
9592
9953
|
}
|
|
9954
|
+
getSumopodProvider() {
|
|
9955
|
+
return this.getProvider("sumopod");
|
|
9956
|
+
}
|
|
9957
|
+
getSumopodClient(config) {
|
|
9958
|
+
return new SumopodClient(config);
|
|
9959
|
+
}
|
|
9593
9960
|
// âââ Unified Operations ââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
9594
9961
|
async createInvoice(providerName, params, config) {
|
|
9595
9962
|
const provider = this.getProvider(providerName);
|
|
@@ -9942,6 +10309,8 @@ var ProviderRegistry = class {
|
|
|
9942
10309
|
if (h["x-oy-username"]) return "oy";
|
|
9943
10310
|
if (h["signature"] && (h["client-id"] || h["request-id"])) return "doku";
|
|
9944
10311
|
if (h["x-signature"] && (payload?.trx_id || payload?.via || payload?.sid)) return "ipaymu";
|
|
10312
|
+
if (h["svix-signature"] || h["svix-id"]) return "sumopod";
|
|
10313
|
+
if (h["x-webhook-token"]) return "sumopod";
|
|
9945
10314
|
}
|
|
9946
10315
|
if (!payload) return void 0;
|
|
9947
10316
|
const p = payload;
|
|
@@ -9956,6 +10325,7 @@ var ProviderRegistry = class {
|
|
|
9956
10325
|
if (p.merchant_id && p.order_id && p.signature) return "prismalink";
|
|
9957
10326
|
if (p.object === "event" || p.type && p.data?.object && p.api_version) return "stripe";
|
|
9958
10327
|
if (p.event && p.payload?.payment?.entity) return "razorpay";
|
|
10328
|
+
if (p.event_type && p.data?.payment_id) return "sumopod";
|
|
9959
10329
|
if (p.external_id && (p.status || p.paid_amount || p.payment_method || p.payment_channel || p.id) || p.event?.startsWith("payment.") || p.event?.startsWith("qr.") || p.data?.reference_id) {
|
|
9960
10330
|
return "xendit";
|
|
9961
10331
|
}
|
|
@@ -9988,7 +10358,8 @@ var ENV_KEYS = {
|
|
|
9988
10358
|
square: ["SQUARE_ACCESS_TOKEN", "SQUARE_LOCATION_ID", "SQUARE_WEBHOOK_SIGNATURE_KEY"],
|
|
9989
10359
|
payu: ["PAYU_POS_ID", "PAYU_MD5_KEY"],
|
|
9990
10360
|
braintree: ["BRAINTREE_MERCHANT_ID", "BRAINTREE_PUBLIC_KEY", "BRAINTREE_PRIVATE_KEY"],
|
|
9991
|
-
twocheckout: ["TWOCHECKOUT_MERCHANT_CODE", "TWOCHECKOUT_SECRET_KEY", "TWOCHECKOUT_SECRET_WORD"]
|
|
10361
|
+
twocheckout: ["TWOCHECKOUT_MERCHANT_CODE", "TWOCHECKOUT_SECRET_KEY", "TWOCHECKOUT_SECRET_WORD"],
|
|
10362
|
+
sumopod: ["SUMOPOD_API_KEY", "SUMOPOD_WEBHOOK_SECRET"]
|
|
9992
10363
|
};
|
|
9993
10364
|
var WORKING_METHODS = {
|
|
9994
10365
|
midtrans: Object.keys(CANONICAL_TO_MIDTRANS),
|
|
@@ -10002,6 +10373,7 @@ var WORKING_METHODS = {
|
|
|
10002
10373
|
nicepay: Object.keys(CANONICAL_TO_NICEPAY),
|
|
10003
10374
|
oy: Object.keys(CANONICAL_TO_OY),
|
|
10004
10375
|
stripe: Object.keys(CANONICAL_TO_STRIPE),
|
|
10376
|
+
sumopod: Object.keys(CANONICAL_TO_SUMOPOD),
|
|
10005
10377
|
paypal: ["credit_card", "paylater", "paypal", "bank_transfer"],
|
|
10006
10378
|
adyen: ["credit_card", "paypal", "qris", "apple_pay", "google_pay", "klarna", "sepa"],
|
|
10007
10379
|
checkoutcom: ["credit_card", "paypal", "apple_pay", "google_pay", "klarna", "sofort", "bank_transfer"],
|
|
@@ -10030,7 +10402,8 @@ var OPERATIONS = {
|
|
|
10030
10402
|
square: { refund: true, checkBalance: true, disburse: false },
|
|
10031
10403
|
payu: { refund: true, checkBalance: false, disburse: false },
|
|
10032
10404
|
braintree: { refund: true, checkBalance: false, disburse: false },
|
|
10033
|
-
twocheckout: { refund: true, checkBalance: false, disburse: false }
|
|
10405
|
+
twocheckout: { refund: true, checkBalance: false, disburse: false },
|
|
10406
|
+
sumopod: { refund: false, checkBalance: false, disburse: false }
|
|
10034
10407
|
};
|
|
10035
10408
|
function buildDefaultDescriptors() {
|
|
10036
10409
|
return Object.keys(ENV_KEYS).map((name) => ({
|
|
@@ -10147,9 +10520,13 @@ var SPECIFIC = {
|
|
|
10147
10520
|
apiKey: ["TWOCHECKOUT_SECRET_KEY"],
|
|
10148
10521
|
merchantCode: ["TWOCHECKOUT_MERCHANT_CODE"],
|
|
10149
10522
|
merchantId: ["TWOCHECKOUT_MERCHANT_CODE"]
|
|
10523
|
+
},
|
|
10524
|
+
sumopod: {
|
|
10525
|
+
apiKey: ["SUMOPOD_API_KEY", "SUMOPOD_PRODUCTION_API_KEY", "SUMOPOD_SANDBOX_API_KEY"]
|
|
10150
10526
|
}
|
|
10151
10527
|
};
|
|
10152
10528
|
function firstDefined(env, keys) {
|
|
10529
|
+
if (!keys) return void 0;
|
|
10153
10530
|
for (const k of keys) {
|
|
10154
10531
|
const v = env[k];
|
|
10155
10532
|
if (typeof v === "string" && v.trim().length > 0) return v.trim();
|
|
@@ -10178,7 +10555,8 @@ function resolveSandbox(env, provider) {
|
|
|
10178
10555
|
payu: ["PAYU_SANDBOX"],
|
|
10179
10556
|
braintree: ["BRAINTREE_SANDBOX"],
|
|
10180
10557
|
twocheckout: ["TWOCHECKOUT_SANDBOX"],
|
|
10181
|
-
xendit: ["XENDIT_SANDBOX"]
|
|
10558
|
+
xendit: ["XENDIT_SANDBOX"],
|
|
10559
|
+
sumopod: ["SUMOPOD_SANDBOX"]
|
|
10182
10560
|
};
|
|
10183
10561
|
const specific = firstDefined(env, specificMap[provider]);
|
|
10184
10562
|
if (specific !== void 0) {
|
|
@@ -10210,8 +10588,8 @@ function resolveConfigFromEnv(customConfig) {
|
|
|
10210
10588
|
const publicKey = customConfig?.publicKey || firstDefined(env, ["BUAYAR_PUBLIC_KEY", "PG_PUBLIC_KEY", "PUBLIC_KEY"]) || cfg.clientKey;
|
|
10211
10589
|
const privateKey = customConfig?.privateKey || firstDefined(env, ["BUAYAR_PRIVATE_KEY", "PG_PRIVATE_KEY", "PRIVATE_KEY"]) || (provider === "braintree" ? cfg.apiKey : void 0);
|
|
10212
10590
|
const extra = {
|
|
10213
|
-
webhookToken: env.XENDIT_WEBHOOK_TOKEN || env.BUAYAR_WEBHOOK_TOKEN,
|
|
10214
|
-
webhookSecret: env.STRIPE_WEBHOOK_SECRET || env.CHECKOUTCOM_WEBHOOK_SECRET || env.RAZORPAY_WEBHOOK_SECRET || env.BUAYAR_WEBHOOK_SECRET,
|
|
10591
|
+
webhookToken: (env.SUMOPOD_SANDBOX === "true" ? env.SUMOPOD_SANDBOX_WEBHOOK_TOKEN : env.SUMOPOD_PRODUCTION_WEBHOOK_TOKEN) || env.SUMOPOD_WEBHOOK_TOKEN || env.XENDIT_WEBHOOK_TOKEN || env.BUAYAR_WEBHOOK_TOKEN,
|
|
10592
|
+
webhookSecret: (env.SUMOPOD_SANDBOX === "true" ? env.SUMOPOD_SANDBOX_WEBHOOK_SECRET : env.SUMOPOD_PRODUCTION_WEBHOOK_SECRET) || env.SUMOPOD_WEBHOOK_SECRET || env.STRIPE_WEBHOOK_SECRET || env.CHECKOUTCOM_WEBHOOK_SECRET || env.RAZORPAY_WEBHOOK_SECRET || env.BUAYAR_WEBHOOK_SECRET,
|
|
10215
10593
|
merchantName: env.FASPAY_MERCHANT_NAME || env.BUAYAR_MERCHANT_NAME,
|
|
10216
10594
|
userId: env.FASPAY_USER_ID,
|
|
10217
10595
|
iMid: env.NICEPAY_IMID,
|
|
@@ -10443,6 +10821,14 @@ var Buayar = class {
|
|
|
10443
10821
|
if (oyUser) {
|
|
10444
10822
|
mergedConfig.extra.oyUsername = Array.isArray(oyUser) ? oyUser[0] : oyUser;
|
|
10445
10823
|
}
|
|
10824
|
+
const svixId = headers["svix-id"] || headers["Svix-Id"];
|
|
10825
|
+
const svixTimestamp = headers["svix-timestamp"] || headers["Svix-Timestamp"];
|
|
10826
|
+
const svixSignature = headers["svix-signature"] || headers["Svix-Signature"];
|
|
10827
|
+
const sumopodToken = headers["x-webhook-token"] || headers["X-Webhook-Token"];
|
|
10828
|
+
if (svixId) mergedConfig.extra.svixId = Array.isArray(svixId) ? svixId[0] : svixId;
|
|
10829
|
+
if (svixTimestamp) mergedConfig.extra.svixTimestamp = Array.isArray(svixTimestamp) ? svixTimestamp[0] : svixTimestamp;
|
|
10830
|
+
if (svixSignature) mergedConfig.extra.svixSignature = Array.isArray(svixSignature) ? svixSignature[0] : svixSignature;
|
|
10831
|
+
if (sumopodToken) mergedConfig.extra.webhookTokenHeader = Array.isArray(sumopodToken) ? sumopodToken[0] : sumopodToken;
|
|
10446
10832
|
}
|
|
10447
10833
|
let providerName = configOverride?.provider !== void 0 ? configOverride.provider : this.provider;
|
|
10448
10834
|
if (!providerName) {
|
|
@@ -10563,6 +10949,9 @@ var Buayar = class {
|
|
|
10563
10949
|
getTwoCheckoutClient(configOverride) {
|
|
10564
10950
|
return new TwoCheckoutClient({ ...this.config, ...configOverride });
|
|
10565
10951
|
}
|
|
10952
|
+
getSumopodClient(configOverride) {
|
|
10953
|
+
return new SumopodClient({ ...this.config, ...configOverride });
|
|
10954
|
+
}
|
|
10566
10955
|
};
|
|
10567
10956
|
var buayar = new Buayar();
|
|
10568
10957
|
export {
|
|
@@ -10582,6 +10971,7 @@ export {
|
|
|
10582
10971
|
CANONICAL_TO_OY,
|
|
10583
10972
|
CANONICAL_TO_PRISMALINK,
|
|
10584
10973
|
CANONICAL_TO_STRIPE,
|
|
10974
|
+
CANONICAL_TO_SUMOPOD,
|
|
10585
10975
|
CANONICAL_TO_XENDIT,
|
|
10586
10976
|
CORE_API_METHODS,
|
|
10587
10977
|
CheckoutComClient,
|
|
@@ -10615,12 +11005,15 @@ export {
|
|
|
10615
11005
|
ProviderRegistry,
|
|
10616
11006
|
RazorpayClient,
|
|
10617
11007
|
RazorpayProvider,
|
|
11008
|
+
SUMOPOD_TO_CANONICAL,
|
|
10618
11009
|
SnapApiError,
|
|
10619
11010
|
SnapClient,
|
|
10620
11011
|
SquareClient,
|
|
10621
11012
|
SquareProvider,
|
|
10622
11013
|
StripeClient,
|
|
10623
11014
|
StripeProvider,
|
|
11015
|
+
SumopodClient,
|
|
11016
|
+
SumopodProvider,
|
|
10624
11017
|
TwoCheckoutClient,
|
|
10625
11018
|
TwoCheckoutProvider,
|
|
10626
11019
|
XenditClient,
|
|
@@ -10680,6 +11073,7 @@ export {
|
|
|
10680
11073
|
toOyPaymentMethod,
|
|
10681
11074
|
toPrismalinkPaymentMethod,
|
|
10682
11075
|
toStripePaymentMethod,
|
|
11076
|
+
toSumopodPaymentMethod,
|
|
10683
11077
|
toXenditPaymentMethod,
|
|
10684
11078
|
verifyAdyenWebhook,
|
|
10685
11079
|
verifyBraintreeWebhook,
|
|
@@ -10699,6 +11093,8 @@ export {
|
|
|
10699
11093
|
verifySnapWebhookSignature,
|
|
10700
11094
|
verifySquareWebhook,
|
|
10701
11095
|
verifyStripeWebhook,
|
|
11096
|
+
verifySumopodSvixSignature,
|
|
11097
|
+
verifySumopodToken,
|
|
10702
11098
|
verifyTwoCheckoutWebhook,
|
|
10703
11099
|
verifyXenditWebhookToken
|
|
10704
11100
|
};
|
package/docs/guide.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# đŗ Panduan Unified `@crediblemark/buayar`
|
|
2
2
|
|
|
3
|
-
Panduan ini adalah **satu-satunya** panduan yang Anda butuhkan untuk mengintegrasikan **semua** payment gateway yang didukung Buayar (
|
|
3
|
+
Panduan ini adalah **satu-satunya** panduan yang Anda butuhkan untuk mengintegrasikan **semua** payment gateway yang didukung Buayar (20 provider: 11 Indonesia + 9 Internasional). Anda **tidak perlu** membaca dokumentasi masing-masing PG â kode yang Anda tulis **identik** untuk semua provider.
|
|
4
4
|
|
|
5
5
|
> đ¯ **Prinsip "mata tertutup":** Anda 100% tidak tahu (dan tidak perlu tahu) provider mana yang sedang aktif. Yang Anda tahu hanya: "Buaya mendukung PG A, PG B, PG C". Cukup ubah kredensial di `.env`, semuanya jalan.
|
|
6
6
|
|
|
@@ -52,7 +52,7 @@ Cukup isi **variabel universal** yang sama untuk semua provider. SDK otomatis me
|
|
|
52
52
|
```env
|
|
53
53
|
# (opsional) Provider aktif: midtrans, duitku, ipaymu, xendit, doku, prismalink,
|
|
54
54
|
# faspay, finpay, nicepay, oy, stripe, paypal, adyen, checkoutcom,
|
|
55
|
-
# razorpay, square, payu, braintree, twocheckout
|
|
55
|
+
# razorpay, square, payu, braintree, twocheckout, sumopod
|
|
56
56
|
# Bila dikosongkan, provider AUTO-DIDETEKSI dari kredensial yang terisi.
|
|
57
57
|
BUAYAR_PROVIDER=midtrans
|
|
58
58
|
|
|
@@ -414,6 +414,9 @@ Rangkuman kemampuan ekstra tiap provider:
|
|
|
414
414
|
| PayU | - | `getPayuClient()` | cancelOrder, getOrder, refundOrder |
|
|
415
415
|
| Braintree | - | `getBraintreeClient()` | getClientToken, findTransaction, refundTransaction, voidTransaction |
|
|
416
416
|
| 2Checkout | - | `getTwoCheckoutClient()` | getOrder, listOrders, getSubscription, refundOrder |
|
|
417
|
+
| [SumoPod](sumopod.md) | Tested | `getSumopodClient()` | createPayment, getPayment. *Lihat [panduan lengkap SumoPod](sumopod.md)* |
|
|
418
|
+
|
|
419
|
+
> âšī¸ **Catatan Status:** Hanya provider yang memiliki file dokumentasi panduan khusus di folder `docs/` yang berstatus **Tested** ([iPaymu](ipaymu.md) dan [SumoPod](sumopod.md)). Provider lain bertanda `-` berstatus siap pakai sesuai spesifikasi API resmi.
|
|
417
420
|
|
|
418
421
|
---
|
|
419
422
|
|
|
@@ -444,6 +447,7 @@ Tabel berikut menunjukkan data apa dari dashboard masing-masing payment gateway
|
|
|
444
447
|
| **PayU** | `payu` | MD5 Key / Secret | POS ID | POS ID |
|
|
445
448
|
| **Braintree** | `braintree` | Private Key | Merchant ID | Public Key |
|
|
446
449
|
| **2Checkout** | `twocheckout` | Secret Key | Merchant Code | Secret Word (`BUAYAR_WEBHOOK_SECRET`) |
|
|
450
|
+
| **SumoPod** | `sumopod` | API Key (`X-Api-Key`) | *(tidak perlu)* | Webhook Secret (`BUAYAR_WEBHOOK_SECRET`) / Token (`BUAYAR_WEBHOOK_TOKEN`) |
|
|
447
451
|
|
|
448
452
|
> đĄ **Mode Sandbox:** Cukup tambahkan `BUAYAR_SANDBOX=true` (atau `false` saat production), SDK otomatis menyesuaikan URL endpoint API seluruh provider di atas tanpa perlu konfigurasi tambahan.
|
|
449
453
|
|