@crediblemark/buayar 0.4.0 → 0.6.0

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.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
  };
@@ -2413,12 +2413,218 @@ Digest:${digest}`;
2413
2413
  return safeCompare(incomingSignature, expectedSignature);
2414
2414
  }
2415
2415
 
2416
+ // src/providers/doku/snap.ts
2417
+ import crypto3 from "crypto";
2418
+ function snapTimestamp(date = /* @__PURE__ */ new Date()) {
2419
+ const wib = new Date(date.getTime() + 7 * 60 * 60 * 1e3);
2420
+ return wib.toISOString().slice(0, 19) + "+07:00";
2421
+ }
2422
+ function minifyJson(obj) {
2423
+ return JSON.stringify(obj);
2424
+ }
2425
+ function sha256Hex(body) {
2426
+ const raw = typeof body === "string" ? body : minifyJson(body);
2427
+ return crypto3.createHash("sha256").update(raw).digest("hex").toLowerCase();
2428
+ }
2429
+ function generateSnapSymmetricSignature(clientSecret, method, endpointUrl, accessToken, body, timestamp) {
2430
+ const hash = sha256Hex(body);
2431
+ const stringToSign = `${method}:${endpointUrl}:${accessToken}:${hash}:${timestamp}`;
2432
+ return crypto3.createHmac("sha512", clientSecret).update(stringToSign).digest("base64");
2433
+ }
2434
+ function generateSnapAsymmetricSignature(privateKey, clientId, timestamp) {
2435
+ const stringToSign = `${clientId}|${timestamp}`;
2436
+ const key = crypto3.createPrivateKey(normalizePem(privateKey));
2437
+ return crypto3.sign("RSA-SHA256", Buffer.from(stringToSign, "utf8"), key).toString("base64");
2438
+ }
2439
+ function normalizePem(key) {
2440
+ const trimmed = key.trim();
2441
+ if (trimmed.includes("-----BEGIN")) {
2442
+ return trimmed;
2443
+ }
2444
+ return [
2445
+ "-----BEGIN PRIVATE KEY-----",
2446
+ trimmed.replace(/\s+/g, "").match(/.{1,64}/g)?.join("\n") || trimmed,
2447
+ "-----END PRIVATE KEY-----"
2448
+ ].join("\n");
2449
+ }
2450
+ function snapExternalId(prefix = "") {
2451
+ return `${prefix}${Date.now()}${Math.floor(Math.random() * 1e3)}`;
2452
+ }
2453
+ function verifySnapWebhookSignature(headers, body, clientSecret, endpointUrl = "/api/payment/webhook") {
2454
+ if (!clientSecret) return false;
2455
+ const incoming = headers["x-signature"] || headers["X-SIGNATURE"] || headers["signature"] || headers["Signature"] || "";
2456
+ if (!incoming) return false;
2457
+ const timestamp = headers["x-timestamp"] || headers["X-TIMESTAMP"] || headers["timestamp"] || headers["Timestamp"] || snapTimestamp();
2458
+ const computed = generateSnapSymmetricSignature(
2459
+ clientSecret,
2460
+ "POST",
2461
+ endpointUrl,
2462
+ "",
2463
+ body,
2464
+ timestamp
2465
+ );
2466
+ return safeCompare(incoming, computed);
2467
+ }
2468
+
2469
+ // src/clients/snap.ts
2470
+ var SnapClient = class {
2471
+ options;
2472
+ token = null;
2473
+ tokenExpiry = 0;
2474
+ constructor(options) {
2475
+ this.options = {
2476
+ clientId: options.clientId || options.merchantCode || "",
2477
+ clientSecret: options.clientSecret || options.apiKey || options.serverKey || options.secretKey || "",
2478
+ privateKey: options.privateKey,
2479
+ sandbox: !!options.sandbox,
2480
+ merchantId: options.merchantId,
2481
+ terminalId: options.terminalId,
2482
+ partnerServiceId: options.partnerServiceId,
2483
+ channelId: options.channelId || "H2H"
2484
+ };
2485
+ }
2486
+ get baseUrl() {
2487
+ return this.options.sandbox ? "https://api-sandbox.doku.com" : "https://api.doku.com";
2488
+ }
2489
+ get isConfigured() {
2490
+ return Boolean(this.options.clientId && this.options.clientSecret);
2491
+ }
2492
+ /**
2493
+ * Get (cached) B2B access token. Mints a new one when expired.
2494
+ */
2495
+ async getAccessToken() {
2496
+ const now = Date.now();
2497
+ if (this.token && now < this.tokenExpiry) {
2498
+ return this.token;
2499
+ }
2500
+ const { clientId, clientSecret, privateKey } = this.options;
2501
+ if (!privateKey) {
2502
+ throw new Error("DOKU SNAP: RSA privateKey required to obtain B2B access token");
2503
+ }
2504
+ const endpoint = "/authorization/v1/access-token/b2b";
2505
+ const timestamp = snapTimestamp();
2506
+ const signature = generateSnapAsymmetricSignature(privateKey, clientId, timestamp);
2507
+ const response = await fetch(this.baseUrl + endpoint, {
2508
+ method: "POST",
2509
+ headers: {
2510
+ "Content-Type": "application/json",
2511
+ "X-CLIENT-KEY": clientId,
2512
+ "X-TIMESTAMP": timestamp,
2513
+ "X-SIGNATURE": signature
2514
+ },
2515
+ body: JSON.stringify({ grantType: "client_credentials" })
2516
+ });
2517
+ const text = await response.text();
2518
+ let data = null;
2519
+ try {
2520
+ data = JSON.parse(text);
2521
+ } catch {
2522
+ }
2523
+ if (!response.ok || !data?.accessToken) {
2524
+ throw new Error(data?.responseMessage || data?.message || `DOKU SNAP get-token failed: HTTP ${response.status} - ${text}`);
2525
+ }
2526
+ if (!clientSecret) {
2527
+ }
2528
+ this.token = data.accessToken;
2529
+ const expiresIn = Number(data.expiresIn || 900);
2530
+ this.tokenExpiry = now + (expiresIn - 30) * 1e3;
2531
+ return this.token;
2532
+ }
2533
+ clearToken() {
2534
+ this.token = null;
2535
+ this.tokenExpiry = 0;
2536
+ }
2537
+ /**
2538
+ * Perform a signed SNAP transaction request.
2539
+ * @param method GET/POST
2540
+ * @param endpoint request-target path (e.g. /virtual-accounts/...)
2541
+ * @param body request body (object)
2542
+ * @param opts extra headers (X-DEVICE-ID, X-IP-ADDRESS, etc.)
2543
+ */
2544
+ async request(method, endpoint, body, opts = {}) {
2545
+ const { clientId, clientSecret, channelId } = this.options;
2546
+ if (!clientSecret) {
2547
+ throw new Error("DOKU SNAP: clientSecret (Secret Key) required for transaction signing");
2548
+ }
2549
+ const accessToken = await this.getAccessToken();
2550
+ const timestamp = snapTimestamp();
2551
+ const externalId = opts.externalId || snapExternalId();
2552
+ const payload = body === void 0 ? "" : body;
2553
+ const signature = generateSnapSymmetricSignature(
2554
+ clientSecret,
2555
+ method,
2556
+ endpoint,
2557
+ accessToken,
2558
+ payload,
2559
+ timestamp
2560
+ );
2561
+ const headers = {
2562
+ "Content-Type": "application/json",
2563
+ "X-PARTNER-ID": clientId,
2564
+ "X-EXTERNAL-ID": externalId,
2565
+ "X-TIMESTAMP": timestamp,
2566
+ "X-SIGNATURE": signature,
2567
+ "CHANNEL-ID": channelId || "H2H",
2568
+ "Authorization": `Bearer ${accessToken}`,
2569
+ ...opts.deviceId ? { "X-DEVICE-ID": opts.deviceId } : {},
2570
+ ...opts.ipAddress ? { "X-IP-ADDRESS": opts.ipAddress } : {},
2571
+ ...opts.extraHeaders || {}
2572
+ };
2573
+ const response = await fetch(this.baseUrl + endpoint, {
2574
+ method,
2575
+ headers,
2576
+ body: method === "POST" && body !== void 0 ? JSON.stringify(body) : void 0
2577
+ });
2578
+ const text = await response.text();
2579
+ let data = null;
2580
+ try {
2581
+ data = JSON.parse(text);
2582
+ } catch {
2583
+ }
2584
+ if (!response.ok) {
2585
+ const err = data?.responseMessage || data?.error?.message || data?.message || `HTTP ${response.status}`;
2586
+ throw new Error(`${err}${data?.responseCode ? ` (${data.responseCode})` : ""}`);
2587
+ }
2588
+ return data;
2589
+ }
2590
+ };
2591
+
2416
2592
  // src/providers/doku/provider.ts
2417
2593
  var DokuProvider = class extends BasePaymentProvider {
2418
2594
  name = "doku";
2419
2595
  getBaseUrl(sandbox) {
2420
2596
  return sandbox ? "https://api-sandbox.doku.com" : "https://api.doku.com";
2421
2597
  }
2598
+ /**
2599
+ * DETEKSI mode integrasi DOKU:
2600
+ * - SNAP : kredensial baru (Client ID `doku_...` + Secret Key `SK-...`) + opsional RSA privateKey
2601
+ * untuk Get Token B2B. Diaktifkan via extra.snap / extra.dokuMode="snap" / doku_ prefix.
2602
+ * - Legacy: Jokul v2 (Client-Id + Signature HMAC-SHA256), default jika bukan SNAP.
2603
+ */
2604
+ isSnap(config) {
2605
+ if (config.extra?.snap === true || config.extra?.snap === "true") return true;
2606
+ if (config.extra?.dokuMode === "snap") return true;
2607
+ const clientId = String(config.merchantCode || config.merchantId || config.clientKey || "").trim().toLowerCase();
2608
+ return /^doku[_:-]/.test(clientId);
2609
+ }
2610
+ buildSnap(config) {
2611
+ const clientId = config.merchantCode || config.merchantId || config.clientKey || "";
2612
+ const clientSecret = config.apiKey || config.serverKey || config.secretKey || "";
2613
+ return new SnapClient({
2614
+ clientId,
2615
+ clientSecret,
2616
+ privateKey: config.privateKey,
2617
+ sandbox: !!config.sandbox,
2618
+ merchantId: config.extra?.merchantId || config.projectId || "",
2619
+ terminalId: config.extra?.terminalId || "",
2620
+ partnerServiceId: config.extra?.partnerServiceId || "",
2621
+ channelId: config.extra?.channelId || "H2H"
2622
+ });
2623
+ }
2624
+ /** Helper: jumlah integer → format SNAP 2-desimal (".00"). */
2625
+ snapAmount(amount, currency = "IDR") {
2626
+ return { value: amount.toFixed(2), currency };
2627
+ }
2422
2628
  async createInvoice(params, config) {
2423
2629
  const { orderId, amount, productDetails, customer, returnUrl } = params;
2424
2630
  const clientId = config.merchantCode || config.merchantId || config.clientKey || "";
@@ -2429,6 +2635,9 @@ var DokuProvider = class extends BasePaymentProvider {
2429
2635
  const isDirect = !!dokuMethod;
2430
2636
  const baseUrl = this.getBaseUrl(sandbox);
2431
2637
  try {
2638
+ if (this.isSnap(config)) {
2639
+ return await this.createSnapInvoice(params, config, baseUrl);
2640
+ }
2432
2641
  if (isDirect) {
2433
2642
  const endpoint = dokuMethod.endpoint;
2434
2643
  const url = `${baseUrl}${endpoint}`;
@@ -2616,7 +2825,178 @@ var DokuProvider = class extends BasePaymentProvider {
2616
2825
  };
2617
2826
  }
2618
2827
  }
2828
+ /**
2829
+ * DOKU SNAP: buat transaksi (Create VA / Generate QRIS / e-Wallet Payment).
2830
+ * Autentikasi via B2B token + symmetric HMAC-SHA512 signature.
2831
+ */
2832
+ async createSnapInvoice(params, config, baseUrl) {
2833
+ const { orderId, amount, productDetails, customer, returnUrl } = params;
2834
+ const snap = this.buildSnap(config);
2835
+ const integerAmount = Math.round(amount);
2836
+ const method = String(params.paymentMethod || "").toLowerCase();
2837
+ const bankMap = {
2838
+ bca: "VIRTUAL_ACCOUNT_BCA",
2839
+ mandiri: "VIRTUAL_ACCOUNT_BANK_MANDIRI",
2840
+ bri: "VIRTUAL_ACCOUNT_BRI",
2841
+ bni: "VIRTUAL_ACCOUNT_BNI",
2842
+ permata: "VIRTUAL_ACCOUNT_BANK_PERMATA",
2843
+ cimb: "VIRTUAL_ACCOUNT_BANK_CIMB",
2844
+ danamon: "VIRTUAL_ACCOUNT_BANK_DANAMON",
2845
+ bsi: "VIRTUAL_ACCOUNT_BANK_SYARIAH_MANDIRI",
2846
+ sinarmas: "VIRTUAL_ACCOUNT_SINARMAS",
2847
+ bjb: "VIRTUAL_ACCOUNT_BANK_BJB",
2848
+ btn: "VIRTUAL_ACCOUNT_BTN",
2849
+ bnc: "VIRTUAL_ACCOUNT_BNC"
2850
+ };
2851
+ const isVa = method.includes("_va");
2852
+ const isQris = method === "qris" || method.includes("qris");
2853
+ try {
2854
+ if (isVa) {
2855
+ return await this.snapCreateVA(params, config, snap, baseUrl, bankMap);
2856
+ }
2857
+ if (isQris) {
2858
+ return await this.snapGenerateQRIS(params, config, snap, baseUrl);
2859
+ }
2860
+ return await this.snapEWalletPayment(params, config, snap, baseUrl);
2861
+ } catch (e) {
2862
+ return {
2863
+ success: false,
2864
+ provider: "doku",
2865
+ orderId,
2866
+ amount: integerAmount,
2867
+ rawResponse: null,
2868
+ error: e.message || "Failed to make SNAP request to DOKU API"
2869
+ };
2870
+ }
2871
+ }
2872
+ /** Create Virtual Account (SNAP) — DOKU Generate Payment Code. */
2873
+ async snapCreateVA(params, config, snap, baseUrl, bankMap) {
2874
+ const { orderId, amount, productDetails, customer } = params;
2875
+ const method = String(params.paymentMethod || "").toLowerCase();
2876
+ const bankKey = method.replace(/_va$/, "");
2877
+ const channel = config.extra?.vaChannel || bankMap[bankKey] || `VIRTUAL_ACCOUNT_${bankKey.toUpperCase()}`;
2878
+ const partnerServiceId = (config.extra?.partnerServiceId || "").replace(/\s/g, "");
2879
+ const customerNo = (config.extra?.customerNo || "").slice(0, 20) || String(Date.now()).slice(-12);
2880
+ const virtualAccountNo = (partnerServiceId + customerNo).slice(0, 28);
2881
+ const reusable = config.extra?.reusableStatus === true;
2882
+ const currency = params.currency || "IDR";
2883
+ const body = {
2884
+ partnerServiceId,
2885
+ customerNo,
2886
+ virtualAccountNo,
2887
+ virtualAccountName: customer.name || "Customer",
2888
+ virtualAccountEmail: customer.email,
2889
+ virtualAccountPhone: customer.phone,
2890
+ trxId: orderId,
2891
+ totalAmount: this.snapAmount(amount, currency),
2892
+ virtualAccountTrxType: "C",
2893
+ additionalInfo: {
2894
+ channel,
2895
+ virtualAccountConfig: { reusableStatus: reusable }
2896
+ }
2897
+ };
2898
+ if (params.providerParams) {
2899
+ Object.assign(body.additionalInfo.virtualAccountConfig, params.providerParams);
2900
+ }
2901
+ if (config.extra?.expiredDate) body.expiredDate = config.extra.expiredDate;
2902
+ const endpoint = "/virtual-accounts/bi-snap-va/v1.1/transfer-va/create-va";
2903
+ const data = await snap.request("POST", endpoint, body);
2904
+ const vaData = data.virtualAccountData || {};
2905
+ return {
2906
+ success: true,
2907
+ provider: "doku",
2908
+ orderId: vaData.trxId || orderId,
2909
+ amount: Number(vaData.totalAmount?.value || amount) || Math.round(amount),
2910
+ reference: vaData.virtualAccountNo || orderId,
2911
+ vaNumber: vaData.virtualAccountNo,
2912
+ vaBank: bankKey.toUpperCase(),
2913
+ paymentUrl: vaData.additionalInfo?.howToPayPage,
2914
+ expiresAt: vaData.expiredDate ? new Date(vaData.expiredDate) : void 0,
2915
+ rawResponse: data
2916
+ };
2917
+ }
2918
+ /** Generate QRIS (SNAP) — dynamic QRIS MPM. */
2919
+ async snapGenerateQRIS(params, config, snap, baseUrl) {
2920
+ const { orderId, amount } = params;
2921
+ const currency = params.currency || "IDR";
2922
+ const merchantId = config.extra?.merchantId || config.projectId || "";
2923
+ const terminalId = config.extra?.terminalId || "0001";
2924
+ const body = {
2925
+ partnerReferenceNo: orderId,
2926
+ amount: this.snapAmount(amount, currency),
2927
+ merchantId,
2928
+ terminalId,
2929
+ validityPeriod: config.extra?.validityPeriod || new Date(Date.now() + 3600 * 1e3).toISOString(),
2930
+ additionalInfo: {
2931
+ postalCode: config.extra?.postalCode || "10110",
2932
+ feeType: 1
2933
+ }
2934
+ };
2935
+ if (params.providerParams) {
2936
+ Object.assign(body.additionalInfo, params.providerParams);
2937
+ }
2938
+ const endpoint = "/snap-adapter/b2b/v1.0/qr/qr-mpm-generate";
2939
+ const data = await snap.request("POST", endpoint, body);
2940
+ return {
2941
+ success: true,
2942
+ provider: "doku",
2943
+ orderId: data.partnerReferenceNo || orderId,
2944
+ amount: Number(data.amount?.value || amount) || Math.round(amount),
2945
+ reference: data.referenceNo || orderId,
2946
+ qrString: data.qrContent,
2947
+ rawResponse: data
2948
+ };
2949
+ }
2950
+ /** e-Wallet payment (SNAP) — DANA / OVO / ShopeePay via payment-host-to-host. */
2951
+ async snapEWalletPayment(params, config, snap, baseUrl) {
2952
+ const { orderId, amount } = params;
2953
+ const method = String(params.paymentMethod || "").toLowerCase();
2954
+ const currency = params.currency || "IDR";
2955
+ const returnUrl = params.returnUrl || config.returnUrl || "";
2956
+ const channelMap = {
2957
+ dana: "EMONEY_DANA_SNAP",
2958
+ ovo: "EMONEY_OVO_SNAP",
2959
+ shopeepay: "EMONEY_SHOPEEPAY_SNAP"
2960
+ };
2961
+ const channel = config.extra?.ewalletChannel || channelMap[method] || `EMONEY_${method.toUpperCase()}_SNAP`;
2962
+ const body = {
2963
+ partnerReferenceNo: orderId,
2964
+ amount: this.snapAmount(amount, currency),
2965
+ pointOfInitiation: "pc",
2966
+ urlParam: {
2967
+ url: returnUrl || "https://example.com/return",
2968
+ type: "PAY_RETURN",
2969
+ isDeepLink: "N"
2970
+ },
2971
+ additionalInfo: {
2972
+ channel,
2973
+ orderTitle: params.productDetails || `Pembayaran ${orderId}`,
2974
+ supportDeepLinkCheckoutUrl: "false"
2975
+ }
2976
+ };
2977
+ if (params.providerParams) {
2978
+ Object.assign(body.additionalInfo, params.providerParams);
2979
+ }
2980
+ const endpoint = "/direct-debit/core/v1/debit/payment-host-to-host";
2981
+ const data = await snap.request("POST", endpoint, body, {
2982
+ deviceId: config.extra?.deviceId,
2983
+ ipAddress: config.extra?.ipAddress
2984
+ });
2985
+ return {
2986
+ success: true,
2987
+ provider: "doku",
2988
+ orderId: data.partnerReferenceNo || orderId,
2989
+ amount: Number(data.amount?.value || amount) || Math.round(amount),
2990
+ reference: data.partnerReferenceNo || orderId,
2991
+ paymentUrl: data.webRedirectUrl || data.paymentUrl,
2992
+ rawResponse: data
2993
+ };
2994
+ }
2619
2995
  async verifyCallback(body, config) {
2996
+ const headers = config.extra?.headers || {};
2997
+ if (this.isSnap(config)) {
2998
+ return this.verifySnapCallback(body, config, headers);
2999
+ }
2620
3000
  const rawStatus = (body.transaction?.status || body.status || "").toUpperCase();
2621
3001
  const isPaid = rawStatus === "SUCCESS" || rawStatus === "PAID" || rawStatus === "SETTLED";
2622
3002
  const isPending = rawStatus === "PENDING";
@@ -2626,7 +3006,6 @@ var DokuProvider = class extends BasePaymentProvider {
2626
3006
  const amount = body.order?.amount || body.amount || 0;
2627
3007
  const status = isPaid ? "paid" : isPending ? "pending" : isExpired ? "expired" : "failed";
2628
3008
  const secretKey = config.secretKey || config.apiKey || "";
2629
- const headers = config.extra?.headers || {};
2630
3009
  const signature = headers["signature"] || headers["Signature"] || config.extra?.dokuSignature || config.extra?.signatureHeader;
2631
3010
  const clientId = config.merchantCode || config.clientKey || "";
2632
3011
  let isValid = true;
@@ -2647,6 +3026,40 @@ var DokuProvider = class extends BasePaymentProvider {
2647
3026
  rawPayload: body
2648
3027
  };
2649
3028
  }
3029
+ /**
3030
+ * Verifikasi webhook / notifikasi DOKU SNAP.
3031
+ * Signature dibangun dengan symmetric HMAC-SHA512 (AccessToken kosong).
3032
+ */
3033
+ verifySnapCallback(body, config, headers) {
3034
+ const clientSecret = config.apiKey || config.serverKey || config.secretKey || "";
3035
+ const endpointUrl = config.extra?.notificationPath || headers["x-path"] || config.extra?.headers?.["request-target"] || "/api/payment/webhook";
3036
+ let isValid = true;
3037
+ const incomingSig = headers["x-signature"] || headers["X-SIGNATURE"] || headers["signature"] || headers["Signature"] || "";
3038
+ if (incomingSig && clientSecret) {
3039
+ isValid = verifySnapWebhookSignature(headers, body, clientSecret, endpointUrl);
3040
+ }
3041
+ const explicitStatus = String(body.transactionStatus || body.status || body.latestTransactionStatus || "").toUpperCase();
3042
+ const isPaid = explicitStatus === "SUCCESS" || explicitStatus === "PAID" || explicitStatus === "SETTLED" || explicitStatus === "00" || !explicitStatus && Boolean(body.paidAmount?.value ?? body.totalAmount?.value);
3043
+ const isPending = explicitStatus === "PENDING" || explicitStatus === "11" || explicitStatus === "ONGOING";
3044
+ const isFailed = explicitStatus === "FAILED" || explicitStatus === "DECLINED" || Boolean(explicitStatus) && !isPaid && !isPending && explicitStatus !== "00";
3045
+ const isExpired = explicitStatus === "EXPIRED";
3046
+ const orderId = body.trxId || body.partnerReferenceNo || body.originalPartnerReferenceNo || body.order?.invoice_number || body.invoice_number || body.order_id || "";
3047
+ const paidValue = body.paidAmount?.value ?? body.totalAmount?.value ?? body.amount?.value ?? body.amount ?? 0;
3048
+ const status = isPaid ? "paid" : isPending ? "pending" : isExpired ? "expired" : "failed";
3049
+ return {
3050
+ isValid,
3051
+ provider: "doku",
3052
+ orderId: String(orderId),
3053
+ amount: Number(paidValue) || 0,
3054
+ status,
3055
+ isPaid,
3056
+ isPending,
3057
+ isFailed,
3058
+ isExpired,
3059
+ statusCode: explicitStatus || "SUCCESS",
3060
+ rawPayload: body
3061
+ };
3062
+ }
2650
3063
  async getPaymentMethods(params, config) {
2651
3064
  const staticMethods = [
2652
3065
  {
@@ -2806,6 +3219,9 @@ var DokuProvider = class extends BasePaymentProvider {
2806
3219
  const clientId = config.merchantCode || config.merchantId || config.clientKey || "";
2807
3220
  const secretKey = config.apiKey || config.serverKey || config.secretKey || "";
2808
3221
  const sandbox = !!config.sandbox;
3222
+ if (this.isSnap(config)) {
3223
+ return this.snapCheckTransaction(params, config, clientId);
3224
+ }
2809
3225
  const endpoint = `/orders/v1/status/${merchantOrderId}`;
2810
3226
  const url = `${this.getBaseUrl(sandbox)}${endpoint}`;
2811
3227
  const headers = generateDokuHeaders(clientId, secretKey, endpoint);
@@ -2883,6 +3299,58 @@ var DokuProvider = class extends BasePaymentProvider {
2883
3299
  };
2884
3300
  }
2885
3301
  }
3302
+ /** Cek status transaksi SNAP (menggunakan Query QRIS bila ref berasal dari QRIS). */
3303
+ async snapCheckTransaction(params, config, clientId) {
3304
+ const { merchantOrderId } = params;
3305
+ const snap = this.buildSnap(config);
3306
+ try {
3307
+ const body = {
3308
+ originalPartnerReferenceNo: merchantOrderId,
3309
+ serviceCode: "47",
3310
+ merchantId: config.extra?.merchantId || config.projectId || ""
3311
+ };
3312
+ const data = await snap.request("POST", "/snap-adapter/b2b/v1.0/qr/qr-mpm-query", body);
3313
+ const txStatus = String(data.latestTransactionStatus || "").toUpperCase();
3314
+ const isPaid = txStatus === "SUCCESS" || txStatus === "00" || txStatus === "PAID" || txStatus === "SETTLED";
3315
+ const isPending = txStatus === "PENDING" || txStatus === "ONGOING" || txStatus === "11";
3316
+ const isExpired = txStatus === "EXPIRED";
3317
+ const isFailed = txStatus === "FAILED" || txStatus === "DECLINED" || Boolean(txStatus) && !isPaid && !isPending && !isExpired;
3318
+ const status = isPaid ? "paid" : isPending ? "pending" : isExpired ? "expired" : "failed";
3319
+ return {
3320
+ success: true,
3321
+ provider: "doku",
3322
+ orderId: data.originalPartnerReferenceNo || merchantOrderId,
3323
+ reference: data.originalReferenceNo || "",
3324
+ amount: Number(data.amount?.value || 0),
3325
+ statusCode: txStatus || String(data.responseCode || ""),
3326
+ status,
3327
+ isPaid,
3328
+ isPending,
3329
+ isFailed,
3330
+ isExpired,
3331
+ statusMessage: txStatus || data.responseMessage || "",
3332
+ paymentType: "QRIS",
3333
+ rawResponse: data
3334
+ };
3335
+ } catch (e) {
3336
+ return {
3337
+ success: false,
3338
+ provider: "doku",
3339
+ orderId: merchantOrderId,
3340
+ reference: "",
3341
+ amount: 0,
3342
+ statusCode: "ERROR",
3343
+ status: "failed",
3344
+ isPaid: false,
3345
+ isPending: false,
3346
+ isFailed: true,
3347
+ isExpired: false,
3348
+ statusMessage: e.message || "Failed to check SNAP transaction status",
3349
+ error: e.message || "Failed to check SNAP transaction status",
3350
+ rawResponse: null
3351
+ };
3352
+ }
3353
+ }
2886
3354
  };
2887
3355
 
2888
3356
  // src/providers/prismalink/signature.ts
@@ -3276,15 +3744,15 @@ var PrismalinkProvider = class extends BasePaymentProvider {
3276
3744
  };
3277
3745
 
3278
3746
  // src/providers/faspay/signature.ts
3279
- import crypto3 from "crypto";
3747
+ import crypto4 from "crypto";
3280
3748
  function generateFaspaySignature(userId, password, billNo) {
3281
- const md5Hash = crypto3.createHash("md5").update(`${userId}${password}${billNo}`).digest("hex");
3282
- return crypto3.createHash("sha1").update(md5Hash).digest("hex");
3749
+ const md5Hash = crypto4.createHash("md5").update(`${userId}${password}${billNo}`).digest("hex");
3750
+ return crypto4.createHash("sha1").update(md5Hash).digest("hex");
3283
3751
  }
3284
3752
  function verifyFaspaySignature(userId, password, billNo, paymentStatusCode, incomingSignature) {
3285
3753
  if (!incomingSignature || !password) return false;
3286
- const md5Hash = crypto3.createHash("md5").update(`${userId}${password}${billNo}${paymentStatusCode}`).digest("hex");
3287
- const computed = crypto3.createHash("sha1").update(md5Hash).digest("hex");
3754
+ const md5Hash = crypto4.createHash("md5").update(`${userId}${password}${billNo}${paymentStatusCode}`).digest("hex");
3755
+ const computed = crypto4.createHash("sha1").update(md5Hash).digest("hex");
3288
3756
  const simpleComputed = generateFaspaySignature(userId, password, billNo);
3289
3757
  return safeCompare(incomingSignature, computed) || safeCompare(incomingSignature, simpleComputed);
3290
3758
  }
@@ -3697,10 +4165,10 @@ var FaspayProvider = class extends BasePaymentProvider {
3697
4165
  };
3698
4166
 
3699
4167
  // src/providers/finpay/signature.ts
3700
- import crypto4 from "crypto";
4168
+ import crypto5 from "crypto";
3701
4169
  function generateFinpaySignature(merchantId, orderId, amount, merchantKey) {
3702
4170
  const data = `${merchantId}%${orderId}%${Math.round(amount)}%${merchantKey}`;
3703
- return crypto4.createHmac("sha512", merchantKey).update(data).digest("hex");
4171
+ return crypto5.createHmac("sha512", merchantKey).update(data).digest("hex");
3704
4172
  }
3705
4173
  function verifyFinpaySignature(merchantId, orderId, amount, merchantKey, incomingSignature) {
3706
4174
  if (!incomingSignature || !merchantKey) return false;
@@ -9442,6 +9910,7 @@ export {
9442
9910
  PrismalinkProvider,
9443
9911
  RazorpayClient,
9444
9912
  RazorpayProvider,
9913
+ SnapClient,
9445
9914
  SquareClient,
9446
9915
  SquareProvider,
9447
9916
  StripeClient,
@@ -9465,6 +9934,8 @@ export {
9465
9934
  generateNicepayToken,
9466
9935
  generateOyHeaders,
9467
9936
  generatePrismalinkSignature,
9937
+ generateSnapAsymmetricSignature,
9938
+ generateSnapSymmetricSignature,
9468
9939
  getDuitkuInquirySignatures,
9469
9940
  getDuitkuPaymentMethodsSignature,
9470
9941
  getDuitkuStatusSignatures,
@@ -9472,6 +9943,7 @@ export {
9472
9943
  getXenditAuthHeader,
9473
9944
  hmacSha256,
9474
9945
  md5,
9946
+ minifyJson,
9475
9947
  parseCoreChargeResponse,
9476
9948
  paymentManager,
9477
9949
  resolveConfigFromEnv,
@@ -9479,7 +9951,10 @@ export {
9479
9951
  serializePaypalParams,
9480
9952
  serializeStripeParams,
9481
9953
  sha256,
9954
+ sha256Hex,
9482
9955
  sha512,
9956
+ snapExternalId,
9957
+ snapTimestamp,
9483
9958
  toCanonicalPaymentMethod,
9484
9959
  toDokuPaymentMethod,
9485
9960
  toDuitkuPaymentMethod,
@@ -9505,6 +9980,7 @@ export {
9505
9980
  verifyPayuWebhook,
9506
9981
  verifyPrismalinkSignature,
9507
9982
  verifyRazorpayWebhook,
9983
+ verifySnapWebhookSignature,
9508
9984
  verifySquareWebhook,
9509
9985
  verifyStripeWebhook,
9510
9986
  verifyTwoCheckoutWebhook,
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@crediblemark/buayar",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
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
-