@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 +4 -4
- package/dist/index.cjs +216 -39
- package/dist/index.d.cts +38 -9
- package/dist/index.d.ts +38 -9
- package/dist/index.js +212 -38
- 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,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(
|
|
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 =
|
|
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,
|
|
61
|
-
return error instanceof ManatError && (
|
|
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
|
|
91
|
-
return
|
|
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(
|
|
369
|
-
if (
|
|
370
|
-
throw new ManatError("provider_error", `kapitalbank: unexpected currency "${
|
|
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(
|
|
670
|
-
if (
|
|
671
|
-
throw new ManatError("provider_error", `azericard: unsupported currency "${
|
|
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(
|
|
857
|
-
if (
|
|
858
|
-
throw new ManatError("provider_error", `payriff: unsupported currency "${
|
|
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
|
|
1000
|
-
return { data:
|
|
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(
|
|
1026
|
-
switch (
|
|
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 ${
|
|
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
|
|
1096
|
-
if (
|
|
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(
|
|
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
|
|
1115
|
-
if (
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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");
|
|
@@ -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(
|
|
327
|
-
if (
|
|
328
|
-
throw new ManatError("provider_error", `kapitalbank: unexpected currency "${
|
|
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(
|
|
628
|
-
if (
|
|
629
|
-
throw new ManatError("provider_error", `azericard: unsupported currency "${
|
|
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(
|
|
815
|
-
if (
|
|
816
|
-
throw new ManatError("provider_error", `payriff: unsupported currency "${
|
|
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
|
|
958
|
-
return { data:
|
|
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(
|
|
984
|
-
switch (
|
|
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 ${
|
|
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
|
|
1054
|
-
if (
|
|
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(
|
|
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
|
|
1073
|
-
if (
|
|
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 =
|
|
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(
|
|
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
|
|
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": [
|
|
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",
|