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