@crediblemark/buayar 0.1.5 → 0.1.6
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 +58 -1
- package/dist/index.d.ts +58 -1
- package/dist/index.js +187 -9
- package/dist/index.mjs +186 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,13 +10,16 @@ Dengan SDK ini, Anda cukup menulis kode satu kali menggunakan struktur API yang
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
## 🚀 Fitur Utama
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
- 🔄 **Unified API**: Satu antarmuka (interface) terpadu untuk semua provider payment gateway.
|
|
15
|
+
- ⚡ **Direct Payment Support**: Otomatis mendeteksi `paymentMethod` dan beralih ke Direct Inquiry API (v2/inquiry) Duitku untuk mengembalikan `vaNumber`, `qrString` (EMVCo), dan `paymentCode` secara instan tanpa redirect eksternal.
|
|
16
|
+
- 🏷️ **Pre-Categorization (Accordion Ready)**: Menyediakan pengelompokan pembayaran bawaan (`Virtual Account`, `QRIS`, `E-Wallet`, `Retail / Gerai`, `Lainnya`) langsung dari API response untuk memudahkan implementasi UI accordion.
|
|
17
|
+
- 🎨 **Headless SDK Philosophy**: Dirancang murni sebagai core logic & data manager tanpa overhead UI/styling. Memberikan kebebasan penuh bagi pengembang untuk mendesain UI/Tailwind/dark mode di tingkat aplikasi.
|
|
15
18
|
- 🛡️ **Tipe Data Kuat (TypeScript)**: Dilengkapi dengan deklarasi tipe data lengkap untuk mencegah *runtime error*.
|
|
16
19
|
- 🇮🇩 **Dukungan Lokal**: Siap digunakan untuk transaksi di Indonesia (seperti Duitku).
|
|
17
20
|
- ⚙️ **Modular & Dapat Diperluas**: Memungkinkan penambahan provider baru dengan mewarisi kelas base yang disediakan.
|
|
18
21
|
- 🔒 **Otomatisasi Signature**: Keamanan transaksi terjamin dengan pembuatan *hash* signature (MD5, SHA-256) otomatis secara internal.
|
|
19
|
-
|
|
22
|
+
|
|
20
23
|
---
|
|
21
24
|
|
|
22
25
|
## 📦 Instalasi
|
|
@@ -218,11 +221,26 @@ interface InvoiceResponse {
|
|
|
218
221
|
success: boolean;
|
|
219
222
|
paymentUrl?: string;
|
|
220
223
|
reference?: string;
|
|
224
|
+
vaNumber?: string; // Nomor Virtual Account (untuk metode Bank VA)
|
|
225
|
+
qrString?: string; // EMVCo QRIS payload mentah (untuk scan langsung)
|
|
226
|
+
qrCodeUrl?: string; // Image URL QRIS dari Duitku
|
|
227
|
+
paymentCode?: string; // Kode pembayaran retail (Indomaret/Alfamart)
|
|
221
228
|
rawResponse: any;
|
|
222
229
|
error?: string;
|
|
223
230
|
}
|
|
224
231
|
```
|
|
225
232
|
|
|
233
|
+
### `PaymentMethod`
|
|
234
|
+
```typescript
|
|
235
|
+
interface PaymentMethod {
|
|
236
|
+
paymentMethod: string;
|
|
237
|
+
paymentName: string;
|
|
238
|
+
paymentImage: string;
|
|
239
|
+
totalFee: string;
|
|
240
|
+
category: "Virtual Account" | "QRIS" | "E-Wallet" | "Retail / Gerai" | "Lainnya";
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
226
244
|
### `VerifyCallbackResult`
|
|
227
245
|
```typescript
|
|
228
246
|
interface VerifyCallbackResult {
|
|
@@ -236,6 +254,19 @@ interface VerifyCallbackResult {
|
|
|
236
254
|
|
|
237
255
|
---
|
|
238
256
|
|
|
257
|
+
## 🛠️ Utilitas Tambahan
|
|
258
|
+
|
|
259
|
+
### `getPaymentMethodCategory(code, name)`
|
|
260
|
+
Mengkategorikan kode dan nama metode pembayaran Duitku ke kategori terstandarisasi untuk UI Accordion.
|
|
261
|
+
```typescript
|
|
262
|
+
import { getPaymentMethodCategory } from "@crediblemark/buayar";
|
|
263
|
+
|
|
264
|
+
const cat = getPaymentMethodCategory("BT", "Permata VA");
|
|
265
|
+
// Output: "Virtual Account"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
239
270
|
## 📄 Lisensi
|
|
240
271
|
|
|
241
272
|
Proyek ini dilisensikan di bawah **MIT License**. Hak Cipta © 2026 Rasyiqi Crediblemark.
|
package/dist/index.d.mts
CHANGED
|
@@ -9,11 +9,21 @@ interface CreateInvoiceParams {
|
|
|
9
9
|
};
|
|
10
10
|
returnUrl: string;
|
|
11
11
|
callbackUrl: string;
|
|
12
|
+
/** Specific Duitku payment method code (e.g. "BCA", "I1", "OV"). If omitted, all methods are available via Duitku's redirect page. */
|
|
13
|
+
paymentMethod?: string;
|
|
12
14
|
}
|
|
13
15
|
interface InvoiceResponse {
|
|
14
16
|
success: boolean;
|
|
15
17
|
paymentUrl?: string;
|
|
16
18
|
reference?: string;
|
|
19
|
+
/** Virtual Account number (for bank VA methods) */
|
|
20
|
+
vaNumber?: string;
|
|
21
|
+
/** QRIS string for display in a QR code */
|
|
22
|
+
qrString?: string;
|
|
23
|
+
/** Duitku-hosted QR code image URL */
|
|
24
|
+
qrCodeUrl?: string;
|
|
25
|
+
/** Generic payment code (for retail/minimarket methods like Indomaret) */
|
|
26
|
+
paymentCode?: string;
|
|
17
27
|
rawResponse: any;
|
|
18
28
|
error?: string;
|
|
19
29
|
}
|
|
@@ -29,19 +39,64 @@ interface ProviderConfig {
|
|
|
29
39
|
apiKey: string;
|
|
30
40
|
sandbox: boolean;
|
|
31
41
|
}
|
|
42
|
+
interface GetPaymentMethodsParams {
|
|
43
|
+
/** Transaction amount (integer, no decimal) */
|
|
44
|
+
amount: number;
|
|
45
|
+
}
|
|
46
|
+
interface PaymentMethod {
|
|
47
|
+
/** Provider-specific method code, e.g. "BT", "I1", "OL" */
|
|
48
|
+
paymentMethod: string;
|
|
49
|
+
/** Human-readable name, e.g. "BCA Virtual Account" */
|
|
50
|
+
paymentName: string;
|
|
51
|
+
/** URL to payment method logo/image */
|
|
52
|
+
paymentImage: string;
|
|
53
|
+
/** Total transaction fee in IDR (string from API) */
|
|
54
|
+
totalFee: string;
|
|
55
|
+
/** Categorized payment channel (Virtual Account, QRIS, E-Wallet, Retail / Gerai, Kartu Kredit, Paylater / Cicilan, Lainnya) */
|
|
56
|
+
category: "Virtual Account" | "QRIS" | "E-Wallet" | "Retail / Gerai" | "Kartu Kredit" | "Paylater / Cicilan" | "Lainnya";
|
|
57
|
+
}
|
|
58
|
+
interface GetPaymentMethodsResult {
|
|
59
|
+
success: boolean;
|
|
60
|
+
methods: PaymentMethod[];
|
|
61
|
+
error?: string;
|
|
62
|
+
rawResponse: any;
|
|
63
|
+
}
|
|
64
|
+
interface CheckTransactionParams {
|
|
65
|
+
/** The merchant order ID used when creating the invoice */
|
|
66
|
+
merchantOrderId: string;
|
|
67
|
+
}
|
|
68
|
+
interface CheckTransactionResult {
|
|
69
|
+
success: boolean;
|
|
70
|
+
orderId: string;
|
|
71
|
+
reference: string;
|
|
72
|
+
amount: number;
|
|
73
|
+
/** "00" = success/paid, "01" = pending, "02" = failed/expired */
|
|
74
|
+
statusCode: string;
|
|
75
|
+
status: "paid" | "pending" | "failed";
|
|
76
|
+
statusMessage: string;
|
|
77
|
+
error?: string;
|
|
78
|
+
rawResponse: any;
|
|
79
|
+
}
|
|
32
80
|
|
|
33
81
|
declare abstract class BasePaymentProvider {
|
|
34
82
|
abstract readonly name: string;
|
|
35
83
|
abstract createInvoice(params: CreateInvoiceParams, config: ProviderConfig): Promise<InvoiceResponse>;
|
|
36
84
|
abstract verifyCallback(body: any, config: ProviderConfig): Promise<VerifyCallbackResult>;
|
|
85
|
+
abstract getPaymentMethods(params: GetPaymentMethodsParams, config: ProviderConfig): Promise<GetPaymentMethodsResult>;
|
|
86
|
+
abstract checkTransaction(params: CheckTransactionParams, config: ProviderConfig): Promise<CheckTransactionResult>;
|
|
37
87
|
}
|
|
38
88
|
|
|
39
89
|
declare class DuitkuProvider extends BasePaymentProvider {
|
|
40
90
|
readonly name = "duitku";
|
|
91
|
+
private getBaseUrl;
|
|
41
92
|
createInvoice(params: CreateInvoiceParams, config: ProviderConfig): Promise<InvoiceResponse>;
|
|
42
93
|
verifyCallback(body: any, config: ProviderConfig): Promise<VerifyCallbackResult>;
|
|
94
|
+
getPaymentMethods(params: GetPaymentMethodsParams, config: ProviderConfig): Promise<GetPaymentMethodsResult>;
|
|
95
|
+
checkTransaction(params: CheckTransactionParams, config: ProviderConfig): Promise<CheckTransactionResult>;
|
|
43
96
|
}
|
|
44
97
|
|
|
98
|
+
declare function getPaymentMethodCategory(code: string, name?: string): "Virtual Account" | "QRIS" | "E-Wallet" | "Retail / Gerai" | "Kartu Kredit" | "Paylater / Cicilan" | "Lainnya";
|
|
99
|
+
|
|
45
100
|
declare class PaymentManager {
|
|
46
101
|
private providers;
|
|
47
102
|
constructor();
|
|
@@ -49,7 +104,9 @@ declare class PaymentManager {
|
|
|
49
104
|
getProvider(name: string): BasePaymentProvider;
|
|
50
105
|
createInvoice(providerName: string, params: CreateInvoiceParams, config: ProviderConfig): Promise<InvoiceResponse>;
|
|
51
106
|
verifyCallback(providerName: string, body: any, config: ProviderConfig): Promise<VerifyCallbackResult>;
|
|
107
|
+
getPaymentMethods(providerName: string, params: GetPaymentMethodsParams, config: ProviderConfig): Promise<GetPaymentMethodsResult>;
|
|
108
|
+
checkTransaction(providerName: string, params: CheckTransactionParams, config: ProviderConfig): Promise<CheckTransactionResult>;
|
|
52
109
|
}
|
|
53
110
|
declare const paymentManager: PaymentManager;
|
|
54
111
|
|
|
55
|
-
export { BasePaymentProvider, type CreateInvoiceParams, DuitkuProvider, type InvoiceResponse, PaymentManager, type ProviderConfig, type VerifyCallbackResult, paymentManager };
|
|
112
|
+
export { BasePaymentProvider, type CheckTransactionParams, type CheckTransactionResult, type CreateInvoiceParams, DuitkuProvider, type GetPaymentMethodsParams, type GetPaymentMethodsResult, type InvoiceResponse, PaymentManager, type PaymentMethod, type ProviderConfig, type VerifyCallbackResult, getPaymentMethodCategory, paymentManager };
|
package/dist/index.d.ts
CHANGED
|
@@ -9,11 +9,21 @@ interface CreateInvoiceParams {
|
|
|
9
9
|
};
|
|
10
10
|
returnUrl: string;
|
|
11
11
|
callbackUrl: string;
|
|
12
|
+
/** Specific Duitku payment method code (e.g. "BCA", "I1", "OV"). If omitted, all methods are available via Duitku's redirect page. */
|
|
13
|
+
paymentMethod?: string;
|
|
12
14
|
}
|
|
13
15
|
interface InvoiceResponse {
|
|
14
16
|
success: boolean;
|
|
15
17
|
paymentUrl?: string;
|
|
16
18
|
reference?: string;
|
|
19
|
+
/** Virtual Account number (for bank VA methods) */
|
|
20
|
+
vaNumber?: string;
|
|
21
|
+
/** QRIS string for display in a QR code */
|
|
22
|
+
qrString?: string;
|
|
23
|
+
/** Duitku-hosted QR code image URL */
|
|
24
|
+
qrCodeUrl?: string;
|
|
25
|
+
/** Generic payment code (for retail/minimarket methods like Indomaret) */
|
|
26
|
+
paymentCode?: string;
|
|
17
27
|
rawResponse: any;
|
|
18
28
|
error?: string;
|
|
19
29
|
}
|
|
@@ -29,19 +39,64 @@ interface ProviderConfig {
|
|
|
29
39
|
apiKey: string;
|
|
30
40
|
sandbox: boolean;
|
|
31
41
|
}
|
|
42
|
+
interface GetPaymentMethodsParams {
|
|
43
|
+
/** Transaction amount (integer, no decimal) */
|
|
44
|
+
amount: number;
|
|
45
|
+
}
|
|
46
|
+
interface PaymentMethod {
|
|
47
|
+
/** Provider-specific method code, e.g. "BT", "I1", "OL" */
|
|
48
|
+
paymentMethod: string;
|
|
49
|
+
/** Human-readable name, e.g. "BCA Virtual Account" */
|
|
50
|
+
paymentName: string;
|
|
51
|
+
/** URL to payment method logo/image */
|
|
52
|
+
paymentImage: string;
|
|
53
|
+
/** Total transaction fee in IDR (string from API) */
|
|
54
|
+
totalFee: string;
|
|
55
|
+
/** Categorized payment channel (Virtual Account, QRIS, E-Wallet, Retail / Gerai, Kartu Kredit, Paylater / Cicilan, Lainnya) */
|
|
56
|
+
category: "Virtual Account" | "QRIS" | "E-Wallet" | "Retail / Gerai" | "Kartu Kredit" | "Paylater / Cicilan" | "Lainnya";
|
|
57
|
+
}
|
|
58
|
+
interface GetPaymentMethodsResult {
|
|
59
|
+
success: boolean;
|
|
60
|
+
methods: PaymentMethod[];
|
|
61
|
+
error?: string;
|
|
62
|
+
rawResponse: any;
|
|
63
|
+
}
|
|
64
|
+
interface CheckTransactionParams {
|
|
65
|
+
/** The merchant order ID used when creating the invoice */
|
|
66
|
+
merchantOrderId: string;
|
|
67
|
+
}
|
|
68
|
+
interface CheckTransactionResult {
|
|
69
|
+
success: boolean;
|
|
70
|
+
orderId: string;
|
|
71
|
+
reference: string;
|
|
72
|
+
amount: number;
|
|
73
|
+
/** "00" = success/paid, "01" = pending, "02" = failed/expired */
|
|
74
|
+
statusCode: string;
|
|
75
|
+
status: "paid" | "pending" | "failed";
|
|
76
|
+
statusMessage: string;
|
|
77
|
+
error?: string;
|
|
78
|
+
rawResponse: any;
|
|
79
|
+
}
|
|
32
80
|
|
|
33
81
|
declare abstract class BasePaymentProvider {
|
|
34
82
|
abstract readonly name: string;
|
|
35
83
|
abstract createInvoice(params: CreateInvoiceParams, config: ProviderConfig): Promise<InvoiceResponse>;
|
|
36
84
|
abstract verifyCallback(body: any, config: ProviderConfig): Promise<VerifyCallbackResult>;
|
|
85
|
+
abstract getPaymentMethods(params: GetPaymentMethodsParams, config: ProviderConfig): Promise<GetPaymentMethodsResult>;
|
|
86
|
+
abstract checkTransaction(params: CheckTransactionParams, config: ProviderConfig): Promise<CheckTransactionResult>;
|
|
37
87
|
}
|
|
38
88
|
|
|
39
89
|
declare class DuitkuProvider extends BasePaymentProvider {
|
|
40
90
|
readonly name = "duitku";
|
|
91
|
+
private getBaseUrl;
|
|
41
92
|
createInvoice(params: CreateInvoiceParams, config: ProviderConfig): Promise<InvoiceResponse>;
|
|
42
93
|
verifyCallback(body: any, config: ProviderConfig): Promise<VerifyCallbackResult>;
|
|
94
|
+
getPaymentMethods(params: GetPaymentMethodsParams, config: ProviderConfig): Promise<GetPaymentMethodsResult>;
|
|
95
|
+
checkTransaction(params: CheckTransactionParams, config: ProviderConfig): Promise<CheckTransactionResult>;
|
|
43
96
|
}
|
|
44
97
|
|
|
98
|
+
declare function getPaymentMethodCategory(code: string, name?: string): "Virtual Account" | "QRIS" | "E-Wallet" | "Retail / Gerai" | "Kartu Kredit" | "Paylater / Cicilan" | "Lainnya";
|
|
99
|
+
|
|
45
100
|
declare class PaymentManager {
|
|
46
101
|
private providers;
|
|
47
102
|
constructor();
|
|
@@ -49,7 +104,9 @@ declare class PaymentManager {
|
|
|
49
104
|
getProvider(name: string): BasePaymentProvider;
|
|
50
105
|
createInvoice(providerName: string, params: CreateInvoiceParams, config: ProviderConfig): Promise<InvoiceResponse>;
|
|
51
106
|
verifyCallback(providerName: string, body: any, config: ProviderConfig): Promise<VerifyCallbackResult>;
|
|
107
|
+
getPaymentMethods(providerName: string, params: GetPaymentMethodsParams, config: ProviderConfig): Promise<GetPaymentMethodsResult>;
|
|
108
|
+
checkTransaction(providerName: string, params: CheckTransactionParams, config: ProviderConfig): Promise<CheckTransactionResult>;
|
|
52
109
|
}
|
|
53
110
|
declare const paymentManager: PaymentManager;
|
|
54
111
|
|
|
55
|
-
export { BasePaymentProvider, type CreateInvoiceParams, DuitkuProvider, type InvoiceResponse, PaymentManager, type ProviderConfig, type VerifyCallbackResult, paymentManager };
|
|
112
|
+
export { BasePaymentProvider, type CheckTransactionParams, type CheckTransactionResult, type CreateInvoiceParams, DuitkuProvider, type GetPaymentMethodsParams, type GetPaymentMethodsResult, type InvoiceResponse, PaymentManager, type PaymentMethod, type ProviderConfig, type VerifyCallbackResult, getPaymentMethodCategory, paymentManager };
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
BasePaymentProvider: () => BasePaymentProvider,
|
|
34
34
|
DuitkuProvider: () => DuitkuProvider,
|
|
35
35
|
PaymentManager: () => PaymentManager,
|
|
36
|
+
getPaymentMethodCategory: () => getPaymentMethodCategory,
|
|
36
37
|
paymentManager: () => paymentManager
|
|
37
38
|
});
|
|
38
39
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -44,13 +45,30 @@ var import_crypto = __toESM(require("crypto"));
|
|
|
44
45
|
var BasePaymentProvider = class {
|
|
45
46
|
};
|
|
46
47
|
|
|
48
|
+
// src/utils.ts
|
|
49
|
+
function getPaymentMethodCategory(code, name = "") {
|
|
50
|
+
const c = code.toUpperCase();
|
|
51
|
+
const n = name.toUpperCase();
|
|
52
|
+
if (c.includes("QR") || n.includes("QRIS")) return "QRIS";
|
|
53
|
+
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";
|
|
54
|
+
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";
|
|
55
|
+
if (c.startsWith("FT") || c.startsWith("AL") || n.includes("INDOMARET") || n.includes("ALFAMART") || n.includes("RETAIL")) return "Retail / Gerai";
|
|
56
|
+
if (c.startsWith("VC") || n.includes("CREDIT CARD") || n.includes("KARTU KREDIT")) return "Kartu Kredit";
|
|
57
|
+
if (n.includes("PAYLATER") || n.includes("INDODANA") || n.includes("AKULAKU") || n.includes("KREDIVO")) return "Paylater / Cicilan";
|
|
58
|
+
return "Lainnya";
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
// src/providers/duitku.ts
|
|
48
62
|
var DuitkuProvider = class extends BasePaymentProvider {
|
|
49
63
|
name = "duitku";
|
|
64
|
+
getBaseUrl(sandbox) {
|
|
65
|
+
return sandbox ? "https://api-sandbox.duitku.com" : "https://api-prod.duitku.com";
|
|
66
|
+
}
|
|
50
67
|
async createInvoice(params, config) {
|
|
51
68
|
const { orderId, amount, productDetails, customer, returnUrl, callbackUrl } = params;
|
|
52
69
|
const { merchantCode, apiKey, sandbox } = config;
|
|
53
|
-
const
|
|
70
|
+
const isDirectInquiry = !!params.paymentMethod;
|
|
71
|
+
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
72
|
const integerAmount = Math.round(amount);
|
|
55
73
|
const rawPayloadSignature = merchantCode + orderId + integerAmount.toString() + apiKey;
|
|
56
74
|
const payloadSignature = import_crypto.default.createHash("md5").update(rawPayloadSignature).digest("hex");
|
|
@@ -67,19 +85,23 @@ var DuitkuProvider = class extends BasePaymentProvider {
|
|
|
67
85
|
signature: payloadSignature,
|
|
68
86
|
callbackUrl,
|
|
69
87
|
returnUrl,
|
|
70
|
-
expiryPeriod: 1440
|
|
88
|
+
expiryPeriod: 1440,
|
|
71
89
|
// 24 hours expiry
|
|
90
|
+
...params.paymentMethod ? { paymentMethod: params.paymentMethod } : {}
|
|
72
91
|
};
|
|
73
92
|
try {
|
|
93
|
+
const headers = {
|
|
94
|
+
"Content-Type": "application/json",
|
|
95
|
+
"Accept": "application/json"
|
|
96
|
+
};
|
|
97
|
+
if (!isDirectInquiry) {
|
|
98
|
+
headers["x-duitku-signature"] = headerSignature;
|
|
99
|
+
headers["x-duitku-timestamp"] = timestamp;
|
|
100
|
+
headers["x-duitku-merchantcode"] = merchantCode;
|
|
101
|
+
}
|
|
74
102
|
const response = await fetch(url, {
|
|
75
103
|
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
|
-
},
|
|
104
|
+
headers,
|
|
83
105
|
body: JSON.stringify(payload)
|
|
84
106
|
});
|
|
85
107
|
const text = await response.text();
|
|
@@ -100,6 +122,10 @@ var DuitkuProvider = class extends BasePaymentProvider {
|
|
|
100
122
|
success: true,
|
|
101
123
|
paymentUrl: data.paymentUrl,
|
|
102
124
|
reference: data.reference,
|
|
125
|
+
vaNumber: data.vaNumber,
|
|
126
|
+
qrString: data.qrString,
|
|
127
|
+
qrCodeUrl: data.qrCodeUrl,
|
|
128
|
+
paymentCode: data.paymentCode,
|
|
103
129
|
rawResponse: data
|
|
104
130
|
};
|
|
105
131
|
} else {
|
|
@@ -135,6 +161,149 @@ var DuitkuProvider = class extends BasePaymentProvider {
|
|
|
135
161
|
rawPayload: body
|
|
136
162
|
};
|
|
137
163
|
}
|
|
164
|
+
async getPaymentMethods(params, config) {
|
|
165
|
+
const { amount } = params;
|
|
166
|
+
const { merchantCode, apiKey, sandbox } = config;
|
|
167
|
+
const url = sandbox ? "https://sandbox.duitku.com/webapi/api/merchant/paymentmethod/getpaymentmethod" : "https://passport.duitku.com/webapi/api/merchant/paymentmethod/getpaymentmethod";
|
|
168
|
+
const integerAmount = Math.round(amount);
|
|
169
|
+
const datetime = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
170
|
+
const stringToSign = merchantCode + integerAmount.toString() + datetime;
|
|
171
|
+
const signature = import_crypto.default.createHmac("sha256", apiKey).update(stringToSign).digest("hex");
|
|
172
|
+
try {
|
|
173
|
+
const response = await fetch(url, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: {
|
|
176
|
+
"Content-Type": "application/json"
|
|
177
|
+
},
|
|
178
|
+
body: JSON.stringify({
|
|
179
|
+
merchantcode: merchantCode,
|
|
180
|
+
amount: integerAmount,
|
|
181
|
+
datetime,
|
|
182
|
+
signature
|
|
183
|
+
})
|
|
184
|
+
});
|
|
185
|
+
const text = await response.text();
|
|
186
|
+
let data = null;
|
|
187
|
+
try {
|
|
188
|
+
data = JSON.parse(text);
|
|
189
|
+
} catch (e) {
|
|
190
|
+
}
|
|
191
|
+
if (!response.ok || !data) {
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
methods: [],
|
|
195
|
+
rawResponse: data,
|
|
196
|
+
error: data?.responseMessage || `HTTP error! Status: ${response.status}`
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (data.responseCode === "00") {
|
|
200
|
+
const rawMethods = data.paymentFee || [];
|
|
201
|
+
const methods = rawMethods.map((m) => ({
|
|
202
|
+
paymentMethod: m.paymentMethod,
|
|
203
|
+
paymentName: m.paymentName,
|
|
204
|
+
paymentImage: m.paymentImage,
|
|
205
|
+
totalFee: m.totalFee,
|
|
206
|
+
category: getPaymentMethodCategory(m.paymentMethod, m.paymentName)
|
|
207
|
+
}));
|
|
208
|
+
return {
|
|
209
|
+
success: true,
|
|
210
|
+
methods,
|
|
211
|
+
rawResponse: data
|
|
212
|
+
};
|
|
213
|
+
} else {
|
|
214
|
+
return {
|
|
215
|
+
success: false,
|
|
216
|
+
methods: [],
|
|
217
|
+
rawResponse: data,
|
|
218
|
+
error: data.responseMessage || `Duitku Error: ${data.responseCode}`
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
} catch (e) {
|
|
222
|
+
return {
|
|
223
|
+
success: false,
|
|
224
|
+
methods: [],
|
|
225
|
+
rawResponse: null,
|
|
226
|
+
error: e.message || "Failed to get payment methods from Duitku"
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async checkTransaction(params, config) {
|
|
231
|
+
const { merchantOrderId } = params;
|
|
232
|
+
const { merchantCode, apiKey, sandbox } = config;
|
|
233
|
+
const url = sandbox ? "https://api-sandbox.duitku.com/api/merchant/transactionStatus" : "https://api-prod.duitku.com/api/merchant/transactionStatus";
|
|
234
|
+
const timestamp = Date.now().toString();
|
|
235
|
+
const rawHeaderSignature = merchantCode + timestamp + apiKey;
|
|
236
|
+
const headerSignature = import_crypto.default.createHash("sha256").update(rawHeaderSignature).digest("hex");
|
|
237
|
+
const rawBodySignature = merchantCode + merchantOrderId + apiKey;
|
|
238
|
+
const bodySignature = import_crypto.default.createHash("md5").update(rawBodySignature).digest("hex");
|
|
239
|
+
try {
|
|
240
|
+
const response = await fetch(url, {
|
|
241
|
+
method: "POST",
|
|
242
|
+
headers: {
|
|
243
|
+
"Content-Type": "application/json",
|
|
244
|
+
"Accept": "application/json",
|
|
245
|
+
"x-duitku-signature": headerSignature,
|
|
246
|
+
"x-duitku-timestamp": timestamp,
|
|
247
|
+
"x-duitku-merchantcode": merchantCode
|
|
248
|
+
},
|
|
249
|
+
body: JSON.stringify({
|
|
250
|
+
merchantCode,
|
|
251
|
+
merchantOrderId,
|
|
252
|
+
signature: bodySignature
|
|
253
|
+
})
|
|
254
|
+
});
|
|
255
|
+
const text = await response.text();
|
|
256
|
+
let data = null;
|
|
257
|
+
try {
|
|
258
|
+
data = JSON.parse(text);
|
|
259
|
+
} catch (e) {
|
|
260
|
+
}
|
|
261
|
+
if (!response.ok || !data) {
|
|
262
|
+
return {
|
|
263
|
+
success: false,
|
|
264
|
+
orderId: merchantOrderId,
|
|
265
|
+
reference: "",
|
|
266
|
+
amount: 0,
|
|
267
|
+
statusCode: "",
|
|
268
|
+
status: "failed",
|
|
269
|
+
statusMessage: `HTTP error! Status: ${response.status}`,
|
|
270
|
+
error: `HTTP ${response.status}`,
|
|
271
|
+
rawResponse: data
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const statusCode = data.statusCode || "";
|
|
275
|
+
let status;
|
|
276
|
+
if (statusCode === "00") {
|
|
277
|
+
status = "paid";
|
|
278
|
+
} else if (statusCode === "01") {
|
|
279
|
+
status = "pending";
|
|
280
|
+
} else {
|
|
281
|
+
status = "failed";
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
success: true,
|
|
285
|
+
orderId: data.merchantOrderId || merchantOrderId,
|
|
286
|
+
reference: data.reference || "",
|
|
287
|
+
amount: data.amount ? Number(data.amount) : 0,
|
|
288
|
+
statusCode,
|
|
289
|
+
status,
|
|
290
|
+
statusMessage: data.statusMessage || "",
|
|
291
|
+
rawResponse: data
|
|
292
|
+
};
|
|
293
|
+
} catch (e) {
|
|
294
|
+
return {
|
|
295
|
+
success: false,
|
|
296
|
+
orderId: merchantOrderId,
|
|
297
|
+
reference: "",
|
|
298
|
+
amount: 0,
|
|
299
|
+
statusCode: "",
|
|
300
|
+
status: "failed",
|
|
301
|
+
statusMessage: "Network error",
|
|
302
|
+
error: e.message || "Failed to check transaction status",
|
|
303
|
+
rawResponse: null
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
138
307
|
};
|
|
139
308
|
|
|
140
309
|
// src/index.ts
|
|
@@ -161,6 +330,14 @@ var PaymentManager = class {
|
|
|
161
330
|
const provider = this.getProvider(providerName);
|
|
162
331
|
return provider.verifyCallback(body, config);
|
|
163
332
|
}
|
|
333
|
+
async getPaymentMethods(providerName, params, config) {
|
|
334
|
+
const provider = this.getProvider(providerName);
|
|
335
|
+
return provider.getPaymentMethods(params, config);
|
|
336
|
+
}
|
|
337
|
+
async checkTransaction(providerName, params, config) {
|
|
338
|
+
const provider = this.getProvider(providerName);
|
|
339
|
+
return provider.checkTransaction(params, config);
|
|
340
|
+
}
|
|
164
341
|
};
|
|
165
342
|
var paymentManager = new PaymentManager();
|
|
166
343
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -168,5 +345,6 @@ var paymentManager = new PaymentManager();
|
|
|
168
345
|
BasePaymentProvider,
|
|
169
346
|
DuitkuProvider,
|
|
170
347
|
PaymentManager,
|
|
348
|
+
getPaymentMethodCategory,
|
|
171
349
|
paymentManager
|
|
172
350
|
});
|
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,149 @@ 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
|
+
}
|
|
99
267
|
};
|
|
100
268
|
|
|
101
269
|
// src/index.ts
|
|
@@ -122,11 +290,20 @@ var PaymentManager = class {
|
|
|
122
290
|
const provider = this.getProvider(providerName);
|
|
123
291
|
return provider.verifyCallback(body, config);
|
|
124
292
|
}
|
|
293
|
+
async getPaymentMethods(providerName, params, config) {
|
|
294
|
+
const provider = this.getProvider(providerName);
|
|
295
|
+
return provider.getPaymentMethods(params, config);
|
|
296
|
+
}
|
|
297
|
+
async checkTransaction(providerName, params, config) {
|
|
298
|
+
const provider = this.getProvider(providerName);
|
|
299
|
+
return provider.checkTransaction(params, config);
|
|
300
|
+
}
|
|
125
301
|
};
|
|
126
302
|
var paymentManager = new PaymentManager();
|
|
127
303
|
export {
|
|
128
304
|
BasePaymentProvider,
|
|
129
305
|
DuitkuProvider,
|
|
130
306
|
PaymentManager,
|
|
307
|
+
getPaymentMethodCategory,
|
|
131
308
|
paymentManager
|
|
132
309
|
};
|
package/package.json
CHANGED