@hypay/typescript-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1067 @@
1
+ import {
2
+ buildItemsString,
3
+ calculateItemsTotal,
4
+ isTestMasof,
5
+ isValidEmail,
6
+ isValidIsraeliId,
7
+ isValidMasof,
8
+ isValidToken,
9
+ parsePaymentPageResponse,
10
+ parseQueryString,
11
+ parseRedirectUrl,
12
+ parseTokef,
13
+ serializeParams,
14
+ toQueryString
15
+ } from "./chunk-LKIXYEBZ.mjs";
16
+
17
+ // src/types.ts
18
+ var Coin = /* @__PURE__ */ ((Coin2) => {
19
+ Coin2[Coin2["ILS"] = 1] = "ILS";
20
+ Coin2[Coin2["USD"] = 2] = "USD";
21
+ Coin2[Coin2["EUR"] = 3] = "EUR";
22
+ Coin2[Coin2["GBP"] = 4] = "GBP";
23
+ return Coin2;
24
+ })(Coin || {});
25
+ var PageLang = /* @__PURE__ */ ((PageLang2) => {
26
+ PageLang2["HEB"] = "HEB";
27
+ PageLang2["ENG"] = "ENG";
28
+ return PageLang2;
29
+ })(PageLang || {});
30
+ var TashType = /* @__PURE__ */ ((TashType2) => {
31
+ TashType2[TashType2["Regular"] = 1] = "Regular";
32
+ TashType2[TashType2["Credit"] = 6] = "Credit";
33
+ return TashType2;
34
+ })(TashType || {});
35
+ var Bank = /* @__PURE__ */ ((Bank2) => {
36
+ Bank2[Bank2["Isracard"] = 1] = "Isracard";
37
+ Bank2[Bank2["VisaCal"] = 2] = "VisaCal";
38
+ Bank2[Bank2["Diners"] = 3] = "Diners";
39
+ Bank2[Bank2["Amex"] = 4] = "Amex";
40
+ Bank2[Bank2["MAX"] = 6] = "MAX";
41
+ Bank2[Bank2["BIT"] = 99] = "BIT";
42
+ return Bank2;
43
+ })(Bank || {});
44
+ var Brand = /* @__PURE__ */ ((Brand2) => {
45
+ Brand2[Brand2["PL"] = 0] = "PL";
46
+ Brand2[Brand2["MasterCard"] = 1] = "MasterCard";
47
+ Brand2[Brand2["Visa"] = 2] = "Visa";
48
+ Brand2[Brand2["Diners"] = 3] = "Diners";
49
+ Brand2[Brand2["Amex"] = 4] = "Amex";
50
+ Brand2[Brand2["Isracard"] = 5] = "Isracard";
51
+ return Brand2;
52
+ })(Brand || {});
53
+ var Issuer = /* @__PURE__ */ ((Issuer2) => {
54
+ Issuer2[Issuer2["Foreign"] = 0] = "Foreign";
55
+ Issuer2[Issuer2["Isracard"] = 1] = "Isracard";
56
+ Issuer2[Issuer2["VisaCal"] = 2] = "VisaCal";
57
+ Issuer2[Issuer2["JCB"] = 5] = "JCB";
58
+ Issuer2[Issuer2["MAX"] = 6] = "MAX";
59
+ return Issuer2;
60
+ })(Issuer || {});
61
+ var HKNewStatus = /* @__PURE__ */ ((HKNewStatus2) => {
62
+ HKNewStatus2[HKNewStatus2["Terminate"] = 1] = "Terminate";
63
+ HKNewStatus2[HKNewStatus2["Activate"] = 2] = "Activate";
64
+ return HKNewStatus2;
65
+ })(HKNewStatus || {});
66
+ var SpecialCardType = /* @__PURE__ */ ((SpecialCardType2) => {
67
+ SpecialCardType2["Default"] = "00";
68
+ SpecialCardType2["Immediate"] = "01";
69
+ SpecialCardType2["Club"] = "70";
70
+ SpecialCardType2["PetrolCard"] = "03";
71
+ SpecialCardType2["DualCard"] = "04";
72
+ SpecialCardType2["DualClub"] = "74";
73
+ SpecialCardType2["PetrolClub"] = "75";
74
+ SpecialCardType2["Chargeable"] = "06";
75
+ SpecialCardType2["Petrol"] = "08";
76
+ SpecialCardType2["Tourist"] = "99";
77
+ return SpecialCardType2;
78
+ })(SpecialCardType || {});
79
+ var HypayError = class extends Error {
80
+ constructor(message, code, raw, params) {
81
+ super(message);
82
+ this.name = "HypayError";
83
+ this.code = code;
84
+ this.raw = raw;
85
+ this.params = params;
86
+ }
87
+ };
88
+ var HypayNetworkError = class extends Error {
89
+ constructor(message, cause) {
90
+ super(message);
91
+ this.name = "HypayNetworkError";
92
+ this.cause = cause;
93
+ }
94
+ };
95
+ var HypayTimeoutError = class extends Error {
96
+ constructor(timeoutMs) {
97
+ super(`Request timed out after ${timeoutMs}ms`);
98
+ this.name = "HypayTimeoutError";
99
+ this.timeoutMs = timeoutMs;
100
+ }
101
+ };
102
+
103
+ // src/errors.ts
104
+ var HYPAY_ERROR_CODES = {
105
+ "33": "Refund amount is greater than the original transaction amount",
106
+ "250": "Deal does not exist or already committed",
107
+ "400": "Sum of items differs from transaction amount",
108
+ "401": "First or last name is required",
109
+ "402": "Transaction information (Info) is required",
110
+ "600": "Checking card number (J2)",
111
+ "700": "Approved without charge (J5 credit line reservation)",
112
+ "800": "Postpone transaction (delayed charge)",
113
+ "901": "Terminal is not permitted to work in this method",
114
+ "902": "Authentication error \u2014 reference differs from configured method",
115
+ "903": "Number of payments exceeds terminal configuration",
116
+ "905": "Wrong parameter value",
117
+ "906": "Agreement does not exist",
118
+ "910": "Token request from invalid transaction \u2014 add allowFalse=True",
119
+ "920": "Deal does not exist or already committed/cancelled",
120
+ "990": "Card details not fully readable",
121
+ "996": "Terminal is not permitted to use token",
122
+ "997": "Token is not valid",
123
+ "998": "Deal cancelled",
124
+ "999": "Communication error"
125
+ };
126
+ var SHVA_ERROR_CODES = {
127
+ "0": "Approved",
128
+ "000": "Approved",
129
+ "1": "Card blocked \u2014 confiscate card",
130
+ "001": "Card blocked",
131
+ "2": "Stolen card \u2014 confiscate card",
132
+ "002": "Stolen card \u2014 confiscate card",
133
+ "3": "Call credit card company",
134
+ "003": "Call credit card company",
135
+ "4": "Transaction not approved",
136
+ "004": "Transaction not approved",
137
+ "5": "Forged card \u2014 confiscate card",
138
+ "005": "Forged card \u2014 confiscate card",
139
+ "6": "Incorrect ID or CVV",
140
+ "006": "Rejected: incorrect CVV2",
141
+ "7": "Must call credit card company",
142
+ "007": "Rejected: incorrect CAVV/UCAF",
143
+ "8": "Error building encryption key for blocked file",
144
+ "008": "Rejected: incorrect AVS",
145
+ "9": "Could not connect \u2014 call credit card company",
146
+ "009": "Rejection \u2014 communication disconnect",
147
+ "010": "Partial approval",
148
+ "011": "Rejected: insufficient points/stars/miles/benefit",
149
+ "012": "Card not authorized at this terminal",
150
+ "013": "Rejected: incorrect balance code",
151
+ "014": "Rejected: card not associated with network",
152
+ "015": "Rejected: card not valid (expired)",
153
+ "016": "Rejected: currency not authorized",
154
+ "017": "Rejected: credit type not authorized for transaction",
155
+ "026": "Rejected: incorrect ID",
156
+ "033": "Invalid card",
157
+ "036": "Expired card",
158
+ "041": "Must query due to ceiling only for J2 transaction",
159
+ "042": "Must query (not ceiling-only) for J2 transaction",
160
+ "173": "Duplicate transaction"
161
+ };
162
+ var ALL_ERROR_CODES = {
163
+ ...SHVA_ERROR_CODES,
164
+ ...HYPAY_ERROR_CODES
165
+ };
166
+ function getErrorMessage(ccode) {
167
+ return ALL_ERROR_CODES[ccode] ?? `Unknown error (CCode=${ccode})`;
168
+ }
169
+ function isSuccessCode(ccode) {
170
+ return ccode === "0" || ccode === "600" || ccode === "700" || ccode === "800";
171
+ }
172
+ function isShvaError(ccode) {
173
+ const num = parseInt(ccode, 10);
174
+ return !isNaN(num) && num >= 1 && num <= 200;
175
+ }
176
+ function isHypayError(ccode) {
177
+ const num = parseInt(ccode, 10);
178
+ if (isNaN(num)) return false;
179
+ if (num === 600 || num === 700 || num === 800) return false;
180
+ return num >= 201 && num <= 999;
181
+ }
182
+
183
+ // src/client.ts
184
+ var DEFAULT_BASE_URL = "https://pay.hyp.co.il/p/";
185
+ var DEFAULT_TIMEOUT = 3e4;
186
+ var HypayClient = class {
187
+ constructor(config) {
188
+ this.masof = config.masof;
189
+ this.apiKey = config.apiKey;
190
+ this.passP = config.passP;
191
+ this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/?$/, "/");
192
+ this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
193
+ this.fetchFn = config.fetch ?? globalThis.fetch;
194
+ this.hooks = config.hooks ?? {};
195
+ }
196
+ // ─── Low-level HTTP ──────────────────────────────────────────────────────
197
+ /**
198
+ * Make a GET request to the Hypay API.
199
+ * All API calls use GET by default, which is the most common method for Hypay.
200
+ */
201
+ async requestGet(action, params) {
202
+ const serialized = serializeParams(params);
203
+ const qs = toQueryString(serialized);
204
+ const url = `${this.baseUrl}?${qs}`;
205
+ const context = {
206
+ action,
207
+ method: "GET",
208
+ url,
209
+ params: Object.fromEntries(
210
+ Object.entries(serialized).filter(([, v]) => v !== void 0).map(([k, v]) => [k, String(v)])
211
+ )
212
+ };
213
+ await this.hooks.onRequest?.(context);
214
+ const start = Date.now();
215
+ let response;
216
+ try {
217
+ const controller = new AbortController();
218
+ const timer = setTimeout(() => controller.abort(), this.timeout);
219
+ try {
220
+ response = await this.fetchFn(url, { signal: controller.signal });
221
+ } finally {
222
+ clearTimeout(timer);
223
+ }
224
+ } catch (err) {
225
+ if (err instanceof DOMException && err.name === "AbortError") {
226
+ throw new HypayTimeoutError(this.timeout);
227
+ }
228
+ throw new HypayNetworkError(
229
+ `Network error while calling Hypay API: ${err instanceof Error ? err.message : String(err)}`,
230
+ err
231
+ );
232
+ }
233
+ const text = await response.text();
234
+ const parsed = parseQueryString(text);
235
+ const durationMs = Date.now() - start;
236
+ await this.hooks.onResponse?.({
237
+ ...context,
238
+ statusCode: response.status,
239
+ raw: text,
240
+ parsedParams: parsed,
241
+ durationMs
242
+ });
243
+ return { raw: text, params: parsed };
244
+ }
245
+ /**
246
+ * Make a POST request to the Hypay API.
247
+ * Use for actions that send large payloads (e.g., WalletToken with Apple Pay).
248
+ */
249
+ async requestPost(action, params) {
250
+ const serialized = serializeParams(params);
251
+ const body = toQueryString(serialized);
252
+ const context = {
253
+ action,
254
+ method: "POST",
255
+ url: this.baseUrl,
256
+ params: Object.fromEntries(
257
+ Object.entries(serialized).filter(([, v]) => v !== void 0).map(([k, v]) => [k, String(v)])
258
+ )
259
+ };
260
+ await this.hooks.onRequest?.(context);
261
+ const start = Date.now();
262
+ let response;
263
+ try {
264
+ const controller = new AbortController();
265
+ const timer = setTimeout(() => controller.abort(), this.timeout);
266
+ try {
267
+ response = await this.fetchFn(this.baseUrl, {
268
+ method: "POST",
269
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
270
+ body,
271
+ signal: controller.signal
272
+ });
273
+ } finally {
274
+ clearTimeout(timer);
275
+ }
276
+ } catch (err) {
277
+ if (err instanceof DOMException && err.name === "AbortError") {
278
+ throw new HypayTimeoutError(this.timeout);
279
+ }
280
+ throw new HypayNetworkError(
281
+ `Network error while calling Hypay API: ${err instanceof Error ? err.message : String(err)}`,
282
+ err
283
+ );
284
+ }
285
+ const text = await response.text();
286
+ const parsed = parseQueryString(text);
287
+ const durationMs = Date.now() - start;
288
+ await this.hooks.onResponse?.({
289
+ ...context,
290
+ statusCode: response.status,
291
+ raw: text,
292
+ parsedParams: parsed,
293
+ durationMs
294
+ });
295
+ return { raw: text, params: parsed };
296
+ }
297
+ /**
298
+ * Unified request method. Defaults to GET, uses POST for wallet/large payloads.
299
+ */
300
+ async request(action, params, method = "GET") {
301
+ return method === "POST" ? this.requestPost(action, params) : this.requestGet(action, params);
302
+ }
303
+ authParams() {
304
+ return {
305
+ Masof: this.masof,
306
+ PassP: this.passP
307
+ };
308
+ }
309
+ throwIfError(parsed, raw, context) {
310
+ if (parsed.CCode && !isSuccessCode(parsed.CCode)) {
311
+ const error = new HypayError(getErrorMessage(parsed.CCode), parsed.CCode, raw, parsed);
312
+ if (context) {
313
+ this.hooks.onError?.(error, context);
314
+ }
315
+ throw error;
316
+ }
317
+ }
318
+ // ═══════════════════════════════════════════════════════════════════════════
319
+ // PAY PROTOCOL — Payment Page
320
+ // ═══════════════════════════════════════════════════════════════════════════
321
+ /**
322
+ * **Step 1** — Sign payment page parameters (APISign + What=SIGN).
323
+ *
324
+ * This generates a `signature` that must be appended to the payment page URL.
325
+ * The signature prevents parameter tampering on the client side.
326
+ *
327
+ * Requires "Verify by signature in the payment page" enabled in terminal settings.
328
+ *
329
+ * @example
330
+ * ```ts
331
+ * const result = await client.sign({
332
+ * Info: "Order #123",
333
+ * Amount: 100,
334
+ * UserId: "203269535",
335
+ * ClientName: "Israel",
336
+ * Coin: Coin.ILS,
337
+ * Sign: true,
338
+ * UTF8: true,
339
+ * UTF8out: true,
340
+ * MoreData: true,
341
+ * });
342
+ * console.log(result.signature);
343
+ * ```
344
+ */
345
+ async sign(params) {
346
+ const allParams = {
347
+ action: "APISign",
348
+ What: "SIGN",
349
+ KEY: this.apiKey,
350
+ ...this.authParams(),
351
+ ...params
352
+ };
353
+ const { raw, params: parsed } = await this.request("APISign", allParams);
354
+ return {
355
+ raw,
356
+ signature: parsed.signature ?? "",
357
+ params: parsed
358
+ };
359
+ }
360
+ /**
361
+ * **Step 2** — Build the full payment page URL from a signed query string.
362
+ *
363
+ * @param signedQueryString - The raw result from `sign()`.
364
+ */
365
+ buildPaymentPageUrl(signedQueryString) {
366
+ return `${this.baseUrl}?${signedQueryString}`;
367
+ }
368
+ /**
369
+ * **Steps 1 + 2 combined** — Sign parameters and return the full payment page URL.
370
+ *
371
+ * @example
372
+ * ```ts
373
+ * const url = await client.createPaymentPageUrl({
374
+ * Info: "Order #123",
375
+ * Amount: 100,
376
+ * UserId: "203269535",
377
+ * ClientName: "Israel",
378
+ * Coin: Coin.ILS,
379
+ * Sign: true,
380
+ * UTF8: true,
381
+ * UTF8out: true,
382
+ * });
383
+ * // Redirect the customer to `url`
384
+ * ```
385
+ */
386
+ async createPaymentPageUrl(params) {
387
+ const signResult = await this.sign(params);
388
+ return this.buildPaymentPageUrl(signResult.raw);
389
+ }
390
+ // ═══════════════════════════════════════════════════════════════════════════
391
+ // VERIFICATION — Verify transaction signature
392
+ // ═══════════════════════════════════════════════════════════════════════════
393
+ /**
394
+ * **Step 4** — Verify a transaction using parameters from the success page.
395
+ *
396
+ * Takes the query parameters from the success/failure redirect URL and verifies
397
+ * them against the Hypay server. Returns `verified: true` when `CCode === "0"`,
398
+ * `CCode === "902"` means verification failed.
399
+ *
400
+ * Requires "Verify by signature in the payment page" enabled in terminal settings.
401
+ *
402
+ * @example
403
+ * ```ts
404
+ * import { parseRedirectUrl } from "@hypay/typescript-sdk";
405
+ *
406
+ * const successParams = parseRedirectUrl(req.url);
407
+ * const result = await client.verify(successParams);
408
+ *
409
+ * if (result.verified) {
410
+ * // Transaction is legitimate — process the order
411
+ * } else {
412
+ * // Verification failed — do not process
413
+ * }
414
+ * ```
415
+ */
416
+ async verify(params) {
417
+ const allParams = {
418
+ action: "APISign",
419
+ What: "VERIFY",
420
+ KEY: this.apiKey,
421
+ ...this.authParams(),
422
+ ...params
423
+ };
424
+ const { raw, params: parsed } = await this.request("APISign-VERIFY", allParams);
425
+ return {
426
+ verified: parsed.CCode === "0",
427
+ CCode: parsed.CCode ?? "",
428
+ raw,
429
+ params: parsed
430
+ };
431
+ }
432
+ /**
433
+ * Convenience method: parse a success page URL and verify it in one call.
434
+ *
435
+ * @example
436
+ * ```ts
437
+ * const result = await client.verifyRedirectUrl(
438
+ * "https://yoursite.com/success?Id=123&CCode=0&Amount=10&..."
439
+ * );
440
+ * if (result.verified) { /* OK *\/ }
441
+ * ```
442
+ */
443
+ async verifyRedirectUrl(redirectUrl) {
444
+ const { parseRedirectUrl: parseRedirectUrl2 } = await import("./helpers-T6ONVODW.mjs");
445
+ const params = parseRedirectUrl2(redirectUrl);
446
+ return this.verify(params);
447
+ }
448
+ // ═══════════════════════════════════════════════════════════════════════════
449
+ // SOFT PROTOCOL — Server-side transactions
450
+ // ═══════════════════════════════════════════════════════════════════════════
451
+ /**
452
+ * Execute a server-side transaction using the Soft protocol.
453
+ *
454
+ * Used for:
455
+ * - Token-based charges (after obtaining a token via `getToken()`)
456
+ * - Apple Pay / Google Pay via WalletToken
457
+ * - Direct credit card charges (PCI-compliant environments)
458
+ *
459
+ * Automatically uses POST for WalletToken requests (large payload).
460
+ *
461
+ * @throws {HypayError} When CCode indicates an error
462
+ *
463
+ * @example
464
+ * ```ts
465
+ * // Token-based transaction
466
+ * const result = await client.soft({
467
+ * CC: "1315872608557940000",
468
+ * Tmonth: "04",
469
+ * Tyear: "2025",
470
+ * Amount: 50,
471
+ * Info: "Subscription renewal",
472
+ * UserId: "203269535",
473
+ * ClientName: "Israel",
474
+ * Token: true,
475
+ * MoreData: true,
476
+ * UTF8: true,
477
+ * UTF8out: true,
478
+ * });
479
+ * ```
480
+ *
481
+ * @example
482
+ * ```ts
483
+ * // Apple Pay / Google Pay
484
+ * const result = await client.soft({
485
+ * WalletToken: walletTokenJson,
486
+ * Amount: 100,
487
+ * Info: "Purchase",
488
+ * UserId: "203269535",
489
+ * ClientName: "Israel",
490
+ * });
491
+ * ```
492
+ */
493
+ async soft(params) {
494
+ const allParams = {
495
+ action: "soft",
496
+ ...this.authParams(),
497
+ ...params
498
+ };
499
+ const method = "WalletToken" in params && params.WalletToken ? "POST" : "GET";
500
+ const { raw, params: parsed } = await this.request("soft", allParams, method);
501
+ if (parsed.CCode && !isSuccessCode(parsed.CCode)) {
502
+ throw new HypayError(getErrorMessage(parsed.CCode), parsed.CCode, raw, parsed);
503
+ }
504
+ return {
505
+ Id: parsed.Id ?? "",
506
+ CCode: parsed.CCode ?? "",
507
+ Amount: parsed.Amount ?? "",
508
+ ACode: parsed.ACode ?? "",
509
+ Fild1: parsed.Fild1,
510
+ Fild2: parsed.Fild2,
511
+ Fild3: parsed.Fild3,
512
+ Hesh: parsed.Hesh,
513
+ UID: parsed.UID,
514
+ errMsg: parsed.errMsg,
515
+ raw,
516
+ params: parsed
517
+ };
518
+ }
519
+ // ═══════════════════════════════════════════════════════════════════════════
520
+ // TOKENS — Get and manage tokens
521
+ // ═══════════════════════════════════════════════════════════════════════════
522
+ /**
523
+ * Obtain a token for a previously completed transaction.
524
+ *
525
+ * The token (19 digits) replaces credit card information for future Soft transactions.
526
+ * The transaction must be in one of these statuses: Approved (0), 600, 700, 800, 998.
527
+ *
528
+ * **Important:** Save the Tokef (card validity) and customer details (UserId, etc.)
529
+ * as they are NOT stored in Hypay servers.
530
+ *
531
+ * The response includes convenience fields `Tmonth` and `Tyear` parsed from `Tokef`.
532
+ *
533
+ * @throws {HypayError} When CCode indicates an error (901, 902, 910, 990)
534
+ *
535
+ * @example
536
+ * ```ts
537
+ * const token = await client.getToken({ TransId: "12788261" });
538
+ *
539
+ * // Save for future charges:
540
+ * // token.Token = "1315872608557940000"
541
+ * // token.Tmonth = "04"
542
+ * // token.Tyear = "2025"
543
+ *
544
+ * // Use in a soft transaction:
545
+ * await client.soft({
546
+ * CC: token.Token,
547
+ * Tmonth: token.Tmonth,
548
+ * Tyear: token.Tyear,
549
+ * Token: true,
550
+ * Amount: 50,
551
+ * Info: "Charge via token",
552
+ * UserId: "203269535",
553
+ * ClientName: "Israel",
554
+ * });
555
+ * ```
556
+ */
557
+ async getToken(params) {
558
+ const allParams = {
559
+ action: "getToken",
560
+ ...this.authParams(),
561
+ TransId: params.TransId,
562
+ Fild1: params.Fild1,
563
+ Fild2: params.Fild2,
564
+ Fild3: params.Fild3,
565
+ allowFalse: params.allowFalse
566
+ };
567
+ const { raw, params: parsed } = await this.request("getToken", allParams);
568
+ if (parsed.CCode && parsed.CCode !== "0") {
569
+ throw new HypayError(getErrorMessage(parsed.CCode), parsed.CCode, raw, parsed);
570
+ }
571
+ let Tmonth = "";
572
+ let Tyear = "";
573
+ if (parsed.Tokef && parsed.Tokef.length === 4) {
574
+ const parsed_tokef = parseTokef(parsed.Tokef);
575
+ Tmonth = parsed_tokef.Tmonth;
576
+ Tyear = parsed_tokef.Tyear;
577
+ }
578
+ return {
579
+ Id: parsed.Id ?? "",
580
+ CCode: parsed.CCode ?? "",
581
+ Token: parsed.Token ?? "",
582
+ Tokef: parsed.Tokef ?? "",
583
+ Tmonth,
584
+ Tyear,
585
+ Fild1: parsed.Fild1,
586
+ Fild2: parsed.Fild2,
587
+ Fild3: parsed.Fild3,
588
+ raw,
589
+ params: parsed
590
+ };
591
+ }
592
+ // ═══════════════════════════════════════════════════════════════════════════
593
+ // TRANSACTION J5 — Charge a J5 reservation
594
+ // ═══════════════════════════════════════════════════════════════════════════
595
+ /**
596
+ * Charge a J5 (credit line reservation) transaction.
597
+ *
598
+ * When a Pay/Soft request was made with `J5=True` and `MoreData=True`,
599
+ * you can later charge the reserved amount using the UID and ACode from the response.
600
+ *
601
+ * **If charge amount <= original J5 amount:** use this method (soft + additional params).
602
+ * **If charge amount > original J5 amount:** use the regular token flow instead.
603
+ *
604
+ * @param tokenParams - Standard Soft token parameters (CC, Tmonth, Tyear, etc.)
605
+ * @param j5Params - J5-specific parameters (originalUid, originalAmount, AuthNum)
606
+ *
607
+ * @throws {HypayError} When CCode indicates an error
608
+ *
609
+ * @example
610
+ * ```ts
611
+ * const result = await client.chargeJ5(
612
+ * {
613
+ * CC: token.Token,
614
+ * Tmonth: token.Tmonth,
615
+ * Tyear: token.Tyear,
616
+ * Token: true,
617
+ * Amount: 50,
618
+ * Info: "J5 charge",
619
+ * UserId: "203269535",
620
+ * ClientName: "Israel",
621
+ * },
622
+ * {
623
+ * "inputObj.originalUid": originalResponse.UID,
624
+ * "inputObj.originalAmount": 5000, // 50 ILS in agorot
625
+ * AuthNum: originalResponse.ACode,
626
+ * "inputObj.authorizationCodeManpik": 7,
627
+ * }
628
+ * );
629
+ * ```
630
+ */
631
+ async chargeJ5(tokenParams, j5Params) {
632
+ const allParams = {
633
+ action: "soft",
634
+ ...this.authParams(),
635
+ ...tokenParams,
636
+ ...j5Params
637
+ };
638
+ const { raw, params: parsed } = await this.request("soft-J5", allParams);
639
+ if (parsed.CCode && !isSuccessCode(parsed.CCode)) {
640
+ throw new HypayError(getErrorMessage(parsed.CCode), parsed.CCode, raw, parsed);
641
+ }
642
+ return {
643
+ Id: parsed.Id ?? "",
644
+ CCode: parsed.CCode ?? "",
645
+ Amount: parsed.Amount ?? "",
646
+ ACode: parsed.ACode ?? "",
647
+ Fild1: parsed.Fild1,
648
+ Fild2: parsed.Fild2,
649
+ Fild3: parsed.Fild3,
650
+ Hesh: parsed.Hesh,
651
+ UID: parsed.UID,
652
+ errMsg: parsed.errMsg,
653
+ raw,
654
+ params: parsed
655
+ };
656
+ }
657
+ // ═══════════════════════════════════════════════════════════════════════════
658
+ // POSTPONE — Commit delayed transactions
659
+ // ═══════════════════════════════════════════════════════════════════════════
660
+ /**
661
+ * Commit a postponed transaction (CCode 800).
662
+ *
663
+ * Postponed transactions should be committed within 72 hours.
664
+ * If not committed, leave at status 800 (do not cancel).
665
+ *
666
+ * @throws {HypayError} When CCode indicates an error (250 = doesn't exist / already committed)
667
+ *
668
+ * @example
669
+ * ```ts
670
+ * const result = await client.commitTransaction({
671
+ * TransId: "5343635",
672
+ * SendHesh: true,
673
+ * heshDesc: "Payment for order 1234",
674
+ * UTF8: true,
675
+ * UTF8out: true,
676
+ * });
677
+ * console.log(result.HeshASM); // Invoice number
678
+ * ```
679
+ */
680
+ async commitTransaction(params) {
681
+ const allParams = {
682
+ action: "commitTrans",
683
+ ...this.authParams(),
684
+ ...params
685
+ };
686
+ const { raw, params: parsed } = await this.request("commitTrans", allParams);
687
+ if (parsed.CCode && parsed.CCode !== "0") {
688
+ throw new HypayError(getErrorMessage(parsed.CCode), parsed.CCode, raw, parsed);
689
+ }
690
+ return {
691
+ Id: parsed.Id ?? "",
692
+ CCode: parsed.CCode ?? "",
693
+ HeshASM: parsed.HeshASM,
694
+ Fild1: parsed.Fild1,
695
+ Fild2: parsed.Fild2,
696
+ Fild3: parsed.Fild3,
697
+ raw,
698
+ params: parsed
699
+ };
700
+ }
701
+ // ═══════════════════════════════════════════════════════════════════════════
702
+ // CANCEL — Cancel a transaction
703
+ // ═══════════════════════════════════════════════════════════════════════════
704
+ /**
705
+ * Cancel a transaction.
706
+ *
707
+ * **Rules:**
708
+ * - Only on the same day, before 23:20 (before SHVA deposit)
709
+ * - Cancelled transactions CANNOT be restored
710
+ * - No cost to the business (transaction won't be presented to credit card companies)
711
+ * - If invoice module is active, a credit memo will be issued
712
+ *
713
+ * @throws {HypayError} When CCode indicates an error (920 = doesn't exist / already committed)
714
+ *
715
+ * @example
716
+ * ```ts
717
+ * const result = await client.cancelTransaction({ TransId: "5890796" });
718
+ * console.log(result.CCode); // "0" = success
719
+ * ```
720
+ */
721
+ async cancelTransaction(params) {
722
+ const allParams = {
723
+ action: "CancelTrans",
724
+ ...this.authParams(),
725
+ TransId: params.TransId
726
+ };
727
+ const { raw, params: parsed } = await this.request("CancelTrans", allParams);
728
+ if (parsed.CCode && parsed.CCode !== "0") {
729
+ throw new HypayError(getErrorMessage(parsed.CCode), parsed.CCode, raw, parsed);
730
+ }
731
+ return {
732
+ TransId: parsed.TransId ?? "",
733
+ CCode: parsed.CCode ?? "",
734
+ Hesh: parsed.Hesh,
735
+ raw,
736
+ params: parsed
737
+ };
738
+ }
739
+ // ═══════════════════════════════════════════════════════════════════════════
740
+ // REFUND — Refund transactions
741
+ // ═══════════════════════════════════════════════════════════════════════════
742
+ /**
743
+ * Refund a transaction by its original transaction ID (zikoyAPI).
744
+ *
745
+ * **Rules:**
746
+ * - Refund amount must not exceed the original transaction amount
747
+ * - Can be done at any stage, even after 23:00
748
+ * - A credit refund is like a new deal for the credit card company
749
+ * - Does NOT require a token or the secret refund password
750
+ *
751
+ * @throws {HypayError} When CCode indicates an error (33 = amount exceeds original)
752
+ *
753
+ * @example
754
+ * ```ts
755
+ * const result = await client.refund({
756
+ * TransId: "12290620",
757
+ * Amount: 10,
758
+ * Tash: 1,
759
+ * SendHesh: true,
760
+ * UTF8: true,
761
+ * UTF8out: true,
762
+ * });
763
+ * console.log(`Refund ID: ${result.Id}`);
764
+ * ```
765
+ */
766
+ async refund(params) {
767
+ const allParams = {
768
+ action: "zikoyAPI",
769
+ ...this.authParams(),
770
+ ...params
771
+ };
772
+ const { raw, params: parsed } = await this.request("zikoyAPI", allParams);
773
+ if (parsed.CCode && parsed.CCode !== "0") {
774
+ throw new HypayError(getErrorMessage(parsed.CCode), parsed.CCode, raw, parsed);
775
+ }
776
+ return {
777
+ Id: parsed.Id ?? "",
778
+ CCode: parsed.CCode ?? "",
779
+ ACode: parsed.ACode,
780
+ HeshASM: parsed.HeshASM,
781
+ raw,
782
+ params: parsed
783
+ };
784
+ }
785
+ /**
786
+ * Refund a transaction using a token and the secret refund password (PAYout).
787
+ *
788
+ * This uses the Soft protocol with the `zPass` parameter.
789
+ * The refund password is sent to the terminal owner's cell phone and is non-interchangeable.
790
+ *
791
+ * @throws {HypayError} When CCode indicates an error
792
+ *
793
+ * @example
794
+ * ```ts
795
+ * const result = await client.refundByToken({
796
+ * CC: "6907500685494032346",
797
+ * Tmonth: "04",
798
+ * Tyear: "2023",
799
+ * Amount: 2,
800
+ * Info: "Refund for order 123",
801
+ * zPass: "1234",
802
+ * Token: true,
803
+ * UserId: "000000000",
804
+ * ClientName: "Israel",
805
+ * SendHesh: true,
806
+ * sendemail: true,
807
+ * });
808
+ * ```
809
+ */
810
+ async refundByToken(params) {
811
+ return this.soft(params);
812
+ }
813
+ // ═══════════════════════════════════════════════════════════════════════════
814
+ // HK MODULE — Standing Orders / Subscriptions
815
+ // ═══════════════════════════════════════════════════════════════════════════
816
+ /**
817
+ * Create a subscription (HK / standing order) payment page URL.
818
+ *
819
+ * This is a convenience wrapper around `createPaymentPageUrl` with HK-specific params.
820
+ *
821
+ * @example
822
+ * ```ts
823
+ * const url = await client.createSubscriptionUrl({
824
+ * Info: "Monthly subscription",
825
+ * Amount: 120,
826
+ * HK: true,
827
+ * Tash: 999, // Unlimited payments
828
+ * freq: 1, // Monthly
829
+ * FirstDate: "2025-06-01",
830
+ * OnlyOnApprove: true,
831
+ * UserId: "203269535",
832
+ * ClientName: "Israel",
833
+ * email: "customer@example.com",
834
+ * Coin: Coin.ILS,
835
+ * UTF8: true,
836
+ * UTF8out: true,
837
+ * Sign: true,
838
+ * MoreData: true,
839
+ * });
840
+ * ```
841
+ */
842
+ async createSubscriptionUrl(params) {
843
+ return this.createPaymentPageUrl(params);
844
+ }
845
+ /**
846
+ * Create a subscription with an initial transaction of a different amount.
847
+ *
848
+ * @example
849
+ * ```ts
850
+ * const url = await client.createSubscriptionWithInitialPayment({
851
+ * Info: "Plan with setup fee",
852
+ * Amount: 120, // Monthly amount
853
+ * HK: true,
854
+ * Tash: 999,
855
+ * freq: 1,
856
+ * TashFirstPayment: 50, // Setup fee
857
+ * FirstPaymentTash: 3, // Charge setup fee for 3 months
858
+ * FixTash: true,
859
+ * OnlyOnApprove: true,
860
+ * UserId: "203269535",
861
+ * ClientName: "Israel",
862
+ * Coin: Coin.ILS,
863
+ * UTF8: true,
864
+ * UTF8out: true,
865
+ * Sign: true,
866
+ * MoreData: true,
867
+ * });
868
+ * ```
869
+ */
870
+ async createSubscriptionWithInitialPayment(params) {
871
+ return this.createPaymentPageUrl(params);
872
+ }
873
+ /**
874
+ * Change the status of a standing order (HK) agreement.
875
+ *
876
+ * @throws {HypayError} When CCode indicates an error (905 = wrong param, 906 = not found)
877
+ *
878
+ * @example
879
+ * ```ts
880
+ * // Terminate
881
+ * await client.updateHKStatus({
882
+ * HKId: "64239",
883
+ * NewStat: HKNewStatus.Terminate,
884
+ * });
885
+ *
886
+ * // Reactivate
887
+ * await client.updateHKStatus({
888
+ * HKId: "64239",
889
+ * NewStat: HKNewStatus.Activate,
890
+ * });
891
+ * ```
892
+ */
893
+ async updateHKStatus(params) {
894
+ const allParams = {
895
+ action: "HKStatus",
896
+ ...this.authParams(),
897
+ HKId: params.HKId,
898
+ NewStat: params.NewStat
899
+ };
900
+ const { raw, params: parsed } = await this.request("HKStatus", allParams);
901
+ if (parsed.CCode && parsed.CCode !== "0") {
902
+ throw new HypayError(getErrorMessage(parsed.CCode), parsed.CCode, raw, parsed);
903
+ }
904
+ return {
905
+ HKId: parsed.HKId ?? "",
906
+ CCode: parsed.CCode ?? "",
907
+ raw,
908
+ params: parsed
909
+ };
910
+ }
911
+ /**
912
+ * Terminate a standing order agreement. Shorthand for `updateHKStatus`.
913
+ */
914
+ async terminateSubscription(hkId) {
915
+ return this.updateHKStatus({ HKId: hkId, NewStat: 1 });
916
+ }
917
+ /**
918
+ * Activate a standing order agreement. Shorthand for `updateHKStatus`.
919
+ */
920
+ async activateSubscription(hkId) {
921
+ return this.updateHKStatus({ HKId: hkId, NewStat: 2 });
922
+ }
923
+ // ═══════════════════════════════════════════════════════════════════════════
924
+ // EZCOUNT INVOICES — Print and manage invoices
925
+ // ═══════════════════════════════════════════════════════════════════════════
926
+ /**
927
+ * Generate a signed URL for printing/viewing an EzCount invoice.
928
+ *
929
+ * Uses a two-step process:
930
+ * 1. Sign the request with APISign (What=SIGN, ACTION=PrintHesh)
931
+ * 2. Build the final URL from the signed response
932
+ *
933
+ * **Note:** `ACTION=PrintHesh` is uppercase, `action=APISign` is lowercase.
934
+ *
935
+ * @example
936
+ * ```ts
937
+ * const url = await client.getInvoiceUrl({
938
+ * TransId: "55373520",
939
+ * type: "EZCOUNT",
940
+ * });
941
+ * // Redirect user or fetch the invoice PDF
942
+ * ```
943
+ */
944
+ async getInvoiceUrl(params) {
945
+ const signParams = {
946
+ action: "APISign",
947
+ What: "SIGN",
948
+ Masof: this.masof,
949
+ KEY: this.apiKey,
950
+ PassP: this.passP,
951
+ ACTION: "PrintHesh",
952
+ type: params.type
953
+ };
954
+ if (params.TransId) signParams.TransId = params.TransId;
955
+ if (params.asm) signParams.asm = params.asm;
956
+ if (params.HeshORCopy !== void 0) signParams.HeshORCopy = params.HeshORCopy;
957
+ const { raw } = await this.request("PrintHesh-SIGN", signParams);
958
+ return `${this.baseUrl}?${raw}`;
959
+ }
960
+ // ═══════════════════════════════════════════════════════════════════════════
961
+ // OFFLINE INVOICES — Cash, Check, Multi
962
+ // ═══════════════════════════════════════════════════════════════════════════
963
+ /**
964
+ * Create an invoice for an offline transaction (Cash, Check, or Multi-check).
965
+ *
966
+ * Uses the Soft protocol with the `TransType` parameter.
967
+ *
968
+ * @throws {HypayError} When CCode indicates an error
969
+ *
970
+ * @example
971
+ * ```ts
972
+ * // Cash invoice
973
+ * const result = await client.createOfflineInvoice({
974
+ * TransType: "Cash",
975
+ * Info: "Cash payment",
976
+ * Amount: 200,
977
+ * UserId: "203269535",
978
+ * ClientName: "Israel",
979
+ * email: "customer@example.com",
980
+ * Pritim: true,
981
+ * heshDesc: "[001~Product A~2~50][002~Product B~1~100]",
982
+ * SendHesh: true,
983
+ * UTF8: true,
984
+ * UTF8out: true,
985
+ * });
986
+ * ```
987
+ *
988
+ * @example
989
+ * ```ts
990
+ * // Check invoice
991
+ * const result = await client.createOfflineInvoice({
992
+ * TransType: "Check",
993
+ * Info: "Check payment",
994
+ * Amount: 200,
995
+ * Bank: "10",
996
+ * Snif: "912",
997
+ * PAN: "1234456",
998
+ * CheckNum: "11111111",
999
+ * Date: "20250211",
1000
+ * UserId: "203269535",
1001
+ * ClientName: "Israel",
1002
+ * SendHesh: true,
1003
+ * UTF8: true,
1004
+ * UTF8out: true,
1005
+ * });
1006
+ * ```
1007
+ */
1008
+ async createOfflineInvoice(params) {
1009
+ const allParams = {
1010
+ action: "soft",
1011
+ ...this.authParams(),
1012
+ ...params
1013
+ };
1014
+ const { raw, params: parsed } = await this.request("soft-offline", allParams);
1015
+ if (parsed.CCode && !isSuccessCode(parsed.CCode)) {
1016
+ throw new HypayError(getErrorMessage(parsed.CCode), parsed.CCode, raw, parsed);
1017
+ }
1018
+ return {
1019
+ Id: parsed.Id ?? "",
1020
+ CCode: parsed.CCode ?? "",
1021
+ Amount: parsed.Amount ?? "",
1022
+ ACode: parsed.ACode ?? "",
1023
+ Fild1: parsed.Fild1,
1024
+ Fild2: parsed.Fild2,
1025
+ Fild3: parsed.Fild3,
1026
+ Hesh: parsed.Hesh,
1027
+ UID: parsed.UID,
1028
+ errMsg: parsed.errMsg,
1029
+ raw,
1030
+ params: parsed
1031
+ };
1032
+ }
1033
+ };
1034
+ export {
1035
+ ALL_ERROR_CODES,
1036
+ Bank,
1037
+ Brand,
1038
+ Coin,
1039
+ HKNewStatus,
1040
+ HYPAY_ERROR_CODES,
1041
+ HypayClient,
1042
+ HypayError,
1043
+ HypayNetworkError,
1044
+ HypayTimeoutError,
1045
+ Issuer,
1046
+ PageLang,
1047
+ SHVA_ERROR_CODES,
1048
+ SpecialCardType,
1049
+ TashType,
1050
+ buildItemsString,
1051
+ calculateItemsTotal,
1052
+ getErrorMessage,
1053
+ isHypayError,
1054
+ isShvaError,
1055
+ isSuccessCode,
1056
+ isTestMasof,
1057
+ isValidEmail,
1058
+ isValidIsraeliId,
1059
+ isValidMasof,
1060
+ isValidToken,
1061
+ parsePaymentPageResponse,
1062
+ parseQueryString,
1063
+ parseRedirectUrl,
1064
+ parseTokef,
1065
+ serializeParams,
1066
+ toQueryString
1067
+ };