@crediblemark/buayar 0.1.5 → 0.1.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 +33 -2
- package/dist/index.d.mts +135 -1
- package/dist/index.d.ts +135 -1
- package/dist/index.js +992 -9
- package/dist/index.mjs +989 -9
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -5,13 +5,30 @@ import crypto from "crypto";
|
|
|
5
5
|
var BasePaymentProvider = class {
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
// src/utils.ts
|
|
9
|
+
function getPaymentMethodCategory(code, name = "") {
|
|
10
|
+
const c = code.toUpperCase();
|
|
11
|
+
const n = name.toUpperCase();
|
|
12
|
+
if (c.includes("QR") || n.includes("QRIS")) return "QRIS";
|
|
13
|
+
if (c.includes("VA") || n.includes("VA") || n.includes("VIRTUAL ACCOUNT") || ["I1", "BT", "A1", "M2", "V1", "B1", "AG", "NC", "BR", "S1", "BS", "MY"].some((p) => c.startsWith(p.slice(0, 2)))) return "Virtual Account";
|
|
14
|
+
if (["OV", "GO", "SH", "DA", "LA", "OL", "SL", "GP", "OP", "DN", "JA", "JN", "JP"].some((p) => c.startsWith(p.slice(0, 2))) || n.includes("OVO") || n.includes("DANA") || n.includes("GOPAY") || n.includes("LINKAJA") || n.includes("SHOPEEPAY") || n.includes("JENIUS")) return "E-Wallet";
|
|
15
|
+
if (c.startsWith("FT") || c.startsWith("AL") || n.includes("INDOMARET") || n.includes("ALFAMART") || n.includes("RETAIL")) return "Retail / Gerai";
|
|
16
|
+
if (c.startsWith("VC") || n.includes("CREDIT CARD") || n.includes("KARTU KREDIT")) return "Kartu Kredit";
|
|
17
|
+
if (n.includes("PAYLATER") || n.includes("INDODANA") || n.includes("AKULAKU") || n.includes("KREDIVO")) return "Paylater / Cicilan";
|
|
18
|
+
return "Lainnya";
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
// src/providers/duitku.ts
|
|
9
22
|
var DuitkuProvider = class extends BasePaymentProvider {
|
|
10
23
|
name = "duitku";
|
|
24
|
+
getBaseUrl(sandbox) {
|
|
25
|
+
return sandbox ? "https://api-sandbox.duitku.com" : "https://api-prod.duitku.com";
|
|
26
|
+
}
|
|
11
27
|
async createInvoice(params, config) {
|
|
12
28
|
const { orderId, amount, productDetails, customer, returnUrl, callbackUrl } = params;
|
|
13
29
|
const { merchantCode, apiKey, sandbox } = config;
|
|
14
|
-
const
|
|
30
|
+
const isDirectInquiry = !!params.paymentMethod;
|
|
31
|
+
const url = isDirectInquiry ? sandbox ? "https://sandbox.duitku.com/webapi/api/merchant/v2/inquiry" : "https://passport.duitku.com/webapi/api/merchant/v2/inquiry" : `${this.getBaseUrl(sandbox)}/api/merchant/createInvoice`;
|
|
15
32
|
const integerAmount = Math.round(amount);
|
|
16
33
|
const rawPayloadSignature = merchantCode + orderId + integerAmount.toString() + apiKey;
|
|
17
34
|
const payloadSignature = crypto.createHash("md5").update(rawPayloadSignature).digest("hex");
|
|
@@ -28,19 +45,23 @@ var DuitkuProvider = class extends BasePaymentProvider {
|
|
|
28
45
|
signature: payloadSignature,
|
|
29
46
|
callbackUrl,
|
|
30
47
|
returnUrl,
|
|
31
|
-
expiryPeriod: 1440
|
|
48
|
+
expiryPeriod: 1440,
|
|
32
49
|
// 24 hours expiry
|
|
50
|
+
...params.paymentMethod ? { paymentMethod: params.paymentMethod } : {}
|
|
33
51
|
};
|
|
34
52
|
try {
|
|
53
|
+
const headers = {
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
"Accept": "application/json"
|
|
56
|
+
};
|
|
57
|
+
if (!isDirectInquiry) {
|
|
58
|
+
headers["x-duitku-signature"] = headerSignature;
|
|
59
|
+
headers["x-duitku-timestamp"] = timestamp;
|
|
60
|
+
headers["x-duitku-merchantcode"] = merchantCode;
|
|
61
|
+
}
|
|
35
62
|
const response = await fetch(url, {
|
|
36
63
|
method: "POST",
|
|
37
|
-
headers
|
|
38
|
-
"Content-Type": "application/json",
|
|
39
|
-
"Accept": "application/json",
|
|
40
|
-
"x-duitku-signature": headerSignature,
|
|
41
|
-
"x-duitku-timestamp": timestamp,
|
|
42
|
-
"x-duitku-merchantcode": merchantCode
|
|
43
|
-
},
|
|
64
|
+
headers,
|
|
44
65
|
body: JSON.stringify(payload)
|
|
45
66
|
});
|
|
46
67
|
const text = await response.text();
|
|
@@ -61,6 +82,10 @@ var DuitkuProvider = class extends BasePaymentProvider {
|
|
|
61
82
|
success: true,
|
|
62
83
|
paymentUrl: data.paymentUrl,
|
|
63
84
|
reference: data.reference,
|
|
85
|
+
vaNumber: data.vaNumber,
|
|
86
|
+
qrString: data.qrString,
|
|
87
|
+
qrCodeUrl: data.qrCodeUrl,
|
|
88
|
+
paymentCode: data.paymentCode,
|
|
64
89
|
rawResponse: data
|
|
65
90
|
};
|
|
66
91
|
} else {
|
|
@@ -96,6 +121,936 @@ var DuitkuProvider = class extends BasePaymentProvider {
|
|
|
96
121
|
rawPayload: body
|
|
97
122
|
};
|
|
98
123
|
}
|
|
124
|
+
async getPaymentMethods(params, config) {
|
|
125
|
+
const { amount } = params;
|
|
126
|
+
const { merchantCode, apiKey, sandbox } = config;
|
|
127
|
+
const url = sandbox ? "https://sandbox.duitku.com/webapi/api/merchant/paymentmethod/getpaymentmethod" : "https://passport.duitku.com/webapi/api/merchant/paymentmethod/getpaymentmethod";
|
|
128
|
+
const integerAmount = Math.round(amount);
|
|
129
|
+
const datetime = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
130
|
+
const stringToSign = merchantCode + integerAmount.toString() + datetime;
|
|
131
|
+
const signature = crypto.createHmac("sha256", apiKey).update(stringToSign).digest("hex");
|
|
132
|
+
try {
|
|
133
|
+
const response = await fetch(url, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: {
|
|
136
|
+
"Content-Type": "application/json"
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify({
|
|
139
|
+
merchantcode: merchantCode,
|
|
140
|
+
amount: integerAmount,
|
|
141
|
+
datetime,
|
|
142
|
+
signature
|
|
143
|
+
})
|
|
144
|
+
});
|
|
145
|
+
const text = await response.text();
|
|
146
|
+
let data = null;
|
|
147
|
+
try {
|
|
148
|
+
data = JSON.parse(text);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
}
|
|
151
|
+
if (!response.ok || !data) {
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
methods: [],
|
|
155
|
+
rawResponse: data,
|
|
156
|
+
error: data?.responseMessage || `HTTP error! Status: ${response.status}`
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (data.responseCode === "00") {
|
|
160
|
+
const rawMethods = data.paymentFee || [];
|
|
161
|
+
const methods = rawMethods.map((m) => ({
|
|
162
|
+
paymentMethod: m.paymentMethod,
|
|
163
|
+
paymentName: m.paymentName,
|
|
164
|
+
paymentImage: m.paymentImage,
|
|
165
|
+
totalFee: m.totalFee,
|
|
166
|
+
category: getPaymentMethodCategory(m.paymentMethod, m.paymentName)
|
|
167
|
+
}));
|
|
168
|
+
return {
|
|
169
|
+
success: true,
|
|
170
|
+
methods,
|
|
171
|
+
rawResponse: data
|
|
172
|
+
};
|
|
173
|
+
} else {
|
|
174
|
+
return {
|
|
175
|
+
success: false,
|
|
176
|
+
methods: [],
|
|
177
|
+
rawResponse: data,
|
|
178
|
+
error: data.responseMessage || `Duitku Error: ${data.responseCode}`
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
} catch (e) {
|
|
182
|
+
return {
|
|
183
|
+
success: false,
|
|
184
|
+
methods: [],
|
|
185
|
+
rawResponse: null,
|
|
186
|
+
error: e.message || "Failed to get payment methods from Duitku"
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async checkTransaction(params, config) {
|
|
191
|
+
const { merchantOrderId } = params;
|
|
192
|
+
const { merchantCode, apiKey, sandbox } = config;
|
|
193
|
+
const url = sandbox ? "https://api-sandbox.duitku.com/api/merchant/transactionStatus" : "https://api-prod.duitku.com/api/merchant/transactionStatus";
|
|
194
|
+
const timestamp = Date.now().toString();
|
|
195
|
+
const rawHeaderSignature = merchantCode + timestamp + apiKey;
|
|
196
|
+
const headerSignature = crypto.createHash("sha256").update(rawHeaderSignature).digest("hex");
|
|
197
|
+
const rawBodySignature = merchantCode + merchantOrderId + apiKey;
|
|
198
|
+
const bodySignature = crypto.createHash("md5").update(rawBodySignature).digest("hex");
|
|
199
|
+
try {
|
|
200
|
+
const response = await fetch(url, {
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers: {
|
|
203
|
+
"Content-Type": "application/json",
|
|
204
|
+
"Accept": "application/json",
|
|
205
|
+
"x-duitku-signature": headerSignature,
|
|
206
|
+
"x-duitku-timestamp": timestamp,
|
|
207
|
+
"x-duitku-merchantcode": merchantCode
|
|
208
|
+
},
|
|
209
|
+
body: JSON.stringify({
|
|
210
|
+
merchantCode,
|
|
211
|
+
merchantOrderId,
|
|
212
|
+
signature: bodySignature
|
|
213
|
+
})
|
|
214
|
+
});
|
|
215
|
+
const text = await response.text();
|
|
216
|
+
let data = null;
|
|
217
|
+
try {
|
|
218
|
+
data = JSON.parse(text);
|
|
219
|
+
} catch (e) {
|
|
220
|
+
}
|
|
221
|
+
if (!response.ok || !data) {
|
|
222
|
+
return {
|
|
223
|
+
success: false,
|
|
224
|
+
orderId: merchantOrderId,
|
|
225
|
+
reference: "",
|
|
226
|
+
amount: 0,
|
|
227
|
+
statusCode: "",
|
|
228
|
+
status: "failed",
|
|
229
|
+
statusMessage: `HTTP error! Status: ${response.status}`,
|
|
230
|
+
error: `HTTP ${response.status}`,
|
|
231
|
+
rawResponse: data
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const statusCode = data.statusCode || "";
|
|
235
|
+
let status;
|
|
236
|
+
if (statusCode === "00") {
|
|
237
|
+
status = "paid";
|
|
238
|
+
} else if (statusCode === "01") {
|
|
239
|
+
status = "pending";
|
|
240
|
+
} else {
|
|
241
|
+
status = "failed";
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
success: true,
|
|
245
|
+
orderId: data.merchantOrderId || merchantOrderId,
|
|
246
|
+
reference: data.reference || "",
|
|
247
|
+
amount: data.amount ? Number(data.amount) : 0,
|
|
248
|
+
statusCode,
|
|
249
|
+
status,
|
|
250
|
+
statusMessage: data.statusMessage || "",
|
|
251
|
+
rawResponse: data
|
|
252
|
+
};
|
|
253
|
+
} catch (e) {
|
|
254
|
+
return {
|
|
255
|
+
success: false,
|
|
256
|
+
orderId: merchantOrderId,
|
|
257
|
+
reference: "",
|
|
258
|
+
amount: 0,
|
|
259
|
+
statusCode: "",
|
|
260
|
+
status: "failed",
|
|
261
|
+
statusMessage: "Network error",
|
|
262
|
+
error: e.message || "Failed to check transaction status",
|
|
263
|
+
rawResponse: null
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async probePaymentMethods(config) {
|
|
268
|
+
const res = await this.getPaymentMethods({ amount: 1e4 }, config);
|
|
269
|
+
if (res.success) {
|
|
270
|
+
return { success: true, enabled: res.methods.map((m) => m.paymentMethod) };
|
|
271
|
+
}
|
|
272
|
+
return { success: false, enabled: [], error: res.error };
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// src/providers/midtrans.ts
|
|
277
|
+
import crypto2 from "crypto";
|
|
278
|
+
|
|
279
|
+
// src/clients/midtrans.ts
|
|
280
|
+
var MidtransClient = class {
|
|
281
|
+
apiKey;
|
|
282
|
+
sandbox;
|
|
283
|
+
constructor(config) {
|
|
284
|
+
this.apiKey = config.apiKey;
|
|
285
|
+
this.sandbox = config.sandbox;
|
|
286
|
+
}
|
|
287
|
+
getApiBaseUrl() {
|
|
288
|
+
return this.sandbox ? "https://api.sandbox.midtrans.com/v2" : "https://api.midtrans.com/v2";
|
|
289
|
+
}
|
|
290
|
+
async request(method, path, body) {
|
|
291
|
+
let url = path.startsWith("http") ? path : `${this.getApiBaseUrl()}${path}`;
|
|
292
|
+
if (!path.startsWith("http") && (path.startsWith("/v1/") || path.startsWith("/v2/"))) {
|
|
293
|
+
url = `${this.sandbox ? "https://api.sandbox.midtrans.com" : "https://api.midtrans.com"}${path}`;
|
|
294
|
+
}
|
|
295
|
+
const authHeader = `Basic ${Buffer.from(this.apiKey + ":").toString("base64")}`;
|
|
296
|
+
const headers = {
|
|
297
|
+
"Content-Type": "application/json",
|
|
298
|
+
"Accept": "application/json",
|
|
299
|
+
"Authorization": authHeader
|
|
300
|
+
};
|
|
301
|
+
const fetchOptions = {
|
|
302
|
+
method,
|
|
303
|
+
headers
|
|
304
|
+
};
|
|
305
|
+
if (body) {
|
|
306
|
+
fetchOptions.body = JSON.stringify(body);
|
|
307
|
+
}
|
|
308
|
+
const response = await fetch(url, fetchOptions);
|
|
309
|
+
const text = await response.text();
|
|
310
|
+
let data = null;
|
|
311
|
+
try {
|
|
312
|
+
data = JSON.parse(text);
|
|
313
|
+
} catch (e) {
|
|
314
|
+
}
|
|
315
|
+
if (!response.ok) {
|
|
316
|
+
throw new Error(data?.message || data?.status_message || `HTTP error! Status: ${response.status} - ${text}`);
|
|
317
|
+
}
|
|
318
|
+
return data || text;
|
|
319
|
+
}
|
|
320
|
+
// ─── TRANSACTION ACTIONS ─────────────────────────────────────────────────────
|
|
321
|
+
async cancelTransaction(orderId) {
|
|
322
|
+
return this.request("POST", `/${orderId}/cancel`, null);
|
|
323
|
+
}
|
|
324
|
+
async refundTransaction(orderId, payload) {
|
|
325
|
+
return this.request("POST", `/${orderId}/refund`, payload);
|
|
326
|
+
}
|
|
327
|
+
async expireTransaction(orderId) {
|
|
328
|
+
return this.request("POST", `/${orderId}/expire`, null);
|
|
329
|
+
}
|
|
330
|
+
async approveTransaction(orderId) {
|
|
331
|
+
return this.request("POST", `/${orderId}/approve`, null);
|
|
332
|
+
}
|
|
333
|
+
async denyTransaction(orderId) {
|
|
334
|
+
return this.request("POST", `/${orderId}/deny`, null);
|
|
335
|
+
}
|
|
336
|
+
async captureTransaction(transactionId, amount) {
|
|
337
|
+
const payload = amount ? { transaction_id: transactionId, gross_amount: Math.round(amount) } : { transaction_id: transactionId };
|
|
338
|
+
return this.request("POST", "/capture", payload);
|
|
339
|
+
}
|
|
340
|
+
// ─── GOPAY TOKENIZATION API ──────────────────────────────────────────────────
|
|
341
|
+
async linkPayAccount(payload) {
|
|
342
|
+
return this.request("POST", "/v2/pay/account", payload);
|
|
343
|
+
}
|
|
344
|
+
async getPayAccount(accountId) {
|
|
345
|
+
return this.request("GET", `/v2/pay/account/${accountId}`, null);
|
|
346
|
+
}
|
|
347
|
+
async unbindPayAccount(accountId) {
|
|
348
|
+
return this.request("POST", `/v2/pay/account/${accountId}/unbind`, null);
|
|
349
|
+
}
|
|
350
|
+
async getGoPayPromo(accountId, grossAmount, currency = "IDR") {
|
|
351
|
+
return this.request("GET", `/v2/gopay/promo/${accountId}?gross_amount=${grossAmount}¤cy=${currency}`, null);
|
|
352
|
+
}
|
|
353
|
+
// ─── SUBSCRIPTION API ────────────────────────────────────────────────────────
|
|
354
|
+
async createSubscription(payload) {
|
|
355
|
+
return this.request("POST", "/v1/subscriptions", payload);
|
|
356
|
+
}
|
|
357
|
+
async getSubscription(subscriptionId) {
|
|
358
|
+
return this.request("GET", `/v1/subscriptions/${subscriptionId}`, null);
|
|
359
|
+
}
|
|
360
|
+
async updateSubscription(subscriptionId, payload) {
|
|
361
|
+
return this.request("PATCH", `/v1/subscriptions/${subscriptionId}`, payload);
|
|
362
|
+
}
|
|
363
|
+
async disableSubscription(subscriptionId) {
|
|
364
|
+
return this.request("POST", `/v1/subscriptions/${subscriptionId}/disable`, null);
|
|
365
|
+
}
|
|
366
|
+
async enableSubscription(subscriptionId) {
|
|
367
|
+
return this.request("POST", `/v1/subscriptions/${subscriptionId}/enable`, null);
|
|
368
|
+
}
|
|
369
|
+
// ─── PAYMENT LINK API ────────────────────────────────────────────────────────
|
|
370
|
+
async createPaymentLink(payload) {
|
|
371
|
+
return this.request("POST", "/v1/payment-links", payload);
|
|
372
|
+
}
|
|
373
|
+
async getPaymentLink(paymentLinkId) {
|
|
374
|
+
return this.request("GET", `/v1/payment-links/${paymentLinkId}`, null);
|
|
375
|
+
}
|
|
376
|
+
async deletePaymentLink(paymentLinkId) {
|
|
377
|
+
return this.request("DELETE", `/v1/payment-links/${paymentLinkId}`, null);
|
|
378
|
+
}
|
|
379
|
+
// ─── BALANCE API ─────────────────────────────────────────────────────────────
|
|
380
|
+
async getBalance() {
|
|
381
|
+
try {
|
|
382
|
+
return await this.request("GET", "/v1/balance", null);
|
|
383
|
+
} catch (e) {
|
|
384
|
+
try {
|
|
385
|
+
const irisUrl = `${this.sandbox ? "https://api.sandbox.midtrans.com" : "https://api.midtrans.com"}/iris/api/v1/balance`;
|
|
386
|
+
const authHeader = `Basic ${Buffer.from(this.apiKey + ":").toString("base64")}`;
|
|
387
|
+
const response = await fetch(irisUrl, {
|
|
388
|
+
method: "GET",
|
|
389
|
+
headers: {
|
|
390
|
+
"Accept": "application/json",
|
|
391
|
+
"Authorization": authHeader
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
const text = await response.text();
|
|
395
|
+
let data = null;
|
|
396
|
+
try {
|
|
397
|
+
data = JSON.parse(text);
|
|
398
|
+
} catch (err) {
|
|
399
|
+
}
|
|
400
|
+
if (response.ok) return data || text;
|
|
401
|
+
} catch (irisErr) {
|
|
402
|
+
}
|
|
403
|
+
throw e;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// ─── INVOICING API ───────────────────────────────────────────────────────────
|
|
407
|
+
async createBillingInvoice(payload) {
|
|
408
|
+
return this.request("POST", "/v1/invoices", payload);
|
|
409
|
+
}
|
|
410
|
+
async getBillingInvoice(invoiceId) {
|
|
411
|
+
return this.request("GET", `/v1/invoices/${invoiceId}`, null);
|
|
412
|
+
}
|
|
413
|
+
async voidBillingInvoice(invoiceId) {
|
|
414
|
+
return this.request("PATCH", `/v1/invoices/${invoiceId}/void`, null);
|
|
415
|
+
}
|
|
416
|
+
async convertBillingInvoice(invoiceId) {
|
|
417
|
+
return this.request("PATCH", `/v1/invoices/${invoiceId}/convert`, null);
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// src/providers/midtrans.ts
|
|
422
|
+
var MidtransProvider = class extends BasePaymentProvider {
|
|
423
|
+
name = "midtrans";
|
|
424
|
+
getSnapBaseUrl(sandbox) {
|
|
425
|
+
return sandbox ? "https://app.sandbox.midtrans.com/snap/v1/transactions" : "https://app.midtrans.com/snap/v1/transactions";
|
|
426
|
+
}
|
|
427
|
+
getApiBaseUrl(sandbox) {
|
|
428
|
+
return sandbox ? "https://api.sandbox.midtrans.com/v2" : "https://api.midtrans.com/v2";
|
|
429
|
+
}
|
|
430
|
+
async createInvoice(params, config) {
|
|
431
|
+
const { orderId, amount, productDetails, customer, returnUrl } = params;
|
|
432
|
+
const { apiKey, sandbox } = config;
|
|
433
|
+
const integerAmount = Math.round(amount);
|
|
434
|
+
const method = params.paymentMethod?.toLowerCase() || "";
|
|
435
|
+
const coreApiMethods = [
|
|
436
|
+
"bca_va",
|
|
437
|
+
"bni_va",
|
|
438
|
+
"bri_va",
|
|
439
|
+
"permata_va",
|
|
440
|
+
"cimb_va",
|
|
441
|
+
"danamon_va",
|
|
442
|
+
"bsi_va",
|
|
443
|
+
"seabank_va",
|
|
444
|
+
"mandiri_va",
|
|
445
|
+
"qris",
|
|
446
|
+
"gopay",
|
|
447
|
+
"shopeepay",
|
|
448
|
+
"ovo",
|
|
449
|
+
"dana",
|
|
450
|
+
"linkaja",
|
|
451
|
+
"alfamart",
|
|
452
|
+
"indomaret",
|
|
453
|
+
"credit_card",
|
|
454
|
+
"googlepay",
|
|
455
|
+
"kredivo",
|
|
456
|
+
"akulaku"
|
|
457
|
+
];
|
|
458
|
+
if (method && coreApiMethods.includes(method)) {
|
|
459
|
+
const url2 = `${this.getApiBaseUrl(sandbox)}/charge`;
|
|
460
|
+
let payload2 = {
|
|
461
|
+
transaction_details: {
|
|
462
|
+
order_id: orderId,
|
|
463
|
+
gross_amount: integerAmount
|
|
464
|
+
},
|
|
465
|
+
customer_details: {
|
|
466
|
+
first_name: customer.name,
|
|
467
|
+
email: customer.email,
|
|
468
|
+
phone: customer.phone || ""
|
|
469
|
+
},
|
|
470
|
+
item_details: [
|
|
471
|
+
{
|
|
472
|
+
id: orderId,
|
|
473
|
+
price: integerAmount,
|
|
474
|
+
quantity: 1,
|
|
475
|
+
name: productDetails.length > 50 ? productDetails.substring(0, 47) + "..." : productDetails
|
|
476
|
+
}
|
|
477
|
+
],
|
|
478
|
+
...params.providerParams
|
|
479
|
+
};
|
|
480
|
+
if (["bca_va", "bni_va", "bri_va", "cimb_va", "permata_va", "danamon_va", "bsi_va", "seabank_va"].includes(method)) {
|
|
481
|
+
const bankName = method.split("_")[0];
|
|
482
|
+
payload2.payment_type = "bank_transfer";
|
|
483
|
+
payload2.bank_transfer = {
|
|
484
|
+
bank: bankName
|
|
485
|
+
};
|
|
486
|
+
} else if (method === "mandiri_va") {
|
|
487
|
+
payload2.payment_type = "echannel";
|
|
488
|
+
payload2.echannel = {
|
|
489
|
+
bill_info1: "Payment for",
|
|
490
|
+
bill_info2: productDetails.length > 30 ? productDetails.substring(0, 27) + "..." : productDetails
|
|
491
|
+
};
|
|
492
|
+
} else if (method === "qris") {
|
|
493
|
+
payload2.payment_type = "qris";
|
|
494
|
+
} else if (method === "gopay") {
|
|
495
|
+
payload2.payment_type = "gopay";
|
|
496
|
+
payload2.gopay = {
|
|
497
|
+
enable_callback: true,
|
|
498
|
+
callback_url: returnUrl
|
|
499
|
+
};
|
|
500
|
+
} else if (method === "shopeepay") {
|
|
501
|
+
payload2.payment_type = "shopeepay";
|
|
502
|
+
payload2.shopeepay = {
|
|
503
|
+
callback_url: returnUrl
|
|
504
|
+
};
|
|
505
|
+
} else if (method === "ovo") {
|
|
506
|
+
if (!customer.phone) {
|
|
507
|
+
return {
|
|
508
|
+
success: false,
|
|
509
|
+
rawResponse: null,
|
|
510
|
+
error: "OVO payment method requires a customer phone number in customer.phone"
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
payload2.payment_type = "ovo";
|
|
514
|
+
payload2.ovo = {
|
|
515
|
+
phone: customer.phone
|
|
516
|
+
};
|
|
517
|
+
} else if (method === "dana") {
|
|
518
|
+
payload2.payment_type = "dana";
|
|
519
|
+
} else if (method === "linkaja") {
|
|
520
|
+
payload2.payment_type = "linkaja";
|
|
521
|
+
} else if (method === "credit_card") {
|
|
522
|
+
const token = params.providerParams?.credit_card?.token_id || params.providerParams?.tokenId;
|
|
523
|
+
if (!token) {
|
|
524
|
+
return {
|
|
525
|
+
success: false,
|
|
526
|
+
rawResponse: null,
|
|
527
|
+
error: "Credit Card payment method requires token_id (passed via providerParams.credit_card.token_id or providerParams.tokenId)"
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
payload2.payment_type = "credit_card";
|
|
531
|
+
payload2.credit_card = {
|
|
532
|
+
token_id: token,
|
|
533
|
+
authentication: params.providerParams?.credit_card?.authentication ?? true,
|
|
534
|
+
save_card: params.providerParams?.credit_card?.save_card,
|
|
535
|
+
bank: params.providerParams?.credit_card?.bank,
|
|
536
|
+
installment_term: params.providerParams?.credit_card?.installment_term,
|
|
537
|
+
bins: params.providerParams?.credit_card?.bins,
|
|
538
|
+
type: params.providerParams?.credit_card?.type
|
|
539
|
+
};
|
|
540
|
+
} else if (method === "googlepay") {
|
|
541
|
+
const token = params.providerParams?.googlepay?.token_id || params.providerParams?.tokenId;
|
|
542
|
+
if (!token) {
|
|
543
|
+
return {
|
|
544
|
+
success: false,
|
|
545
|
+
rawResponse: null,
|
|
546
|
+
error: "Google Pay payment method requires token_id (passed via providerParams.googlepay.token_id or providerParams.tokenId)"
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
payload2.payment_type = "googlepay";
|
|
550
|
+
payload2.googlepay = {
|
|
551
|
+
token_id: token
|
|
552
|
+
};
|
|
553
|
+
} else if (method === "kredivo") {
|
|
554
|
+
payload2.payment_type = "kredivo";
|
|
555
|
+
payload2.kredivo = {
|
|
556
|
+
address: params.providerParams?.kredivo?.address,
|
|
557
|
+
first_name: params.providerParams?.kredivo?.first_name || customer.name.split(" ")[0],
|
|
558
|
+
last_name: params.providerParams?.kredivo?.last_name || customer.name.split(" ").slice(1).join(" "),
|
|
559
|
+
email: params.providerParams?.kredivo?.email || customer.email,
|
|
560
|
+
phone: params.providerParams?.kredivo?.phone || customer.phone || ""
|
|
561
|
+
};
|
|
562
|
+
} else if (method === "akulaku") {
|
|
563
|
+
payload2.payment_type = "akulaku";
|
|
564
|
+
} else if (["alfamart", "indomaret"].includes(method)) {
|
|
565
|
+
payload2.payment_type = "cstore";
|
|
566
|
+
payload2.cstore = {
|
|
567
|
+
store: method,
|
|
568
|
+
message: productDetails.length > 30 ? productDetails.substring(0, 27) + "..." : productDetails
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
try {
|
|
572
|
+
const authHeader = `Basic ${Buffer.from(apiKey + ":").toString("base64")}`;
|
|
573
|
+
const response = await fetch(url2, {
|
|
574
|
+
method: "POST",
|
|
575
|
+
headers: {
|
|
576
|
+
"Content-Type": "application/json",
|
|
577
|
+
"Accept": "application/json",
|
|
578
|
+
"Authorization": authHeader
|
|
579
|
+
},
|
|
580
|
+
body: JSON.stringify(payload2)
|
|
581
|
+
});
|
|
582
|
+
const text = await response.text();
|
|
583
|
+
let data = null;
|
|
584
|
+
try {
|
|
585
|
+
data = JSON.parse(text);
|
|
586
|
+
} catch (e) {
|
|
587
|
+
}
|
|
588
|
+
if (!response.ok || !data) {
|
|
589
|
+
return {
|
|
590
|
+
success: false,
|
|
591
|
+
rawResponse: data || text,
|
|
592
|
+
error: data?.status_message || `HTTP error! Status: ${response.status} - ${text}`
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
if (data.status_code === "201" || data.status_code === "200") {
|
|
596
|
+
const res = {
|
|
597
|
+
success: true,
|
|
598
|
+
reference: data.transaction_id || data.order_id,
|
|
599
|
+
rawResponse: data
|
|
600
|
+
};
|
|
601
|
+
if (["bca_va", "bni_va", "bri_va", "cimb_va", "danamon_va", "bsi_va", "seabank_va"].includes(method)) {
|
|
602
|
+
res.vaNumber = data.va_numbers?.[0]?.va_number;
|
|
603
|
+
} else if (method === "permata_va") {
|
|
604
|
+
res.vaNumber = data.permata_va_number;
|
|
605
|
+
} else if (method === "mandiri_va") {
|
|
606
|
+
res.vaNumber = `${data.biller_code}-${data.bill_key}`;
|
|
607
|
+
} else if (method === "qris") {
|
|
608
|
+
res.qrString = data.qr_string;
|
|
609
|
+
res.qrCodeUrl = data.actions?.find((a) => a.name === "generate-qr-code")?.url;
|
|
610
|
+
} else if (["gopay", "shopeepay", "dana", "linkaja", "kredivo", "akulaku", "googlepay"].includes(method)) {
|
|
611
|
+
res.paymentUrl = data.actions?.find((a) => a.name === "deeplink-redirect")?.url || data.actions?.find((a) => a.name === "web-redirect")?.url || data.actions?.find((a) => a.name === "generate-qr-code")?.url || data.redirect_url;
|
|
612
|
+
res.qrCodeUrl = data.actions?.find((a) => a.name === "generate-qr-code")?.url;
|
|
613
|
+
} else if (method === "credit_card") {
|
|
614
|
+
res.paymentUrl = data.redirect_url || data.actions?.find((a) => a.name === "redirect")?.url;
|
|
615
|
+
} else if (method === "ovo") {
|
|
616
|
+
res.paymentUrl = "";
|
|
617
|
+
} else if (["alfamart", "indomaret"].includes(method)) {
|
|
618
|
+
res.paymentCode = data.payment_code;
|
|
619
|
+
}
|
|
620
|
+
return res;
|
|
621
|
+
} else {
|
|
622
|
+
return {
|
|
623
|
+
success: false,
|
|
624
|
+
rawResponse: data,
|
|
625
|
+
error: data.status_message || `Midtrans Core Error: ${data.status_code}`
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
} catch (e) {
|
|
629
|
+
return {
|
|
630
|
+
success: false,
|
|
631
|
+
rawResponse: null,
|
|
632
|
+
error: e.message || "Failed to make request to Midtrans Core API"
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const url = this.getSnapBaseUrl(sandbox);
|
|
637
|
+
const payload = {
|
|
638
|
+
transaction_details: {
|
|
639
|
+
order_id: orderId,
|
|
640
|
+
gross_amount: integerAmount
|
|
641
|
+
},
|
|
642
|
+
customer_details: {
|
|
643
|
+
first_name: customer.name,
|
|
644
|
+
email: customer.email,
|
|
645
|
+
phone: customer.phone || ""
|
|
646
|
+
},
|
|
647
|
+
item_details: [
|
|
648
|
+
{
|
|
649
|
+
id: orderId,
|
|
650
|
+
price: integerAmount,
|
|
651
|
+
quantity: 1,
|
|
652
|
+
name: productDetails.length > 50 ? productDetails.substring(0, 47) + "..." : productDetails
|
|
653
|
+
}
|
|
654
|
+
],
|
|
655
|
+
callbacks: {
|
|
656
|
+
finish: returnUrl
|
|
657
|
+
},
|
|
658
|
+
...params.paymentMethod ? { enabled_payments: [params.paymentMethod] } : {},
|
|
659
|
+
...params.providerParams
|
|
660
|
+
};
|
|
661
|
+
try {
|
|
662
|
+
const authHeader = `Basic ${Buffer.from(apiKey + ":").toString("base64")}`;
|
|
663
|
+
const response = await fetch(url, {
|
|
664
|
+
method: "POST",
|
|
665
|
+
headers: {
|
|
666
|
+
"Content-Type": "application/json",
|
|
667
|
+
"Accept": "application/json",
|
|
668
|
+
"Authorization": authHeader
|
|
669
|
+
},
|
|
670
|
+
body: JSON.stringify(payload)
|
|
671
|
+
});
|
|
672
|
+
const text = await response.text();
|
|
673
|
+
let data = null;
|
|
674
|
+
try {
|
|
675
|
+
data = JSON.parse(text);
|
|
676
|
+
} catch (e) {
|
|
677
|
+
}
|
|
678
|
+
if (!response.ok || !data) {
|
|
679
|
+
return {
|
|
680
|
+
success: false,
|
|
681
|
+
rawResponse: data || text,
|
|
682
|
+
error: data?.error_messages?.[0] || `HTTP error! Status: ${response.status} - ${text}`
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
if (data.token) {
|
|
686
|
+
return {
|
|
687
|
+
success: true,
|
|
688
|
+
paymentUrl: data.redirect_url,
|
|
689
|
+
reference: data.token,
|
|
690
|
+
rawResponse: data
|
|
691
|
+
};
|
|
692
|
+
} else {
|
|
693
|
+
return {
|
|
694
|
+
success: false,
|
|
695
|
+
rawResponse: data,
|
|
696
|
+
error: data.error_messages?.[0] || "Failed to create Midtrans Snap transaction"
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
} catch (e) {
|
|
700
|
+
return {
|
|
701
|
+
success: false,
|
|
702
|
+
rawResponse: null,
|
|
703
|
+
error: e.message || "Failed to make request to Midtrans Snap API"
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
async verifyCallback(body, config) {
|
|
708
|
+
const { apiKey } = config;
|
|
709
|
+
const orderId = body.order_id || "";
|
|
710
|
+
const statusCode = body.status_code || "";
|
|
711
|
+
const grossAmount = body.gross_amount || "";
|
|
712
|
+
const signatureKey = body.signature_key || "";
|
|
713
|
+
const rawSignature = orderId + statusCode + grossAmount + apiKey;
|
|
714
|
+
const computedSignature = crypto2.createHash("sha512").update(rawSignature).digest("hex");
|
|
715
|
+
const isValid = signatureKey.toLowerCase() === computedSignature.toLowerCase();
|
|
716
|
+
const transactionStatus = body.transaction_status || "";
|
|
717
|
+
const fraudStatus = body.fraud_status || "";
|
|
718
|
+
let status = "pending";
|
|
719
|
+
if (transactionStatus === "capture" || transactionStatus === "settlement") {
|
|
720
|
+
if (fraudStatus === "accept" || !fraudStatus) {
|
|
721
|
+
status = "paid";
|
|
722
|
+
} else if (fraudStatus === "challenge") {
|
|
723
|
+
status = "pending";
|
|
724
|
+
} else {
|
|
725
|
+
status = "failed";
|
|
726
|
+
}
|
|
727
|
+
} else if (transactionStatus === "pending") {
|
|
728
|
+
status = "pending";
|
|
729
|
+
} else if (["deny", "cancel", "expire"].includes(transactionStatus)) {
|
|
730
|
+
status = "failed";
|
|
731
|
+
}
|
|
732
|
+
return {
|
|
733
|
+
isValid,
|
|
734
|
+
orderId,
|
|
735
|
+
amount: grossAmount ? Number(grossAmount) : 0,
|
|
736
|
+
status: isValid ? status : "failed",
|
|
737
|
+
rawPayload: body
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
async getPaymentMethods(params, config) {
|
|
741
|
+
const staticMethods = [
|
|
742
|
+
{
|
|
743
|
+
paymentMethod: "credit_card",
|
|
744
|
+
paymentName: "Credit / Debit Card",
|
|
745
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/credit_card.png",
|
|
746
|
+
totalFee: "2.9% + IDR 2,000",
|
|
747
|
+
category: "Kartu Kredit"
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
paymentMethod: "googlepay",
|
|
751
|
+
paymentName: "Google Pay\u2122",
|
|
752
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/googlepay.png",
|
|
753
|
+
totalFee: "2.9% + IDR 2,000",
|
|
754
|
+
category: "Kartu Kredit"
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
paymentMethod: "bca_va",
|
|
758
|
+
paymentName: "BCA Virtual Account",
|
|
759
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/bca_va.png",
|
|
760
|
+
totalFee: "IDR 4,000",
|
|
761
|
+
category: "Virtual Account"
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
paymentMethod: "bni_va",
|
|
765
|
+
paymentName: "BNI Virtual Account",
|
|
766
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/bni_va.png",
|
|
767
|
+
totalFee: "IDR 4,000",
|
|
768
|
+
category: "Virtual Account"
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
paymentMethod: "bri_va",
|
|
772
|
+
paymentName: "BRI Virtual Account",
|
|
773
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/bri_va.png",
|
|
774
|
+
totalFee: "IDR 4,000",
|
|
775
|
+
category: "Virtual Account"
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
paymentMethod: "mandiri_va",
|
|
779
|
+
paymentName: "Mandiri Bill Payment / VA",
|
|
780
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/mandiri_va.png",
|
|
781
|
+
totalFee: "IDR 4,000",
|
|
782
|
+
category: "Virtual Account"
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
paymentMethod: "permata_va",
|
|
786
|
+
paymentName: "Permata Virtual Account",
|
|
787
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/permata_va.png",
|
|
788
|
+
totalFee: "IDR 4,000",
|
|
789
|
+
category: "Virtual Account"
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
paymentMethod: "cimb_va",
|
|
793
|
+
paymentName: "CIMB Virtual Account",
|
|
794
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/cimb_va.png",
|
|
795
|
+
totalFee: "IDR 4,000",
|
|
796
|
+
category: "Virtual Account"
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
paymentMethod: "danamon_va",
|
|
800
|
+
paymentName: "Danamon Virtual Account",
|
|
801
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/danamon_va.png",
|
|
802
|
+
totalFee: "IDR 4,000",
|
|
803
|
+
category: "Virtual Account"
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
paymentMethod: "bsi_va",
|
|
807
|
+
paymentName: "BSI Virtual Account",
|
|
808
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/bsi_va.png",
|
|
809
|
+
totalFee: "IDR 4,000",
|
|
810
|
+
category: "Virtual Account"
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
paymentMethod: "seabank_va",
|
|
814
|
+
paymentName: "SeaBank Virtual Account",
|
|
815
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/seabank_va.png",
|
|
816
|
+
totalFee: "IDR 4,000",
|
|
817
|
+
category: "Virtual Account"
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
paymentMethod: "other_va",
|
|
821
|
+
paymentName: "Other Banks (ATM Bersama, Prima, Alto)",
|
|
822
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/other_va.png",
|
|
823
|
+
totalFee: "IDR 4,000",
|
|
824
|
+
category: "Virtual Account"
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
paymentMethod: "qris",
|
|
828
|
+
paymentName: "QRIS (GoPay, ShopeePay, Dana, LinkAja)",
|
|
829
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/qris.png",
|
|
830
|
+
totalFee: "0.7%",
|
|
831
|
+
category: "QRIS"
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
paymentMethod: "other_qris",
|
|
835
|
+
paymentName: "Other QRIS",
|
|
836
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/other_qris.png",
|
|
837
|
+
totalFee: "0.7%",
|
|
838
|
+
category: "QRIS"
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
paymentMethod: "gopay",
|
|
842
|
+
paymentName: "GoPay E-Wallet",
|
|
843
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/gopay.png",
|
|
844
|
+
totalFee: "2.0%",
|
|
845
|
+
category: "E-Wallet"
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
paymentMethod: "shopeepay",
|
|
849
|
+
paymentName: "ShopeePay E-Wallet",
|
|
850
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/shopeepay.png",
|
|
851
|
+
totalFee: "2.0%",
|
|
852
|
+
category: "E-Wallet"
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
paymentMethod: "ovo",
|
|
856
|
+
paymentName: "OVO E-Wallet",
|
|
857
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/ovo.png",
|
|
858
|
+
totalFee: "1.5%",
|
|
859
|
+
category: "E-Wallet"
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
paymentMethod: "dana",
|
|
863
|
+
paymentName: "DANA E-Wallet",
|
|
864
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/dana.png",
|
|
865
|
+
totalFee: "1.7%",
|
|
866
|
+
category: "E-Wallet"
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
paymentMethod: "linkaja",
|
|
870
|
+
paymentName: "LinkAja E-Wallet",
|
|
871
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/linkaja.png",
|
|
872
|
+
totalFee: "1.7%",
|
|
873
|
+
category: "E-Wallet"
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
paymentMethod: "indomaret",
|
|
877
|
+
paymentName: "Indomaret",
|
|
878
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/indomaret.png",
|
|
879
|
+
totalFee: "IDR 5,000",
|
|
880
|
+
category: "Retail / Gerai"
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
paymentMethod: "alfamart",
|
|
884
|
+
paymentName: "Alfamart / Alfamidi",
|
|
885
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/alfamart.png",
|
|
886
|
+
totalFee: "IDR 5,000",
|
|
887
|
+
category: "Retail / Gerai"
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
paymentMethod: "kredivo",
|
|
891
|
+
paymentName: "Kredivo Paylater",
|
|
892
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/kredivo.png",
|
|
893
|
+
totalFee: "2.3%",
|
|
894
|
+
category: "Paylater / Cicilan"
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
paymentMethod: "akulaku",
|
|
898
|
+
paymentName: "Akulaku Paylater",
|
|
899
|
+
paymentImage: "https://docs.midtrans.com/asset/payment_methods/akulaku.png",
|
|
900
|
+
totalFee: "1.7%",
|
|
901
|
+
category: "Paylater / Cicilan"
|
|
902
|
+
}
|
|
903
|
+
];
|
|
904
|
+
return {
|
|
905
|
+
success: true,
|
|
906
|
+
methods: staticMethods,
|
|
907
|
+
rawResponse: staticMethods
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
async checkTransaction(params, config) {
|
|
911
|
+
const { merchantOrderId } = params;
|
|
912
|
+
const { apiKey, sandbox } = config;
|
|
913
|
+
const url = `${this.getApiBaseUrl(sandbox)}/${merchantOrderId}/status`;
|
|
914
|
+
try {
|
|
915
|
+
const authHeader = `Basic ${Buffer.from(apiKey + ":").toString("base64")}`;
|
|
916
|
+
const response = await fetch(url, {
|
|
917
|
+
method: "GET",
|
|
918
|
+
headers: {
|
|
919
|
+
"Content-Type": "application/json",
|
|
920
|
+
"Accept": "application/json",
|
|
921
|
+
"Authorization": authHeader
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
const text = await response.text();
|
|
925
|
+
let data = null;
|
|
926
|
+
try {
|
|
927
|
+
data = JSON.parse(text);
|
|
928
|
+
} catch (e) {
|
|
929
|
+
}
|
|
930
|
+
if (!response.ok || !data) {
|
|
931
|
+
return {
|
|
932
|
+
success: false,
|
|
933
|
+
orderId: merchantOrderId,
|
|
934
|
+
reference: "",
|
|
935
|
+
amount: 0,
|
|
936
|
+
statusCode: "",
|
|
937
|
+
status: "failed",
|
|
938
|
+
statusMessage: `HTTP error! Status: ${response.status}`,
|
|
939
|
+
error: `HTTP ${response.status} - ${text}`,
|
|
940
|
+
rawResponse: data || text
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
const transactionStatus = data.transaction_status || "";
|
|
944
|
+
const fraudStatus = data.fraud_status || "";
|
|
945
|
+
let status = "pending";
|
|
946
|
+
if (transactionStatus === "capture" || transactionStatus === "settlement") {
|
|
947
|
+
if (fraudStatus === "accept" || !fraudStatus) {
|
|
948
|
+
status = "paid";
|
|
949
|
+
} else if (fraudStatus === "challenge") {
|
|
950
|
+
status = "pending";
|
|
951
|
+
} else {
|
|
952
|
+
status = "failed";
|
|
953
|
+
}
|
|
954
|
+
} else if (transactionStatus === "pending") {
|
|
955
|
+
status = "pending";
|
|
956
|
+
} else if (["deny", "cancel", "expire"].includes(transactionStatus)) {
|
|
957
|
+
status = "failed";
|
|
958
|
+
}
|
|
959
|
+
return {
|
|
960
|
+
success: true,
|
|
961
|
+
orderId: data.order_id || merchantOrderId,
|
|
962
|
+
reference: data.transaction_id || "",
|
|
963
|
+
amount: data.gross_amount ? Number(data.gross_amount) : 0,
|
|
964
|
+
statusCode: data.status_code || "",
|
|
965
|
+
status,
|
|
966
|
+
statusMessage: data.status_message || "",
|
|
967
|
+
rawResponse: data
|
|
968
|
+
};
|
|
969
|
+
} catch (e) {
|
|
970
|
+
return {
|
|
971
|
+
success: false,
|
|
972
|
+
orderId: merchantOrderId,
|
|
973
|
+
reference: "",
|
|
974
|
+
amount: 0,
|
|
975
|
+
statusCode: "",
|
|
976
|
+
status: "failed",
|
|
977
|
+
statusMessage: "Network error",
|
|
978
|
+
error: e.message || "Failed to check transaction status with Midtrans",
|
|
979
|
+
rawResponse: null
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Get an instance of MidtransClient to perform transaction actions, subscriptions, invoicing, etc.
|
|
985
|
+
*/
|
|
986
|
+
getClient(config) {
|
|
987
|
+
return new MidtransClient(config);
|
|
988
|
+
}
|
|
989
|
+
async probePaymentMethods(config) {
|
|
990
|
+
const { apiKey, sandbox } = config;
|
|
991
|
+
const baseUrl = sandbox ? "https://api.sandbox.midtrans.com/v2" : "https://api.midtrans.com/v2";
|
|
992
|
+
const auth = Buffer.from(apiKey + ":").toString("base64");
|
|
993
|
+
const probePayloads = {
|
|
994
|
+
qris: { payment_type: "qris", qris: { acquirer: "gopay" } },
|
|
995
|
+
gopay: { payment_type: "gopay", gopay: { enable_callback: true, callback_url: "https://example.com" } },
|
|
996
|
+
shopeepay: { payment_type: "shopeepay", shopeepay: { callback_url: "https://example.com" } },
|
|
997
|
+
bca: { payment_type: "bank_transfer", bank_transfer: { bank: "bca" } },
|
|
998
|
+
bni: { payment_type: "bank_transfer", bank_transfer: { bank: "bni" } },
|
|
999
|
+
bri: { payment_type: "bank_transfer", bank_transfer: { bank: "bri" } },
|
|
1000
|
+
cimb: { payment_type: "bank_transfer", bank_transfer: { bank: "cimb" } },
|
|
1001
|
+
mandiri: { payment_type: "echannel", echannel: { bill_info1: "Payment", bill_info2: "Probe" } },
|
|
1002
|
+
permata: { payment_type: "permata" },
|
|
1003
|
+
alfamart: { payment_type: "cstore", cstore: { store: "alfamart", message: "Probe" } },
|
|
1004
|
+
indomaret: { payment_type: "cstore", cstore: { store: "indomaret", message: "Probe" } },
|
|
1005
|
+
akulaku: { payment_type: "akulaku" },
|
|
1006
|
+
kredivo: {
|
|
1007
|
+
payment_type: "kredivo",
|
|
1008
|
+
seller_details: { address: { city: "Jakarta" } }
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
const enabled = [];
|
|
1012
|
+
for (const [methodId, specificPayload] of Object.entries(probePayloads)) {
|
|
1013
|
+
try {
|
|
1014
|
+
const probeOrderId = `PROBE-${methodId}-${Date.now()}`;
|
|
1015
|
+
const probeBody = {
|
|
1016
|
+
...specificPayload,
|
|
1017
|
+
transaction_details: { order_id: probeOrderId, gross_amount: 15e3 },
|
|
1018
|
+
item_details: [{ id: probeOrderId, name: "Probe", price: 15e3, quantity: 1 }],
|
|
1019
|
+
customer_details: { first_name: "Probe", email: "probe@test.com" }
|
|
1020
|
+
};
|
|
1021
|
+
const res = await fetch(`${baseUrl}/charge`, {
|
|
1022
|
+
method: "POST",
|
|
1023
|
+
headers: {
|
|
1024
|
+
"Content-Type": "application/json",
|
|
1025
|
+
"Accept": "application/json",
|
|
1026
|
+
"Authorization": `Basic ${auth}`
|
|
1027
|
+
},
|
|
1028
|
+
body: JSON.stringify(probeBody)
|
|
1029
|
+
});
|
|
1030
|
+
const text = await res.text();
|
|
1031
|
+
let result = null;
|
|
1032
|
+
try {
|
|
1033
|
+
result = JSON.parse(text);
|
|
1034
|
+
} catch (e) {
|
|
1035
|
+
}
|
|
1036
|
+
if (result && ["200", "201", "202"].includes(result.status_code)) {
|
|
1037
|
+
enabled.push(methodId);
|
|
1038
|
+
try {
|
|
1039
|
+
await fetch(`${baseUrl}/${probeOrderId}/cancel`, {
|
|
1040
|
+
method: "POST",
|
|
1041
|
+
headers: {
|
|
1042
|
+
"Accept": "application/json",
|
|
1043
|
+
"Authorization": `Basic ${auth}`
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
} catch (e) {
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
} catch (e) {
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return { success: true, enabled };
|
|
1053
|
+
}
|
|
99
1054
|
};
|
|
100
1055
|
|
|
101
1056
|
// src/index.ts
|
|
@@ -103,6 +1058,7 @@ var PaymentManager = class {
|
|
|
103
1058
|
providers = /* @__PURE__ */ new Map();
|
|
104
1059
|
constructor() {
|
|
105
1060
|
this.registerProvider(new DuitkuProvider());
|
|
1061
|
+
this.registerProvider(new MidtransProvider());
|
|
106
1062
|
}
|
|
107
1063
|
registerProvider(provider) {
|
|
108
1064
|
this.providers.set(provider.name.toLowerCase(), provider);
|
|
@@ -114,6 +1070,12 @@ var PaymentManager = class {
|
|
|
114
1070
|
}
|
|
115
1071
|
return provider;
|
|
116
1072
|
}
|
|
1073
|
+
getMidtransProvider() {
|
|
1074
|
+
return this.getProvider("midtrans");
|
|
1075
|
+
}
|
|
1076
|
+
getMidtransClient(config) {
|
|
1077
|
+
return new MidtransClient(config);
|
|
1078
|
+
}
|
|
117
1079
|
async createInvoice(providerName, params, config) {
|
|
118
1080
|
const provider = this.getProvider(providerName);
|
|
119
1081
|
return provider.createInvoice(params, config);
|
|
@@ -122,11 +1084,29 @@ var PaymentManager = class {
|
|
|
122
1084
|
const provider = this.getProvider(providerName);
|
|
123
1085
|
return provider.verifyCallback(body, config);
|
|
124
1086
|
}
|
|
1087
|
+
async getPaymentMethods(providerName, params, config) {
|
|
1088
|
+
const provider = this.getProvider(providerName);
|
|
1089
|
+
return provider.getPaymentMethods(params, config);
|
|
1090
|
+
}
|
|
1091
|
+
async checkTransaction(providerName, params, config) {
|
|
1092
|
+
const provider = this.getProvider(providerName);
|
|
1093
|
+
return provider.checkTransaction(params, config);
|
|
1094
|
+
}
|
|
1095
|
+
async probePaymentMethods(providerName, config) {
|
|
1096
|
+
const provider = this.getProvider(providerName);
|
|
1097
|
+
if (provider.probePaymentMethods) {
|
|
1098
|
+
return provider.probePaymentMethods(config);
|
|
1099
|
+
}
|
|
1100
|
+
return { success: false, enabled: [], error: `Provider '${providerName}' does not support payment methods probing` };
|
|
1101
|
+
}
|
|
125
1102
|
};
|
|
126
1103
|
var paymentManager = new PaymentManager();
|
|
127
1104
|
export {
|
|
128
1105
|
BasePaymentProvider,
|
|
129
1106
|
DuitkuProvider,
|
|
1107
|
+
MidtransClient,
|
|
1108
|
+
MidtransProvider,
|
|
130
1109
|
PaymentManager,
|
|
1110
|
+
getPaymentMethodCategory,
|
|
131
1111
|
paymentManager
|
|
132
1112
|
};
|