@crediblemark/buayar 0.4.0 → 0.5.1

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.
@@ -0,0 +1,643 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli/index.ts
27
+ var path2 = __toESM(require("path"));
28
+ var fs2 = __toESM(require("fs"));
29
+ var p2 = __toESM(require("@clack/prompts"));
30
+
31
+ // src/cli/templates.ts
32
+ var DOT_ENV_TEMPLATE = `# \u2500\u2500 @crediblemark/buayar \xB7 Konfigurasi \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
33
+ # Provider aktif (salah satu dari 19): midtrans, duitku, ipaymu, xendit, doku,
34
+ # prismalink, faspay, finpay, nicepay, oy, stripe, paypal, adyen, checkoutcom,
35
+ # razorpay, square, payu, braintree, twocheckout
36
+ PROVIDER_PG=midtrans
37
+
38
+ # Kredensial Universal (dipetakan otomatis per provider)
39
+ BUAYAR_API_KEY=your-server-key-atau-secret
40
+ BUAYAR_MERCHANT_CODE=merchant-id-atau-username
41
+ BUAYAR_SANDBOX=true
42
+
43
+ # Callback & Return URL
44
+ BUAYAR_CALLBACK_URL=http://localhost:3000/api/payment/webhook
45
+ BUAYAR_RETURN_URL=http://localhost:3000/payment/success
46
+
47
+ # \u2500\u2500 Kredensial Spesifik Provider (isi sesuai yang aktif saja) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
48
+ # MIDTRANS_SERVER_KEY=
49
+ # MIDTRANS_CLIENT_KEY=
50
+ # DUITKU_API_KEY=
51
+ # DUITKU_MERCHANT_CODE=
52
+ # IPAYMU_API_KEY=
53
+ # IPAYMU_VA=
54
+ # XENDIT_SECRET_KEY=
55
+ # XENDIT_WEBHOOK_TOKEN=
56
+ # DOKU_CLIENT_ID=
57
+ # DOKU_SECRET_KEY=
58
+ # PRISMALINK_MERCHANT_ID=
59
+ # PRISMALINK_SECRET_KEY=
60
+ # FASPAY_MERCHANT_ID=
61
+ # FASPAY_USER_ID=
62
+ # FASPAY_PASSWORD=
63
+ # FINPAY_MERCHANT_ID=
64
+ # FINPAY_MERCHANT_KEY=
65
+ # NICEPAY_IMID=
66
+ # NICEPAY_KEY=
67
+ # OY_USERNAME=
68
+ # OY_API_KEY=
69
+ # STRIPE_SECRET_KEY=
70
+ # STRIPE_PUBLIC_KEY=
71
+ # STRIPE_WEBHOOK_SECRET=
72
+ # PAYPAL_CLIENT_ID=
73
+ # PAYPAL_CLIENT_SECRET=
74
+ # PAYPAL_WEBHOOK_ID=
75
+ # ADYEN_API_KEY=
76
+ # ADYEN_MERCHANT_ACCOUNT=
77
+ # ADYEN_HMAC_KEY=
78
+ # CHECKOUTCOM_SECRET_KEY=
79
+ # CHECKOUTCOM_WEBHOOK_SECRET=
80
+ # RAZORPAY_KEY_ID=
81
+ # RAZORPAY_KEY_SECRET=
82
+ # RAZORPAY_WEBHOOK_SECRET=
83
+ # SQUARE_ACCESS_TOKEN=
84
+ # SQUARE_LOCATION_ID=
85
+ # PAYU_POS_ID=
86
+ # PAYU_MD5_KEY=
87
+ # BRAINTREE_MERCHANT_ID=
88
+ # BRAINTREE_PUBLIC_KEY=
89
+ # BRAINTREE_PRIVATE_KEY=
90
+ # TWOCHECKOUT_MERCHANT_CODE=
91
+ # TWOCHECKOUT_SECRET_KEY=
92
+ # TWOCHECKOUT_SECRET_WORD=
93
+ `;
94
+ var CONFIG_TEMPLATE = `// src/payment/buayar.ts
95
+ // Inisialisasi Buayar \u2014 baca otomatis dari process.env (.env)
96
+ import { buayar } from "@crediblemark/buayar";
97
+
98
+ export { buayar };
99
+
100
+ // Bila perlu inisialisasi programatik (menggantikan .env):
101
+ // export const buayar = new Buayar({
102
+ // provider: "midtrans",
103
+ // apiKey: "SB-Mid-server-xxxx",
104
+ // sandbox: true,
105
+ // });
106
+ `;
107
+ var SERVICE_TEMPLATE = `// src/payment/service.ts
108
+ // Service layer \u2014 satu-satunya tempat aplikasi berinteraksi dengan Buayar.
109
+ // Ganti provider cukup dengan ubah .env; kode ini tidak berubah.
110
+ import { buayar } from "./buayar";
111
+
112
+ export interface PaymentService {
113
+ createCheckout(input: {
114
+ orderId: string;
115
+ amount: number;
116
+ productDetails: string;
117
+ customer: { name: string; email: string; phone?: string };
118
+ paymentMethod?: string;
119
+ currency?: string;
120
+ }): Promise<any>;
121
+ getMethods(amount?: number): Promise<any>;
122
+ checkStatus(orderId: string): Promise<any>;
123
+ handleWebhook(payload: any, headers: Record<string, string | string[] | undefined>): Promise<any>;
124
+ refund(transactionId: string, amount?: number, reason?: string): Promise<any>;
125
+ checkBalance(): Promise<any>;
126
+ disburse(payload: {
127
+ externalId: string;
128
+ bankCode: string;
129
+ accountNumber: string;
130
+ amount: number;
131
+ accountHolderName?: string;
132
+ description?: string;
133
+ }): Promise<any>;
134
+ }
135
+
136
+ export const paymentService: PaymentService = {
137
+ async createCheckout({ orderId, amount, productDetails, customer, paymentMethod, currency }) {
138
+ return buayar.createInvoice({
139
+ orderId,
140
+ amount,
141
+ productDetails,
142
+ customer,
143
+ paymentMethod,
144
+ currency,
145
+ });
146
+ },
147
+
148
+ async getMethods(amount = 10000) {
149
+ return buayar.getPaymentMethods({ amount });
150
+ },
151
+
152
+ async checkStatus(orderId) {
153
+ return buayar.checkTransaction({ merchantOrderId: orderId });
154
+ },
155
+
156
+ async handleWebhook(payload, headers) {
157
+ return buayar.verifyWebhook(payload, headers);
158
+ },
159
+
160
+ async refund(transactionId, amount, reason) {
161
+ return buayar.refund({ transactionId, amount, reason });
162
+ },
163
+
164
+ async checkBalance() {
165
+ return buayar.checkBalance();
166
+ },
167
+
168
+ async disburse({ externalId, bankCode, accountNumber, amount, accountHolderName, description }) {
169
+ return buayar.disburse({
170
+ externalId,
171
+ bankCode,
172
+ accountNumber,
173
+ amount,
174
+ accountHolderName,
175
+ description,
176
+ });
177
+ },
178
+ };
179
+ `;
180
+ var ROUTES_EXPRESS_TEMPLATE = `// src/payment/routes/express.ts
181
+ // Controller + route untuk Express. Mount via:
182
+ // import { paymentRoutes } from "./payment/routes/express";
183
+ // app.use("/api/payment", paymentRoutes);
184
+ import { Router } from "express";
185
+ import { paymentService } from "../service";
186
+
187
+ export const paymentRoutes = Router();
188
+
189
+ // POST /api/payment/checkout \u2014 buat transaksi (redirect / direct)
190
+ paymentRoutes.post("/checkout", async (req, res) => {
191
+ const result = await paymentService.createCheckout(req.body);
192
+ res.status(result.success ? 200 : 400).json(result);
193
+ });
194
+
195
+ // GET /api/payment/methods \u2014 daftar channel pembayaran
196
+ paymentRoutes.get("/methods", async (req, res) => {
197
+ const amount = Number(req.query.amount) || 10000;
198
+ const result = await paymentService.getMethods(amount);
199
+ res.json(result);
200
+ });
201
+
202
+ // GET /api/payment/status/:orderId \u2014 cek status transaksi
203
+ paymentRoutes.get("/status/:orderId", async (req, res) => {
204
+ const result = await paymentService.checkStatus(req.params.orderId);
205
+ res.json(result);
206
+ });
207
+
208
+ // POST /api/payment/webhook \u2014 notifikasi dari payment gateway
209
+ paymentRoutes.post("/webhook", async (req, res) => {
210
+ const verification = await paymentService.handleWebhook(req.body, req.headers as any);
211
+ return verification.isValid
212
+ ? res.status(200).json({ status: "OK" })
213
+ : res.status(400).json({ error: "Invalid signature" });
214
+ });
215
+
216
+ // POST /api/payment/refund \u2014 refund transaksi
217
+ paymentRoutes.post("/refund", async (req, res) => {
218
+ const { transactionId, amount, reason } = req.body;
219
+ const result = await paymentService.refund(transactionId, amount, reason);
220
+ res.json(result);
221
+ });
222
+
223
+ // GET /api/payment/balance \u2014 cek saldo merchant
224
+ paymentRoutes.get("/balance", async (_req, res) => {
225
+ res.json(await paymentService.checkBalance());
226
+ });
227
+ `;
228
+ var ROUTES_HONO_TEMPLATE = `// src/payment/routes/hono.ts
229
+ // Controller + route untuk Hono. Mount via:
230
+ // app.route("/api/payment", paymentRoutes);
231
+ import { Hono } from "hono";
232
+ import { paymentService } from "../service";
233
+
234
+ export const paymentRoutes = new Hono();
235
+
236
+ paymentRoutes.post("/checkout", async (c) => {
237
+ const body = await c.req.json();
238
+ const result = await paymentService.createCheckout(body);
239
+ return c.json(result, result.success ? 200 : 400);
240
+ });
241
+
242
+ paymentRoutes.get("/methods", async (c) => {
243
+ const amount = Number(c.req.query("amount")) || 10000;
244
+ return c.json(await paymentService.getMethods(amount));
245
+ });
246
+
247
+ paymentRoutes.get("/status/:orderId", async (c) => {
248
+ return c.json(await paymentService.checkStatus(c.req.param("orderId")));
249
+ });
250
+
251
+ paymentRoutes.post("/webhook", async (c) => {
252
+ const payload = await c.req.json();
253
+ const headers = c.req.raw.headers;
254
+ const verification = await paymentService.handleWebhook(payload, Object.fromEntries(headers) as any);
255
+ return verification.isValid
256
+ ? c.json({ status: "OK" }, 200)
257
+ : c.json({ error: "Invalid signature" }, 400);
258
+ });
259
+
260
+ paymentRoutes.post("/refund", async (c) => {
261
+ const { transactionId, amount, reason } = await c.req.json();
262
+ return c.json(await paymentService.refund(transactionId, amount, reason));
263
+ });
264
+
265
+ paymentRoutes.get("/balance", async (c) => {
266
+ return c.json(await paymentService.checkBalance());
267
+ });
268
+ `;
269
+ var ROUTES_NEXTJS_TEMPLATE = `// src/app/api/payment/webhook/route.ts
270
+ // Route handler untuk Next.js App Router.
271
+ // Jalankan: npm run build && npm run start
272
+ import { NextResponse } from "next/server";
273
+ import { paymentService } from "@/payment/service";
274
+
275
+ export async function POST(request: Request) {
276
+ const payload = await request.json();
277
+ const headers: Record<string, string | string[] | undefined> = {};
278
+ request.headers.forEach((value, key) => {
279
+ headers[key] = value;
280
+ });
281
+
282
+ const verification = await paymentService.handleWebhook(payload, headers);
283
+
284
+ return verification.isValid
285
+ ? NextResponse.json({ status: "OK" })
286
+ : NextResponse.json({ error: "Invalid signature" }, { status: 400 });
287
+ }
288
+ `;
289
+ var README_PAYMENT_TEMPLATE = (framework, provider) => `# \u{1F4B3} Payment \u2014 @crediblemark/buayar
290
+
291
+ Scaffold dibuat otomatis oleh \`buayar init\`.
292
+
293
+ - **Provider aktif:** \`${provider}\` (ganti di \`.env\` \u2192 \`PROVIDER_PG\`)
294
+ - **Framework route:** \`${framework}\`
295
+
296
+ ## Struktur
297
+ \`\`\`
298
+ src/payment/
299
+ \u251C\u2500\u2500 buayar.ts # Inisialisasi Buayar (baca .env)
300
+ \u251C\u2500\u2500 service.ts # Service layer wrapper \u2014 semua interaksi lewat sini
301
+ \u2514\u2500\u2500 routes/ # Controller/route per framework
302
+ \`\`\`
303
+
304
+ ## Alur
305
+ 1. Salin \`.env.example\` ke \`.env\` dan isi kredensial.
306
+ 2. Mount route (lihat template framework di folder \`routes/\`).
307
+ 3. Frontend: panggil \`POST /api/payment/checkout\` \u2192 terima \`paymentUrl\` (redirect) atau \`vaNumber\`/\`qrString\` (direct).
308
+ 4. Notifikasi masuk ke \`POST /api/payment/webhook\` \u2192 diverifikasi otomatis.
309
+
310
+ ## Ganti Provider
311
+ Ubah \`PROVIDER_PG\` + kredensial di \`.env\`. Kode \`service.ts\`/route **tidak berubah**.
312
+
313
+ > Dokumentasi lengkap: https://github.com/crediblemark-official/Buayar#readme
314
+ `;
315
+ var TYPE_DECL_TEMPLATE = `// src/payment/types.ts
316
+ // Tipe-tipe hasil dari Buayar (dapat disesuaikan)
317
+ export interface CheckoutResult {
318
+ success: boolean;
319
+ provider?: string;
320
+ orderId?: string;
321
+ paymentUrl?: string;
322
+ reference?: string;
323
+ vaNumber?: string;
324
+ qrString?: string;
325
+ paymentCode?: string;
326
+ deeplink?: string;
327
+ error?: string;
328
+ }
329
+
330
+ export interface WebhookVerification {
331
+ isValid: boolean;
332
+ provider?: string;
333
+ orderId: string;
334
+ amount: number;
335
+ isPaid: boolean;
336
+ status: "paid" | "pending" | "failed" | "expired";
337
+ }
338
+ `;
339
+ var PROVIDERS = [
340
+ "midtrans",
341
+ "duitku",
342
+ "ipaymu",
343
+ "xendit",
344
+ "doku",
345
+ "prismalink",
346
+ "faspay",
347
+ "finpay",
348
+ "nicepay",
349
+ "oy",
350
+ "stripe",
351
+ "paypal",
352
+ "adyen",
353
+ "checkoutcom",
354
+ "razorpay",
355
+ "square",
356
+ "payu",
357
+ "braintree",
358
+ "twocheckout"
359
+ ];
360
+ var FRAMEWORKS = ["express", "hono", "nextjs"];
361
+ function getRouteTemplate(framework) {
362
+ switch (framework) {
363
+ case "hono":
364
+ return ROUTES_HONO_TEMPLATE;
365
+ case "nextjs":
366
+ return ROUTES_NEXTJS_TEMPLATE;
367
+ case "express":
368
+ default:
369
+ return ROUTES_EXPRESS_TEMPLATE;
370
+ }
371
+ }
372
+ function buildScaffold(provider, framework) {
373
+ const routeRel = framework === "nextjs" ? "src/app/api/payment/webhook/route.ts" : "src/payment/routes/index.ts";
374
+ return {
375
+ ".env.example": DOT_ENV_TEMPLATE,
376
+ "src/payment/buayar.ts": CONFIG_TEMPLATE,
377
+ "src/payment/service.ts": SERVICE_TEMPLATE,
378
+ "src/payment/types.ts": TYPE_DECL_TEMPLATE,
379
+ [routeRel]: getRouteTemplate(framework),
380
+ "README-PAYMENT.md": README_PAYMENT_TEMPLATE(framework, provider)
381
+ };
382
+ }
383
+
384
+ // src/cli/prompts.ts
385
+ var p = __toESM(require("@clack/prompts"));
386
+ async function selectPrompt(message, choices, initialValue) {
387
+ const result = await p.select({
388
+ message,
389
+ initialValue,
390
+ options: choices.map((c) => {
391
+ const opt = {
392
+ value: c.value,
393
+ label: c.label || c.value
394
+ };
395
+ if (c.hint) opt.hint = c.hint;
396
+ return opt;
397
+ })
398
+ });
399
+ if (p.isCancel(result)) {
400
+ p.cancel("Dibatalkan oleh user.");
401
+ process.exit(0);
402
+ }
403
+ return result;
404
+ }
405
+ async function confirmPrompt(message, initialValue = true) {
406
+ const result = await p.confirm({
407
+ message,
408
+ initialValue
409
+ });
410
+ if (p.isCancel(result)) {
411
+ p.cancel("Dibatalkan oleh user.");
412
+ process.exit(0);
413
+ }
414
+ return result;
415
+ }
416
+
417
+ // src/cli/scaffold.ts
418
+ var fs = __toESM(require("fs"));
419
+ var path = __toESM(require("path"));
420
+ function scaffold(targetDir, provider, framework, opts = {}) {
421
+ const files = buildScaffold(provider, framework);
422
+ const written = [];
423
+ const skipped = [];
424
+ const copied = [];
425
+ const errors = [];
426
+ if (!fs.existsSync(targetDir)) {
427
+ fs.mkdirSync(targetDir, { recursive: true });
428
+ }
429
+ for (const [relPath, content] of Object.entries(files)) {
430
+ const fullPath = path.join(targetDir, relPath);
431
+ const dir = path.dirname(fullPath);
432
+ try {
433
+ fs.mkdirSync(dir, { recursive: true });
434
+ if (fs.existsSync(fullPath) && !opts.overwrite) {
435
+ skipped.push(relPath);
436
+ continue;
437
+ }
438
+ fs.writeFileSync(fullPath, content, "utf8");
439
+ written.push(relPath);
440
+ } catch (e) {
441
+ errors.push(`${relPath}: ${e.message}`);
442
+ }
443
+ }
444
+ const envExample = path.join(targetDir, ".env.example");
445
+ const env = path.join(targetDir, ".env");
446
+ if (fs.existsSync(envExample) && !fs.existsSync(env)) {
447
+ try {
448
+ fs.copyFileSync(envExample, env);
449
+ copied.push(".env");
450
+ } catch (e) {
451
+ errors.push(`.env: ${e.message}`);
452
+ }
453
+ }
454
+ return { written, skipped, copied, errors };
455
+ }
456
+ function printScaffoldSummary(result) {
457
+ const color = (c, s) => process.stdout.isTTY ? `\x1B[${c}m${s}\x1B[0m` : s;
458
+ if (result.written.length) {
459
+ console.log(color("32", "\n\u2714 File dibuat:"));
460
+ for (const f of result.written) console.log(` ${color("36", f)}`);
461
+ }
462
+ if (result.copied.length) {
463
+ console.log(color("32", "\n\u21AA Disalin dari .env.example (karena .env belum ada):"));
464
+ for (const f of result.copied) console.log(` ${color("36", f)}`);
465
+ }
466
+ if (result.skipped.length) {
467
+ console.log(color("33", "\n\u2022 Dilewati (sudah ada, gunakan --force untuk menimpa):"));
468
+ for (const f of result.skipped) console.log(` ${color("36", f)}`);
469
+ }
470
+ if (result.errors.length) {
471
+ console.log(color("31", "\n\u2716 Error:"));
472
+ for (const e of result.errors) console.log(` ${color("31", e)}`);
473
+ }
474
+ }
475
+
476
+ // src/cli/index.ts
477
+ var VERSION = "0.5.0";
478
+ function parseArgs(argv) {
479
+ const flags = [];
480
+ const rest = [];
481
+ let i = 0;
482
+ while (i < argv.length) {
483
+ const a = argv[i];
484
+ if (a.startsWith("--")) {
485
+ const idx = a.indexOf("=");
486
+ if (idx !== -1) {
487
+ flags.push({ flag: a.slice(2, idx), value: a.slice(idx + 1) });
488
+ i++;
489
+ continue;
490
+ }
491
+ const name = a.slice(2);
492
+ const next = argv[i + 1];
493
+ if (next !== void 0 && !next.startsWith("-")) {
494
+ flags.push({ flag: name, value: next });
495
+ i += 2;
496
+ } else {
497
+ flags.push({ flag: name, value: void 0 });
498
+ i++;
499
+ }
500
+ } else if (a.startsWith("-") && a.length === 2) {
501
+ flags.push({ flag: a.slice(1), value: void 0 });
502
+ i++;
503
+ } else {
504
+ rest.push(a);
505
+ i++;
506
+ }
507
+ }
508
+ const command = rest.shift() || "init";
509
+ return { command, flags, rest };
510
+ }
511
+ function getFlag(flags, name) {
512
+ const found = flags.find((f) => f.flag === name);
513
+ if (found) {
514
+ if (found.value !== void 0) return found.value;
515
+ return "";
516
+ }
517
+ return void 0;
518
+ }
519
+ function hasFlag(flags, name) {
520
+ return flags.some((f) => f.flag === name);
521
+ }
522
+ function printHelp() {
523
+ console.log(`
524
+ @crediblemark/buayar \u2014 Unified Payment Gateway SDK
525
+
526
+ Usage:
527
+ buayar init Scaffold boilerplate (interaktif, arrow-key)
528
+ buayar init --yes Scaffold non-interaktif (defaults)
529
+ buayar init --out <dir> Direktori target (default: ./)
530
+ buayar init --provider <id> Paksa provider (default: midtrans)
531
+ buayar init --framework <fw> express | hono | nextjs (default: express)
532
+ buayar init --force Timpa file yang sudah ada
533
+ buayar --version | -v Tampilkan versi
534
+ buayar --help | -h Tampilkan bantuan
535
+
536
+ Contoh:
537
+ buayar init --yes --framework hono --provider xendit
538
+ `);
539
+ }
540
+ async function runInit(argv) {
541
+ const { flags } = parseArgs(argv);
542
+ const yes = hasFlag(flags, "yes");
543
+ const force = hasFlag(flags, "force");
544
+ const outRaw = getFlag(flags, "out");
545
+ const targetDir = outRaw ? outRaw === "" ? "./" : outRaw : "./";
546
+ const resolvedDir = path2.resolve(targetDir);
547
+ p2.intro("\u{1F40A} Buayar init \u2014 Unified Payment Gateway scaffold");
548
+ let provider = getFlag(flags, "provider") || void 0;
549
+ if (!provider && !yes) {
550
+ provider = await selectPrompt(
551
+ "Pilih provider aktif",
552
+ PROVIDERS.map((value) => ({
553
+ value,
554
+ hint: value === "midtrans" ? "populer di Indonesia" : void 0
555
+ })),
556
+ "midtrans"
557
+ );
558
+ }
559
+ provider = provider || "midtrans";
560
+ if (!PROVIDERS.includes(provider)) {
561
+ p2.cancel(`Provider tidak dikenal: "${provider}"`);
562
+ return 1;
563
+ }
564
+ let framework = getFlag(flags, "framework") || void 0;
565
+ if (!framework && !yes) {
566
+ framework = await selectPrompt(
567
+ "Pilih framework route",
568
+ [
569
+ { value: "express", hint: "Node.js REST API" },
570
+ { value: "hono", hint: "Ringan, edge-ready" },
571
+ { value: "nextjs", hint: "App Router" }
572
+ ],
573
+ "express"
574
+ );
575
+ }
576
+ framework = framework || "express";
577
+ if (!FRAMEWORKS.includes(framework)) {
578
+ p2.cancel(`Framework tidak dikenal: "${framework}"`);
579
+ return 1;
580
+ }
581
+ if (!yes && !fs2.existsSync(path2.join(resolvedDir, "package.json"))) {
582
+ const cont = await confirmPrompt(
583
+ `"${resolvedDir}" bukan proyek Node yang terlihat. Lanjutkan?`,
584
+ true
585
+ );
586
+ if (!cont) {
587
+ p2.outro("Dibatalkan.");
588
+ return 0;
589
+ }
590
+ }
591
+ if (!yes) {
592
+ const ok = await confirmPrompt(
593
+ `Scaffold ${framework} (provider ${provider}) ke "${resolvedDir}"?`,
594
+ true
595
+ );
596
+ if (!ok) {
597
+ p2.outro("Dibatalkan.");
598
+ return 0;
599
+ }
600
+ }
601
+ const result = scaffold(resolvedDir, provider, framework, { overwrite: force });
602
+ printScaffoldSummary(result);
603
+ p2.outro(`
604
+ Selesai! Langkah berikutnya:
605
+ 1. Salin .env.example ke .env & isi kredensial (PROVIDER_PG + key provider aktif).
606
+ 2. Mount route payment (lihat README-PAYMENT.md).
607
+ 3. Ganti provider cukup ubah .env \u2014 kode tidak berubah.
608
+ `);
609
+ return result.errors.length ? 1 : 0;
610
+ }
611
+ async function main() {
612
+ const argv = process.argv.slice(2);
613
+ const { command, flags } = parseArgs(argv);
614
+ if (argv.length === 0 || command === "init") {
615
+ if (hasFlag(flags, "version") || hasFlag(flags, "v")) {
616
+ console.log(VERSION);
617
+ return 0;
618
+ }
619
+ if (hasFlag(flags, "help") || hasFlag(flags, "h")) {
620
+ printHelp();
621
+ return 0;
622
+ }
623
+ return runInit(argv);
624
+ }
625
+ if (command === "--version" || command === "-v" || command === "version") {
626
+ console.log(VERSION);
627
+ return 0;
628
+ }
629
+ if (command === "--help" || command === "-h" || command === "help") {
630
+ printHelp();
631
+ return 0;
632
+ }
633
+ console.error(`
634
+ \u2716 Perintah tidak dikenal: "${command}"`);
635
+ printHelp();
636
+ return 1;
637
+ }
638
+ main().then((code) => {
639
+ process.exitCode = code;
640
+ }).catch((e) => {
641
+ console.error(e);
642
+ process.exitCode = 1;
643
+ });
package/dist/index.js CHANGED
@@ -2130,11 +2130,11 @@ var XenditProvider = class extends BasePaymentProvider {
2130
2130
  amount: integerAmount,
2131
2131
  reference_id: orderId,
2132
2132
  description: productDetails,
2133
- customer: {
2134
- given_names: customer.name,
2135
- email: customer.email,
2136
- mobile_number: customer.phone || ""
2137
- },
2133
+ // Xendit Payment Requests API rejects the inline `customer` object
2134
+ // with API_VALIDATION_ERROR. Attribution is only supported via a
2135
+ // pre-created `customer_id` (Customer API), so we forward that when
2136
+ // provided through providerParams and otherwise omit customer data.
2137
+ ...params.providerParams?.customer_id ? { customer_id: params.providerParams.customer_id } : {},
2138
2138
  payment_method: paymentMethodPayload,
2139
2139
  ...params.providerParams
2140
2140
  };
