@crediblemark/buayar 0.8.5 → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -7
- package/dist/cli/index.js +7 -3
- package/dist/index.d.mts +132 -8
- package/dist/index.d.ts +132 -8
- package/dist/index.js +593 -22
- package/dist/index.mjs +586 -22
- package/docs/AUDIT-BUG-DAN-PREMATURE.md +166 -0
- package/docs/guide.md +30 -4
- package/docs/ipaymu.md +22 -7
- package/docs/sumopod.md +359 -0
- package/package.json +5 -4
package/docs/sumopod.md
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# 💳 Panduan Integrasi SumoPod di Buayar
|
|
2
|
+
|
|
3
|
+
Dokumentasi lengkap mengenai integrasi payment gateway **SumoPod** pada SDK Buayar ([`@crediblemark/buayar`](https://github.com/crediblemark-official/Buayar)), mencakup pembuatan link pembayaran (Payment Links), kanal QRIS, pengecekan status, dan verifikasi webhook ganda (**Svix HMAC-SHA256** dan **X-Webhook-Token**).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📑 Daftar Isi
|
|
8
|
+
|
|
9
|
+
1. [Fitur & Kemampuan](#-fitur--kemampuan)
|
|
10
|
+
2. [Konfigurasi Lingkungan (.env)](#-konfigurasi-lingkungan-env)
|
|
11
|
+
3. [Inisialisasi SDK](#-inisialisasi-sdk)
|
|
12
|
+
4. [Membuat Transaksi Pembayaran](#-membuat-transaksi-pembayaran)
|
|
13
|
+
5. [Mendapatkan Metode Pembayaran (QRIS)](#-mendapatkan-metode-pembayaran-qris)
|
|
14
|
+
6. [Mengecek Status Transaksi](#-mengecek-status-transaksi)
|
|
15
|
+
7. [Penanganan Webhook Universal](#-penanganan-webhook-universal)
|
|
16
|
+
8. [Menggunakan Direct Client (`SumopodClient`)](#-menggunakan-direct-client-sumopodclient)
|
|
17
|
+
9. [Contoh Implementasi Webhook (Express, Hono, Next.js)](#-contoh-implementasi-webhook)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 🚀 Fitur & Kemampuan
|
|
22
|
+
|
|
23
|
+
- ⚡ **Host API**:
|
|
24
|
+
- **Live / Production**: `https://api-pay.sumopod.com`
|
|
25
|
+
- **Sandbox / Testing**: `https://api-pay-sandbox.sumopod.com`
|
|
26
|
+
- 📱 **Metode Pembayaran**: **QRIS** (Settlement 2 hari, Biaya Transaksi: `0.7% + Rp 300`)
|
|
27
|
+
- 🏦 **Virtual Account / Payment Code**: Otomatis mendeteksi kode bayar/VA (misal `BRI.VA`, `ACCOUNT_NUMBER`) pada response transaksi.
|
|
28
|
+
- 🔗 **Mode Pembayaran**: Hosted Payment Link / QRIS Checkout Page
|
|
29
|
+
- 🪝 **Dukungan Webhook Ganda**:
|
|
30
|
+
- **Svix Signature** (`svix-id`, `svix-timestamp`, `svix-signature` HMAC-SHA256) dengan proteksi replay attack (5 menit) dan toleransi rotasi secret.
|
|
31
|
+
- **X-Webhook-Token** (`x-webhook-token`) dengan perbandingan konstan waktu (*timing-safe*).
|
|
32
|
+
- 🔄 **Zero-Code PG Switcher**: Ganti provider ke/dari SumoPod cukup dengan mengubah variabel `.env`.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## ⚙️ Konfigurasi Lingkungan (.env)
|
|
37
|
+
|
|
38
|
+
### 1. Variabel Universal (`BUAYAR_*`) — **Direkomendasikan**
|
|
39
|
+
|
|
40
|
+
```env
|
|
41
|
+
# Aktifkan SumoPod sebagai provider
|
|
42
|
+
BUAYAR_PROVIDER=sumopod
|
|
43
|
+
|
|
44
|
+
# Mode Sandbox:
|
|
45
|
+
# true -> Mengarah ke https://api-pay-sandbox.sumopod.com
|
|
46
|
+
# false -> Mengarah ke https://api-pay.sumopod.com (Production)
|
|
47
|
+
BUAYAR_SANDBOX=true
|
|
48
|
+
|
|
49
|
+
# API Key SumoPod dari dashboard Anda
|
|
50
|
+
BUAYAR_API_KEY=your_sumopod_api_key_here
|
|
51
|
+
|
|
52
|
+
# Signing secret webhook Svix (dimulai dengan whsec_...)
|
|
53
|
+
BUAYAR_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
54
|
+
|
|
55
|
+
# ATAU Token webhook (dimulai dengan whtok_...)
|
|
56
|
+
BUAYAR_WEBHOOK_TOKEN=whtok_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
57
|
+
|
|
58
|
+
# URL callback dan redirect
|
|
59
|
+
BUAYAR_CALLBACK_URL=https://domain-anda.com/api/payment/webhook
|
|
60
|
+
BUAYAR_RETURN_URL=https://domain-anda.com/payment/success
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. Variabel Spesifik SumoPod (Opsional)
|
|
64
|
+
|
|
65
|
+
Jika Anda ingin menggunakan format env khusus SumoPod:
|
|
66
|
+
|
|
67
|
+
```env
|
|
68
|
+
SUMOPOD_API_KEY=your_sumopod_api_key_here
|
|
69
|
+
SUMOPOD_SANDBOX=true
|
|
70
|
+
SUMOPOD_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
71
|
+
SUMOPOD_WEBHOOK_TOKEN=whtok_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 🚀 Inisialisasi SDK
|
|
77
|
+
|
|
78
|
+
### Menggunakan Singleton Global (Membaca `.env` Otomatis)
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { buayar } from "@crediblemark/buayar";
|
|
82
|
+
|
|
83
|
+
// Siap langsung digunakan
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Inisialisasi Instance Manual
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { Buayar } from "@crediblemark/buayar";
|
|
90
|
+
|
|
91
|
+
const buayar = new Buayar({
|
|
92
|
+
provider: "sumopod",
|
|
93
|
+
apiKey: "your_sumopod_api_key_here",
|
|
94
|
+
extra: {
|
|
95
|
+
webhookSecret: "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
96
|
+
webhookToken: "whtok_xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 🛒 Membuat Transaksi Pembayaran
|
|
104
|
+
|
|
105
|
+
Panggil `createInvoice()` untuk membuat payment link SumoPod:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const invoice = await buayar.createInvoice({
|
|
109
|
+
orderId: "INV-2026-001",
|
|
110
|
+
amount: 50000,
|
|
111
|
+
productDetails: "Langganan Premium 1 Bulan",
|
|
112
|
+
customer: {
|
|
113
|
+
name: "Budi Pratama",
|
|
114
|
+
email: "budi@example.com",
|
|
115
|
+
phone: "081234567890",
|
|
116
|
+
},
|
|
117
|
+
// Opsional: override URL per transaksi
|
|
118
|
+
returnUrl: "https://domain-anda.com/payment/success",
|
|
119
|
+
// Opsional: kustomisasi durasi kedaluwarsa (jam, 1-24)
|
|
120
|
+
providerParams: {
|
|
121
|
+
expires_in_hours: 24,
|
|
122
|
+
cancel_return_url: "https://domain-anda.com/payment/cancel",
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (invoice.success) {
|
|
127
|
+
console.log("Payment URL:", invoice.paymentUrl);
|
|
128
|
+
// https://pay.sumopod.com/pay/uuid-xxxx
|
|
129
|
+
console.log("Payment ID:", invoice.reference);
|
|
130
|
+
console.log("Kode Bayar / VA:", invoice.paymentCode || invoice.vaNumber);
|
|
131
|
+
console.log("Bank VA:", invoice.vaBank); // misal: "bri" jika mode VA
|
|
132
|
+
console.log("Mode:", invoice.mode); // "va" | "qris" | "checkout"
|
|
133
|
+
console.log("Kedaluwarsa pada:", invoice.expiresAt);
|
|
134
|
+
|
|
135
|
+
// Redirect pelanggan ke invoice.paymentUrl atau tampilkan nomor VA langsung
|
|
136
|
+
} else {
|
|
137
|
+
console.error("Gagal membuat invoice:", invoice.error);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 📱 Mendapatkan Metode Pembayaran (QRIS)
|
|
144
|
+
|
|
145
|
+
SumoPod menyediakan kanal pembayaran QRIS nasional:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const methods = await buayar.getPaymentMethods({ amount: 50000 });
|
|
149
|
+
|
|
150
|
+
if (methods.success) {
|
|
151
|
+
console.log(methods.methods);
|
|
152
|
+
/* Output:
|
|
153
|
+
[
|
|
154
|
+
{
|
|
155
|
+
paymentMethod: "qris",
|
|
156
|
+
code: "QRIS",
|
|
157
|
+
paymentName: "QRIS",
|
|
158
|
+
paymentImage: "https://upload.wikimedia.org/wikipedia/commons/a/a2/Logo_QRIS.svg",
|
|
159
|
+
totalFee: "0.7% + Rp 300",
|
|
160
|
+
category: "QRIS",
|
|
161
|
+
feeDetail: {
|
|
162
|
+
percent: 0.7,
|
|
163
|
+
flat: 300,
|
|
164
|
+
totalFee: 650
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
*/
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 🔍 Mengecek Status Transaksi
|
|
175
|
+
|
|
176
|
+
Untuk mengecek status transaksi yang sudah dibuat:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const status = await buayar.checkTransaction({
|
|
180
|
+
merchantOrderId: "INV-2026-001", // atau payment_id
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (status.success) {
|
|
184
|
+
console.log("Status:", status.status); // "paid" | "pending" | "failed" | "expired"
|
|
185
|
+
console.log("Apakah sudah terbayar?", status.isPaid);
|
|
186
|
+
console.log("Nominal:", status.amount);
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 🪝 Penanganan Webhook Universal
|
|
193
|
+
|
|
194
|
+
SumoPod mengirimkan HTTP `POST` webhook untuk setiap perubahan status pembayaran.
|
|
195
|
+
|
|
196
|
+
### Struktur Payload Masuk dari SumoPod
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"event_type": "payment.completed",
|
|
201
|
+
"data": {
|
|
202
|
+
"payment_id": "uuid-xxxx",
|
|
203
|
+
"order_id": "INV-2026-001",
|
|
204
|
+
"amount": 50000,
|
|
205
|
+
"fee": 750,
|
|
206
|
+
"net_amount": 49250,
|
|
207
|
+
"status": "completed",
|
|
208
|
+
"payment_method": "qris",
|
|
209
|
+
"completed_at": "2026-06-18T12:00:00Z"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Event yang Didukung:
|
|
215
|
+
| Event | Status Buayar | Deskripsi |
|
|
216
|
+
|---|---|---|
|
|
217
|
+
| `payment.completed` | `paid` (`isPaid: true`) | Pembayaran berhasil |
|
|
218
|
+
| `payment.failed` | `failed` (`isFailed: true`) | Pembayaran gagal |
|
|
219
|
+
| `payment.expired` | `expired` (`isExpired: true`) | Tagihan kedaluwarsa |
|
|
220
|
+
| `payment.test` | `pending` (`isPending: true`) | Event pengujian dari Dashboard |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 🌐 Contoh Implementasi Webhook
|
|
225
|
+
|
|
226
|
+
### 1. Express.js
|
|
227
|
+
|
|
228
|
+
> **PENTING untuk Verifikasi Svix:**
|
|
229
|
+
> Header Svix membutuhkan string raw body asli tanpa modifikasi spasi. Gunakan `express.raw({ type: "application/json" })` atau `express.json({ verify: ... })`.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import express from "express";
|
|
233
|
+
import { buayar } from "@crediblemark/buayar";
|
|
234
|
+
|
|
235
|
+
const app = express();
|
|
236
|
+
|
|
237
|
+
app.post(
|
|
238
|
+
"/api/payment/webhook",
|
|
239
|
+
express.raw({ type: "application/json" }),
|
|
240
|
+
async (req, res) => {
|
|
241
|
+
const rawBody = req.body.toString("utf8");
|
|
242
|
+
const payload = JSON.parse(rawBody);
|
|
243
|
+
|
|
244
|
+
// Kirim payload dan headers (svix-id, svix-timestamp, svix-signature, atau x-webhook-token)
|
|
245
|
+
const result = await buayar.verifyWebhook(payload, req.headers, {
|
|
246
|
+
extra: { rawBody },
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (!result.isValid) {
|
|
250
|
+
return res.status(401).send("Invalid signature or token");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (result.isPaid) {
|
|
254
|
+
console.log(`Order ${result.orderId} senilai Rp ${result.amount} sukses terbayar!`);
|
|
255
|
+
// Update status pesanan di database Anda
|
|
256
|
+
} else if (result.isExpired) {
|
|
257
|
+
console.log(`Order ${result.orderId} kedaluwarsa.`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return res.status(200).send("OK");
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### 2. Hono
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { Hono } from "hono";
|
|
269
|
+
import { buayar } from "@crediblemark/buayar";
|
|
270
|
+
|
|
271
|
+
const app = new Hono();
|
|
272
|
+
|
|
273
|
+
app.post("/api/payment/webhook", async (c) => {
|
|
274
|
+
const rawBody = await c.req.text();
|
|
275
|
+
const payload = JSON.parse(rawBody);
|
|
276
|
+
const headers = c.req.header();
|
|
277
|
+
|
|
278
|
+
const result = await buayar.verifyWebhook(payload, headers, {
|
|
279
|
+
extra: { rawBody },
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
if (!result.isValid) {
|
|
283
|
+
return c.text("Invalid webhook signature", 401);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (result.isPaid) {
|
|
287
|
+
// Proses pembayaran sukses
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return c.text("OK", 200);
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### 3. Next.js App Router (`src/app/api/payment/webhook/route.ts`)
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { NextResponse } from "next/server";
|
|
298
|
+
import { buayar } from "@crediblemark/buayar";
|
|
299
|
+
|
|
300
|
+
export async function POST(request: Request) {
|
|
301
|
+
const rawBody = await request.text();
|
|
302
|
+
const payload = JSON.parse(rawBody);
|
|
303
|
+
|
|
304
|
+
const headers: Record<string, string> = {};
|
|
305
|
+
request.headers.forEach((val, key) => {
|
|
306
|
+
headers[key] = val;
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const result = await buayar.verifyWebhook(payload, headers, {
|
|
310
|
+
extra: { rawBody },
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
if (!result.isValid) {
|
|
314
|
+
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (result.isPaid) {
|
|
318
|
+
// Update order status di database
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return NextResponse.json({ status: "OK" });
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## 🛠️ Menggunakan Direct Client (`SumopodClient`)
|
|
328
|
+
|
|
329
|
+
Jika Anda memerlukan akses langsung tanpa melalui abstraksi universal:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { SumopodClient } from "@crediblemark/buayar";
|
|
333
|
+
|
|
334
|
+
const client = new SumopodClient({
|
|
335
|
+
apiKey: process.env.SUMOPOD_API_KEY,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// 1. Buat transaksi
|
|
339
|
+
const payment = await client.createPayment({
|
|
340
|
+
order_id: "ORD-999",
|
|
341
|
+
amount: 25000,
|
|
342
|
+
currency: "IDR",
|
|
343
|
+
expires_in_hours: 12,
|
|
344
|
+
payment_method_type_code: "QRIS",
|
|
345
|
+
});
|
|
346
|
+
console.log("Payment URL:", payment.payment_link_url);
|
|
347
|
+
|
|
348
|
+
// 2. Ambil detail pembayaran
|
|
349
|
+
const detail = await client.getPayment(payment.payment_id);
|
|
350
|
+
console.log("Status terkini:", detail.status);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## 🔒 Catatan Keamanan
|
|
356
|
+
|
|
357
|
+
1. **Replay Attack Protection**: Verifikasi Svix otomatis menolak webhook jika timestamp berbeda lebih dari 5 menit (300 detik) dari waktu server.
|
|
358
|
+
2. **Timing-Safe Comparison**: Perbandingan signature HMAC dan `x-webhook-token` dilakukan menggunakan `safeCompare` berbasis `crypto.timingSafeEqual` untuk mencegah kerentanan timing attack.
|
|
359
|
+
3. **Secret Rotation**: Saat Anda melakukan rotasi webhook secret di dashboard SumoPod, header `svix-signature` dapat memuat beberapa signature sekaligus. SDK Buayar secara otomatis memverifikasi seluruh kandidat signature.
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crediblemark/buayar",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "Unified Payment Gateway SDK for Node.js & TypeScript —
|
|
3
|
+
"version": "0.8.7",
|
|
4
|
+
"description": "Unified Payment Gateway SDK for Node.js & TypeScript — 20 providers (Midtrans, Xendit, Duitku, Stripe, PayPal, Adyen, Razorpay, Square, Checkout.com, PayU, Braintree, 2Checkout, DOKU, iPaymu, PrismaLink, Faspay, Finpay, Nicepay, OY!, SumoPod) with zero-code switching via .env",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"buayar": "
|
|
9
|
+
"buayar": "dist/cli/index.js"
|
|
10
10
|
},
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"finpay",
|
|
41
41
|
"nicepay",
|
|
42
42
|
"oy-indonesia",
|
|
43
|
+
"sumopod",
|
|
43
44
|
"virtual-account",
|
|
44
45
|
"qris",
|
|
45
46
|
"ewallet",
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
},
|
|
65
66
|
"repository": {
|
|
66
67
|
"type": "git",
|
|
67
|
-
"url": "https://github.com/crediblemark-official/Buayar.git"
|
|
68
|
+
"url": "git+https://github.com/crediblemark-official/Buayar.git"
|
|
68
69
|
},
|
|
69
70
|
"publishConfig": {
|
|
70
71
|
"access": "public"
|