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