@keytd/manat 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Manat for TypeScript / JavaScript
2
2
 
3
- One API for Azerbaijani online payment providers: Kapital Bank, Azericard, Payriff, Epoint and GoldenPay.
3
+ One API for Azerbaijani online payment providers: Kapital Bank, Azericard, Payriff, Epoint, GoldenPay and Yığım.
4
4
  Node.js 18+, ESM and CommonJS, no runtime dependencies.
5
5
 
6
6
  ```bash
@@ -27,13 +27,14 @@ Store `paymentId` with the order. It is what `getStatus`, `refund`, `capture` an
27
27
  ## Providers
28
28
 
29
29
  ```ts
30
- import {azericard, epoint, goldenPay, kapitalBank, payriff} from '@keytd/manat';
30
+ import {azericard, epoint, goldenPay, kapitalBank, payriff, yigim} from '@keytd/manat';
31
31
 
32
32
  kapitalBank({username, password, environment: 'test'});
33
33
  azericard({terminal, privateKey, gatewayPublicKey, merchantName, merchantUrl, environment: 'test'});
34
34
  payriff({secretKey});
35
35
  epoint({publicKey, privateKey});
36
36
  goldenPay({authKey, merchantName, environment: 'test'});
37
+ yigim({merchant, apiKey, biller, environment: 'test'});
37
38
  ```
38
39
 
39
40
  | Provider | createPayment | verifyCallback | getStatus | refund | capture | cancel |
@@ -43,10 +44,9 @@ goldenPay({authKey, merchantName, environment: 'test'});
43
44
  | Payriff | ✓ | ✓ | ✓ | ✓ | ✓ | — |
44
45
  | Epoint | ✓ | ✓ | ✓ | ✓ | — | — |
45
46
  | GoldenPay | ✓ | ✓ | ✓ | ✓ | — | — |
47
+ | Yığım | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
46
48
 
47
49
  `pay.supports('capture')` tells you at runtime; an unsupported call throws `unsupported_operation`.
48
- Provider details (what was checked live, quirks, why some operations are missing) are in the
49
- [main README](https://github.com/KeyTechAndDesign/manat#providers).
50
50
 
51
51
  ## Callbacks
52
52
 
package/dist/index.cjs CHANGED
@@ -28,6 +28,8 @@ __export(index_exports, {
28
28
  Manat: () => Manat,
29
29
  ManatError: () => ManatError,
30
30
  PayriffProvider: () => PayriffProvider,
31
+ VERSION: () => VERSION,
32
+ YigimProvider: () => YigimProvider,
31
33
  azericard: () => azericard,
32
34
  azn: () => azn,
33
35
  epoint: () => epoint,
@@ -36,7 +38,8 @@ __export(index_exports, {
36
38
  isManatError: () => isManatError,
37
39
  kapitalBank: () => kapitalBank,
38
40
  payriff: () => payriff,
39
- toDecimalString: () => toDecimalString
41
+ toDecimalString: () => toDecimalString,
42
+ yigim: () => yigim
40
43
  });
41
44
  module.exports = __toCommonJS(index_exports);
42
45
 
@@ -47,18 +50,18 @@ var ManatError = class extends Error {
47
50
  providerCode;
48
51
  providerMessage;
49
52
  raw;
50
- constructor(code, message, details = {}) {
53
+ constructor(code2, message, details = {}) {
51
54
  super(message, details.cause === void 0 ? void 0 : { cause: details.cause });
52
55
  this.name = "ManatError";
53
- this.code = code;
56
+ this.code = code2;
54
57
  this.provider = details.provider;
55
58
  this.providerCode = details.providerCode;
56
59
  this.providerMessage = details.providerMessage;
57
60
  this.raw = details.raw;
58
61
  }
59
62
  };
60
- function isManatError(error, code) {
61
- return error instanceof ManatError && (code === void 0 || error.code === code);
63
+ function isManatError(error, code2) {
64
+ return error instanceof ManatError && (code2 === void 0 || error.code === code2);
62
65
  }
63
66
 
64
67
  // src/money.ts
@@ -87,8 +90,11 @@ function fromDecimal(value, currency) {
87
90
  const fraction = (match[2] ?? "").padEnd(digits, "0");
88
91
  return { amount: Number(`${match[1]}${fraction}`), currency };
89
92
  }
90
- function currencyFromNumeric(code) {
91
- return Object.keys(NUMERIC_CODES).find((c) => NUMERIC_CODES[c] === code);
93
+ function currencyNumericCode(currency) {
94
+ return NUMERIC_CODES[currency];
95
+ }
96
+ function currencyFromNumeric(code2) {
97
+ return Object.keys(NUMERIC_CODES).find((c) => NUMERIC_CODES[c] === code2);
92
98
  }
93
99
  function azn(value) {
94
100
  return fromDecimal(value, "AZN");
@@ -154,6 +160,10 @@ function assertUrl(value, field) {
154
160
  }
155
161
  }
156
162
 
163
+ // src/version.ts
164
+ var VERSION = "0.2.1";
165
+ var USER_AGENT = `keytd-manat/${VERSION} (node; +https://github.com/KeyTechAndDesign/manat)`;
166
+
157
167
  // src/http.ts
158
168
  var HttpClient = class {
159
169
  constructor(provider, options = {}) {
@@ -167,7 +177,7 @@ var HttpClient = class {
167
177
  fetchImpl;
168
178
  timeoutMs;
169
179
  async send(request) {
170
- const headers = { ...request.headers };
180
+ const headers = { "user-agent": USER_AGENT, ...request.headers };
171
181
  let body;
172
182
  if (request.body instanceof URLSearchParams) {
173
183
  body = request.body.toString();
@@ -365,9 +375,9 @@ function kapitalBank(config, options) {
365
375
  function assertOrderId(paymentId) {
366
376
  if (!/^\d+$/.test(paymentId)) throw new ManatError("invalid_request", `kapitalbank: paymentId must be the numeric order id, got "${paymentId}"`);
367
377
  }
368
- function toCurrency(code, paymentId) {
369
- if (code === "AZN" || code === "USD" || code === "EUR" || code === "RUB" || code === "TRY" || code === "GBP") return code;
370
- throw new ManatError("provider_error", `kapitalbank: unexpected currency "${code}" on order ${paymentId}`, { provider: "kapitalbank" });
378
+ function toCurrency(code2, paymentId) {
379
+ if (code2 === "AZN" || code2 === "USD" || code2 === "EUR" || code2 === "RUB" || code2 === "TRY" || code2 === "GBP") return code2;
380
+ throw new ManatError("provider_error", `kapitalbank: unexpected currency "${code2}" on order ${paymentId}`, { provider: "kapitalbank" });
371
381
  }
372
382
  function missingAmount(paymentId) {
373
383
  throw new ManatError("provider_error", `kapitalbank: order ${paymentId} has no amount to capture`, { provider: "kapitalbank" });
@@ -387,7 +397,6 @@ function pickBody(body, key) {
387
397
  var import_node_crypto = require("crypto");
388
398
  var GATEWAY_URLS = {
389
399
  test: "https://testmpi.3dsecure.az/cgi-bin/cgi_link",
390
- // Not in the public docs (they only publish the test URL); used by existing integrations.
391
400
  production: "https://mpi.3dsecure.az/cgi-bin/cgi_link"
392
401
  };
393
402
  var AZERICARD_MAC_FIELDS = {
@@ -500,13 +509,7 @@ var AzericardProvider = class {
500
509
  }
501
510
  return event;
502
511
  }
503
- /**
504
- * TRTYPE 90. Works for 24 hours after the payment. The original TRTYPE is not known from the
505
- * paymentId alone, so a purchase (1) is asked for first and a pre-auth (0) on "context mismatch".
506
- * The answer only describes the transaction type asked about, so completion (21) and the
507
- * reversals (22 online, 24 offline) are queried too. Only the latest offline reversal is
508
- * visible this way, so refundedAmount can be lower than the truth after several 24s.
509
- */
512
+ /** TRTYPE 90 answers for one transaction type, so the purchase (or pre-auth), completion and reversals are each queried. */
510
513
  async getStatus({ paymentId }) {
511
514
  if (!/^\d{6,32}$/.test(paymentId)) throw new ManatError("invalid_request", `azericard: paymentId must be the numeric ORDER, got "${paymentId}"`);
512
515
  let trtype = "1";
@@ -666,9 +669,9 @@ function parseAmount(amount, currency) {
666
669
  const alpha = /^\d{3}$/.test(currency) ? currencyFromNumeric(currency) : currency;
667
670
  return alpha ? fromDecimal(amount, toCurrency2(alpha)) : void 0;
668
671
  }
669
- function toCurrency2(code) {
670
- if (code === "AZN" || code === "USD" || code === "EUR" || code === "RUB" || code === "TRY" || code === "GBP") return code;
671
- throw new ManatError("provider_error", `azericard: unsupported currency "${code}"`, { provider: "azericard" });
672
+ function toCurrency2(code2) {
673
+ if (code2 === "AZN" || code2 === "USD" || code2 === "EUR" || code2 === "RUB" || code2 === "TRY" || code2 === "GBP") return code2;
674
+ throw new ManatError("provider_error", `azericard: unsupported currency "${code2}"`, { provider: "azericard" });
672
675
  }
673
676
  function parseTimestamp(value) {
674
677
  const m = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/.exec(value);
@@ -853,9 +856,9 @@ function encodeId(paymentId) {
853
856
  if (!/^[A-Za-z0-9-]{1,64}$/.test(paymentId)) throw new ManatError("invalid_request", `payriff: invalid paymentId "${paymentId}"`);
854
857
  return paymentId;
855
858
  }
856
- function toCurrency3(code) {
857
- if (code === "AZN" || code === "USD" || code === "EUR" || code === "RUB" || code === "TRY" || code === "GBP") return code;
858
- throw new ManatError("provider_error", `payriff: unsupported currency "${code}"`, { provider: "payriff" });
859
+ function toCurrency3(code2) {
860
+ if (code2 === "AZN" || code2 === "USD" || code2 === "EUR" || code2 === "RUB" || code2 === "TRY" || code2 === "GBP") return code2;
861
+ throw new ManatError("provider_error", `payriff: unsupported currency "${code2}"`, { provider: "payriff" });
859
862
  }
860
863
 
861
864
  // src/crypto.ts
@@ -996,8 +999,8 @@ function epoint(config, options) {
996
999
  function formFields(body) {
997
1000
  if (typeof body === "string") return Object.fromEntries(new URLSearchParams(body));
998
1001
  if (!body || typeof body !== "object") return {};
999
- const pick3 = (key) => typeof body[key] === "string" ? body[key] : void 0;
1000
- return { data: pick3("data"), signature: pick3("signature") };
1002
+ const pick4 = (key) => typeof body[key] === "string" ? body[key] : void 0;
1003
+ return { data: pick4("data"), signature: pick4("signature") };
1001
1004
  }
1002
1005
  function decodeData(data) {
1003
1006
  if (!/^[A-Za-z0-9+/]+={0,2}$/.test(data)) throw new ManatError("provider_error", "epoint: callback data is not base64", { provider: "epoint", raw: data });
@@ -1022,8 +1025,8 @@ var CAPABILITIES5 = /* @__PURE__ */ new Set(["createPayment", "verifyCallback",
1022
1025
  var SUCCESS2 = 1;
1023
1026
  var REVERSED = 817;
1024
1027
  var MAX_DESCRIPTION_BYTES = 500;
1025
- function mapGoldenPayStatus(code) {
1026
- switch (code) {
1028
+ function mapGoldenPayStatus(code2) {
1029
+ switch (code2) {
1027
1030
  case SUCCESS2:
1028
1031
  return "paid";
1029
1032
  case 813:
@@ -1041,7 +1044,7 @@ function mapGoldenPayStatus(code) {
1041
1044
  case REVERSED:
1042
1045
  return "refunded";
1043
1046
  default:
1044
- throw new ManatError("provider_error", `goldenpay: status code ${code} does not describe a payment`, { provider: "goldenpay" });
1047
+ throw new ManatError("provider_error", `goldenpay: status code ${code2} does not describe a payment`, { provider: "goldenpay" });
1045
1048
  }
1046
1049
  }
1047
1050
  var PAYMENT_CODES = /* @__PURE__ */ new Set([SUCCESS2, 813, 814, 815, 816, REVERSED, 818, 819]);
@@ -1092,10 +1095,10 @@ var GoldenPayProvider = class {
1092
1095
  assertPaymentKey(paymentId);
1093
1096
  const query = new URLSearchParams({ payment_key: paymentId, hash_code: md5(this.config.authKey + paymentId) });
1094
1097
  const response = await this.call("GET", `getPaymentResult?${query}`);
1095
- const code = response.status?.code;
1096
- if (code === void 0 || !PAYMENT_CODES.has(code)) throw this.error("status is not available", response);
1098
+ const code2 = response.status?.code;
1099
+ if (code2 === void 0 || !PAYMENT_CODES.has(code2)) throw this.error("status is not available", response);
1097
1100
  if (response.paymentKey && response.paymentKey !== paymentId) throw this.error(`result is for payment ${response.paymentKey}, not ${paymentId}`, response);
1098
- const state = { paymentId, status: mapGoldenPayStatus(code), raw: response };
1101
+ const state = { paymentId, status: mapGoldenPayStatus(code2), raw: response };
1099
1102
  if (typeof response.amount === "number" && response.amount > 0) state.amount = { amount: response.amount, currency: "AZN" };
1100
1103
  return state;
1101
1104
  }
@@ -1111,8 +1114,8 @@ var GoldenPayProvider = class {
1111
1114
  amount: amount.amount,
1112
1115
  secretKey: md5(hashCode + amount.amount)
1113
1116
  });
1114
- const code = response.status?.code;
1115
- if (code !== REVERSED && code !== SUCCESS2) throw this.error("refund failed", response);
1117
+ const code2 = response.status?.code;
1118
+ if (code2 !== REVERSED && code2 !== SUCCESS2) throw this.error("refund failed", response);
1116
1119
  const partial = payment.amount !== void 0 && amount.amount < payment.amount.amount;
1117
1120
  return { paymentId: request.paymentId, status: partial ? "partially_refunded" : "refunded", raw: { refund: response, payment: payment.raw } };
1118
1121
  }
@@ -1123,11 +1126,11 @@ var GoldenPayProvider = class {
1123
1126
  return data;
1124
1127
  }
1125
1128
  error(what, response) {
1126
- const { code, message } = response.status ?? {};
1127
- const fromProvider = code !== void 0 && code !== SUCCESS2;
1129
+ const { code: code2, message } = response.status ?? {};
1130
+ const fromProvider = code2 !== void 0 && code2 !== SUCCESS2;
1128
1131
  return new ManatError("provider_error", `goldenpay: ${fromProvider && message ? message : what}`, {
1129
1132
  provider: this.name,
1130
- ...fromProvider ? { providerCode: String(code) } : {},
1133
+ ...fromProvider ? { providerCode: String(code2) } : {},
1131
1134
  ...fromProvider && message ? { providerMessage: message } : {},
1132
1135
  raw: response
1133
1136
  });
@@ -1165,6 +1168,177 @@ function pickBody2(body, key) {
1165
1168
  const value = body[key];
1166
1169
  return typeof value === "string" ? value : void 0;
1167
1170
  }
1171
+
1172
+ // src/providers/yigim.ts
1173
+ var BASE_URLS3 = {
1174
+ test: "https://sandbox.api.pay.yigim.az",
1175
+ production: "https://api.pay.yigim.az"
1176
+ };
1177
+ var CAPABILITIES6 = /* @__PURE__ */ new Set(["createPayment", "verifyCallback", "getStatus", "refund", "capture", "cancel"]);
1178
+ var OK = 0;
1179
+ var DEFAULT_TEMPLATE = "TPL0001";
1180
+ var REFERENCE = /^[^\s\u0000-\u001f]{1,255}$/;
1181
+ function mapYigimStatus(status) {
1182
+ switch (status) {
1183
+ case "S0":
1184
+ // created, waiting for card data
1185
+ case "S2":
1186
+ // in progress
1187
+ case "S7":
1188
+ // system malfunction
1189
+ case "09":
1190
+ return "pending";
1191
+ case "S1":
1192
+ return "authorized";
1193
+ case "00":
1194
+ return "paid";
1195
+ case "S4":
1196
+ return "cancelled";
1197
+ case "S3":
1198
+ // unknown error
1199
+ case "01":
1200
+ // refer to issuer
1201
+ case "02":
1202
+ // expired card
1203
+ case "03":
1204
+ // invalid amount
1205
+ case "04":
1206
+ // inactive card
1207
+ case "05":
1208
+ // insufficient funds
1209
+ case "06":
1210
+ // suspected fraud
1211
+ case "07":
1212
+ // exceeds withdrawal limit
1213
+ case "08":
1214
+ return "failed";
1215
+ default:
1216
+ throw new ManatError("provider_error", `yigim: unknown payment status "${status}"`, { provider: "yigim" });
1217
+ }
1218
+ }
1219
+ var YigimProvider = class {
1220
+ constructor(config, options = {}) {
1221
+ this.config = config;
1222
+ if (!config?.merchant) throw new ManatError("invalid_request", "yigim: merchant is required");
1223
+ if (!config.apiKey) throw new ManatError("invalid_request", "yigim: apiKey is required");
1224
+ if (!config.biller) throw new ManatError("invalid_request", "yigim: biller is required");
1225
+ this.baseUrl = (config.baseUrl ?? BASE_URLS3[config.environment ?? "production"]).replace(/\/+$/, "");
1226
+ this.http = new HttpClient(this.name, options);
1227
+ }
1228
+ config;
1229
+ name = "yigim";
1230
+ capabilities = CAPABILITIES6;
1231
+ http;
1232
+ baseUrl;
1233
+ async createPayment(request) {
1234
+ assertReference(request.orderId);
1235
+ if (/[&;]/.test(request.returnUrl)) {
1236
+ throw new ManatError("invalid_request", 'yigim: returnUrl may carry at most one query parameter and no ";"');
1237
+ }
1238
+ const params = {
1239
+ reference: request.orderId,
1240
+ type: request.intent === "authorize" ? "DMS" : "SMS",
1241
+ amount: String(request.amount.amount),
1242
+ currency: currencyNumericCode(request.amount.currency),
1243
+ biller: this.config.biller
1244
+ };
1245
+ if (request.description) params.description = request.description;
1246
+ params.template = this.config.template ?? DEFAULT_TEMPLATE;
1247
+ params.language = request.language ?? "az";
1248
+ if (request.callbackUrl) params.callback = request.callbackUrl.includes("?") ? request.callbackUrl : `${request.callbackUrl}?`;
1249
+ params.extra = `back-url=${request.returnUrl};fail-url=${request.returnUrl}`;
1250
+ const reply = await this.call("payment/create", params);
1251
+ if (!reply.url) throw this.error("payment page URL is missing", reply);
1252
+ return { paymentId: request.orderId, status: "pending", next: { type: "redirect", url: reply.url }, raw: reply };
1253
+ }
1254
+ async verifyCallback(callback) {
1255
+ const reference = pick3(callback.query, "reference") ?? pickBody3(callback.body, "reference");
1256
+ if (!reference || !REFERENCE.test(reference)) {
1257
+ throw new ManatError("invalid_signature", "yigim: callback has no reference", { provider: this.name, raw: callback });
1258
+ }
1259
+ const state = await this.getStatus({ paymentId: reference });
1260
+ const event = { paymentId: state.paymentId, orderId: state.paymentId, status: state.status, raw: state.raw };
1261
+ if (state.amount) event.amount = state.amount;
1262
+ return event;
1263
+ }
1264
+ async getStatus({ paymentId }) {
1265
+ assertReference(paymentId);
1266
+ const reply = await this.call("payment/status", { reference: paymentId });
1267
+ if (reply.reference && reply.reference !== paymentId) throw this.error(`status is for payment ${reply.reference}, not ${paymentId}`, reply);
1268
+ const state = { paymentId, status: mapYigimStatus(reply.status), raw: reply };
1269
+ const currency = typeof reply.currency === "string" ? currencyFromNumeric(reply.currency) : void 0;
1270
+ if (reply.amount !== void 0 && reply.amount !== "" && currency) state.amount = fromDecimal(String(reply.amount), currency);
1271
+ return state;
1272
+ }
1273
+ async refund(request) {
1274
+ const reply = await this.call("payment/refund", withAmount(request.paymentId, request.amount));
1275
+ const state = await this.getStatus({ paymentId: request.paymentId });
1276
+ return { paymentId: request.paymentId, status: refundStatus(state, request.amount), raw: { refund: reply, payment: state.raw } };
1277
+ }
1278
+ async capture({ paymentId, amount }) {
1279
+ await this.call("payment/charge", withAmount(paymentId, amount));
1280
+ return this.getStatus({ paymentId });
1281
+ }
1282
+ async cancel({ paymentId }) {
1283
+ await this.call("payment/cancel", { reference: paymentId });
1284
+ return this.getStatus({ paymentId });
1285
+ }
1286
+ async call(command, params) {
1287
+ assertReference(params.reference ?? "");
1288
+ const { status, data } = await this.http.json({
1289
+ method: "GET",
1290
+ url: `${this.baseUrl}/${command}?${new URLSearchParams(params)}`,
1291
+ headers: { "x-merchant": this.config.merchant, "x-api-key": this.config.apiKey, "x-type": "JSON" }
1292
+ });
1293
+ const reply = unwrap(data);
1294
+ if (!reply || code(reply) === void 0) {
1295
+ throw new ManatError("provider_error", `yigim: unexpected response (HTTP ${status})`, { provider: this.name, raw: data });
1296
+ }
1297
+ if (status >= 400 || code(reply) !== OK) throw this.error(`HTTP ${status}`, reply);
1298
+ return reply;
1299
+ }
1300
+ error(what, reply) {
1301
+ const fromProvider = code(reply) !== void 0 && code(reply) !== OK;
1302
+ return new ManatError("provider_error", `yigim: ${fromProvider && reply.message ? reply.message : what}`, {
1303
+ provider: this.name,
1304
+ ...fromProvider ? { providerCode: String(reply.code) } : {},
1305
+ ...fromProvider && reply.message ? { providerMessage: reply.message } : {},
1306
+ raw: reply
1307
+ });
1308
+ }
1309
+ };
1310
+ function yigim(config, options) {
1311
+ return new YigimProvider(config, options);
1312
+ }
1313
+ function code(reply) {
1314
+ if (typeof reply.code === "number") return reply.code;
1315
+ return typeof reply.code === "string" && /^-?\d+$/.test(reply.code) ? Number(reply.code) : void 0;
1316
+ }
1317
+ function unwrap(data) {
1318
+ if (!data || typeof data !== "object") return void 0;
1319
+ const inner = data.response;
1320
+ return inner && typeof inner === "object" ? inner : data;
1321
+ }
1322
+ function withAmount(reference, amount) {
1323
+ return amount ? { reference, amount: String(amount.amount) } : { reference };
1324
+ }
1325
+ function refundStatus(state, amount) {
1326
+ if (state.status !== "paid") return state.status;
1327
+ return amount && state.amount && amount.amount < state.amount.amount ? "partially_refunded" : "refunded";
1328
+ }
1329
+ function assertReference(reference) {
1330
+ if (!REFERENCE.test(reference)) throw new ManatError("invalid_request", `yigim: invalid reference "${reference}"`);
1331
+ }
1332
+ function pick3(source, key) {
1333
+ const value = source?.[key];
1334
+ return Array.isArray(value) ? value[0] : value;
1335
+ }
1336
+ function pickBody3(body, key) {
1337
+ if (!body) return void 0;
1338
+ if (typeof body === "string") return new URLSearchParams(body).get(key) ?? void 0;
1339
+ const value = body[key];
1340
+ return typeof value === "string" ? value : void 0;
1341
+ }
1168
1342
  // Annotate the CommonJS export names for ESM import in node:
1169
1343
  0 && (module.exports = {
1170
1344
  AZERICARD_MAC_FIELDS,
@@ -1175,6 +1349,8 @@ function pickBody2(body, key) {
1175
1349
  Manat,
1176
1350
  ManatError,
1177
1351
  PayriffProvider,
1352
+ VERSION,
1353
+ YigimProvider,
1178
1354
  azericard,
1179
1355
  azn,
1180
1356
  epoint,
@@ -1183,5 +1359,6 @@ function pickBody2(body, key) {
1183
1359
  isManatError,
1184
1360
  kapitalBank,
1185
1361
  payriff,
1186
- toDecimalString
1362
+ toDecimalString,
1363
+ yigim
1187
1364
  });
package/dist/index.d.cts CHANGED
@@ -118,6 +118,8 @@ declare class Manat {
118
118
  private call;
119
119
  }
120
120
 
121
+ declare const VERSION = "0.2.1";
122
+
121
123
  type ManatErrorCode = 'invalid_request' | 'invalid_signature' | 'provider_error' | 'network_error' | 'unsupported_operation';
122
124
  declare class ManatError extends Error {
123
125
  readonly code: ManatErrorCode;
@@ -207,7 +209,7 @@ interface AzericardConfig {
207
209
  clock?: () => Date;
208
210
  nonce?: () => string;
209
211
  }
210
- /** MAC field orders from the docs (sections 2.2.1–2.2.4 and 4.3). */
212
+ /** MAC field orders per message type. */
211
213
  declare const AZERICARD_MAC_FIELDS: Readonly<Record<AzericardMessage, readonly string[]>>;
212
214
  declare class AzericardProvider implements PaymentProvider {
213
215
  private readonly config;
@@ -223,13 +225,7 @@ declare class AzericardProvider implements PaymentProvider {
223
225
  constructor(config: AzericardConfig, options?: HttpOptions);
224
226
  createPayment(request: CreatePaymentRequest): Promise<CreatePaymentResult>;
225
227
  verifyCallback(callback: IncomingCallback): Promise<PaymentEvent>;
226
- /**
227
- * TRTYPE 90. Works for 24 hours after the payment. The original TRTYPE is not known from the
228
- * paymentId alone, so a purchase (1) is asked for first and a pre-auth (0) on "context mismatch".
229
- * The answer only describes the transaction type asked about, so completion (21) and the
230
- * reversals (22 online, 24 offline) are queried too. Only the latest offline reversal is
231
- * visible this way, so refundedAmount can be lower than the truth after several 24s.
232
- */
228
+ /** TRTYPE 90 answers for one transaction type, so the purchase (or pre-auth), completion and reversals are each queried. */
233
229
  getStatus({ paymentId }: {
234
230
  paymentId: string;
235
231
  }): Promise<PaymentState>;
@@ -321,4 +317,37 @@ declare class GoldenPayProvider implements PaymentProvider {
321
317
  }
322
318
  declare function goldenPay(config: GoldenPayConfig, options?: HttpOptions): GoldenPayProvider;
323
319
 
324
- export { AZERICARD_MAC_FIELDS, type AzericardConfig, type AzericardMessage, AzericardProvider, type Capability, type CreatePaymentRequest, type CreatePaymentResult, type Currency, type EpointConfig, EpointProvider, type FetchLike, type GoldenPayConfig, GoldenPayProvider, type HttpOptions, type IncomingCallback, type KapitalBankConfig, KapitalBankProvider, type Language, Manat, ManatError, type ManatErrorCode, type Money, type NextAction, type PaymentAction, type PaymentEvent, type PaymentProvider, type PaymentState, type PaymentStatus, type PayriffConfig, PayriffProvider, type RefundRequest, type RefundResult, azericard, azn, epoint, fromDecimal, goldenPay, isManatError, kapitalBank, payriff, toDecimalString };
320
+ interface YigimConfig {
321
+ /** Merchant name issued by Yığım (`X-Merchant`). */
322
+ merchant: string;
323
+ /** API key issued by Yığım (`X-API-Key`). */
324
+ apiKey: string;
325
+ /** Biller name provided by the acquirer. */
326
+ biller: string;
327
+ /** Payment page template; default `TPL0001`. */
328
+ template?: string;
329
+ environment?: 'test' | 'production';
330
+ /** Override the API host. */
331
+ baseUrl?: string;
332
+ }
333
+ declare class YigimProvider implements PaymentProvider {
334
+ private readonly config;
335
+ readonly name = "yigim";
336
+ readonly capabilities: ReadonlySet<Capability>;
337
+ private readonly http;
338
+ private readonly baseUrl;
339
+ constructor(config: YigimConfig, options?: HttpOptions);
340
+ createPayment(request: CreatePaymentRequest): Promise<CreatePaymentResult>;
341
+ verifyCallback(callback: IncomingCallback): Promise<PaymentEvent>;
342
+ getStatus({ paymentId }: {
343
+ paymentId: string;
344
+ }): Promise<PaymentState>;
345
+ refund(request: RefundRequest): Promise<RefundResult>;
346
+ capture({ paymentId, amount }: PaymentAction): Promise<PaymentState>;
347
+ cancel({ paymentId }: PaymentAction): Promise<PaymentState>;
348
+ private call;
349
+ private error;
350
+ }
351
+ declare function yigim(config: YigimConfig, options?: HttpOptions): YigimProvider;
352
+
353
+ export { AZERICARD_MAC_FIELDS, type AzericardConfig, type AzericardMessage, AzericardProvider, type Capability, type CreatePaymentRequest, type CreatePaymentResult, type Currency, type EpointConfig, EpointProvider, type FetchLike, type GoldenPayConfig, GoldenPayProvider, type HttpOptions, type IncomingCallback, type KapitalBankConfig, KapitalBankProvider, type Language, Manat, ManatError, type ManatErrorCode, type Money, type NextAction, type PaymentAction, type PaymentEvent, type PaymentProvider, type PaymentState, type PaymentStatus, type PayriffConfig, PayriffProvider, type RefundRequest, type RefundResult, VERSION, type YigimConfig, YigimProvider, azericard, azn, epoint, fromDecimal, goldenPay, isManatError, kapitalBank, payriff, toDecimalString, yigim };
package/dist/index.d.ts CHANGED
@@ -118,6 +118,8 @@ declare class Manat {
118
118
  private call;
119
119
  }
120
120
 
121
+ declare const VERSION = "0.2.1";
122
+
121
123
  type ManatErrorCode = 'invalid_request' | 'invalid_signature' | 'provider_error' | 'network_error' | 'unsupported_operation';
122
124
  declare class ManatError extends Error {
123
125
  readonly code: ManatErrorCode;
@@ -207,7 +209,7 @@ interface AzericardConfig {
207
209
  clock?: () => Date;
208
210
  nonce?: () => string;
209
211
  }
210
- /** MAC field orders from the docs (sections 2.2.1–2.2.4 and 4.3). */
212
+ /** MAC field orders per message type. */
211
213
  declare const AZERICARD_MAC_FIELDS: Readonly<Record<AzericardMessage, readonly string[]>>;
212
214
  declare class AzericardProvider implements PaymentProvider {
213
215
  private readonly config;
@@ -223,13 +225,7 @@ declare class AzericardProvider implements PaymentProvider {
223
225
  constructor(config: AzericardConfig, options?: HttpOptions);
224
226
  createPayment(request: CreatePaymentRequest): Promise<CreatePaymentResult>;
225
227
  verifyCallback(callback: IncomingCallback): Promise<PaymentEvent>;
226
- /**
227
- * TRTYPE 90. Works for 24 hours after the payment. The original TRTYPE is not known from the
228
- * paymentId alone, so a purchase (1) is asked for first and a pre-auth (0) on "context mismatch".
229
- * The answer only describes the transaction type asked about, so completion (21) and the
230
- * reversals (22 online, 24 offline) are queried too. Only the latest offline reversal is
231
- * visible this way, so refundedAmount can be lower than the truth after several 24s.
232
- */
228
+ /** TRTYPE 90 answers for one transaction type, so the purchase (or pre-auth), completion and reversals are each queried. */
233
229
  getStatus({ paymentId }: {
234
230
  paymentId: string;
235
231
  }): Promise<PaymentState>;
@@ -321,4 +317,37 @@ declare class GoldenPayProvider implements PaymentProvider {
321
317
  }
322
318
  declare function goldenPay(config: GoldenPayConfig, options?: HttpOptions): GoldenPayProvider;
323
319
 
324
- export { AZERICARD_MAC_FIELDS, type AzericardConfig, type AzericardMessage, AzericardProvider, type Capability, type CreatePaymentRequest, type CreatePaymentResult, type Currency, type EpointConfig, EpointProvider, type FetchLike, type GoldenPayConfig, GoldenPayProvider, type HttpOptions, type IncomingCallback, type KapitalBankConfig, KapitalBankProvider, type Language, Manat, ManatError, type ManatErrorCode, type Money, type NextAction, type PaymentAction, type PaymentEvent, type PaymentProvider, type PaymentState, type PaymentStatus, type PayriffConfig, PayriffProvider, type RefundRequest, type RefundResult, azericard, azn, epoint, fromDecimal, goldenPay, isManatError, kapitalBank, payriff, toDecimalString };
320
+ interface YigimConfig {
321
+ /** Merchant name issued by Yığım (`X-Merchant`). */
322
+ merchant: string;
323
+ /** API key issued by Yığım (`X-API-Key`). */
324
+ apiKey: string;
325
+ /** Biller name provided by the acquirer. */
326
+ biller: string;
327
+ /** Payment page template; default `TPL0001`. */
328
+ template?: string;
329
+ environment?: 'test' | 'production';
330
+ /** Override the API host. */
331
+ baseUrl?: string;
332
+ }
333
+ declare class YigimProvider implements PaymentProvider {
334
+ private readonly config;
335
+ readonly name = "yigim";
336
+ readonly capabilities: ReadonlySet<Capability>;
337
+ private readonly http;
338
+ private readonly baseUrl;
339
+ constructor(config: YigimConfig, options?: HttpOptions);
340
+ createPayment(request: CreatePaymentRequest): Promise<CreatePaymentResult>;
341
+ verifyCallback(callback: IncomingCallback): Promise<PaymentEvent>;
342
+ getStatus({ paymentId }: {
343
+ paymentId: string;
344
+ }): Promise<PaymentState>;
345
+ refund(request: RefundRequest): Promise<RefundResult>;
346
+ capture({ paymentId, amount }: PaymentAction): Promise<PaymentState>;
347
+ cancel({ paymentId }: PaymentAction): Promise<PaymentState>;
348
+ private call;
349
+ private error;
350
+ }
351
+ declare function yigim(config: YigimConfig, options?: HttpOptions): YigimProvider;
352
+
353
+ export { AZERICARD_MAC_FIELDS, type AzericardConfig, type AzericardMessage, AzericardProvider, type Capability, type CreatePaymentRequest, type CreatePaymentResult, type Currency, type EpointConfig, EpointProvider, type FetchLike, type GoldenPayConfig, GoldenPayProvider, type HttpOptions, type IncomingCallback, type KapitalBankConfig, KapitalBankProvider, type Language, Manat, ManatError, type ManatErrorCode, type Money, type NextAction, type PaymentAction, type PaymentEvent, type PaymentProvider, type PaymentState, type PaymentStatus, type PayriffConfig, PayriffProvider, type RefundRequest, type RefundResult, VERSION, type YigimConfig, YigimProvider, azericard, azn, epoint, fromDecimal, goldenPay, isManatError, kapitalBank, payriff, toDecimalString, yigim };
package/dist/index.js CHANGED
@@ -5,18 +5,18 @@ var ManatError = class extends Error {
5
5
  providerCode;
6
6
  providerMessage;
7
7
  raw;
8
- constructor(code, message, details = {}) {
8
+ constructor(code2, message, details = {}) {
9
9
  super(message, details.cause === void 0 ? void 0 : { cause: details.cause });
10
10
  this.name = "ManatError";
11
- this.code = code;
11
+ this.code = code2;
12
12
  this.provider = details.provider;
13
13
  this.providerCode = details.providerCode;
14
14
  this.providerMessage = details.providerMessage;
15
15
  this.raw = details.raw;
16
16
  }
17
17
  };
18
- function isManatError(error, code) {
19
- return error instanceof ManatError && (code === void 0 || error.code === code);
18
+ function isManatError(error, code2) {
19
+ return error instanceof ManatError && (code2 === void 0 || error.code === code2);
20
20
  }
21
21
 
22
22
  // src/money.ts
@@ -45,8 +45,11 @@ function fromDecimal(value, currency) {
45
45
  const fraction = (match[2] ?? "").padEnd(digits, "0");
46
46
  return { amount: Number(`${match[1]}${fraction}`), currency };
47
47
  }
48
- function currencyFromNumeric(code) {
49
- return Object.keys(NUMERIC_CODES).find((c) => NUMERIC_CODES[c] === code);
48
+ function currencyNumericCode(currency) {
49
+ return NUMERIC_CODES[currency];
50
+ }
51
+ function currencyFromNumeric(code2) {
52
+ return Object.keys(NUMERIC_CODES).find((c) => NUMERIC_CODES[c] === code2);
50
53
  }
51
54
  function azn(value) {
52
55
  return fromDecimal(value, "AZN");
@@ -112,6 +115,10 @@ function assertUrl(value, field) {
112
115
  }
113
116
  }
114
117
 
118
+ // src/version.ts
119
+ var VERSION = "0.2.1";
120
+ var USER_AGENT = `keytd-manat/${VERSION} (node; +https://github.com/KeyTechAndDesign/manat)`;
121
+
115
122
  // src/http.ts
116
123
  var HttpClient = class {
117
124
  constructor(provider, options = {}) {
@@ -125,7 +132,7 @@ var HttpClient = class {
125
132
  fetchImpl;
126
133
  timeoutMs;
127
134
  async send(request) {
128
- const headers = { ...request.headers };
135
+ const headers = { "user-agent": USER_AGENT, ...request.headers };
129
136
  let body;
130
137
  if (request.body instanceof URLSearchParams) {
131
138
  body = request.body.toString();
@@ -323,9 +330,9 @@ function kapitalBank(config, options) {
323
330
  function assertOrderId(paymentId) {
324
331
  if (!/^\d+$/.test(paymentId)) throw new ManatError("invalid_request", `kapitalbank: paymentId must be the numeric order id, got "${paymentId}"`);
325
332
  }
326
- function toCurrency(code, paymentId) {
327
- if (code === "AZN" || code === "USD" || code === "EUR" || code === "RUB" || code === "TRY" || code === "GBP") return code;
328
- throw new ManatError("provider_error", `kapitalbank: unexpected currency "${code}" on order ${paymentId}`, { provider: "kapitalbank" });
333
+ function toCurrency(code2, paymentId) {
334
+ if (code2 === "AZN" || code2 === "USD" || code2 === "EUR" || code2 === "RUB" || code2 === "TRY" || code2 === "GBP") return code2;
335
+ throw new ManatError("provider_error", `kapitalbank: unexpected currency "${code2}" on order ${paymentId}`, { provider: "kapitalbank" });
329
336
  }
330
337
  function missingAmount(paymentId) {
331
338
  throw new ManatError("provider_error", `kapitalbank: order ${paymentId} has no amount to capture`, { provider: "kapitalbank" });
@@ -345,7 +352,6 @@ function pickBody(body, key) {
345
352
  import { createPrivateKey, createPublicKey, randomBytes, sign, verify } from "crypto";
346
353
  var GATEWAY_URLS = {
347
354
  test: "https://testmpi.3dsecure.az/cgi-bin/cgi_link",
348
- // Not in the public docs (they only publish the test URL); used by existing integrations.
349
355
  production: "https://mpi.3dsecure.az/cgi-bin/cgi_link"
350
356
  };
351
357
  var AZERICARD_MAC_FIELDS = {
@@ -458,13 +464,7 @@ var AzericardProvider = class {
458
464
  }
459
465
  return event;
460
466
  }
461
- /**
462
- * TRTYPE 90. Works for 24 hours after the payment. The original TRTYPE is not known from the
463
- * paymentId alone, so a purchase (1) is asked for first and a pre-auth (0) on "context mismatch".
464
- * The answer only describes the transaction type asked about, so completion (21) and the
465
- * reversals (22 online, 24 offline) are queried too. Only the latest offline reversal is
466
- * visible this way, so refundedAmount can be lower than the truth after several 24s.
467
- */
467
+ /** TRTYPE 90 answers for one transaction type, so the purchase (or pre-auth), completion and reversals are each queried. */
468
468
  async getStatus({ paymentId }) {
469
469
  if (!/^\d{6,32}$/.test(paymentId)) throw new ManatError("invalid_request", `azericard: paymentId must be the numeric ORDER, got "${paymentId}"`);
470
470
  let trtype = "1";
@@ -624,9 +624,9 @@ function parseAmount(amount, currency) {
624
624
  const alpha = /^\d{3}$/.test(currency) ? currencyFromNumeric(currency) : currency;
625
625
  return alpha ? fromDecimal(amount, toCurrency2(alpha)) : void 0;
626
626
  }
627
- function toCurrency2(code) {
628
- if (code === "AZN" || code === "USD" || code === "EUR" || code === "RUB" || code === "TRY" || code === "GBP") return code;
629
- throw new ManatError("provider_error", `azericard: unsupported currency "${code}"`, { provider: "azericard" });
627
+ function toCurrency2(code2) {
628
+ if (code2 === "AZN" || code2 === "USD" || code2 === "EUR" || code2 === "RUB" || code2 === "TRY" || code2 === "GBP") return code2;
629
+ throw new ManatError("provider_error", `azericard: unsupported currency "${code2}"`, { provider: "azericard" });
630
630
  }
631
631
  function parseTimestamp(value) {
632
632
  const m = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/.exec(value);
@@ -811,9 +811,9 @@ function encodeId(paymentId) {
811
811
  if (!/^[A-Za-z0-9-]{1,64}$/.test(paymentId)) throw new ManatError("invalid_request", `payriff: invalid paymentId "${paymentId}"`);
812
812
  return paymentId;
813
813
  }
814
- function toCurrency3(code) {
815
- if (code === "AZN" || code === "USD" || code === "EUR" || code === "RUB" || code === "TRY" || code === "GBP") return code;
816
- throw new ManatError("provider_error", `payriff: unsupported currency "${code}"`, { provider: "payriff" });
814
+ function toCurrency3(code2) {
815
+ if (code2 === "AZN" || code2 === "USD" || code2 === "EUR" || code2 === "RUB" || code2 === "TRY" || code2 === "GBP") return code2;
816
+ throw new ManatError("provider_error", `payriff: unsupported currency "${code2}"`, { provider: "payriff" });
817
817
  }
818
818
 
819
819
  // src/crypto.ts
@@ -954,8 +954,8 @@ function epoint(config, options) {
954
954
  function formFields(body) {
955
955
  if (typeof body === "string") return Object.fromEntries(new URLSearchParams(body));
956
956
  if (!body || typeof body !== "object") return {};
957
- const pick3 = (key) => typeof body[key] === "string" ? body[key] : void 0;
958
- return { data: pick3("data"), signature: pick3("signature") };
957
+ const pick4 = (key) => typeof body[key] === "string" ? body[key] : void 0;
958
+ return { data: pick4("data"), signature: pick4("signature") };
959
959
  }
960
960
  function decodeData(data) {
961
961
  if (!/^[A-Za-z0-9+/]+={0,2}$/.test(data)) throw new ManatError("provider_error", "epoint: callback data is not base64", { provider: "epoint", raw: data });
@@ -980,8 +980,8 @@ var CAPABILITIES5 = /* @__PURE__ */ new Set(["createPayment", "verifyCallback",
980
980
  var SUCCESS2 = 1;
981
981
  var REVERSED = 817;
982
982
  var MAX_DESCRIPTION_BYTES = 500;
983
- function mapGoldenPayStatus(code) {
984
- switch (code) {
983
+ function mapGoldenPayStatus(code2) {
984
+ switch (code2) {
985
985
  case SUCCESS2:
986
986
  return "paid";
987
987
  case 813:
@@ -999,7 +999,7 @@ function mapGoldenPayStatus(code) {
999
999
  case REVERSED:
1000
1000
  return "refunded";
1001
1001
  default:
1002
- throw new ManatError("provider_error", `goldenpay: status code ${code} does not describe a payment`, { provider: "goldenpay" });
1002
+ throw new ManatError("provider_error", `goldenpay: status code ${code2} does not describe a payment`, { provider: "goldenpay" });
1003
1003
  }
1004
1004
  }
1005
1005
  var PAYMENT_CODES = /* @__PURE__ */ new Set([SUCCESS2, 813, 814, 815, 816, REVERSED, 818, 819]);
@@ -1050,10 +1050,10 @@ var GoldenPayProvider = class {
1050
1050
  assertPaymentKey(paymentId);
1051
1051
  const query = new URLSearchParams({ payment_key: paymentId, hash_code: md5(this.config.authKey + paymentId) });
1052
1052
  const response = await this.call("GET", `getPaymentResult?${query}`);
1053
- const code = response.status?.code;
1054
- if (code === void 0 || !PAYMENT_CODES.has(code)) throw this.error("status is not available", response);
1053
+ const code2 = response.status?.code;
1054
+ if (code2 === void 0 || !PAYMENT_CODES.has(code2)) throw this.error("status is not available", response);
1055
1055
  if (response.paymentKey && response.paymentKey !== paymentId) throw this.error(`result is for payment ${response.paymentKey}, not ${paymentId}`, response);
1056
- const state = { paymentId, status: mapGoldenPayStatus(code), raw: response };
1056
+ const state = { paymentId, status: mapGoldenPayStatus(code2), raw: response };
1057
1057
  if (typeof response.amount === "number" && response.amount > 0) state.amount = { amount: response.amount, currency: "AZN" };
1058
1058
  return state;
1059
1059
  }
@@ -1069,8 +1069,8 @@ var GoldenPayProvider = class {
1069
1069
  amount: amount.amount,
1070
1070
  secretKey: md5(hashCode + amount.amount)
1071
1071
  });
1072
- const code = response.status?.code;
1073
- if (code !== REVERSED && code !== SUCCESS2) throw this.error("refund failed", response);
1072
+ const code2 = response.status?.code;
1073
+ if (code2 !== REVERSED && code2 !== SUCCESS2) throw this.error("refund failed", response);
1074
1074
  const partial = payment.amount !== void 0 && amount.amount < payment.amount.amount;
1075
1075
  return { paymentId: request.paymentId, status: partial ? "partially_refunded" : "refunded", raw: { refund: response, payment: payment.raw } };
1076
1076
  }
@@ -1081,11 +1081,11 @@ var GoldenPayProvider = class {
1081
1081
  return data;
1082
1082
  }
1083
1083
  error(what, response) {
1084
- const { code, message } = response.status ?? {};
1085
- const fromProvider = code !== void 0 && code !== SUCCESS2;
1084
+ const { code: code2, message } = response.status ?? {};
1085
+ const fromProvider = code2 !== void 0 && code2 !== SUCCESS2;
1086
1086
  return new ManatError("provider_error", `goldenpay: ${fromProvider && message ? message : what}`, {
1087
1087
  provider: this.name,
1088
- ...fromProvider ? { providerCode: String(code) } : {},
1088
+ ...fromProvider ? { providerCode: String(code2) } : {},
1089
1089
  ...fromProvider && message ? { providerMessage: message } : {},
1090
1090
  raw: response
1091
1091
  });
@@ -1123,6 +1123,177 @@ function pickBody2(body, key) {
1123
1123
  const value = body[key];
1124
1124
  return typeof value === "string" ? value : void 0;
1125
1125
  }
1126
+
1127
+ // src/providers/yigim.ts
1128
+ var BASE_URLS3 = {
1129
+ test: "https://sandbox.api.pay.yigim.az",
1130
+ production: "https://api.pay.yigim.az"
1131
+ };
1132
+ var CAPABILITIES6 = /* @__PURE__ */ new Set(["createPayment", "verifyCallback", "getStatus", "refund", "capture", "cancel"]);
1133
+ var OK = 0;
1134
+ var DEFAULT_TEMPLATE = "TPL0001";
1135
+ var REFERENCE = /^[^\s\u0000-\u001f]{1,255}$/;
1136
+ function mapYigimStatus(status) {
1137
+ switch (status) {
1138
+ case "S0":
1139
+ // created, waiting for card data
1140
+ case "S2":
1141
+ // in progress
1142
+ case "S7":
1143
+ // system malfunction
1144
+ case "09":
1145
+ return "pending";
1146
+ case "S1":
1147
+ return "authorized";
1148
+ case "00":
1149
+ return "paid";
1150
+ case "S4":
1151
+ return "cancelled";
1152
+ case "S3":
1153
+ // unknown error
1154
+ case "01":
1155
+ // refer to issuer
1156
+ case "02":
1157
+ // expired card
1158
+ case "03":
1159
+ // invalid amount
1160
+ case "04":
1161
+ // inactive card
1162
+ case "05":
1163
+ // insufficient funds
1164
+ case "06":
1165
+ // suspected fraud
1166
+ case "07":
1167
+ // exceeds withdrawal limit
1168
+ case "08":
1169
+ return "failed";
1170
+ default:
1171
+ throw new ManatError("provider_error", `yigim: unknown payment status "${status}"`, { provider: "yigim" });
1172
+ }
1173
+ }
1174
+ var YigimProvider = class {
1175
+ constructor(config, options = {}) {
1176
+ this.config = config;
1177
+ if (!config?.merchant) throw new ManatError("invalid_request", "yigim: merchant is required");
1178
+ if (!config.apiKey) throw new ManatError("invalid_request", "yigim: apiKey is required");
1179
+ if (!config.biller) throw new ManatError("invalid_request", "yigim: biller is required");
1180
+ this.baseUrl = (config.baseUrl ?? BASE_URLS3[config.environment ?? "production"]).replace(/\/+$/, "");
1181
+ this.http = new HttpClient(this.name, options);
1182
+ }
1183
+ config;
1184
+ name = "yigim";
1185
+ capabilities = CAPABILITIES6;
1186
+ http;
1187
+ baseUrl;
1188
+ async createPayment(request) {
1189
+ assertReference(request.orderId);
1190
+ if (/[&;]/.test(request.returnUrl)) {
1191
+ throw new ManatError("invalid_request", 'yigim: returnUrl may carry at most one query parameter and no ";"');
1192
+ }
1193
+ const params = {
1194
+ reference: request.orderId,
1195
+ type: request.intent === "authorize" ? "DMS" : "SMS",
1196
+ amount: String(request.amount.amount),
1197
+ currency: currencyNumericCode(request.amount.currency),
1198
+ biller: this.config.biller
1199
+ };
1200
+ if (request.description) params.description = request.description;
1201
+ params.template = this.config.template ?? DEFAULT_TEMPLATE;
1202
+ params.language = request.language ?? "az";
1203
+ if (request.callbackUrl) params.callback = request.callbackUrl.includes("?") ? request.callbackUrl : `${request.callbackUrl}?`;
1204
+ params.extra = `back-url=${request.returnUrl};fail-url=${request.returnUrl}`;
1205
+ const reply = await this.call("payment/create", params);
1206
+ if (!reply.url) throw this.error("payment page URL is missing", reply);
1207
+ return { paymentId: request.orderId, status: "pending", next: { type: "redirect", url: reply.url }, raw: reply };
1208
+ }
1209
+ async verifyCallback(callback) {
1210
+ const reference = pick3(callback.query, "reference") ?? pickBody3(callback.body, "reference");
1211
+ if (!reference || !REFERENCE.test(reference)) {
1212
+ throw new ManatError("invalid_signature", "yigim: callback has no reference", { provider: this.name, raw: callback });
1213
+ }
1214
+ const state = await this.getStatus({ paymentId: reference });
1215
+ const event = { paymentId: state.paymentId, orderId: state.paymentId, status: state.status, raw: state.raw };
1216
+ if (state.amount) event.amount = state.amount;
1217
+ return event;
1218
+ }
1219
+ async getStatus({ paymentId }) {
1220
+ assertReference(paymentId);
1221
+ const reply = await this.call("payment/status", { reference: paymentId });
1222
+ if (reply.reference && reply.reference !== paymentId) throw this.error(`status is for payment ${reply.reference}, not ${paymentId}`, reply);
1223
+ const state = { paymentId, status: mapYigimStatus(reply.status), raw: reply };
1224
+ const currency = typeof reply.currency === "string" ? currencyFromNumeric(reply.currency) : void 0;
1225
+ if (reply.amount !== void 0 && reply.amount !== "" && currency) state.amount = fromDecimal(String(reply.amount), currency);
1226
+ return state;
1227
+ }
1228
+ async refund(request) {
1229
+ const reply = await this.call("payment/refund", withAmount(request.paymentId, request.amount));
1230
+ const state = await this.getStatus({ paymentId: request.paymentId });
1231
+ return { paymentId: request.paymentId, status: refundStatus(state, request.amount), raw: { refund: reply, payment: state.raw } };
1232
+ }
1233
+ async capture({ paymentId, amount }) {
1234
+ await this.call("payment/charge", withAmount(paymentId, amount));
1235
+ return this.getStatus({ paymentId });
1236
+ }
1237
+ async cancel({ paymentId }) {
1238
+ await this.call("payment/cancel", { reference: paymentId });
1239
+ return this.getStatus({ paymentId });
1240
+ }
1241
+ async call(command, params) {
1242
+ assertReference(params.reference ?? "");
1243
+ const { status, data } = await this.http.json({
1244
+ method: "GET",
1245
+ url: `${this.baseUrl}/${command}?${new URLSearchParams(params)}`,
1246
+ headers: { "x-merchant": this.config.merchant, "x-api-key": this.config.apiKey, "x-type": "JSON" }
1247
+ });
1248
+ const reply = unwrap(data);
1249
+ if (!reply || code(reply) === void 0) {
1250
+ throw new ManatError("provider_error", `yigim: unexpected response (HTTP ${status})`, { provider: this.name, raw: data });
1251
+ }
1252
+ if (status >= 400 || code(reply) !== OK) throw this.error(`HTTP ${status}`, reply);
1253
+ return reply;
1254
+ }
1255
+ error(what, reply) {
1256
+ const fromProvider = code(reply) !== void 0 && code(reply) !== OK;
1257
+ return new ManatError("provider_error", `yigim: ${fromProvider && reply.message ? reply.message : what}`, {
1258
+ provider: this.name,
1259
+ ...fromProvider ? { providerCode: String(reply.code) } : {},
1260
+ ...fromProvider && reply.message ? { providerMessage: reply.message } : {},
1261
+ raw: reply
1262
+ });
1263
+ }
1264
+ };
1265
+ function yigim(config, options) {
1266
+ return new YigimProvider(config, options);
1267
+ }
1268
+ function code(reply) {
1269
+ if (typeof reply.code === "number") return reply.code;
1270
+ return typeof reply.code === "string" && /^-?\d+$/.test(reply.code) ? Number(reply.code) : void 0;
1271
+ }
1272
+ function unwrap(data) {
1273
+ if (!data || typeof data !== "object") return void 0;
1274
+ const inner = data.response;
1275
+ return inner && typeof inner === "object" ? inner : data;
1276
+ }
1277
+ function withAmount(reference, amount) {
1278
+ return amount ? { reference, amount: String(amount.amount) } : { reference };
1279
+ }
1280
+ function refundStatus(state, amount) {
1281
+ if (state.status !== "paid") return state.status;
1282
+ return amount && state.amount && amount.amount < state.amount.amount ? "partially_refunded" : "refunded";
1283
+ }
1284
+ function assertReference(reference) {
1285
+ if (!REFERENCE.test(reference)) throw new ManatError("invalid_request", `yigim: invalid reference "${reference}"`);
1286
+ }
1287
+ function pick3(source, key) {
1288
+ const value = source?.[key];
1289
+ return Array.isArray(value) ? value[0] : value;
1290
+ }
1291
+ function pickBody3(body, key) {
1292
+ if (!body) return void 0;
1293
+ if (typeof body === "string") return new URLSearchParams(body).get(key) ?? void 0;
1294
+ const value = body[key];
1295
+ return typeof value === "string" ? value : void 0;
1296
+ }
1126
1297
  export {
1127
1298
  AZERICARD_MAC_FIELDS,
1128
1299
  AzericardProvider,
@@ -1132,6 +1303,8 @@ export {
1132
1303
  Manat,
1133
1304
  ManatError,
1134
1305
  PayriffProvider,
1306
+ VERSION,
1307
+ YigimProvider,
1135
1308
  azericard,
1136
1309
  azn,
1137
1310
  epoint,
@@ -1140,5 +1313,6 @@ export {
1140
1313
  isManatError,
1141
1314
  kapitalBank,
1142
1315
  payriff,
1143
- toDecimalString
1316
+ toDecimalString,
1317
+ yigim
1144
1318
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keytd/manat",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "One API for Azerbaijani online payment providers.",
5
5
  "license": "MIT",
6
6
  "author": "KeyTD <info@keytd.com>",
@@ -23,7 +23,9 @@
23
23
  "require": "./dist/index.cjs"
24
24
  }
25
25
  },
26
- "files": ["dist"],
26
+ "files": [
27
+ "dist"
28
+ ],
27
29
  "publishConfig": {
28
30
  "access": "public"
29
31
  },
@@ -36,7 +38,19 @@
36
38
  "typecheck": "tsc --noEmit",
37
39
  "test": "vitest run"
38
40
  },
39
- "keywords": ["azerbaijan", "payments", "azn", "manat", "payment-gateway", "kapital-bank", "azericard", "payriff", "epoint", "goldenpay"],
41
+ "keywords": [
42
+ "azerbaijan",
43
+ "payments",
44
+ "azn",
45
+ "manat",
46
+ "payment-gateway",
47
+ "kapital-bank",
48
+ "azericard",
49
+ "payriff",
50
+ "epoint",
51
+ "goldenpay",
52
+ "yigim"
53
+ ],
40
54
  "devDependencies": {
41
55
  "@types/node": "^20.14.0",
42
56
  "tsup": "^8.3.0",