@keytd/manat 0.1.0 → 0.2.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/README.md +4 -4
- package/dist/index.cjs +209 -38
- package/dist/index.d.cts +36 -9
- package/dist/index.d.ts +36 -9
- package/dist/index.js +206 -37
- package/package.json +17 -3
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
|
|
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,7 @@ __export(index_exports, {
|
|
|
28
28
|
Manat: () => Manat,
|
|
29
29
|
ManatError: () => ManatError,
|
|
30
30
|
PayriffProvider: () => PayriffProvider,
|
|
31
|
+
YigimProvider: () => YigimProvider,
|
|
31
32
|
azericard: () => azericard,
|
|
32
33
|
azn: () => azn,
|
|
33
34
|
epoint: () => epoint,
|
|
@@ -36,7 +37,8 @@ __export(index_exports, {
|
|
|
36
37
|
isManatError: () => isManatError,
|
|
37
38
|
kapitalBank: () => kapitalBank,
|
|
38
39
|
payriff: () => payriff,
|
|
39
|
-
toDecimalString: () => toDecimalString
|
|
40
|
+
toDecimalString: () => toDecimalString,
|
|
41
|
+
yigim: () => yigim
|
|
40
42
|
});
|
|
41
43
|
module.exports = __toCommonJS(index_exports);
|
|
42
44
|
|
|
@@ -47,18 +49,18 @@ var ManatError = class extends Error {
|
|
|
47
49
|
providerCode;
|
|
48
50
|
providerMessage;
|
|
49
51
|
raw;
|
|
50
|
-
constructor(
|
|
52
|
+
constructor(code2, message, details = {}) {
|
|
51
53
|
super(message, details.cause === void 0 ? void 0 : { cause: details.cause });
|
|
52
54
|
this.name = "ManatError";
|
|
53
|
-
this.code =
|
|
55
|
+
this.code = code2;
|
|
54
56
|
this.provider = details.provider;
|
|
55
57
|
this.providerCode = details.providerCode;
|
|
56
58
|
this.providerMessage = details.providerMessage;
|
|
57
59
|
this.raw = details.raw;
|
|
58
60
|
}
|
|
59
61
|
};
|
|
60
|
-
function isManatError(error,
|
|
61
|
-
return error instanceof ManatError && (
|
|
62
|
+
function isManatError(error, code2) {
|
|
63
|
+
return error instanceof ManatError && (code2 === void 0 || error.code === code2);
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
// src/money.ts
|
|
@@ -87,8 +89,11 @@ function fromDecimal(value, currency) {
|
|
|
87
89
|
const fraction = (match[2] ?? "").padEnd(digits, "0");
|
|
88
90
|
return { amount: Number(`${match[1]}${fraction}`), currency };
|
|
89
91
|
}
|
|
90
|
-
function
|
|
91
|
-
return
|
|
92
|
+
function currencyNumericCode(currency) {
|
|
93
|
+
return NUMERIC_CODES[currency];
|
|
94
|
+
}
|
|
95
|
+
function currencyFromNumeric(code2) {
|
|
96
|
+
return Object.keys(NUMERIC_CODES).find((c) => NUMERIC_CODES[c] === code2);
|
|
92
97
|
}
|
|
93
98
|
function azn(value) {
|
|
94
99
|
return fromDecimal(value, "AZN");
|
|
@@ -365,9 +370,9 @@ function kapitalBank(config, options) {
|
|
|
365
370
|
function assertOrderId(paymentId) {
|
|
366
371
|
if (!/^\d+$/.test(paymentId)) throw new ManatError("invalid_request", `kapitalbank: paymentId must be the numeric order id, got "${paymentId}"`);
|
|
367
372
|
}
|
|
368
|
-
function toCurrency(
|
|
369
|
-
if (
|
|
370
|
-
throw new ManatError("provider_error", `kapitalbank: unexpected currency "${
|
|
373
|
+
function toCurrency(code2, paymentId) {
|
|
374
|
+
if (code2 === "AZN" || code2 === "USD" || code2 === "EUR" || code2 === "RUB" || code2 === "TRY" || code2 === "GBP") return code2;
|
|
375
|
+
throw new ManatError("provider_error", `kapitalbank: unexpected currency "${code2}" on order ${paymentId}`, { provider: "kapitalbank" });
|
|
371
376
|
}
|
|
372
377
|
function missingAmount(paymentId) {
|
|
373
378
|
throw new ManatError("provider_error", `kapitalbank: order ${paymentId} has no amount to capture`, { provider: "kapitalbank" });
|
|
@@ -387,7 +392,6 @@ function pickBody(body, key) {
|
|
|
387
392
|
var import_node_crypto = require("crypto");
|
|
388
393
|
var GATEWAY_URLS = {
|
|
389
394
|
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
395
|
production: "https://mpi.3dsecure.az/cgi-bin/cgi_link"
|
|
392
396
|
};
|
|
393
397
|
var AZERICARD_MAC_FIELDS = {
|
|
@@ -500,13 +504,7 @@ var AzericardProvider = class {
|
|
|
500
504
|
}
|
|
501
505
|
return event;
|
|
502
506
|
}
|
|
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
|
-
*/
|
|
507
|
+
/** TRTYPE 90 answers for one transaction type, so the purchase (or pre-auth), completion and reversals are each queried. */
|
|
510
508
|
async getStatus({ paymentId }) {
|
|
511
509
|
if (!/^\d{6,32}$/.test(paymentId)) throw new ManatError("invalid_request", `azericard: paymentId must be the numeric ORDER, got "${paymentId}"`);
|
|
512
510
|
let trtype = "1";
|
|
@@ -666,9 +664,9 @@ function parseAmount(amount, currency) {
|
|
|
666
664
|
const alpha = /^\d{3}$/.test(currency) ? currencyFromNumeric(currency) : currency;
|
|
667
665
|
return alpha ? fromDecimal(amount, toCurrency2(alpha)) : void 0;
|
|
668
666
|
}
|
|
669
|
-
function toCurrency2(
|
|
670
|
-
if (
|
|
671
|
-
throw new ManatError("provider_error", `azericard: unsupported currency "${
|
|
667
|
+
function toCurrency2(code2) {
|
|
668
|
+
if (code2 === "AZN" || code2 === "USD" || code2 === "EUR" || code2 === "RUB" || code2 === "TRY" || code2 === "GBP") return code2;
|
|
669
|
+
throw new ManatError("provider_error", `azericard: unsupported currency "${code2}"`, { provider: "azericard" });
|
|
672
670
|
}
|
|
673
671
|
function parseTimestamp(value) {
|
|
674
672
|
const m = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/.exec(value);
|
|
@@ -853,9 +851,9 @@ function encodeId(paymentId) {
|
|
|
853
851
|
if (!/^[A-Za-z0-9-]{1,64}$/.test(paymentId)) throw new ManatError("invalid_request", `payriff: invalid paymentId "${paymentId}"`);
|
|
854
852
|
return paymentId;
|
|
855
853
|
}
|
|
856
|
-
function toCurrency3(
|
|
857
|
-
if (
|
|
858
|
-
throw new ManatError("provider_error", `payriff: unsupported currency "${
|
|
854
|
+
function toCurrency3(code2) {
|
|
855
|
+
if (code2 === "AZN" || code2 === "USD" || code2 === "EUR" || code2 === "RUB" || code2 === "TRY" || code2 === "GBP") return code2;
|
|
856
|
+
throw new ManatError("provider_error", `payriff: unsupported currency "${code2}"`, { provider: "payriff" });
|
|
859
857
|
}
|
|
860
858
|
|
|
861
859
|
// src/crypto.ts
|
|
@@ -996,8 +994,8 @@ function epoint(config, options) {
|
|
|
996
994
|
function formFields(body) {
|
|
997
995
|
if (typeof body === "string") return Object.fromEntries(new URLSearchParams(body));
|
|
998
996
|
if (!body || typeof body !== "object") return {};
|
|
999
|
-
const
|
|
1000
|
-
return { data:
|
|
997
|
+
const pick4 = (key) => typeof body[key] === "string" ? body[key] : void 0;
|
|
998
|
+
return { data: pick4("data"), signature: pick4("signature") };
|
|
1001
999
|
}
|
|
1002
1000
|
function decodeData(data) {
|
|
1003
1001
|
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 +1020,8 @@ var CAPABILITIES5 = /* @__PURE__ */ new Set(["createPayment", "verifyCallback",
|
|
|
1022
1020
|
var SUCCESS2 = 1;
|
|
1023
1021
|
var REVERSED = 817;
|
|
1024
1022
|
var MAX_DESCRIPTION_BYTES = 500;
|
|
1025
|
-
function mapGoldenPayStatus(
|
|
1026
|
-
switch (
|
|
1023
|
+
function mapGoldenPayStatus(code2) {
|
|
1024
|
+
switch (code2) {
|
|
1027
1025
|
case SUCCESS2:
|
|
1028
1026
|
return "paid";
|
|
1029
1027
|
case 813:
|
|
@@ -1041,7 +1039,7 @@ function mapGoldenPayStatus(code) {
|
|
|
1041
1039
|
case REVERSED:
|
|
1042
1040
|
return "refunded";
|
|
1043
1041
|
default:
|
|
1044
|
-
throw new ManatError("provider_error", `goldenpay: status code ${
|
|
1042
|
+
throw new ManatError("provider_error", `goldenpay: status code ${code2} does not describe a payment`, { provider: "goldenpay" });
|
|
1045
1043
|
}
|
|
1046
1044
|
}
|
|
1047
1045
|
var PAYMENT_CODES = /* @__PURE__ */ new Set([SUCCESS2, 813, 814, 815, 816, REVERSED, 818, 819]);
|
|
@@ -1092,10 +1090,10 @@ var GoldenPayProvider = class {
|
|
|
1092
1090
|
assertPaymentKey(paymentId);
|
|
1093
1091
|
const query = new URLSearchParams({ payment_key: paymentId, hash_code: md5(this.config.authKey + paymentId) });
|
|
1094
1092
|
const response = await this.call("GET", `getPaymentResult?${query}`);
|
|
1095
|
-
const
|
|
1096
|
-
if (
|
|
1093
|
+
const code2 = response.status?.code;
|
|
1094
|
+
if (code2 === void 0 || !PAYMENT_CODES.has(code2)) throw this.error("status is not available", response);
|
|
1097
1095
|
if (response.paymentKey && response.paymentKey !== paymentId) throw this.error(`result is for payment ${response.paymentKey}, not ${paymentId}`, response);
|
|
1098
|
-
const state = { paymentId, status: mapGoldenPayStatus(
|
|
1096
|
+
const state = { paymentId, status: mapGoldenPayStatus(code2), raw: response };
|
|
1099
1097
|
if (typeof response.amount === "number" && response.amount > 0) state.amount = { amount: response.amount, currency: "AZN" };
|
|
1100
1098
|
return state;
|
|
1101
1099
|
}
|
|
@@ -1111,8 +1109,8 @@ var GoldenPayProvider = class {
|
|
|
1111
1109
|
amount: amount.amount,
|
|
1112
1110
|
secretKey: md5(hashCode + amount.amount)
|
|
1113
1111
|
});
|
|
1114
|
-
const
|
|
1115
|
-
if (
|
|
1112
|
+
const code2 = response.status?.code;
|
|
1113
|
+
if (code2 !== REVERSED && code2 !== SUCCESS2) throw this.error("refund failed", response);
|
|
1116
1114
|
const partial = payment.amount !== void 0 && amount.amount < payment.amount.amount;
|
|
1117
1115
|
return { paymentId: request.paymentId, status: partial ? "partially_refunded" : "refunded", raw: { refund: response, payment: payment.raw } };
|
|
1118
1116
|
}
|
|
@@ -1123,11 +1121,11 @@ var GoldenPayProvider = class {
|
|
|
1123
1121
|
return data;
|
|
1124
1122
|
}
|
|
1125
1123
|
error(what, response) {
|
|
1126
|
-
const { code, message } = response.status ?? {};
|
|
1127
|
-
const fromProvider =
|
|
1124
|
+
const { code: code2, message } = response.status ?? {};
|
|
1125
|
+
const fromProvider = code2 !== void 0 && code2 !== SUCCESS2;
|
|
1128
1126
|
return new ManatError("provider_error", `goldenpay: ${fromProvider && message ? message : what}`, {
|
|
1129
1127
|
provider: this.name,
|
|
1130
|
-
...fromProvider ? { providerCode: String(
|
|
1128
|
+
...fromProvider ? { providerCode: String(code2) } : {},
|
|
1131
1129
|
...fromProvider && message ? { providerMessage: message } : {},
|
|
1132
1130
|
raw: response
|
|
1133
1131
|
});
|
|
@@ -1165,6 +1163,177 @@ function pickBody2(body, key) {
|
|
|
1165
1163
|
const value = body[key];
|
|
1166
1164
|
return typeof value === "string" ? value : void 0;
|
|
1167
1165
|
}
|
|
1166
|
+
|
|
1167
|
+
// src/providers/yigim.ts
|
|
1168
|
+
var BASE_URLS3 = {
|
|
1169
|
+
test: "https://sandbox.api.pay.yigim.az",
|
|
1170
|
+
production: "https://api.pay.yigim.az"
|
|
1171
|
+
};
|
|
1172
|
+
var CAPABILITIES6 = /* @__PURE__ */ new Set(["createPayment", "verifyCallback", "getStatus", "refund", "capture", "cancel"]);
|
|
1173
|
+
var OK = 0;
|
|
1174
|
+
var DEFAULT_TEMPLATE = "TPL0001";
|
|
1175
|
+
var REFERENCE = /^[^\s\u0000-\u001f]{1,255}$/;
|
|
1176
|
+
function mapYigimStatus(status) {
|
|
1177
|
+
switch (status) {
|
|
1178
|
+
case "S0":
|
|
1179
|
+
// created, waiting for card data
|
|
1180
|
+
case "S2":
|
|
1181
|
+
// in progress
|
|
1182
|
+
case "S7":
|
|
1183
|
+
// system malfunction
|
|
1184
|
+
case "09":
|
|
1185
|
+
return "pending";
|
|
1186
|
+
case "S1":
|
|
1187
|
+
return "authorized";
|
|
1188
|
+
case "00":
|
|
1189
|
+
return "paid";
|
|
1190
|
+
case "S4":
|
|
1191
|
+
return "cancelled";
|
|
1192
|
+
case "S3":
|
|
1193
|
+
// unknown error
|
|
1194
|
+
case "01":
|
|
1195
|
+
// refer to issuer
|
|
1196
|
+
case "02":
|
|
1197
|
+
// expired card
|
|
1198
|
+
case "03":
|
|
1199
|
+
// invalid amount
|
|
1200
|
+
case "04":
|
|
1201
|
+
// inactive card
|
|
1202
|
+
case "05":
|
|
1203
|
+
// insufficient funds
|
|
1204
|
+
case "06":
|
|
1205
|
+
// suspected fraud
|
|
1206
|
+
case "07":
|
|
1207
|
+
// exceeds withdrawal limit
|
|
1208
|
+
case "08":
|
|
1209
|
+
return "failed";
|
|
1210
|
+
default:
|
|
1211
|
+
throw new ManatError("provider_error", `yigim: unknown payment status "${status}"`, { provider: "yigim" });
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
var YigimProvider = class {
|
|
1215
|
+
constructor(config, options = {}) {
|
|
1216
|
+
this.config = config;
|
|
1217
|
+
if (!config?.merchant) throw new ManatError("invalid_request", "yigim: merchant is required");
|
|
1218
|
+
if (!config.apiKey) throw new ManatError("invalid_request", "yigim: apiKey is required");
|
|
1219
|
+
if (!config.biller) throw new ManatError("invalid_request", "yigim: biller is required");
|
|
1220
|
+
this.baseUrl = (config.baseUrl ?? BASE_URLS3[config.environment ?? "production"]).replace(/\/+$/, "");
|
|
1221
|
+
this.http = new HttpClient(this.name, options);
|
|
1222
|
+
}
|
|
1223
|
+
config;
|
|
1224
|
+
name = "yigim";
|
|
1225
|
+
capabilities = CAPABILITIES6;
|
|
1226
|
+
http;
|
|
1227
|
+
baseUrl;
|
|
1228
|
+
async createPayment(request) {
|
|
1229
|
+
assertReference(request.orderId);
|
|
1230
|
+
if (/[&;]/.test(request.returnUrl)) {
|
|
1231
|
+
throw new ManatError("invalid_request", 'yigim: returnUrl may carry at most one query parameter and no ";"');
|
|
1232
|
+
}
|
|
1233
|
+
const params = {
|
|
1234
|
+
reference: request.orderId,
|
|
1235
|
+
type: request.intent === "authorize" ? "DMS" : "SMS",
|
|
1236
|
+
amount: String(request.amount.amount),
|
|
1237
|
+
currency: currencyNumericCode(request.amount.currency),
|
|
1238
|
+
biller: this.config.biller
|
|
1239
|
+
};
|
|
1240
|
+
if (request.description) params.description = request.description;
|
|
1241
|
+
params.template = this.config.template ?? DEFAULT_TEMPLATE;
|
|
1242
|
+
params.language = request.language ?? "az";
|
|
1243
|
+
if (request.callbackUrl) params.callback = request.callbackUrl.includes("?") ? request.callbackUrl : `${request.callbackUrl}?`;
|
|
1244
|
+
params.extra = `back-url=${request.returnUrl};fail-url=${request.returnUrl}`;
|
|
1245
|
+
const reply = await this.call("payment/create", params);
|
|
1246
|
+
if (!reply.url) throw this.error("payment page URL is missing", reply);
|
|
1247
|
+
return { paymentId: request.orderId, status: "pending", next: { type: "redirect", url: reply.url }, raw: reply };
|
|
1248
|
+
}
|
|
1249
|
+
async verifyCallback(callback) {
|
|
1250
|
+
const reference = pick3(callback.query, "reference") ?? pickBody3(callback.body, "reference");
|
|
1251
|
+
if (!reference || !REFERENCE.test(reference)) {
|
|
1252
|
+
throw new ManatError("invalid_signature", "yigim: callback has no reference", { provider: this.name, raw: callback });
|
|
1253
|
+
}
|
|
1254
|
+
const state = await this.getStatus({ paymentId: reference });
|
|
1255
|
+
const event = { paymentId: state.paymentId, orderId: state.paymentId, status: state.status, raw: state.raw };
|
|
1256
|
+
if (state.amount) event.amount = state.amount;
|
|
1257
|
+
return event;
|
|
1258
|
+
}
|
|
1259
|
+
async getStatus({ paymentId }) {
|
|
1260
|
+
assertReference(paymentId);
|
|
1261
|
+
const reply = await this.call("payment/status", { reference: paymentId });
|
|
1262
|
+
if (reply.reference && reply.reference !== paymentId) throw this.error(`status is for payment ${reply.reference}, not ${paymentId}`, reply);
|
|
1263
|
+
const state = { paymentId, status: mapYigimStatus(reply.status), raw: reply };
|
|
1264
|
+
const currency = typeof reply.currency === "string" ? currencyFromNumeric(reply.currency) : void 0;
|
|
1265
|
+
if (reply.amount !== void 0 && reply.amount !== "" && currency) state.amount = fromDecimal(String(reply.amount), currency);
|
|
1266
|
+
return state;
|
|
1267
|
+
}
|
|
1268
|
+
async refund(request) {
|
|
1269
|
+
const reply = await this.call("payment/refund", withAmount(request.paymentId, request.amount));
|
|
1270
|
+
const state = await this.getStatus({ paymentId: request.paymentId });
|
|
1271
|
+
return { paymentId: request.paymentId, status: refundStatus(state, request.amount), raw: { refund: reply, payment: state.raw } };
|
|
1272
|
+
}
|
|
1273
|
+
async capture({ paymentId, amount }) {
|
|
1274
|
+
await this.call("payment/charge", withAmount(paymentId, amount));
|
|
1275
|
+
return this.getStatus({ paymentId });
|
|
1276
|
+
}
|
|
1277
|
+
async cancel({ paymentId }) {
|
|
1278
|
+
await this.call("payment/cancel", { reference: paymentId });
|
|
1279
|
+
return this.getStatus({ paymentId });
|
|
1280
|
+
}
|
|
1281
|
+
async call(command, params) {
|
|
1282
|
+
assertReference(params.reference ?? "");
|
|
1283
|
+
const { status, data } = await this.http.json({
|
|
1284
|
+
method: "GET",
|
|
1285
|
+
url: `${this.baseUrl}/${command}?${new URLSearchParams(params)}`,
|
|
1286
|
+
headers: { "x-merchant": this.config.merchant, "x-api-key": this.config.apiKey, "x-type": "JSON" }
|
|
1287
|
+
});
|
|
1288
|
+
const reply = unwrap(data);
|
|
1289
|
+
if (!reply || code(reply) === void 0) {
|
|
1290
|
+
throw new ManatError("provider_error", `yigim: unexpected response (HTTP ${status})`, { provider: this.name, raw: data });
|
|
1291
|
+
}
|
|
1292
|
+
if (status >= 400 || code(reply) !== OK) throw this.error(`HTTP ${status}`, reply);
|
|
1293
|
+
return reply;
|
|
1294
|
+
}
|
|
1295
|
+
error(what, reply) {
|
|
1296
|
+
const fromProvider = code(reply) !== void 0 && code(reply) !== OK;
|
|
1297
|
+
return new ManatError("provider_error", `yigim: ${fromProvider && reply.message ? reply.message : what}`, {
|
|
1298
|
+
provider: this.name,
|
|
1299
|
+
...fromProvider ? { providerCode: String(reply.code) } : {},
|
|
1300
|
+
...fromProvider && reply.message ? { providerMessage: reply.message } : {},
|
|
1301
|
+
raw: reply
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
function yigim(config, options) {
|
|
1306
|
+
return new YigimProvider(config, options);
|
|
1307
|
+
}
|
|
1308
|
+
function code(reply) {
|
|
1309
|
+
if (typeof reply.code === "number") return reply.code;
|
|
1310
|
+
return typeof reply.code === "string" && /^-?\d+$/.test(reply.code) ? Number(reply.code) : void 0;
|
|
1311
|
+
}
|
|
1312
|
+
function unwrap(data) {
|
|
1313
|
+
if (!data || typeof data !== "object") return void 0;
|
|
1314
|
+
const inner = data.response;
|
|
1315
|
+
return inner && typeof inner === "object" ? inner : data;
|
|
1316
|
+
}
|
|
1317
|
+
function withAmount(reference, amount) {
|
|
1318
|
+
return amount ? { reference, amount: String(amount.amount) } : { reference };
|
|
1319
|
+
}
|
|
1320
|
+
function refundStatus(state, amount) {
|
|
1321
|
+
if (state.status !== "paid") return state.status;
|
|
1322
|
+
return amount && state.amount && amount.amount < state.amount.amount ? "partially_refunded" : "refunded";
|
|
1323
|
+
}
|
|
1324
|
+
function assertReference(reference) {
|
|
1325
|
+
if (!REFERENCE.test(reference)) throw new ManatError("invalid_request", `yigim: invalid reference "${reference}"`);
|
|
1326
|
+
}
|
|
1327
|
+
function pick3(source, key) {
|
|
1328
|
+
const value = source?.[key];
|
|
1329
|
+
return Array.isArray(value) ? value[0] : value;
|
|
1330
|
+
}
|
|
1331
|
+
function pickBody3(body, key) {
|
|
1332
|
+
if (!body) return void 0;
|
|
1333
|
+
if (typeof body === "string") return new URLSearchParams(body).get(key) ?? void 0;
|
|
1334
|
+
const value = body[key];
|
|
1335
|
+
return typeof value === "string" ? value : void 0;
|
|
1336
|
+
}
|
|
1168
1337
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1169
1338
|
0 && (module.exports = {
|
|
1170
1339
|
AZERICARD_MAC_FIELDS,
|
|
@@ -1175,6 +1344,7 @@ function pickBody2(body, key) {
|
|
|
1175
1344
|
Manat,
|
|
1176
1345
|
ManatError,
|
|
1177
1346
|
PayriffProvider,
|
|
1347
|
+
YigimProvider,
|
|
1178
1348
|
azericard,
|
|
1179
1349
|
azn,
|
|
1180
1350
|
epoint,
|
|
@@ -1183,5 +1353,6 @@ function pickBody2(body, key) {
|
|
|
1183
1353
|
isManatError,
|
|
1184
1354
|
kapitalBank,
|
|
1185
1355
|
payriff,
|
|
1186
|
-
toDecimalString
|
|
1356
|
+
toDecimalString,
|
|
1357
|
+
yigim
|
|
1187
1358
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -207,7 +207,7 @@ interface AzericardConfig {
|
|
|
207
207
|
clock?: () => Date;
|
|
208
208
|
nonce?: () => string;
|
|
209
209
|
}
|
|
210
|
-
/** MAC field orders
|
|
210
|
+
/** MAC field orders per message type. */
|
|
211
211
|
declare const AZERICARD_MAC_FIELDS: Readonly<Record<AzericardMessage, readonly string[]>>;
|
|
212
212
|
declare class AzericardProvider implements PaymentProvider {
|
|
213
213
|
private readonly config;
|
|
@@ -223,13 +223,7 @@ declare class AzericardProvider implements PaymentProvider {
|
|
|
223
223
|
constructor(config: AzericardConfig, options?: HttpOptions);
|
|
224
224
|
createPayment(request: CreatePaymentRequest): Promise<CreatePaymentResult>;
|
|
225
225
|
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
|
-
*/
|
|
226
|
+
/** TRTYPE 90 answers for one transaction type, so the purchase (or pre-auth), completion and reversals are each queried. */
|
|
233
227
|
getStatus({ paymentId }: {
|
|
234
228
|
paymentId: string;
|
|
235
229
|
}): Promise<PaymentState>;
|
|
@@ -321,4 +315,37 @@ declare class GoldenPayProvider implements PaymentProvider {
|
|
|
321
315
|
}
|
|
322
316
|
declare function goldenPay(config: GoldenPayConfig, options?: HttpOptions): GoldenPayProvider;
|
|
323
317
|
|
|
324
|
-
|
|
318
|
+
interface YigimConfig {
|
|
319
|
+
/** Merchant name issued by Yığım (`X-Merchant`). */
|
|
320
|
+
merchant: string;
|
|
321
|
+
/** API key issued by Yığım (`X-API-Key`). */
|
|
322
|
+
apiKey: string;
|
|
323
|
+
/** Biller name provided by the acquirer. */
|
|
324
|
+
biller: string;
|
|
325
|
+
/** Payment page template; default `TPL0001`. */
|
|
326
|
+
template?: string;
|
|
327
|
+
environment?: 'test' | 'production';
|
|
328
|
+
/** Override the API host. */
|
|
329
|
+
baseUrl?: string;
|
|
330
|
+
}
|
|
331
|
+
declare class YigimProvider implements PaymentProvider {
|
|
332
|
+
private readonly config;
|
|
333
|
+
readonly name = "yigim";
|
|
334
|
+
readonly capabilities: ReadonlySet<Capability>;
|
|
335
|
+
private readonly http;
|
|
336
|
+
private readonly baseUrl;
|
|
337
|
+
constructor(config: YigimConfig, options?: HttpOptions);
|
|
338
|
+
createPayment(request: CreatePaymentRequest): Promise<CreatePaymentResult>;
|
|
339
|
+
verifyCallback(callback: IncomingCallback): Promise<PaymentEvent>;
|
|
340
|
+
getStatus({ paymentId }: {
|
|
341
|
+
paymentId: string;
|
|
342
|
+
}): Promise<PaymentState>;
|
|
343
|
+
refund(request: RefundRequest): Promise<RefundResult>;
|
|
344
|
+
capture({ paymentId, amount }: PaymentAction): Promise<PaymentState>;
|
|
345
|
+
cancel({ paymentId }: PaymentAction): Promise<PaymentState>;
|
|
346
|
+
private call;
|
|
347
|
+
private error;
|
|
348
|
+
}
|
|
349
|
+
declare function yigim(config: YigimConfig, options?: HttpOptions): YigimProvider;
|
|
350
|
+
|
|
351
|
+
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, type YigimConfig, YigimProvider, azericard, azn, epoint, fromDecimal, goldenPay, isManatError, kapitalBank, payriff, toDecimalString, yigim };
|
package/dist/index.d.ts
CHANGED
|
@@ -207,7 +207,7 @@ interface AzericardConfig {
|
|
|
207
207
|
clock?: () => Date;
|
|
208
208
|
nonce?: () => string;
|
|
209
209
|
}
|
|
210
|
-
/** MAC field orders
|
|
210
|
+
/** MAC field orders per message type. */
|
|
211
211
|
declare const AZERICARD_MAC_FIELDS: Readonly<Record<AzericardMessage, readonly string[]>>;
|
|
212
212
|
declare class AzericardProvider implements PaymentProvider {
|
|
213
213
|
private readonly config;
|
|
@@ -223,13 +223,7 @@ declare class AzericardProvider implements PaymentProvider {
|
|
|
223
223
|
constructor(config: AzericardConfig, options?: HttpOptions);
|
|
224
224
|
createPayment(request: CreatePaymentRequest): Promise<CreatePaymentResult>;
|
|
225
225
|
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
|
-
*/
|
|
226
|
+
/** TRTYPE 90 answers for one transaction type, so the purchase (or pre-auth), completion and reversals are each queried. */
|
|
233
227
|
getStatus({ paymentId }: {
|
|
234
228
|
paymentId: string;
|
|
235
229
|
}): Promise<PaymentState>;
|
|
@@ -321,4 +315,37 @@ declare class GoldenPayProvider implements PaymentProvider {
|
|
|
321
315
|
}
|
|
322
316
|
declare function goldenPay(config: GoldenPayConfig, options?: HttpOptions): GoldenPayProvider;
|
|
323
317
|
|
|
324
|
-
|
|
318
|
+
interface YigimConfig {
|
|
319
|
+
/** Merchant name issued by Yığım (`X-Merchant`). */
|
|
320
|
+
merchant: string;
|
|
321
|
+
/** API key issued by Yığım (`X-API-Key`). */
|
|
322
|
+
apiKey: string;
|
|
323
|
+
/** Biller name provided by the acquirer. */
|
|
324
|
+
biller: string;
|
|
325
|
+
/** Payment page template; default `TPL0001`. */
|
|
326
|
+
template?: string;
|
|
327
|
+
environment?: 'test' | 'production';
|
|
328
|
+
/** Override the API host. */
|
|
329
|
+
baseUrl?: string;
|
|
330
|
+
}
|
|
331
|
+
declare class YigimProvider implements PaymentProvider {
|
|
332
|
+
private readonly config;
|
|
333
|
+
readonly name = "yigim";
|
|
334
|
+
readonly capabilities: ReadonlySet<Capability>;
|
|
335
|
+
private readonly http;
|
|
336
|
+
private readonly baseUrl;
|
|
337
|
+
constructor(config: YigimConfig, options?: HttpOptions);
|
|
338
|
+
createPayment(request: CreatePaymentRequest): Promise<CreatePaymentResult>;
|
|
339
|
+
verifyCallback(callback: IncomingCallback): Promise<PaymentEvent>;
|
|
340
|
+
getStatus({ paymentId }: {
|
|
341
|
+
paymentId: string;
|
|
342
|
+
}): Promise<PaymentState>;
|
|
343
|
+
refund(request: RefundRequest): Promise<RefundResult>;
|
|
344
|
+
capture({ paymentId, amount }: PaymentAction): Promise<PaymentState>;
|
|
345
|
+
cancel({ paymentId }: PaymentAction): Promise<PaymentState>;
|
|
346
|
+
private call;
|
|
347
|
+
private error;
|
|
348
|
+
}
|
|
349
|
+
declare function yigim(config: YigimConfig, options?: HttpOptions): YigimProvider;
|
|
350
|
+
|
|
351
|
+
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, 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(
|
|
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 =
|
|
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,
|
|
19
|
-
return error instanceof ManatError && (
|
|
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
|
|
49
|
-
return
|
|
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");
|
|
@@ -323,9 +326,9 @@ function kapitalBank(config, options) {
|
|
|
323
326
|
function assertOrderId(paymentId) {
|
|
324
327
|
if (!/^\d+$/.test(paymentId)) throw new ManatError("invalid_request", `kapitalbank: paymentId must be the numeric order id, got "${paymentId}"`);
|
|
325
328
|
}
|
|
326
|
-
function toCurrency(
|
|
327
|
-
if (
|
|
328
|
-
throw new ManatError("provider_error", `kapitalbank: unexpected currency "${
|
|
329
|
+
function toCurrency(code2, paymentId) {
|
|
330
|
+
if (code2 === "AZN" || code2 === "USD" || code2 === "EUR" || code2 === "RUB" || code2 === "TRY" || code2 === "GBP") return code2;
|
|
331
|
+
throw new ManatError("provider_error", `kapitalbank: unexpected currency "${code2}" on order ${paymentId}`, { provider: "kapitalbank" });
|
|
329
332
|
}
|
|
330
333
|
function missingAmount(paymentId) {
|
|
331
334
|
throw new ManatError("provider_error", `kapitalbank: order ${paymentId} has no amount to capture`, { provider: "kapitalbank" });
|
|
@@ -345,7 +348,6 @@ function pickBody(body, key) {
|
|
|
345
348
|
import { createPrivateKey, createPublicKey, randomBytes, sign, verify } from "crypto";
|
|
346
349
|
var GATEWAY_URLS = {
|
|
347
350
|
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
351
|
production: "https://mpi.3dsecure.az/cgi-bin/cgi_link"
|
|
350
352
|
};
|
|
351
353
|
var AZERICARD_MAC_FIELDS = {
|
|
@@ -458,13 +460,7 @@ var AzericardProvider = class {
|
|
|
458
460
|
}
|
|
459
461
|
return event;
|
|
460
462
|
}
|
|
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
|
-
*/
|
|
463
|
+
/** TRTYPE 90 answers for one transaction type, so the purchase (or pre-auth), completion and reversals are each queried. */
|
|
468
464
|
async getStatus({ paymentId }) {
|
|
469
465
|
if (!/^\d{6,32}$/.test(paymentId)) throw new ManatError("invalid_request", `azericard: paymentId must be the numeric ORDER, got "${paymentId}"`);
|
|
470
466
|
let trtype = "1";
|
|
@@ -624,9 +620,9 @@ function parseAmount(amount, currency) {
|
|
|
624
620
|
const alpha = /^\d{3}$/.test(currency) ? currencyFromNumeric(currency) : currency;
|
|
625
621
|
return alpha ? fromDecimal(amount, toCurrency2(alpha)) : void 0;
|
|
626
622
|
}
|
|
627
|
-
function toCurrency2(
|
|
628
|
-
if (
|
|
629
|
-
throw new ManatError("provider_error", `azericard: unsupported currency "${
|
|
623
|
+
function toCurrency2(code2) {
|
|
624
|
+
if (code2 === "AZN" || code2 === "USD" || code2 === "EUR" || code2 === "RUB" || code2 === "TRY" || code2 === "GBP") return code2;
|
|
625
|
+
throw new ManatError("provider_error", `azericard: unsupported currency "${code2}"`, { provider: "azericard" });
|
|
630
626
|
}
|
|
631
627
|
function parseTimestamp(value) {
|
|
632
628
|
const m = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/.exec(value);
|
|
@@ -811,9 +807,9 @@ function encodeId(paymentId) {
|
|
|
811
807
|
if (!/^[A-Za-z0-9-]{1,64}$/.test(paymentId)) throw new ManatError("invalid_request", `payriff: invalid paymentId "${paymentId}"`);
|
|
812
808
|
return paymentId;
|
|
813
809
|
}
|
|
814
|
-
function toCurrency3(
|
|
815
|
-
if (
|
|
816
|
-
throw new ManatError("provider_error", `payriff: unsupported currency "${
|
|
810
|
+
function toCurrency3(code2) {
|
|
811
|
+
if (code2 === "AZN" || code2 === "USD" || code2 === "EUR" || code2 === "RUB" || code2 === "TRY" || code2 === "GBP") return code2;
|
|
812
|
+
throw new ManatError("provider_error", `payriff: unsupported currency "${code2}"`, { provider: "payriff" });
|
|
817
813
|
}
|
|
818
814
|
|
|
819
815
|
// src/crypto.ts
|
|
@@ -954,8 +950,8 @@ function epoint(config, options) {
|
|
|
954
950
|
function formFields(body) {
|
|
955
951
|
if (typeof body === "string") return Object.fromEntries(new URLSearchParams(body));
|
|
956
952
|
if (!body || typeof body !== "object") return {};
|
|
957
|
-
const
|
|
958
|
-
return { data:
|
|
953
|
+
const pick4 = (key) => typeof body[key] === "string" ? body[key] : void 0;
|
|
954
|
+
return { data: pick4("data"), signature: pick4("signature") };
|
|
959
955
|
}
|
|
960
956
|
function decodeData(data) {
|
|
961
957
|
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 +976,8 @@ var CAPABILITIES5 = /* @__PURE__ */ new Set(["createPayment", "verifyCallback",
|
|
|
980
976
|
var SUCCESS2 = 1;
|
|
981
977
|
var REVERSED = 817;
|
|
982
978
|
var MAX_DESCRIPTION_BYTES = 500;
|
|
983
|
-
function mapGoldenPayStatus(
|
|
984
|
-
switch (
|
|
979
|
+
function mapGoldenPayStatus(code2) {
|
|
980
|
+
switch (code2) {
|
|
985
981
|
case SUCCESS2:
|
|
986
982
|
return "paid";
|
|
987
983
|
case 813:
|
|
@@ -999,7 +995,7 @@ function mapGoldenPayStatus(code) {
|
|
|
999
995
|
case REVERSED:
|
|
1000
996
|
return "refunded";
|
|
1001
997
|
default:
|
|
1002
|
-
throw new ManatError("provider_error", `goldenpay: status code ${
|
|
998
|
+
throw new ManatError("provider_error", `goldenpay: status code ${code2} does not describe a payment`, { provider: "goldenpay" });
|
|
1003
999
|
}
|
|
1004
1000
|
}
|
|
1005
1001
|
var PAYMENT_CODES = /* @__PURE__ */ new Set([SUCCESS2, 813, 814, 815, 816, REVERSED, 818, 819]);
|
|
@@ -1050,10 +1046,10 @@ var GoldenPayProvider = class {
|
|
|
1050
1046
|
assertPaymentKey(paymentId);
|
|
1051
1047
|
const query = new URLSearchParams({ payment_key: paymentId, hash_code: md5(this.config.authKey + paymentId) });
|
|
1052
1048
|
const response = await this.call("GET", `getPaymentResult?${query}`);
|
|
1053
|
-
const
|
|
1054
|
-
if (
|
|
1049
|
+
const code2 = response.status?.code;
|
|
1050
|
+
if (code2 === void 0 || !PAYMENT_CODES.has(code2)) throw this.error("status is not available", response);
|
|
1055
1051
|
if (response.paymentKey && response.paymentKey !== paymentId) throw this.error(`result is for payment ${response.paymentKey}, not ${paymentId}`, response);
|
|
1056
|
-
const state = { paymentId, status: mapGoldenPayStatus(
|
|
1052
|
+
const state = { paymentId, status: mapGoldenPayStatus(code2), raw: response };
|
|
1057
1053
|
if (typeof response.amount === "number" && response.amount > 0) state.amount = { amount: response.amount, currency: "AZN" };
|
|
1058
1054
|
return state;
|
|
1059
1055
|
}
|
|
@@ -1069,8 +1065,8 @@ var GoldenPayProvider = class {
|
|
|
1069
1065
|
amount: amount.amount,
|
|
1070
1066
|
secretKey: md5(hashCode + amount.amount)
|
|
1071
1067
|
});
|
|
1072
|
-
const
|
|
1073
|
-
if (
|
|
1068
|
+
const code2 = response.status?.code;
|
|
1069
|
+
if (code2 !== REVERSED && code2 !== SUCCESS2) throw this.error("refund failed", response);
|
|
1074
1070
|
const partial = payment.amount !== void 0 && amount.amount < payment.amount.amount;
|
|
1075
1071
|
return { paymentId: request.paymentId, status: partial ? "partially_refunded" : "refunded", raw: { refund: response, payment: payment.raw } };
|
|
1076
1072
|
}
|
|
@@ -1081,11 +1077,11 @@ var GoldenPayProvider = class {
|
|
|
1081
1077
|
return data;
|
|
1082
1078
|
}
|
|
1083
1079
|
error(what, response) {
|
|
1084
|
-
const { code, message } = response.status ?? {};
|
|
1085
|
-
const fromProvider =
|
|
1080
|
+
const { code: code2, message } = response.status ?? {};
|
|
1081
|
+
const fromProvider = code2 !== void 0 && code2 !== SUCCESS2;
|
|
1086
1082
|
return new ManatError("provider_error", `goldenpay: ${fromProvider && message ? message : what}`, {
|
|
1087
1083
|
provider: this.name,
|
|
1088
|
-
...fromProvider ? { providerCode: String(
|
|
1084
|
+
...fromProvider ? { providerCode: String(code2) } : {},
|
|
1089
1085
|
...fromProvider && message ? { providerMessage: message } : {},
|
|
1090
1086
|
raw: response
|
|
1091
1087
|
});
|
|
@@ -1123,6 +1119,177 @@ function pickBody2(body, key) {
|
|
|
1123
1119
|
const value = body[key];
|
|
1124
1120
|
return typeof value === "string" ? value : void 0;
|
|
1125
1121
|
}
|
|
1122
|
+
|
|
1123
|
+
// src/providers/yigim.ts
|
|
1124
|
+
var BASE_URLS3 = {
|
|
1125
|
+
test: "https://sandbox.api.pay.yigim.az",
|
|
1126
|
+
production: "https://api.pay.yigim.az"
|
|
1127
|
+
};
|
|
1128
|
+
var CAPABILITIES6 = /* @__PURE__ */ new Set(["createPayment", "verifyCallback", "getStatus", "refund", "capture", "cancel"]);
|
|
1129
|
+
var OK = 0;
|
|
1130
|
+
var DEFAULT_TEMPLATE = "TPL0001";
|
|
1131
|
+
var REFERENCE = /^[^\s\u0000-\u001f]{1,255}$/;
|
|
1132
|
+
function mapYigimStatus(status) {
|
|
1133
|
+
switch (status) {
|
|
1134
|
+
case "S0":
|
|
1135
|
+
// created, waiting for card data
|
|
1136
|
+
case "S2":
|
|
1137
|
+
// in progress
|
|
1138
|
+
case "S7":
|
|
1139
|
+
// system malfunction
|
|
1140
|
+
case "09":
|
|
1141
|
+
return "pending";
|
|
1142
|
+
case "S1":
|
|
1143
|
+
return "authorized";
|
|
1144
|
+
case "00":
|
|
1145
|
+
return "paid";
|
|
1146
|
+
case "S4":
|
|
1147
|
+
return "cancelled";
|
|
1148
|
+
case "S3":
|
|
1149
|
+
// unknown error
|
|
1150
|
+
case "01":
|
|
1151
|
+
// refer to issuer
|
|
1152
|
+
case "02":
|
|
1153
|
+
// expired card
|
|
1154
|
+
case "03":
|
|
1155
|
+
// invalid amount
|
|
1156
|
+
case "04":
|
|
1157
|
+
// inactive card
|
|
1158
|
+
case "05":
|
|
1159
|
+
// insufficient funds
|
|
1160
|
+
case "06":
|
|
1161
|
+
// suspected fraud
|
|
1162
|
+
case "07":
|
|
1163
|
+
// exceeds withdrawal limit
|
|
1164
|
+
case "08":
|
|
1165
|
+
return "failed";
|
|
1166
|
+
default:
|
|
1167
|
+
throw new ManatError("provider_error", `yigim: unknown payment status "${status}"`, { provider: "yigim" });
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
var YigimProvider = class {
|
|
1171
|
+
constructor(config, options = {}) {
|
|
1172
|
+
this.config = config;
|
|
1173
|
+
if (!config?.merchant) throw new ManatError("invalid_request", "yigim: merchant is required");
|
|
1174
|
+
if (!config.apiKey) throw new ManatError("invalid_request", "yigim: apiKey is required");
|
|
1175
|
+
if (!config.biller) throw new ManatError("invalid_request", "yigim: biller is required");
|
|
1176
|
+
this.baseUrl = (config.baseUrl ?? BASE_URLS3[config.environment ?? "production"]).replace(/\/+$/, "");
|
|
1177
|
+
this.http = new HttpClient(this.name, options);
|
|
1178
|
+
}
|
|
1179
|
+
config;
|
|
1180
|
+
name = "yigim";
|
|
1181
|
+
capabilities = CAPABILITIES6;
|
|
1182
|
+
http;
|
|
1183
|
+
baseUrl;
|
|
1184
|
+
async createPayment(request) {
|
|
1185
|
+
assertReference(request.orderId);
|
|
1186
|
+
if (/[&;]/.test(request.returnUrl)) {
|
|
1187
|
+
throw new ManatError("invalid_request", 'yigim: returnUrl may carry at most one query parameter and no ";"');
|
|
1188
|
+
}
|
|
1189
|
+
const params = {
|
|
1190
|
+
reference: request.orderId,
|
|
1191
|
+
type: request.intent === "authorize" ? "DMS" : "SMS",
|
|
1192
|
+
amount: String(request.amount.amount),
|
|
1193
|
+
currency: currencyNumericCode(request.amount.currency),
|
|
1194
|
+
biller: this.config.biller
|
|
1195
|
+
};
|
|
1196
|
+
if (request.description) params.description = request.description;
|
|
1197
|
+
params.template = this.config.template ?? DEFAULT_TEMPLATE;
|
|
1198
|
+
params.language = request.language ?? "az";
|
|
1199
|
+
if (request.callbackUrl) params.callback = request.callbackUrl.includes("?") ? request.callbackUrl : `${request.callbackUrl}?`;
|
|
1200
|
+
params.extra = `back-url=${request.returnUrl};fail-url=${request.returnUrl}`;
|
|
1201
|
+
const reply = await this.call("payment/create", params);
|
|
1202
|
+
if (!reply.url) throw this.error("payment page URL is missing", reply);
|
|
1203
|
+
return { paymentId: request.orderId, status: "pending", next: { type: "redirect", url: reply.url }, raw: reply };
|
|
1204
|
+
}
|
|
1205
|
+
async verifyCallback(callback) {
|
|
1206
|
+
const reference = pick3(callback.query, "reference") ?? pickBody3(callback.body, "reference");
|
|
1207
|
+
if (!reference || !REFERENCE.test(reference)) {
|
|
1208
|
+
throw new ManatError("invalid_signature", "yigim: callback has no reference", { provider: this.name, raw: callback });
|
|
1209
|
+
}
|
|
1210
|
+
const state = await this.getStatus({ paymentId: reference });
|
|
1211
|
+
const event = { paymentId: state.paymentId, orderId: state.paymentId, status: state.status, raw: state.raw };
|
|
1212
|
+
if (state.amount) event.amount = state.amount;
|
|
1213
|
+
return event;
|
|
1214
|
+
}
|
|
1215
|
+
async getStatus({ paymentId }) {
|
|
1216
|
+
assertReference(paymentId);
|
|
1217
|
+
const reply = await this.call("payment/status", { reference: paymentId });
|
|
1218
|
+
if (reply.reference && reply.reference !== paymentId) throw this.error(`status is for payment ${reply.reference}, not ${paymentId}`, reply);
|
|
1219
|
+
const state = { paymentId, status: mapYigimStatus(reply.status), raw: reply };
|
|
1220
|
+
const currency = typeof reply.currency === "string" ? currencyFromNumeric(reply.currency) : void 0;
|
|
1221
|
+
if (reply.amount !== void 0 && reply.amount !== "" && currency) state.amount = fromDecimal(String(reply.amount), currency);
|
|
1222
|
+
return state;
|
|
1223
|
+
}
|
|
1224
|
+
async refund(request) {
|
|
1225
|
+
const reply = await this.call("payment/refund", withAmount(request.paymentId, request.amount));
|
|
1226
|
+
const state = await this.getStatus({ paymentId: request.paymentId });
|
|
1227
|
+
return { paymentId: request.paymentId, status: refundStatus(state, request.amount), raw: { refund: reply, payment: state.raw } };
|
|
1228
|
+
}
|
|
1229
|
+
async capture({ paymentId, amount }) {
|
|
1230
|
+
await this.call("payment/charge", withAmount(paymentId, amount));
|
|
1231
|
+
return this.getStatus({ paymentId });
|
|
1232
|
+
}
|
|
1233
|
+
async cancel({ paymentId }) {
|
|
1234
|
+
await this.call("payment/cancel", { reference: paymentId });
|
|
1235
|
+
return this.getStatus({ paymentId });
|
|
1236
|
+
}
|
|
1237
|
+
async call(command, params) {
|
|
1238
|
+
assertReference(params.reference ?? "");
|
|
1239
|
+
const { status, data } = await this.http.json({
|
|
1240
|
+
method: "GET",
|
|
1241
|
+
url: `${this.baseUrl}/${command}?${new URLSearchParams(params)}`,
|
|
1242
|
+
headers: { "x-merchant": this.config.merchant, "x-api-key": this.config.apiKey, "x-type": "JSON" }
|
|
1243
|
+
});
|
|
1244
|
+
const reply = unwrap(data);
|
|
1245
|
+
if (!reply || code(reply) === void 0) {
|
|
1246
|
+
throw new ManatError("provider_error", `yigim: unexpected response (HTTP ${status})`, { provider: this.name, raw: data });
|
|
1247
|
+
}
|
|
1248
|
+
if (status >= 400 || code(reply) !== OK) throw this.error(`HTTP ${status}`, reply);
|
|
1249
|
+
return reply;
|
|
1250
|
+
}
|
|
1251
|
+
error(what, reply) {
|
|
1252
|
+
const fromProvider = code(reply) !== void 0 && code(reply) !== OK;
|
|
1253
|
+
return new ManatError("provider_error", `yigim: ${fromProvider && reply.message ? reply.message : what}`, {
|
|
1254
|
+
provider: this.name,
|
|
1255
|
+
...fromProvider ? { providerCode: String(reply.code) } : {},
|
|
1256
|
+
...fromProvider && reply.message ? { providerMessage: reply.message } : {},
|
|
1257
|
+
raw: reply
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
function yigim(config, options) {
|
|
1262
|
+
return new YigimProvider(config, options);
|
|
1263
|
+
}
|
|
1264
|
+
function code(reply) {
|
|
1265
|
+
if (typeof reply.code === "number") return reply.code;
|
|
1266
|
+
return typeof reply.code === "string" && /^-?\d+$/.test(reply.code) ? Number(reply.code) : void 0;
|
|
1267
|
+
}
|
|
1268
|
+
function unwrap(data) {
|
|
1269
|
+
if (!data || typeof data !== "object") return void 0;
|
|
1270
|
+
const inner = data.response;
|
|
1271
|
+
return inner && typeof inner === "object" ? inner : data;
|
|
1272
|
+
}
|
|
1273
|
+
function withAmount(reference, amount) {
|
|
1274
|
+
return amount ? { reference, amount: String(amount.amount) } : { reference };
|
|
1275
|
+
}
|
|
1276
|
+
function refundStatus(state, amount) {
|
|
1277
|
+
if (state.status !== "paid") return state.status;
|
|
1278
|
+
return amount && state.amount && amount.amount < state.amount.amount ? "partially_refunded" : "refunded";
|
|
1279
|
+
}
|
|
1280
|
+
function assertReference(reference) {
|
|
1281
|
+
if (!REFERENCE.test(reference)) throw new ManatError("invalid_request", `yigim: invalid reference "${reference}"`);
|
|
1282
|
+
}
|
|
1283
|
+
function pick3(source, key) {
|
|
1284
|
+
const value = source?.[key];
|
|
1285
|
+
return Array.isArray(value) ? value[0] : value;
|
|
1286
|
+
}
|
|
1287
|
+
function pickBody3(body, key) {
|
|
1288
|
+
if (!body) return void 0;
|
|
1289
|
+
if (typeof body === "string") return new URLSearchParams(body).get(key) ?? void 0;
|
|
1290
|
+
const value = body[key];
|
|
1291
|
+
return typeof value === "string" ? value : void 0;
|
|
1292
|
+
}
|
|
1126
1293
|
export {
|
|
1127
1294
|
AZERICARD_MAC_FIELDS,
|
|
1128
1295
|
AzericardProvider,
|
|
@@ -1132,6 +1299,7 @@ export {
|
|
|
1132
1299
|
Manat,
|
|
1133
1300
|
ManatError,
|
|
1134
1301
|
PayriffProvider,
|
|
1302
|
+
YigimProvider,
|
|
1135
1303
|
azericard,
|
|
1136
1304
|
azn,
|
|
1137
1305
|
epoint,
|
|
@@ -1140,5 +1308,6 @@ export {
|
|
|
1140
1308
|
isManatError,
|
|
1141
1309
|
kapitalBank,
|
|
1142
1310
|
payriff,
|
|
1143
|
-
toDecimalString
|
|
1311
|
+
toDecimalString,
|
|
1312
|
+
yigim
|
|
1144
1313
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keytd/manat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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": [
|
|
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": [
|
|
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",
|