@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.js CHANGED
@@ -78,6 +78,7 @@ __export(index_exports, {
78
78
  PrismalinkProvider: () => PrismalinkProvider,
79
79
  RazorpayClient: () => RazorpayClient,
80
80
  RazorpayProvider: () => RazorpayProvider,
81
+ SnapClient: () => SnapClient,
81
82
  SquareClient: () => SquareClient,
82
83
  SquareProvider: () => SquareProvider,
83
84
  StripeClient: () => StripeClient,
@@ -101,6 +102,8 @@ __export(index_exports, {
101
102
  generateNicepayToken: () => generateNicepayToken,
102
103
  generateOyHeaders: () => generateOyHeaders,
103
104
  generatePrismalinkSignature: () => generatePrismalinkSignature,
105
+ generateSnapAsymmetricSignature: () => generateSnapAsymmetricSignature,
106
+ generateSnapSymmetricSignature: () => generateSnapSymmetricSignature,
104
107
  getDuitkuInquirySignatures: () => getDuitkuInquirySignatures,
105
108
  getDuitkuPaymentMethodsSignature: () => getDuitkuPaymentMethodsSignature,
106
109
  getDuitkuStatusSignatures: () => getDuitkuStatusSignatures,
@@ -108,6 +111,7 @@ __export(index_exports, {
108
111
  getXenditAuthHeader: () => getXenditAuthHeader,
109
112
  hmacSha256: () => hmacSha256,
110
113
  md5: () => md5,
114
+ minifyJson: () => minifyJson,
111
115
  parseCoreChargeResponse: () => parseCoreChargeResponse,
112
116
  paymentManager: () => paymentManager,
113
117
  resolveConfigFromEnv: () => resolveConfigFromEnv,
@@ -115,7 +119,11 @@ __export(index_exports, {
115
119
  serializePaypalParams: () => serializePaypalParams,
116
120
  serializeStripeParams: () => serializeStripeParams,
117
121
  sha256: () => sha256,
122
+ sha256Hex: () => sha256Hex,
118
123
  sha512: () => sha512,
124
+ snapExternalId: () => snapExternalId,
125
+ snapTimestamp: () => snapTimestamp,
126
+ snapUtcTimestamp: () => snapUtcTimestamp,
119
127
  toCanonicalPaymentMethod: () => toCanonicalPaymentMethod,
120
128
  toDokuPaymentMethod: () => toDokuPaymentMethod,
121
129
  toDuitkuPaymentMethod: () => toDuitkuPaymentMethod,
@@ -141,6 +149,7 @@ __export(index_exports, {
141
149
  verifyPayuWebhook: () => verifyPayuWebhook,
142
150
  verifyPrismalinkSignature: () => verifyPrismalinkSignature,
143
151
  verifyRazorpayWebhook: () => verifyRazorpayWebhook,
152
+ verifySnapWebhookSignature: () => verifySnapWebhookSignature,
144
153
  verifySquareWebhook: () => verifySquareWebhook,
145
154
  verifyStripeWebhook: () => verifyStripeWebhook,
146
155
  verifyTwoCheckoutWebhook: () => verifyTwoCheckoutWebhook,
@@ -2563,12 +2572,221 @@ Digest:${digest}`;
2563
2572
  return safeCompare(incomingSignature, expectedSignature);
2564
2573
  }
2565
2574
 
2575
+ // src/providers/doku/snap.ts
2576
+ var import_crypto8 = __toESM(require("crypto"));
2577
+ function snapTimestamp(date = /* @__PURE__ */ new Date()) {
2578
+ const wib = new Date(date.getTime() + 7 * 60 * 60 * 1e3);
2579
+ return wib.toISOString().slice(0, 19) + "+07:00";
2580
+ }
2581
+ function snapUtcTimestamp(date = /* @__PURE__ */ new Date()) {
2582
+ return date.toISOString().slice(0, 19) + "Z";
2583
+ }
2584
+ function minifyJson(obj) {
2585
+ return JSON.stringify(obj);
2586
+ }
2587
+ function sha256Hex(body) {
2588
+ const raw = typeof body === "string" ? body : minifyJson(body);
2589
+ return import_crypto8.default.createHash("sha256").update(raw).digest("hex").toLowerCase();
2590
+ }
2591
+ function generateSnapSymmetricSignature(clientSecret, method, endpointUrl, accessToken, body, timestamp) {
2592
+ const hash = sha256Hex(body);
2593
+ const stringToSign = `${method}:${endpointUrl}:${accessToken}:${hash}:${timestamp}`;
2594
+ return import_crypto8.default.createHmac("sha512", clientSecret).update(stringToSign).digest("base64");
2595
+ }
2596
+ function generateSnapAsymmetricSignature(privateKey, clientId, timestamp) {
2597
+ const stringToSign = `${clientId}|${timestamp}`;
2598
+ const key = import_crypto8.default.createPrivateKey(normalizePem(privateKey));
2599
+ return import_crypto8.default.sign("RSA-SHA256", Buffer.from(stringToSign, "utf8"), key).toString("base64");
2600
+ }
2601
+ function normalizePem(key) {
2602
+ const trimmed = key.trim();
2603
+ if (trimmed.includes("-----BEGIN")) {
2604
+ return trimmed;
2605
+ }
2606
+ return [
2607
+ "-----BEGIN PRIVATE KEY-----",
2608
+ trimmed.replace(/\s+/g, "").match(/.{1,64}/g)?.join("\n") || trimmed,
2609
+ "-----END PRIVATE KEY-----"
2610
+ ].join("\n");
2611
+ }
2612
+ function snapExternalId(prefix = "") {
2613
+ return `${prefix}${Date.now()}${Math.floor(Math.random() * 1e3)}`;
2614
+ }
2615
+ function verifySnapWebhookSignature(headers, body, clientSecret, endpointUrl = "/api/payment/webhook") {
2616
+ if (!clientSecret) return false;
2617
+ const incoming = headers["x-signature"] || headers["X-SIGNATURE"] || headers["signature"] || headers["Signature"] || "";
2618
+ if (!incoming) return false;
2619
+ const timestamp = headers["x-timestamp"] || headers["X-TIMESTAMP"] || headers["timestamp"] || headers["Timestamp"] || snapTimestamp();
2620
+ const computed = generateSnapSymmetricSignature(
2621
+ clientSecret,
2622
+ "POST",
2623
+ endpointUrl,
2624
+ "",
2625
+ body,
2626
+ timestamp
2627
+ );
2628
+ return safeCompare(incoming, computed);
2629
+ }
2630
+
2631
+ // src/clients/snap.ts
2632
+ var SnapClient = class {
2633
+ options;
2634
+ token = null;
2635
+ tokenExpiry = 0;
2636
+ constructor(options) {
2637
+ this.options = {
2638
+ clientId: options.clientId || options.merchantCode || "",
2639
+ clientSecret: options.clientSecret || options.apiKey || options.serverKey || options.secretKey || "",
2640
+ privateKey: options.privateKey,
2641
+ sandbox: !!options.sandbox,
2642
+ merchantId: options.merchantId,
2643
+ terminalId: options.terminalId,
2644
+ partnerServiceId: options.partnerServiceId,
2645
+ channelId: options.channelId || "H2H"
2646
+ };
2647
+ }
2648
+ get baseUrl() {
2649
+ return this.options.sandbox ? "https://api-sandbox.doku.com" : "https://api.doku.com";
2650
+ }
2651
+ get isConfigured() {
2652
+ return Boolean(this.options.clientId && this.options.clientSecret);
2653
+ }
2654
+ /**
2655
+ * Get (cached) B2B access token. Mints a new one when expired.
2656
+ */
2657
+ async getAccessToken() {
2658
+ const now = Date.now();
2659
+ if (this.token && now < this.tokenExpiry) {
2660
+ return this.token;
2661
+ }
2662
+ const { clientId, clientSecret, privateKey } = this.options;
2663
+ if (!privateKey) {
2664
+ throw new Error("DOKU SNAP: RSA privateKey required to obtain B2B access token");
2665
+ }
2666
+ const endpoint = "/authorization/v1/access-token/b2b";
2667
+ const timestamp = snapUtcTimestamp();
2668
+ const signature = generateSnapAsymmetricSignature(privateKey, clientId, timestamp);
2669
+ const response = await fetch(this.baseUrl + endpoint, {
2670
+ method: "POST",
2671
+ headers: {
2672
+ "Content-Type": "application/json",
2673
+ "X-CLIENT-KEY": clientId,
2674
+ "X-TIMESTAMP": timestamp,
2675
+ "X-SIGNATURE": signature
2676
+ },
2677
+ body: JSON.stringify({ grantType: "client_credentials" })
2678
+ });
2679
+ const text = await response.text();
2680
+ let data = null;
2681
+ try {
2682
+ data = JSON.parse(text);
2683
+ } catch {
2684
+ }
2685
+ if (!response.ok || !data?.accessToken) {
2686
+ throw new Error(data?.responseMessage || data?.message || `DOKU SNAP get-token failed: HTTP ${response.status} - ${text}`);
2687
+ }
2688
+ if (!clientSecret) {
2689
+ }
2690
+ this.token = data.accessToken;
2691
+ const expiresIn = Number(data.expiresIn || 900);
2692
+ this.tokenExpiry = now + (expiresIn - 30) * 1e3;
2693
+ return this.token;
2694
+ }
2695
+ clearToken() {
2696
+ this.token = null;
2697
+ this.tokenExpiry = 0;
2698
+ }
2699
+ /**
2700
+ * Perform a signed SNAP transaction request.
2701
+ * @param method GET/POST
2702
+ * @param endpoint request-target path (e.g. /virtual-accounts/...)
2703
+ * @param body request body (object)
2704
+ * @param opts extra headers (X-DEVICE-ID, X-IP-ADDRESS, etc.)
2705
+ */
2706
+ async request(method, endpoint, body, opts = {}) {
2707
+ const { clientId, clientSecret, channelId } = this.options;
2708
+ if (!clientSecret) {
2709
+ throw new Error("DOKU SNAP: clientSecret (Secret Key) required for transaction signing");
2710
+ }
2711
+ const accessToken = await this.getAccessToken();
2712
+ const timestamp = snapTimestamp();
2713
+ const externalId = opts.externalId || snapExternalId();
2714
+ const payload = body === void 0 ? "" : body;
2715
+ const signature = generateSnapSymmetricSignature(
2716
+ clientSecret,
2717
+ method,
2718
+ endpoint,
2719
+ accessToken,
2720
+ payload,
2721
+ timestamp
2722
+ );
2723
+ const headers = {
2724
+ "Content-Type": "application/json",
2725
+ "X-PARTNER-ID": clientId,
2726
+ "X-EXTERNAL-ID": externalId,
2727
+ "X-TIMESTAMP": timestamp,
2728
+ "X-SIGNATURE": signature,
2729
+ "CHANNEL-ID": channelId || "H2H",
2730
+ "Authorization": `Bearer ${accessToken}`,
2731
+ ...opts.deviceId ? { "X-DEVICE-ID": opts.deviceId } : {},
2732
+ ...opts.ipAddress ? { "X-IP-ADDRESS": opts.ipAddress } : {},
2733
+ ...opts.extraHeaders || {}
2734
+ };
2735
+ const response = await fetch(this.baseUrl + endpoint, {
2736
+ method,
2737
+ headers,
2738
+ body: method === "POST" && body !== void 0 ? JSON.stringify(body) : void 0
2739
+ });
2740
+ const text = await response.text();
2741
+ let data = null;
2742
+ try {
2743
+ data = JSON.parse(text);
2744
+ } catch {
2745
+ }
2746
+ if (!response.ok) {
2747
+ const err = data?.responseMessage || data?.error?.message || data?.message || `HTTP ${response.status}`;
2748
+ throw new Error(`${err}${data?.responseCode ? ` (${data.responseCode})` : ""}`);
2749
+ }
2750
+ return data;
2751
+ }
2752
+ };
2753
+
2566
2754
  // src/providers/doku/provider.ts
2567
2755
  var DokuProvider = class extends BasePaymentProvider {
2568
2756
  name = "doku";
2569
2757
  getBaseUrl(sandbox) {
2570
2758
  return sandbox ? "https://api-sandbox.doku.com" : "https://api.doku.com";
2571
2759
  }
2760
+ /**
2761
+ * DETEKSI mode integrasi DOKU:
2762
+ * - SNAP : kredensial baru (Client ID `doku_...` + Secret Key `SK-...`) + opsional RSA privateKey
2763
+ * untuk Get Token B2B. Diaktifkan via extra.snap / extra.dokuMode="snap" / doku_ prefix.
2764
+ * - Legacy: Jokul v2 (Client-Id + Signature HMAC-SHA256), default jika bukan SNAP.
2765
+ */
2766
+ isSnap(config) {
2767
+ if (config.extra?.snap === true || config.extra?.snap === "true") return true;
2768
+ if (config.extra?.dokuMode === "snap") return true;
2769
+ const clientId = String(config.merchantCode || config.merchantId || config.clientKey || "").trim().toLowerCase();
2770
+ return /^doku[_:-]/.test(clientId);
2771
+ }
2772
+ buildSnap(config) {
2773
+ const clientId = config.merchantCode || config.merchantId || config.clientKey || "";
2774
+ const clientSecret = config.apiKey || config.serverKey || config.secretKey || "";
2775
+ return new SnapClient({
2776
+ clientId,
2777
+ clientSecret,
2778
+ privateKey: config.privateKey,
2779
+ sandbox: !!config.sandbox,
2780
+ merchantId: config.extra?.merchantId || config.projectId || "",
2781
+ terminalId: config.extra?.terminalId || "",
2782
+ partnerServiceId: config.extra?.partnerServiceId || "",
2783
+ channelId: config.extra?.channelId || "H2H"
2784
+ });
2785
+ }
2786
+ /** Helper: jumlah integer → format SNAP 2-desimal (".00"). */
2787
+ snapAmount(amount, currency = "IDR") {
2788
+ return { value: amount.toFixed(2), currency };
2789
+ }
2572
2790
  async createInvoice(params, config) {
2573
2791
  const { orderId, amount, productDetails, customer, returnUrl } = params;
2574
2792
  const clientId = config.merchantCode || config.merchantId || config.clientKey || "";
@@ -2579,6 +2797,9 @@ var DokuProvider = class extends BasePaymentProvider {
2579
2797
  const isDirect = !!dokuMethod;
2580
2798
  const baseUrl = this.getBaseUrl(sandbox);
2581
2799
  try {
2800
+ if (this.isSnap(config)) {
2801
+ return await this.createSnapInvoice(params, config, baseUrl);
2802
+ }
2582
2803
  if (isDirect) {
2583
2804
  const endpoint = dokuMethod.endpoint;
2584
2805
  const url = `${baseUrl}${endpoint}`;
@@ -2766,7 +2987,178 @@ var DokuProvider = class extends BasePaymentProvider {
2766
2987
  };
2767
2988
  }
2768
2989
  }
2990
+ /**
2991
+ * DOKU SNAP: buat transaksi (Create VA / Generate QRIS / e-Wallet Payment).
2992
+ * Autentikasi via B2B token + symmetric HMAC-SHA512 signature.
2993
+ */
2994
+ async createSnapInvoice(params, config, baseUrl) {
2995
+ const { orderId, amount, productDetails, customer, returnUrl } = params;
2996
+ const snap = this.buildSnap(config);
2997
+ const integerAmount = Math.round(amount);
2998
+ const method = String(params.paymentMethod || "").toLowerCase();
2999
+ const bankMap = {
3000
+ bca: "VIRTUAL_ACCOUNT_BCA",
3001
+ mandiri: "VIRTUAL_ACCOUNT_BANK_MANDIRI",
3002
+ bri: "VIRTUAL_ACCOUNT_BRI",
3003
+ bni: "VIRTUAL_ACCOUNT_BNI",
3004
+ permata: "VIRTUAL_ACCOUNT_BANK_PERMATA",
3005
+ cimb: "VIRTUAL_ACCOUNT_BANK_CIMB",
3006
+ danamon: "VIRTUAL_ACCOUNT_BANK_DANAMON",
3007
+ bsi: "VIRTUAL_ACCOUNT_BANK_SYARIAH_MANDIRI",
3008
+ sinarmas: "VIRTUAL_ACCOUNT_SINARMAS",
3009
+ bjb: "VIRTUAL_ACCOUNT_BANK_BJB",
3010
+ btn: "VIRTUAL_ACCOUNT_BTN",
3011
+ bnc: "VIRTUAL_ACCOUNT_BNC"
3012
+ };
3013
+ const isVa = method.includes("_va");
3014
+ const isQris = method === "qris" || method.includes("qris");
3015
+ try {
3016
+ if (isVa) {
3017
+ return await this.snapCreateVA(params, config, snap, baseUrl, bankMap);
3018
+ }
3019
+ if (isQris) {
3020
+ return await this.snapGenerateQRIS(params, config, snap, baseUrl);
3021
+ }
3022
+ return await this.snapEWalletPayment(params, config, snap, baseUrl);
3023
+ } catch (e) {
3024
+ return {
3025
+ success: false,
3026
+ provider: "doku",
3027
+ orderId,
3028
+ amount: integerAmount,
3029
+ rawResponse: null,
3030
+ error: e.message || "Failed to make SNAP request to DOKU API"
3031
+ };
3032
+ }
3033
+ }
3034
+ /** Create Virtual Account (SNAP) — DOKU Generate Payment Code. */
3035
+ async snapCreateVA(params, config, snap, baseUrl, bankMap) {
3036
+ const { orderId, amount, productDetails, customer } = params;
3037
+ const method = String(params.paymentMethod || "").toLowerCase();
3038
+ const bankKey = method.replace(/_va$/, "");
3039
+ const channel = config.extra?.vaChannel || bankMap[bankKey] || `VIRTUAL_ACCOUNT_${bankKey.toUpperCase()}`;
3040
+ const partnerServiceId = (config.extra?.partnerServiceId || "").replace(/\s/g, "");
3041
+ const customerNo = (config.extra?.customerNo || "").slice(0, 20) || String(Date.now()).slice(-12);
3042
+ const virtualAccountNo = (partnerServiceId + customerNo).slice(0, 28);
3043
+ const reusable = config.extra?.reusableStatus === true;
3044
+ const currency = params.currency || "IDR";
3045
+ const body = {
3046
+ partnerServiceId,
3047
+ customerNo,
3048
+ virtualAccountNo,
3049
+ virtualAccountName: customer.name || "Customer",
3050
+ virtualAccountEmail: customer.email,
3051
+ virtualAccountPhone: customer.phone,
3052
+ trxId: orderId,
3053
+ totalAmount: this.snapAmount(amount, currency),
3054
+ virtualAccountTrxType: "C",
3055
+ additionalInfo: {
3056
+ channel,
3057
+ virtualAccountConfig: { reusableStatus: reusable }
3058
+ }
3059
+ };
3060
+ if (params.providerParams) {
3061
+ Object.assign(body.additionalInfo.virtualAccountConfig, params.providerParams);
3062
+ }
3063
+ if (config.extra?.expiredDate) body.expiredDate = config.extra.expiredDate;
3064
+ const endpoint = "/virtual-accounts/bi-snap-va/v1.1/transfer-va/create-va";
3065
+ const data = await snap.request("POST", endpoint, body);
3066
+ const vaData = data.virtualAccountData || {};
3067
+ return {
3068
+ success: true,
3069
+ provider: "doku",
3070
+ orderId: vaData.trxId || orderId,
3071
+ amount: Number(vaData.totalAmount?.value || amount) || Math.round(amount),
3072
+ reference: vaData.virtualAccountNo || orderId,
3073
+ vaNumber: vaData.virtualAccountNo,
3074
+ vaBank: bankKey.toUpperCase(),
3075
+ paymentUrl: vaData.additionalInfo?.howToPayPage,
3076
+ expiresAt: vaData.expiredDate ? new Date(vaData.expiredDate) : void 0,
3077
+ rawResponse: data
3078
+ };
3079
+ }
3080
+ /** Generate QRIS (SNAP) — dynamic QRIS MPM. */
3081
+ async snapGenerateQRIS(params, config, snap, baseUrl) {
3082
+ const { orderId, amount } = params;
3083
+ const currency = params.currency || "IDR";
3084
+ const merchantId = config.extra?.merchantId || config.projectId || "";
3085
+ const terminalId = config.extra?.terminalId || "0001";
3086
+ const body = {
3087
+ partnerReferenceNo: orderId,
3088
+ amount: this.snapAmount(amount, currency),
3089
+ merchantId,
3090
+ terminalId,
3091
+ validityPeriod: config.extra?.validityPeriod || new Date(Date.now() + 3600 * 1e3).toISOString(),
3092
+ additionalInfo: {
3093
+ postalCode: config.extra?.postalCode || "10110",
3094
+ feeType: 1
3095
+ }
3096
+ };
3097
+ if (params.providerParams) {
3098
+ Object.assign(body.additionalInfo, params.providerParams);
3099
+ }
3100
+ const endpoint = "/snap-adapter/b2b/v1.0/qr/qr-mpm-generate";
3101
+ const data = await snap.request("POST", endpoint, body);
3102
+ return {
3103
+ success: true,
3104
+ provider: "doku",
3105
+ orderId: data.partnerReferenceNo || orderId,
3106
+ amount: Number(data.amount?.value || amount) || Math.round(amount),
3107
+ reference: data.referenceNo || orderId,
3108
+ qrString: data.qrContent,
3109
+ rawResponse: data
3110
+ };
3111
+ }
3112
+ /** e-Wallet payment (SNAP) — DANA / OVO / ShopeePay via payment-host-to-host. */
3113
+ async snapEWalletPayment(params, config, snap, baseUrl) {
3114
+ const { orderId, amount } = params;
3115
+ const method = String(params.paymentMethod || "").toLowerCase();
3116
+ const currency = params.currency || "IDR";
3117
+ const returnUrl = params.returnUrl || config.returnUrl || "";
3118
+ const channelMap = {
3119
+ dana: "EMONEY_DANA_SNAP",
3120
+ ovo: "EMONEY_OVO_SNAP",
3121
+ shopeepay: "EMONEY_SHOPEEPAY_SNAP"
3122
+ };
3123
+ const channel = config.extra?.ewalletChannel || channelMap[method] || `EMONEY_${method.toUpperCase()}_SNAP`;
3124
+ const body = {
3125
+ partnerReferenceNo: orderId,
3126
+ amount: this.snapAmount(amount, currency),
3127
+ pointOfInitiation: "pc",
3128
+ urlParam: {
3129
+ url: returnUrl || "https://example.com/return",
3130
+ type: "PAY_RETURN",
3131
+ isDeepLink: "N"
3132
+ },
3133
+ additionalInfo: {
3134
+ channel,
3135
+ orderTitle: params.productDetails || `Pembayaran ${orderId}`,
3136
+ supportDeepLinkCheckoutUrl: "false"
3137
+ }
3138
+ };
3139
+ if (params.providerParams) {
3140
+ Object.assign(body.additionalInfo, params.providerParams);
3141
+ }
3142
+ const endpoint = "/direct-debit/core/v1/debit/payment-host-to-host";
3143
+ const data = await snap.request("POST", endpoint, body, {
3144
+ deviceId: config.extra?.deviceId,
3145
+ ipAddress: config.extra?.ipAddress
3146
+ });
3147
+ return {
3148
+ success: true,
3149
+ provider: "doku",
3150
+ orderId: data.partnerReferenceNo || orderId,
3151
+ amount: Number(data.amount?.value || amount) || Math.round(amount),
3152
+ reference: data.partnerReferenceNo || orderId,
3153
+ paymentUrl: data.webRedirectUrl || data.paymentUrl,
3154
+ rawResponse: data
3155
+ };
3156
+ }
2769
3157
  async verifyCallback(body, config) {
3158
+ const headers = config.extra?.headers || {};
3159
+ if (this.isSnap(config)) {
3160
+ return this.verifySnapCallback(body, config, headers);
3161
+ }
2770
3162
  const rawStatus = (body.transaction?.status || body.status || "").toUpperCase();
2771
3163
  const isPaid = rawStatus === "SUCCESS" || rawStatus === "PAID" || rawStatus === "SETTLED";
2772
3164
  const isPending = rawStatus === "PENDING";
@@ -2776,7 +3168,6 @@ var DokuProvider = class extends BasePaymentProvider {
2776
3168
  const amount = body.order?.amount || body.amount || 0;
2777
3169
  const status = isPaid ? "paid" : isPending ? "pending" : isExpired ? "expired" : "failed";
2778
3170
  const secretKey = config.secretKey || config.apiKey || "";
2779
- const headers = config.extra?.headers || {};
2780
3171
  const signature = headers["signature"] || headers["Signature"] || config.extra?.dokuSignature || config.extra?.signatureHeader;
2781
3172
  const clientId = config.merchantCode || config.clientKey || "";
2782
3173
  let isValid = true;
@@ -2797,6 +3188,40 @@ var DokuProvider = class extends BasePaymentProvider {
2797
3188
  rawPayload: body
2798
3189
  };
2799
3190
  }
3191
+ /**
3192
+ * Verifikasi webhook / notifikasi DOKU SNAP.
3193
+ * Signature dibangun dengan symmetric HMAC-SHA512 (AccessToken kosong).
3194
+ */
3195
+ verifySnapCallback(body, config, headers) {
3196
+ const clientSecret = config.apiKey || config.serverKey || config.secretKey || "";
3197
+ const endpointUrl = config.extra?.notificationPath || headers["x-path"] || config.extra?.headers?.["request-target"] || "/api/payment/webhook";
3198
+ let isValid = true;
3199
+ const incomingSig = headers["x-signature"] || headers["X-SIGNATURE"] || headers["signature"] || headers["Signature"] || "";
3200
+ if (incomingSig && clientSecret) {
3201
+ isValid = verifySnapWebhookSignature(headers, body, clientSecret, endpointUrl);
3202
+ }
3203
+ const explicitStatus = String(body.transactionStatus || body.status || body.latestTransactionStatus || "").toUpperCase();
3204
+ const isPaid = explicitStatus === "SUCCESS" || explicitStatus === "PAID" || explicitStatus === "SETTLED" || explicitStatus === "00" || !explicitStatus && Boolean(body.paidAmount?.value ?? body.totalAmount?.value);
3205
+ const isPending = explicitStatus === "PENDING" || explicitStatus === "11" || explicitStatus === "ONGOING";
3206
+ const isFailed = explicitStatus === "FAILED" || explicitStatus === "DECLINED" || Boolean(explicitStatus) && !isPaid && !isPending && explicitStatus !== "00";
3207
+ const isExpired = explicitStatus === "EXPIRED";
3208
+ const orderId = body.trxId || body.partnerReferenceNo || body.originalPartnerReferenceNo || body.order?.invoice_number || body.invoice_number || body.order_id || "";
3209
+ const paidValue = body.paidAmount?.value ?? body.totalAmount?.value ?? body.amount?.value ?? body.amount ?? 0;
3210
+ const status = isPaid ? "paid" : isPending ? "pending" : isExpired ? "expired" : "failed";
3211
+ return {
3212
+ isValid,
3213
+ provider: "doku",
3214
+ orderId: String(orderId),
3215
+ amount: Number(paidValue) || 0,
3216
+ status,
3217
+ isPaid,
3218
+ isPending,
3219
+ isFailed,
3220
+ isExpired,
3221
+ statusCode: explicitStatus || "SUCCESS",
3222
+ rawPayload: body
3223
+ };
3224
+ }
2800
3225
  async getPaymentMethods(params, config) {
2801
3226
  const staticMethods = [
2802
3227
  {
@@ -2956,6 +3381,9 @@ var DokuProvider = class extends BasePaymentProvider {
2956
3381
  const clientId = config.merchantCode || config.merchantId || config.clientKey || "";
2957
3382
  const secretKey = config.apiKey || config.serverKey || config.secretKey || "";
2958
3383
  const sandbox = !!config.sandbox;
3384
+ if (this.isSnap(config)) {
3385
+ return this.snapCheckTransaction(params, config, clientId);
3386
+ }
2959
3387
  const endpoint = `/orders/v1/status/${merchantOrderId}`;
2960
3388
  const url = `${this.getBaseUrl(sandbox)}${endpoint}`;
2961
3389
  const headers = generateDokuHeaders(clientId, secretKey, endpoint);
@@ -3033,6 +3461,58 @@ var DokuProvider = class extends BasePaymentProvider {
3033
3461
  };
3034
3462
  }
3035
3463
  }
3464
+ /** Cek status transaksi SNAP (menggunakan Query QRIS bila ref berasal dari QRIS). */
3465
+ async snapCheckTransaction(params, config, clientId) {
3466
+ const { merchantOrderId } = params;
3467
+ const snap = this.buildSnap(config);
3468
+ try {
3469
+ const body = {
3470
+ originalPartnerReferenceNo: merchantOrderId,
3471
+ serviceCode: "47",
3472
+ merchantId: config.extra?.merchantId || config.projectId || ""
3473
+ };
3474
+ const data = await snap.request("POST", "/snap-adapter/b2b/v1.0/qr/qr-mpm-query", body);
3475
+ const txStatus = String(data.latestTransactionStatus || "").toUpperCase();
3476
+ const isPaid = txStatus === "SUCCESS" || txStatus === "00" || txStatus === "PAID" || txStatus === "SETTLED";
3477
+ const isPending = txStatus === "PENDING" || txStatus === "ONGOING" || txStatus === "11";
3478
+ const isExpired = txStatus === "EXPIRED";
3479
+ const isFailed = txStatus === "FAILED" || txStatus === "DECLINED" || Boolean(txStatus) && !isPaid && !isPending && !isExpired;
3480
+ const status = isPaid ? "paid" : isPending ? "pending" : isExpired ? "expired" : "failed";
3481
+ return {
3482
+ success: true,
3483
+ provider: "doku",
3484
+ orderId: data.originalPartnerReferenceNo || merchantOrderId,
3485
+ reference: data.originalReferenceNo || "",
3486
+ amount: Number(data.amount?.value || 0),
3487
+ statusCode: txStatus || String(data.responseCode || ""),
3488
+ status,
3489
+ isPaid,
3490
+ isPending,
3491
+ isFailed,
3492
+ isExpired,
3493
+ statusMessage: txStatus || data.responseMessage || "",
3494
+ paymentType: "QRIS",
3495
+ rawResponse: data
3496
+ };
3497
+ } catch (e) {
3498
+ return {
3499
+ success: false,
3500
+ provider: "doku",
3501
+ orderId: merchantOrderId,
3502
+ reference: "",
3503
+ amount: 0,
3504
+ statusCode: "ERROR",
3505
+ status: "failed",
3506
+ isPaid: false,
3507
+ isPending: false,
3508
+ isFailed: true,
3509
+ isExpired: false,
3510
+ statusMessage: e.message || "Failed to check SNAP transaction status",
3511
+ error: e.message || "Failed to check SNAP transaction status",
3512
+ rawResponse: null
3513
+ };
3514
+ }
3515
+ }
3036
3516
  };
3037
3517
 
3038
3518
  // src/providers/prismalink/signature.ts
@@ -3426,15 +3906,15 @@ var PrismalinkProvider = class extends BasePaymentProvider {
3426
3906
  };
3427
3907
 
3428
3908
  // src/providers/faspay/signature.ts
3429
- var import_crypto9 = __toESM(require("crypto"));
3909
+ var import_crypto11 = __toESM(require("crypto"));
3430
3910
  function generateFaspaySignature(userId, password, billNo) {
3431
- const md5Hash = import_crypto9.default.createHash("md5").update(`${userId}${password}${billNo}`).digest("hex");
3432
- return import_crypto9.default.createHash("sha1").update(md5Hash).digest("hex");
3911
+ const md5Hash = import_crypto11.default.createHash("md5").update(`${userId}${password}${billNo}`).digest("hex");
3912
+ return import_crypto11.default.createHash("sha1").update(md5Hash).digest("hex");
3433
3913
  }
3434
3914
  function verifyFaspaySignature(userId, password, billNo, paymentStatusCode, incomingSignature) {
3435
3915
  if (!incomingSignature || !password) return false;
3436
- const md5Hash = import_crypto9.default.createHash("md5").update(`${userId}${password}${billNo}${paymentStatusCode}`).digest("hex");
3437
- const computed = import_crypto9.default.createHash("sha1").update(md5Hash).digest("hex");
3916
+ const md5Hash = import_crypto11.default.createHash("md5").update(`${userId}${password}${billNo}${paymentStatusCode}`).digest("hex");
3917
+ const computed = import_crypto11.default.createHash("sha1").update(md5Hash).digest("hex");
3438
3918
  const simpleComputed = generateFaspaySignature(userId, password, billNo);
3439
3919
  return safeCompare(incomingSignature, computed) || safeCompare(incomingSignature, simpleComputed);
3440
3920
  }
@@ -3847,10 +4327,10 @@ var FaspayProvider = class extends BasePaymentProvider {
3847
4327
  };
3848
4328
 
3849
4329
  // src/providers/finpay/signature.ts
3850
- var import_crypto11 = __toESM(require("crypto"));
4330
+ var import_crypto13 = __toESM(require("crypto"));
3851
4331
  function generateFinpaySignature(merchantId, orderId, amount, merchantKey) {
3852
4332
  const data = `${merchantId}%${orderId}%${Math.round(amount)}%${merchantKey}`;
3853
- return import_crypto11.default.createHmac("sha512", merchantKey).update(data).digest("hex");
4333
+ return import_crypto13.default.createHmac("sha512", merchantKey).update(data).digest("hex");
3854
4334
  }
3855
4335
  function verifyFinpaySignature(merchantId, orderId, amount, merchantKey, incomingSignature) {
3856
4336
  if (!incomingSignature || !merchantKey) return false;
@@ -5747,7 +6227,7 @@ var PaypalProvider = class extends BasePaymentProvider {
5747
6227
  };
5748
6228
 
5749
6229
  // src/providers/adyen/signature.ts
5750
- var import_crypto16 = require("crypto");
6230
+ var import_crypto18 = require("crypto");
5751
6231
  function verifyAdyenWebhook(notificationItem, hmacKey) {
5752
6232
  if (!hmacKey || !notificationItem) return false;
5753
6233
  try {
@@ -5764,7 +6244,7 @@ function verifyAdyenWebhook(notificationItem, hmacKey) {
5764
6244
  ];
5765
6245
  const signedData = fields.join(":");
5766
6246
  const keyBytes = Buffer.from(hmacKey, "hex");
5767
- const expected = (0, import_crypto16.createHmac)("sha256", keyBytes).update(signedData, "utf8").digest("base64");
6247
+ const expected = (0, import_crypto18.createHmac)("sha256", keyBytes).update(signedData, "utf8").digest("base64");
5768
6248
  const provided = notificationItem.additionalData?.hmacSignature || "";
5769
6249
  return safeCompare(expected, provided);
5770
6250
  } catch {
@@ -5997,11 +6477,11 @@ var AdyenProvider = class extends BasePaymentProvider {
5997
6477
  };
5998
6478
 
5999
6479
  // src/providers/checkoutcom/signature.ts
6000
- var import_crypto18 = require("crypto");
6480
+ var import_crypto20 = require("crypto");
6001
6481
  function verifyCheckoutComWebhook(body, signatureHeader, secret) {
6002
6482
  if (!secret || !signatureHeader || !body) return false;
6003
6483
  try {
6004
- const expected = (0, import_crypto18.createHmac)("sha256", secret).update(body, "utf8").digest("hex");
6484
+ const expected = (0, import_crypto20.createHmac)("sha256", secret).update(body, "utf8").digest("hex");
6005
6485
  const provided = signatureHeader.replace(/^sha256=/, "");
6006
6486
  return safeCompare(expected, provided);
6007
6487
  } catch {
@@ -6221,11 +6701,11 @@ var CheckoutComProvider = class extends BasePaymentProvider {
6221
6701
  };
6222
6702
 
6223
6703
  // src/providers/razorpay/signature.ts
6224
- var import_crypto20 = require("crypto");
6704
+ var import_crypto22 = require("crypto");
6225
6705
  function verifyRazorpayWebhook(rawBody, signature, webhookSecret) {
6226
6706
  if (!webhookSecret || !signature || !rawBody) return false;
6227
6707
  try {
6228
- const expected = (0, import_crypto20.createHmac)("sha256", webhookSecret).update(rawBody).digest("hex");
6708
+ const expected = (0, import_crypto22.createHmac)("sha256", webhookSecret).update(rawBody).digest("hex");
6229
6709
  return safeCompare(expected, signature);
6230
6710
  } catch {
6231
6711
  return false;
@@ -6451,12 +6931,12 @@ var RazorpayProvider = class extends BasePaymentProvider {
6451
6931
  };
6452
6932
 
6453
6933
  // src/providers/square/signature.ts
6454
- var import_crypto22 = require("crypto");
6934
+ var import_crypto24 = require("crypto");
6455
6935
  function verifySquareWebhook(rawBody, signatureHeader, signatureKey, notificationUrl) {
6456
6936
  if (!signatureKey || !signatureHeader || !rawBody) return false;
6457
6937
  try {
6458
6938
  const payload = notificationUrl + rawBody;
6459
- const expected = (0, import_crypto22.createHmac)("sha256", signatureKey).update(payload).digest("base64");
6939
+ const expected = (0, import_crypto24.createHmac)("sha256", signatureKey).update(payload).digest("base64");
6460
6940
  return safeCompare(expected, signatureHeader);
6461
6941
  } catch {
6462
6942
  return false;
@@ -6696,7 +7176,7 @@ var SquareProvider = class extends BasePaymentProvider {
6696
7176
  };
6697
7177
 
6698
7178
  // src/providers/payu/signature.ts
6699
- var import_crypto24 = require("crypto");
7179
+ var import_crypto26 = require("crypto");
6700
7180
  function verifyPayuWebhook(rawBody, signatureHeader, md5Key) {
6701
7181
  if (!md5Key || !signatureHeader || !rawBody) return false;
6702
7182
  try {
@@ -6709,10 +7189,10 @@ function verifyPayuWebhook(rawBody, signatureHeader, md5Key) {
6709
7189
  const algorithm = (parts["algorithm"] || "MD5").toUpperCase();
6710
7190
  if (!providedSig) return false;
6711
7191
  if (algorithm === "MD5") {
6712
- const expected = (0, import_crypto24.createHash)("md5").update(rawBody + md5Key).digest("hex");
7192
+ const expected = (0, import_crypto26.createHash)("md5").update(rawBody + md5Key).digest("hex");
6713
7193
  return safeCompare(expected, providedSig);
6714
7194
  } else if (algorithm === "SHA-256") {
6715
- const expected = (0, import_crypto24.createHash)("sha256").update(rawBody + md5Key).digest("hex");
7195
+ const expected = (0, import_crypto26.createHash)("sha256").update(rawBody + md5Key).digest("hex");
6716
7196
  return safeCompare(expected, providedSig);
6717
7197
  }
6718
7198
  return false;
@@ -6988,7 +7468,7 @@ var PayuProvider = class extends BasePaymentProvider {
6988
7468
  };
6989
7469
 
6990
7470
  // src/providers/braintree/signature.ts
6991
- var import_crypto26 = require("crypto");
7471
+ var import_crypto28 = require("crypto");
6992
7472
  function verifyBraintreeWebhook(btSignature, btPayload, privateKey) {
6993
7473
  if (!privateKey || !btSignature || !btPayload) return false;
6994
7474
  try {
@@ -6996,8 +7476,8 @@ function verifyBraintreeWebhook(btSignature, btPayload, privateKey) {
6996
7476
  if (parts.length < 2) return false;
6997
7477
  const providedHmac = parts[1];
6998
7478
  const payload = Buffer.from(btPayload, "base64").toString("utf8");
6999
- const secretHash = (0, import_crypto26.createHash)("sha1").update(privateKey).digest("hex");
7000
- const expected = (0, import_crypto26.createHmac)("sha1", secretHash).update(payload).digest("hex");
7479
+ const secretHash = (0, import_crypto28.createHash)("sha1").update(privateKey).digest("hex");
7480
+ const expected = (0, import_crypto28.createHmac)("sha1", secretHash).update(payload).digest("hex");
7001
7481
  return safeCompare(expected, providedHmac);
7002
7482
  } catch {
7003
7483
  return false;
@@ -7224,11 +7704,11 @@ var BraintreeProvider = class extends BasePaymentProvider {
7224
7704
  };
7225
7705
 
7226
7706
  // src/providers/twocheckout/signature.ts
7227
- var import_crypto28 = require("crypto");
7707
+ var import_crypto30 = require("crypto");
7228
7708
  function buildTwoCheckoutAuth(merchantCode, secretKey) {
7229
7709
  const date = Math.floor(Date.now() / 1e3).toString();
7230
7710
  const raw = merchantCode + date;
7231
- const hmac = (0, import_crypto28.createHmac)("sha256", secretKey).update(raw).digest("hex");
7711
+ const hmac = (0, import_crypto30.createHmac)("sha256", secretKey).update(raw).digest("hex");
7232
7712
  const header = `code="${merchantCode}" date="${date}" hash="${hmac}"`;
7233
7713
  return { header, date };
7234
7714
  }
@@ -7236,7 +7716,7 @@ function verifyTwoCheckoutWebhook(secretWord, saleId, productId, invoiceId, prov
7236
7716
  if (!secretWord || !providedHash) return false;
7237
7717
  try {
7238
7718
  const raw = secretWord + saleId + productId + invoiceId;
7239
- const expected = (0, import_crypto28.createHash)("md5").update(raw).digest("hex");
7719
+ const expected = (0, import_crypto30.createHash)("md5").update(raw).digest("hex");
7240
7720
  return safeCompare(expected, providedHash);
7241
7721
  } catch {
7242
7722
  return false;
@@ -9593,6 +10073,7 @@ var buayar = new Buayar();
9593
10073
  PrismalinkProvider,
9594
10074
  RazorpayClient,
9595
10075
  RazorpayProvider,
10076
+ SnapClient,
9596
10077
  SquareClient,
9597
10078
  SquareProvider,
9598
10079
  StripeClient,
@@ -9616,6 +10097,8 @@ var buayar = new Buayar();
9616
10097
  generateNicepayToken,
9617
10098
  generateOyHeaders,
9618
10099
  generatePrismalinkSignature,
10100
+ generateSnapAsymmetricSignature,
10101
+ generateSnapSymmetricSignature,
9619
10102
  getDuitkuInquirySignatures,
9620
10103
  getDuitkuPaymentMethodsSignature,
9621
10104
  getDuitkuStatusSignatures,
@@ -9623,6 +10106,7 @@ var buayar = new Buayar();
9623
10106
  getXenditAuthHeader,
9624
10107
  hmacSha256,
9625
10108
  md5,
10109
+ minifyJson,
9626
10110
  parseCoreChargeResponse,
9627
10111
  paymentManager,
9628
10112
  resolveConfigFromEnv,
@@ -9630,7 +10114,11 @@ var buayar = new Buayar();
9630
10114
  serializePaypalParams,
9631
10115
  serializeStripeParams,
9632
10116
  sha256,
10117
+ sha256Hex,
9633
10118
  sha512,
10119
+ snapExternalId,
10120
+ snapTimestamp,
10121
+ snapUtcTimestamp,
9634
10122
  toCanonicalPaymentMethod,
9635
10123
  toDokuPaymentMethod,
9636
10124
  toDuitkuPaymentMethod,
@@ -9656,6 +10144,7 @@ var buayar = new Buayar();
9656
10144
  verifyPayuWebhook,
9657
10145
  verifyPrismalinkSignature,
9658
10146
  verifyRazorpayWebhook,
10147
+ verifySnapWebhookSignature,
9659
10148
  verifySquareWebhook,
9660
10149
  verifyStripeWebhook,
9661
10150
  verifyTwoCheckoutWebhook,