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