package/dist/index.mjs CHANGED
@@ -1980,11 +1980,11 @@ var XenditProvider = class extends BasePaymentProvider {
1980
1980
  amount: integerAmount,
1981
1981
  reference_id: orderId,
1982
1982
  description: productDetails,
1983
- customer: {
1984
- given_names: customer.name,
1985
- email: customer.email,
1986
- mobile_number: customer.phone || ""
1987
- },
1983
+ // Xendit Payment Requests API rejects the inline `customer` object
1984
+ // with API_VALIDATION_ERROR. Attribution is only supported via a
1985
+ // pre-created `customer_id` (Customer API), so we forward that when
1986
+ // provided through providerParams and otherwise omit customer data.
1987
+ ...params.providerParams?.customer_id ? { customer_id: params.providerParams.customer_id } : {},
1988
1988
  payment_method: paymentMethodPayload,
1989
1989
  ...params.providerParams
1990
1990
  };
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@crediblemark/buayar",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Unified Payment Gateway SDK for Node.js & TypeScript — 19 providers (Midtrans, Xendit, Duitku, Stripe, PayPal, Adyen, Razorpay, Square, Checkout.com, PayU, Braintree, 2Checkout, DOKU, iPaymu, PrismaLink, Faspay, Finpay, Nicepay, OY!) 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
+ "bin": {
9
+ "buayar": "./dist/cli/index.js"
10
+ },
8
11
  "exports": {
9
12
  ".": {
10
13
  "types": "./dist/index.d.ts",
@@ -68,14 +71,19 @@
68
71
  },
69
72
  "license": "MIT",
70
73
  "scripts": {
71
- "build": "tsup src/index.ts --format cjs,esm --dts",
72
- "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
74
+ "build": "tsup src/index.ts --format cjs,esm --dts && tsup src/cli/index.ts --format cjs --platform node --out-dir dist/cli && node -e \"const fs=require('fs');const f='dist/cli/index.js';const s=fs.readFileSync(f,'utf8');if(!s.startsWith('#!'))fs.writeFileSync(f,'#!/usr/bin/env node\\n'+s);fs.chmodSync(f,0o755);\"",
75
+ "dev": "tsup src/index.ts src/cli/index.ts --format cjs,esm --dts --out-dir dist --watch",
76
+ "init": "node ./dist/cli/index.js init",
77
+ "postinstall": "echo \"🐊 Buayar terpasang! Jalankan \\\"npx buayar init\\\" untuk membuat boilerplate payment yang siap pakai.\"",
78
+ "test": "bun test"
73
79
  },
74
80
  "devDependencies": {
75
81
  "@types/bun": "^1.4.0",
76
82
  "@types/node": "^24.12.2",
77
83
  "tsup": "^8.5.1",
78
84
  "typescript": "^5.9.3"
85
+ },
86
+ "dependencies": {
87
+ "@clack/prompts": "^1.7.0"
79
88
  }
80
89
  }
81
-