simplepay-js-sdk 0.6.1 → 0.7.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 +81 -3
- package/dist/index.d.ts +47 -19
- package/dist/index.js +127 -69
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +8 -157
- package/src/oneTime.spec.ts +97 -0
- package/src/oneTime.ts +33 -0
- package/src/recurring.spec.ts +71 -0
- package/src/recurring.ts +77 -0
- package/src/types.ts +57 -12
- package/src/{index.spec.ts → utils.spec.ts} +6 -78
- package/src/utils.ts +142 -0
package/README.md
CHANGED
|
@@ -33,7 +33,9 @@ Set the following environment variables in your `.env` file:
|
|
|
33
33
|
|
|
34
34
|
You should create 3 endpoints, to start the payment, get the payment response and handle the IPN.
|
|
35
35
|
|
|
36
|
-
###
|
|
36
|
+
### One Time Payment
|
|
37
|
+
|
|
38
|
+
#### Start Payment Endpoint
|
|
37
39
|
|
|
38
40
|
```typescript
|
|
39
41
|
import { startPayment } from 'simplepay-js-sdk'
|
|
@@ -64,7 +66,7 @@ try {
|
|
|
64
66
|
|
|
65
67
|
`response.paymentUrl` will contain the Simplepay payment URL, which you can redirect the customer to.
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
#### Get Payment Response Endpoint
|
|
68
70
|
|
|
69
71
|
When the customer returns from the Simplepay payment page, you need to get the payment response at your `SIMPLEPAY_REDIRECT_URL`. The url will contain 2 parameters: `r` and `s`.
|
|
70
72
|
|
|
@@ -84,7 +86,7 @@ const response = getPaymentResponse(r, s)
|
|
|
84
86
|
- `merchantId`: the merchant id
|
|
85
87
|
- `orderRef`: the order id
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
#### IPN Endpoint
|
|
88
90
|
|
|
89
91
|
Simplepay will send a `POST` request to the IPN url and you should send a response back.
|
|
90
92
|
At this endpoint you should
|
|
@@ -94,6 +96,82 @@ At this endpoint you should
|
|
|
94
96
|
- calculate the new signature - use `generateSignature(responseText, SIMPLEPAY_MERCHANT_KEY_HUF)`
|
|
95
97
|
- send the `response` with the new `signature`
|
|
96
98
|
|
|
99
|
+
|
|
100
|
+
### Recurring Payment
|
|
101
|
+
|
|
102
|
+
#### Start Recurring Payment Endpoint
|
|
103
|
+
|
|
104
|
+
Here you have to use the `startRecurringPayment()` function what works the same way as the `startPayment()` function. The only difference is that you have to pass 2 additional properties: `customer` and `recurring`.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
try {
|
|
108
|
+
const response = await startRecurringPayment({
|
|
109
|
+
// ... other preoperties
|
|
110
|
+
customer: 'Radharadhya Dasa',
|
|
111
|
+
recurring: {
|
|
112
|
+
times: 3, // how many times the payment will be made, number of tokens
|
|
113
|
+
until: '2025-12-31T18:00:00+02:00', // the end date of the recurring payment - use the toISO8601DateString() helper function
|
|
114
|
+
maxAmount: 100000 // the maximum amount of the recurring payment
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The response will have an additional `tokens` property, what will contain the tokens of the registered cards.
|
|
121
|
+
You are responsible to save the tokens to your database, so you can use them later to make a payment.
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
#### Get Recurring Payment Response Endpoint
|
|
125
|
+
|
|
126
|
+
Use the same enpoint as the one time payment.
|
|
127
|
+
|
|
128
|
+
#### IPN Endpoint on card registration
|
|
129
|
+
|
|
130
|
+
It works the same as the `IPN` endpoint of the one time payment.
|
|
131
|
+
The response will have the same properties, and 2 additional properties:
|
|
132
|
+
|
|
133
|
+
- `cardMask`: xxxx-xxxx-xxxx-1234 - the masked card number what is registered
|
|
134
|
+
- `expiry`: 2025-01-31T00:00:00+02:00 - the expiry date of the registered card
|
|
135
|
+
|
|
136
|
+
#### Token Payment Endpoint
|
|
137
|
+
|
|
138
|
+
After a card is registered you can use the tokens to make a payment without any user intercation for example by a daily `cron`
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { startTokenPayment } from 'simplepay-js-sdk'
|
|
142
|
+
|
|
143
|
+
// TODO: get payment data from your database, where you saved the tokens
|
|
144
|
+
|
|
145
|
+
const payment = {
|
|
146
|
+
token: '1234567890123456',
|
|
147
|
+
total: 1212,
|
|
148
|
+
currency: 'HUF' as Currency,
|
|
149
|
+
customer: 'Radharadhya Dasa',
|
|
150
|
+
customerEmail: 'rrd@webmania.cc',
|
|
151
|
+
invoice: {
|
|
152
|
+
name: 'Radharadhya Dasa',
|
|
153
|
+
country: 'HU',
|
|
154
|
+
state: 'Budapest',
|
|
155
|
+
city: 'Budapest',
|
|
156
|
+
zip: '1234',
|
|
157
|
+
address: 'Sehol u. 0',
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const response = await startTokenPayment({
|
|
163
|
+
orderRef: Date.now().toString(),
|
|
164
|
+
language: 'HU',
|
|
165
|
+
method: 'CARD', // must be CARD
|
|
166
|
+
...payment,
|
|
167
|
+
})
|
|
168
|
+
return response
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Token payment initiation failed:', error)
|
|
171
|
+
return error
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
97
175
|
## License
|
|
98
176
|
|
|
99
177
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -2,28 +2,15 @@ export declare const checkSignature: (responseText: string, signature: string, m
|
|
|
2
2
|
|
|
3
3
|
declare const CURRENCIES: readonly ["HUF", "EUR", "USD"];
|
|
4
4
|
|
|
5
|
-
declare type Currency = typeof CURRENCIES[number];
|
|
5
|
+
export declare type Currency = typeof CURRENCIES[number];
|
|
6
6
|
|
|
7
7
|
export declare const generateSignature: (body: string, merchantKey: string) => string;
|
|
8
8
|
|
|
9
|
-
export declare const
|
|
9
|
+
export declare const getPaymentResponse: (r: string, signature: string) => SimplePayResult;
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
responseCode: number;
|
|
13
|
-
transactionId: string;
|
|
14
|
-
event: "success" | "fail" | "timeout" | "cancel";
|
|
15
|
-
merchantId: string;
|
|
16
|
-
orderRef: string;
|
|
17
|
-
};
|
|
11
|
+
declare type ISO8601DateString = string;
|
|
18
12
|
|
|
19
|
-
export declare
|
|
20
|
-
MERCHANT_KEY: string | undefined;
|
|
21
|
-
MERCHANT_ID: string | undefined;
|
|
22
|
-
API_URL: string;
|
|
23
|
-
SDK_VERSION: string;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
declare type Language = typeof LANGUAGES[number];
|
|
13
|
+
export declare type Language = typeof LANGUAGES[number];
|
|
27
14
|
|
|
28
15
|
declare const LANGUAGES: readonly ["AR", "BG", "CS", "DE", "EN", "ES", "FR", "IT", "HR", "HU", "PL", "RO", "RU", "SK", "TR", "ZH"];
|
|
29
16
|
|
|
@@ -46,7 +33,24 @@ declare interface PaymentData {
|
|
|
46
33
|
};
|
|
47
34
|
}
|
|
48
35
|
|
|
49
|
-
declare type PaymentMethod = 'CARD' | 'WIRE';
|
|
36
|
+
export declare type PaymentMethod = 'CARD' | 'WIRE';
|
|
37
|
+
|
|
38
|
+
declare interface Recurring {
|
|
39
|
+
times: number;
|
|
40
|
+
until: ISO8601DateString;
|
|
41
|
+
maxAmount: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
declare interface RecurringPaymentData extends PaymentData {
|
|
45
|
+
customer: string;
|
|
46
|
+
recurring: Recurring;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
declare type SimplePayEvents = 'SUCCESS' | 'FAIL' | 'TIMEOUT' | 'CANCEL';
|
|
50
|
+
|
|
51
|
+
declare interface SimplePayRecurringResponse extends SimplePayResponse {
|
|
52
|
+
tokens: string[];
|
|
53
|
+
}
|
|
50
54
|
|
|
51
55
|
declare interface SimplePayResponse {
|
|
52
56
|
salt: string;
|
|
@@ -54,12 +58,36 @@ declare interface SimplePayResponse {
|
|
|
54
58
|
orderRef: string;
|
|
55
59
|
currency: Currency;
|
|
56
60
|
transactionId: string;
|
|
57
|
-
timeout:
|
|
61
|
+
timeout: ISO8601DateString;
|
|
58
62
|
total: string;
|
|
59
63
|
paymentUrl: string;
|
|
60
64
|
errorCodes?: string[];
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
declare interface SimplePayResult {
|
|
68
|
+
responseCode: number;
|
|
69
|
+
transactionId: string;
|
|
70
|
+
event: SimplePayEvents;
|
|
71
|
+
merchantId: string;
|
|
72
|
+
orderRef: string;
|
|
73
|
+
tokens?: string[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
declare interface SimplePayTokenResponse extends Omit<SimplePayResponse, 'paymentUrl' | 'timeout'> {
|
|
77
|
+
}
|
|
78
|
+
|
|
63
79
|
export declare const startPayment: (paymentData: PaymentData) => Promise<SimplePayResponse>;
|
|
64
80
|
|
|
81
|
+
export declare const startRecurringPayment: (paymentData: RecurringPaymentData) => Promise<SimplePayRecurringResponse>;
|
|
82
|
+
|
|
83
|
+
export declare const startTokenPayment: (paymentData: TokenPaymentData) => Promise<SimplePayTokenResponse>;
|
|
84
|
+
|
|
85
|
+
export declare const toISO8601DateString: (date: Date) => ISO8601DateString;
|
|
86
|
+
|
|
87
|
+
declare interface TokenPaymentData extends Omit<PaymentData, 'method'> {
|
|
88
|
+
method: 'CARD';
|
|
89
|
+
customer: string;
|
|
90
|
+
token: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
65
93
|
export { }
|
package/dist/index.js
CHANGED
|
@@ -1,89 +1,147 @@
|
|
|
1
|
-
import
|
|
2
|
-
const
|
|
3
|
-
process.env.SIMPLEPAY_LOGGER === "true" && console.log(...e);
|
|
4
|
-
},
|
|
5
|
-
|
|
6
|
-
return r.update(e, "utf8"), r.digest("base64");
|
|
7
|
-
}, _ = (e, t, r) => t === R(e, r), I = (e) => JSON.stringify(e).replace(/\//g, "\\/"), g = (e) => {
|
|
8
|
-
if (!h.includes(e))
|
|
1
|
+
import S from "crypto";
|
|
2
|
+
const d = ["HUF", "EUR", "USD"], i = (...e) => {
|
|
3
|
+
process.env.SIMPLEPAY_LOGGER === "true" && console.log("👉 ", ...e);
|
|
4
|
+
}, m = (e) => {
|
|
5
|
+
if (!d.includes(e))
|
|
9
6
|
throw new Error(`Unsupported currency: ${e}`);
|
|
10
|
-
const
|
|
7
|
+
const r = "https://secure.simplepay.hu/payment/v2", t = "https://sandbox.simplepay.hu/payment/v2/start", n = "SimplePayV2.1_Rrd_0.6.1", s = process.env[`SIMPLEPAY_MERCHANT_KEY_${e}`], c = process.env[`SIMPLEPAY_MERCHANT_ID_${e}`], o = process.env.SIMPLEPAY_PRODUCTION === "true" ? r : t, u = process.env.SIMPLEPAY_PRODUCTION === "true" ? r : "https://sandbox.simplepay.hu/payment/v2/dorecurring";
|
|
11
8
|
return {
|
|
12
9
|
MERCHANT_KEY: s,
|
|
13
10
|
MERCHANT_ID: c,
|
|
14
|
-
API_URL:
|
|
15
|
-
|
|
11
|
+
API_URL: o,
|
|
12
|
+
API_URL_RECURRING: u,
|
|
13
|
+
SDK_VERSION: n
|
|
16
14
|
};
|
|
17
|
-
},
|
|
18
|
-
const t =
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
timeout: new Date(Date.now() + 30 * 60 * 1e3).toISOString().replace(/\.\d{3}Z$/, "+00:00"),
|
|
32
|
-
url: process.env.SIMPLEPAY_REDIRECT_URL || "http://url.to.redirect",
|
|
33
|
-
invoice: e.invoice
|
|
34
|
-
}, p = I(n), u = R(p, r);
|
|
35
|
-
a({ bodyString: p, signature: u });
|
|
15
|
+
}, p = (e) => JSON.stringify(e).replace(/\//g, "\\/"), l = (e, r) => {
|
|
16
|
+
const t = S.createHmac("sha384", r.trim());
|
|
17
|
+
return t.update(e, "utf8"), t.digest("base64");
|
|
18
|
+
}, g = (e, r, t) => r === l(e, t), R = (e) => e.toISOString().replace(/\.\d{3}Z$/, "+00:00"), _ = (e) => {
|
|
19
|
+
var t, n;
|
|
20
|
+
const r = (n = (t = Object.entries(process.env).find(
|
|
21
|
+
([s, c]) => s.startsWith("SIMPLEPAY_MERCHANT_ID_") && c === e
|
|
22
|
+
)) == null ? void 0 : t[0]) == null ? void 0 : n.replace("SIMPLEPAY_MERCHANT_ID_", "");
|
|
23
|
+
if (!r)
|
|
24
|
+
throw new Error(`Merchant id not found in the environment: ${e}`);
|
|
25
|
+
return r;
|
|
26
|
+
}, I = async (e, r, t) => E(e, r, t, "oneTime"), f = async (e, r, t) => E(e, r, t, "recurring"), h = async (e, r, t) => E(e, r, t, "token"), E = async (e, r, t, n) => {
|
|
27
|
+
const s = p(r), c = l(s, t);
|
|
28
|
+
i({ function: `SimplePay/makeRequest/${n}`, bodyString: s, signature: c });
|
|
36
29
|
try {
|
|
37
|
-
const
|
|
30
|
+
const o = await fetch(e, {
|
|
38
31
|
method: "POST",
|
|
39
32
|
headers: {
|
|
40
33
|
"Content-Type": "application/json",
|
|
41
|
-
Signature:
|
|
34
|
+
Signature: c
|
|
42
35
|
},
|
|
43
|
-
body:
|
|
36
|
+
body: s
|
|
44
37
|
});
|
|
45
|
-
if (
|
|
46
|
-
throw new Error(`SimplePay API error: ${
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
38
|
+
if (i({ function: `SimplePay/makeRequest/${n}`, response: o }), !o.ok)
|
|
39
|
+
throw new Error(`SimplePay API error: ${o.status}`);
|
|
40
|
+
const u = o.headers.get("Signature");
|
|
41
|
+
if (i({ function: `SimplePay/makeRequest/${n}`, responseSignature: u }), !u)
|
|
49
42
|
throw new Error("Missing response signature");
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
52
|
-
throw new Error(`SimplePay API error: ${
|
|
53
|
-
if (!
|
|
43
|
+
const P = await o.text(), a = JSON.parse(P);
|
|
44
|
+
if (i({ function: `SimplePay/makeRequest/${n}`, responseText: P, responseJSON: a }), a.errorCodes)
|
|
45
|
+
throw new Error(`SimplePay API error: ${a.errorCodes}`);
|
|
46
|
+
if (!g(P, u, t))
|
|
54
47
|
throw new Error("Invalid response signature");
|
|
55
|
-
return
|
|
56
|
-
} catch (
|
|
57
|
-
throw
|
|
48
|
+
return a;
|
|
49
|
+
} catch (o) {
|
|
50
|
+
throw o;
|
|
58
51
|
}
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
const t = (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
throw new Error(`Merchant id not found in the environment: ${e}`);
|
|
66
|
-
return t;
|
|
67
|
-
}, f = (e, t) => {
|
|
68
|
-
t = decodeURIComponent(t);
|
|
69
|
-
const r = Buffer.from(e, "base64").toString("utf-8"), o = JSON.parse(r), s = m(o.m), { MERCHANT_KEY: c } = g(s);
|
|
70
|
-
if (!_(r, t, c || ""))
|
|
71
|
-
throw a({ rDecoded: r, signature: t }), new Error("Invalid response signature");
|
|
72
|
-
const n = JSON.parse(r);
|
|
52
|
+
}, C = (e, r) => {
|
|
53
|
+
i({ function: "SimplePay/getPaymentResponse", r: e, signature: r }), r = decodeURIComponent(r);
|
|
54
|
+
const t = Buffer.from(e, "base64").toString("utf-8"), n = JSON.parse(t), s = _(n.m), { MERCHANT_KEY: c } = m(s);
|
|
55
|
+
if (!g(t, r, c || ""))
|
|
56
|
+
throw i({ function: "SimplePay/getPaymentResponse", rDecoded: t, signature: r }), new Error("Invalid response signature");
|
|
57
|
+
const o = JSON.parse(t);
|
|
73
58
|
return {
|
|
74
|
-
responseCode:
|
|
75
|
-
transactionId:
|
|
76
|
-
event:
|
|
77
|
-
merchantId:
|
|
78
|
-
orderRef:
|
|
59
|
+
responseCode: o.r,
|
|
60
|
+
transactionId: o.t,
|
|
61
|
+
event: o.e,
|
|
62
|
+
merchantId: o.m,
|
|
63
|
+
orderRef: o.o,
|
|
64
|
+
tokens: o.tokens
|
|
65
|
+
};
|
|
66
|
+
}, w = async (e) => {
|
|
67
|
+
i({ function: "SimplePay/startPayment", paymentData: e });
|
|
68
|
+
const r = e.currency || "HUF", { MERCHANT_KEY: t, MERCHANT_ID: n, API_URL: s, SDK_VERSION: c } = m(r);
|
|
69
|
+
if (i({ function: "SimplePay/startPayment", MERCHANT_KEY: t, MERCHANT_ID: n, API_URL: s }), !t || !n)
|
|
70
|
+
throw new Error(`Missing SimplePay configuration for ${r}`);
|
|
71
|
+
const o = {
|
|
72
|
+
salt: S.randomBytes(16).toString("hex"),
|
|
73
|
+
merchant: n,
|
|
74
|
+
orderRef: e.orderRef,
|
|
75
|
+
currency: r,
|
|
76
|
+
customerEmail: e.customerEmail,
|
|
77
|
+
language: e.language || "HU",
|
|
78
|
+
sdkVersion: c,
|
|
79
|
+
methods: [e.method || "CARD"],
|
|
80
|
+
total: String(e.total),
|
|
81
|
+
timeout: R(new Date(Date.now() + 30 * 60 * 1e3)),
|
|
82
|
+
url: process.env.SIMPLEPAY_REDIRECT_URL || "http://url.to.redirect",
|
|
83
|
+
invoice: e.invoice
|
|
84
|
+
};
|
|
85
|
+
return I(s, o, t);
|
|
86
|
+
}, A = 6, y = new Date(Date.now() + A * 30 * 24 * 60 * 60 * 1e3), M = 12e3, T = 3, U = async (e) => {
|
|
87
|
+
i({ function: "SimplePay/startRecurringPayment", paymentData: e });
|
|
88
|
+
const r = e.currency || "HUF", { MERCHANT_KEY: t, MERCHANT_ID: n, API_URL: s, SDK_VERSION: c } = m(r);
|
|
89
|
+
if (i({ function: "SimplePay/startRecurringPayment", MERCHANT_KEY: t, MERCHANT_ID: n, API_URL: s }), !t || !n)
|
|
90
|
+
throw new Error(`Missing SimplePay configuration for ${r}`);
|
|
91
|
+
const o = {
|
|
92
|
+
salt: S.randomBytes(16).toString("hex"),
|
|
93
|
+
merchant: n,
|
|
94
|
+
orderRef: e.orderRef,
|
|
95
|
+
currency: r,
|
|
96
|
+
customer: e.customer,
|
|
97
|
+
customerEmail: e.customerEmail,
|
|
98
|
+
language: e.language || "HU",
|
|
99
|
+
sdkVersion: c,
|
|
100
|
+
methods: ["CARD"],
|
|
101
|
+
recurring: {
|
|
102
|
+
times: e.recurring.times || T,
|
|
103
|
+
until: e.recurring.until || R(y),
|
|
104
|
+
maxAmount: e.recurring.maxAmount || M
|
|
105
|
+
},
|
|
106
|
+
threeDSReqAuthMethod: "02",
|
|
107
|
+
total: String(e.total),
|
|
108
|
+
timeout: R(new Date(Date.now() + 30 * 60 * 1e3)),
|
|
109
|
+
url: process.env.SIMPLEPAY_REDIRECT_URL || "http://url.to.redirect",
|
|
110
|
+
invoice: e.invoice
|
|
111
|
+
};
|
|
112
|
+
return f(s, o, t);
|
|
113
|
+
}, L = async (e) => {
|
|
114
|
+
i({ function: "SimplePay/startTokenPayment", paymentData: e });
|
|
115
|
+
const r = e.currency || "HUF", { MERCHANT_KEY: t, MERCHANT_ID: n, API_URL_RECURRING: s, SDK_VERSION: c } = m(r);
|
|
116
|
+
if (i({ function: "SimplePay/startTokenPayment", MERCHANT_KEY: t, MERCHANT_ID: n, API_URL_RECURRING: s }), !t || !n)
|
|
117
|
+
throw new Error(`Missing SimplePay configuration for ${r}`);
|
|
118
|
+
const o = {
|
|
119
|
+
salt: S.randomBytes(16).toString("hex"),
|
|
120
|
+
merchant: n,
|
|
121
|
+
orderRef: e.orderRef,
|
|
122
|
+
currency: r,
|
|
123
|
+
customer: e.customer,
|
|
124
|
+
customerEmail: e.customerEmail,
|
|
125
|
+
language: e.language || "HU",
|
|
126
|
+
sdkVersion: c,
|
|
127
|
+
methods: ["CARD"],
|
|
128
|
+
token: e.token,
|
|
129
|
+
type: "MIT",
|
|
130
|
+
threeDSReqAuthMethod: "02",
|
|
131
|
+
total: String(e.total),
|
|
132
|
+
timeout: R(new Date(Date.now() + 30 * 60 * 1e3)),
|
|
133
|
+
url: process.env.SIMPLEPAY_REDIRECT_URL || "http://recurring.url.to.redirect",
|
|
134
|
+
invoice: e.invoice
|
|
79
135
|
};
|
|
136
|
+
return h(s, o, t);
|
|
80
137
|
};
|
|
81
138
|
export {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
139
|
+
g as checkSignature,
|
|
140
|
+
l as generateSignature,
|
|
141
|
+
C as getPaymentResponse,
|
|
142
|
+
w as startPayment,
|
|
143
|
+
U as startRecurringPayment,
|
|
144
|
+
L as startTokenPayment,
|
|
145
|
+
R as toISO8601DateString
|
|
88
146
|
};
|
|
89
147
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/types.ts","../src/index.ts"],"sourcesContent":["type PaymentMethod = 'CARD' | 'WIRE'\n\nconst CURRENCIES = ['HUF', 'EUR', 'USD'] as const\ntype Currency = typeof CURRENCIES[number]\n\nconst LANGUAGES = [\n 'AR', // Arabic\n 'BG', // Bulgarian\n 'CS', // Czech\n 'DE', // German\n 'EN', // English\n 'ES', // Spanish\n 'FR', // French\n 'IT', // Italian\n 'HR', // Croatian\n 'HU', // Hungarian\n 'PL', // Polish\n 'RO', // Romanian\n 'RU', // Russian\n 'SK', // Slovak\n 'TR', // Turkish\n 'ZH', // Chinese\n] as const\ntype Language = typeof LANGUAGES[number]\n\ninterface PaymentData {\n orderRef: string\n total: number | string\n customerEmail: string\n currency?: Currency\n language?: Language\n method?: PaymentMethod\n invoice?: {\n name: string\n country: string\n state: string\n city: string\n zip: string\n address: string\n address2?: string\n phone?: string\n }\n}\n\ninterface SimplePayRequestBody extends Omit<PaymentData, 'total'> {\n total: string\n salt: string\n merchant: string\n sdkVersion: string\n methods: PaymentMethod[]\n timeout: string\n url: string\n}\n\ninterface SimplePayResponse {\n salt: string\n merchant: string\n orderRef: string\n currency: Currency\n transactionId: string\n timeout: string\n total: string\n paymentUrl: string\n errorCodes?: string[]\n}\n\ninterface SimplepayResult {\n r: number // response code\n t: string // transaction id\n e: 'success' | 'fail' | 'timeout' | 'cancel' // event\n m: string // merchant id\n o: string // order id\n}\n\nexport { PaymentData, SimplePayRequestBody, SimplePayResponse, SimplepayResult, CURRENCIES, Currency, PaymentMethod, LANGUAGES, Language }","import crypto from 'crypto'\nimport { PaymentData, SimplePayRequestBody, SimplePayResponse, SimplepayResult, Currency, CURRENCIES } from './types'\n\n// Existing interfaces remain the same\n\nconst simplepayLogger = (...args: any[]) => {\n if (process.env.SIMPLEPAY_LOGGER !== 'true') {\n return\n }\n console.log(...args)\n}\n\nconst generateSignature = (body: string, merchantKey: string) => {\n const hmac = crypto.createHmac('sha384', merchantKey.trim())\n hmac.update(body, 'utf8')\n return hmac.digest('base64')\n}\n\nconst checkSignature = (responseText: string, signature: string, merchantKey: string) =>\n signature === generateSignature(responseText, merchantKey)\n\n// escaping slashes for the request body to prevent strange SimplePay API errors (eg Missing Signature)\nconst prepareRequestBody = (body: SimplePayRequestBody) =>\n JSON.stringify(body).replace(/\\//g, '\\\\/')\n\n\nconst getSimplePayConfig = (currency: Currency) => {\n if (!CURRENCIES.includes(currency)) {\n throw new Error(`Unsupported currency: ${currency}`)\n }\n\n const SIMPLEPAY_API_URL = 'https://secure.simplepay.hu/payment/v2'\n const SIMPLEPAY_SANDBOX_URL = 'https://sandbox.simplepay.hu/payment/v2/start'\n const SDK_VERSION = 'SimplePayV2.1_Rrd_0.6.0'\n const MERCHANT_KEY = process.env[`SIMPLEPAY_MERCHANT_KEY_${currency}`]\n const MERCHANT_ID = process.env[`SIMPLEPAY_MERCHANT_ID_${currency}`]\n const API_URL = process.env.SIMPLEPAY_PRODUCTION === 'true' ? SIMPLEPAY_API_URL : SIMPLEPAY_SANDBOX_URL\n\n return {\n MERCHANT_KEY,\n MERCHANT_ID,\n API_URL,\n SDK_VERSION\n }\n}\n\nconst startPayment = async (paymentData: PaymentData) => {\n const currency = paymentData.currency || 'HUF'\n const { MERCHANT_KEY, MERCHANT_ID, API_URL, SDK_VERSION } = getSimplePayConfig(currency)\n simplepayLogger({ MERCHANT_KEY, MERCHANT_ID, API_URL })\n\n if (!MERCHANT_KEY || !MERCHANT_ID) {\n throw new Error('Missing SimplePay configuration')\n }\n\n const requestBody: SimplePayRequestBody = {\n salt: crypto.randomBytes(16).toString('hex'),\n merchant: MERCHANT_ID,\n orderRef: paymentData.orderRef,\n currency,\n customerEmail: paymentData.customerEmail,\n language: paymentData.language || 'HU',\n sdkVersion: SDK_VERSION,\n methods: [paymentData.method || 'CARD'],\n total: String(paymentData.total),\n timeout: new Date(Date.now() + 30 * 60 * 1000)\n .toISOString()\n .replace(/\\.\\d{3}Z$/, '+00:00'),\n url: process.env.SIMPLEPAY_REDIRECT_URL || 'http://url.to.redirect',\n invoice: paymentData.invoice,\n }\n\n const bodyString = prepareRequestBody(requestBody)\n const signature = generateSignature(bodyString, MERCHANT_KEY)\n simplepayLogger({ bodyString, signature })\n\n try {\n const response = await fetch(API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Signature': signature,\n },\n body: bodyString,\n })\n\n simplepayLogger({ response })\n\n if (!response.ok) {\n throw new Error(`SimplePay API error: ${response.status}`)\n }\n\n const responseSignature = response.headers.get('Signature')\n simplepayLogger({ responseSignature })\n if (!responseSignature) {\n throw new Error('Missing response signature')\n }\n\n const responseText = await response.text()\n const responseJSON = JSON.parse(responseText) as SimplePayResponse\n simplepayLogger({ responseText, responseJSON })\n\n if (responseJSON.errorCodes) {\n throw new Error(`SimplePay API error: ${responseJSON.errorCodes}`)\n }\n\n if (!checkSignature(responseText, responseSignature, MERCHANT_KEY)) {\n throw new Error('Invalid response signature')\n }\n\n return responseJSON\n\n } catch (error) {\n throw error\n }\n}\n\nconst getCurrencyFromMerchantId = (merchantId: string) => {\n const currency = Object.entries(process.env)\n .find(([key, value]) =>\n key.startsWith('SIMPLEPAY_MERCHANT_ID_') && value === merchantId\n )?.[0]?.replace('SIMPLEPAY_MERCHANT_ID_', '') as Currency\n if (!currency) {\n throw new Error(`Merchant id not found in the environment: ${merchantId}`)\n }\n return currency\n}\n\nconst getPaymentResponse = (r: string, signature: string) => {\n signature = decodeURIComponent(signature)\n const rDecoded = Buffer.from(r, 'base64').toString('utf-8')\n const rDecodedJSON = JSON.parse(rDecoded)\n const currency = getCurrencyFromMerchantId(rDecodedJSON.m)\n const { MERCHANT_KEY } = getSimplePayConfig(currency as Currency)\n\n if (!checkSignature(rDecoded, signature, MERCHANT_KEY || '')) {\n simplepayLogger({ rDecoded, signature })\n throw new Error('Invalid response signature')\n }\n\n const responseJson: SimplepayResult = JSON.parse(rDecoded)\n const response = {\n responseCode: responseJson.r,\n transactionId: responseJson.t,\n event: responseJson.e,\n merchantId: responseJson.m,\n orderRef: responseJson.o,\n }\n\n return response\n}\n\nexport {\n startPayment,\n getPaymentResponse,\n getSimplePayConfig,\n generateSignature,\n checkSignature,\n getCurrencyFromMerchantId\n}"],"names":["CURRENCIES","simplepayLogger","args","generateSignature","body","merchantKey","hmac","crypto","checkSignature","responseText","signature","prepareRequestBody","getSimplePayConfig","currency","SIMPLEPAY_API_URL","SIMPLEPAY_SANDBOX_URL","SDK_VERSION","MERCHANT_KEY","MERCHANT_ID","API_URL","startPayment","paymentData","requestBody","bodyString","response","responseSignature","responseJSON","error","getCurrencyFromMerchantId","merchantId","_b","_a","key","value","getPaymentResponse","r","rDecoded","rDecodedJSON","responseJson"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/types.ts","../src/utils.ts","../src/oneTime.ts","../src/recurring.ts"],"sourcesContent":["export type PaymentMethod = 'CARD' | 'WIRE'\n\nexport const CURRENCIES = ['HUF', 'EUR', 'USD'] as const\nexport type Currency = typeof CURRENCIES[number]\n\nexport const LANGUAGES = [\n 'AR', // Arabic\n 'BG', // Bulgarian\n 'CS', // Czech\n 'DE', // German\n 'EN', // English\n 'ES', // Spanish\n 'FR', // French\n 'IT', // Italian\n 'HR', // Croatian\n 'HU', // Hungarian\n 'PL', // Polish\n 'RO', // Romanian\n 'RU', // Russian\n 'SK', // Slovak\n 'TR', // Turkish\n 'ZH', // Chinese\n] as const\nexport type Language = typeof LANGUAGES[number]\n\nexport interface PaymentData {\n orderRef: string\n total: number | string\n customerEmail: string\n currency?: Currency\n language?: Language\n method?: PaymentMethod\n invoice?: {\n name: string\n country: string\n state: string\n city: string\n zip: string\n address: string\n address2?: string\n phone?: string\n }\n}\n\nexport type ISO8601DateString = string\nexport interface Recurring {\n times: number,\n until: ISO8601DateString,\n maxAmount: number\n}\nexport interface RecurringPaymentData extends PaymentData {\n customer: string,\n recurring: Recurring\n}\n\nexport interface TokenPaymentData extends Omit<PaymentData, 'method'> {\n method: 'CARD',\n customer: string,\n token: string\n}\n\nexport interface SimplePayRequestBody extends Omit<PaymentData, 'total'> {\n total: string\n salt: string\n merchant: string\n sdkVersion: string\n methods: PaymentMethod[]\n timeout: string\n url: string\n}\n\nexport interface SimplePayRecurringRequestBody extends SimplePayRequestBody {\n customer: string\n recurring: Recurring\n threeDSReqAuthMethod: '02' // only registered users can use this\n}\n\nexport interface SimplePayTokenRequestBody extends SimplePayRequestBody {\n customer: string\n token: string\n threeDSReqAuthMethod: '02' // only registered users can use this\n type: 'MIT' // Merchant Initiated Transaction\n}\n\nexport interface SimplePayResponse {\n salt: string\n merchant: string\n orderRef: string\n currency: Currency\n transactionId: string\n timeout: ISO8601DateString\n total: string\n paymentUrl: string\n errorCodes?: string[]\n}\n\nexport interface SimplePayRecurringResponse extends SimplePayResponse {\n tokens: string[]\n}\n\nexport interface SimplePayTokenResponse extends Omit<SimplePayResponse, 'paymentUrl' | 'timeout'> { }\n\nexport type SimplePayEvents = 'SUCCESS' | 'FAIL' | 'TIMEOUT' | 'CANCEL'\n\nexport interface SimplePayAPIResult {\n r: number // response code\n t: string // transaction id\n e: SimplePayEvents // event\n m: string // merchant id\n o: string // order id\n}\n\nexport interface SimplePayResult {\n responseCode: number,\n transactionId: string,\n event: SimplePayEvents,\n merchantId: string,\n orderRef: string,\n tokens?: string[],\n}\n","import crypto from 'crypto'\nimport { CURRENCIES, Currency, ISO8601DateString, SimplePayAPIResult, SimplePayRecurringRequestBody, SimplePayRecurringResponse, SimplePayRequestBody, SimplePayResponse, SimplePayResult, SimplePayTokenRequestBody, SimplePayTokenResponse } from \"./types\"\n\nexport const simplepayLogger = (...args: any[]) => {\n if (process.env.SIMPLEPAY_LOGGER !== 'true') {\n return\n }\n\n console.log('👉 ', ...args)\n}\n\nexport const getSimplePayConfig = (currency: Currency) => {\n if (!CURRENCIES.includes(currency)) {\n throw new Error(`Unsupported currency: ${currency}`)\n }\n\n const SIMPLEPAY_API_URL = 'https://secure.simplepay.hu/payment/v2'\n const SIMPLEPAY_SANDBOX_URL = 'https://sandbox.simplepay.hu/payment/v2/start'\n const SDK_VERSION = 'SimplePayV2.1_Rrd_0.6.1'\n const MERCHANT_KEY = process.env[`SIMPLEPAY_MERCHANT_KEY_${currency}`]\n const MERCHANT_ID = process.env[`SIMPLEPAY_MERCHANT_ID_${currency}`]\n const API_URL = process.env.SIMPLEPAY_PRODUCTION === 'true' ? SIMPLEPAY_API_URL : SIMPLEPAY_SANDBOX_URL\n const API_URL_RECURRING = process.env.SIMPLEPAY_PRODUCTION === 'true' ? SIMPLEPAY_API_URL : 'https://sandbox.simplepay.hu/payment/v2/dorecurring'\n\n return {\n MERCHANT_KEY,\n MERCHANT_ID,\n API_URL,\n API_URL_RECURRING,\n SDK_VERSION\n }\n}\n\n// escaping slashes for the request body to prevent strange SimplePay API errors (eg Missing Signature)\nexport const prepareRequestBody = (body: any) =>\n JSON.stringify(body).replace(/\\//g, '\\\\/')\n\nexport const generateSignature = (body: string, merchantKey: string) => {\n const hmac = crypto.createHmac('sha384', merchantKey.trim())\n hmac.update(body, 'utf8')\n return hmac.digest('base64')\n}\n\nexport const checkSignature = (responseText: string, signature: string, merchantKey: string) =>\n signature === generateSignature(responseText, merchantKey)\n\nexport const toISO8601DateString = (date: Date): ISO8601DateString => date.toISOString().replace(/\\.\\d{3}Z$/, '+00:00')\n\nexport const getCurrencyFromMerchantId = (merchantId: string) => {\n const currency = Object.entries(process.env)\n .find(([key, value]) =>\n key.startsWith('SIMPLEPAY_MERCHANT_ID_') && value === merchantId\n )?.[0]?.replace('SIMPLEPAY_MERCHANT_ID_', '') as Currency\n if (!currency) {\n throw new Error(`Merchant id not found in the environment: ${merchantId}`)\n }\n return currency\n}\n\nexport const makeSimplePayRequest = async (apiUrl: string, requestBody: SimplePayRequestBody, merchantKey: string) => {\n return makeRequest(apiUrl, requestBody, merchantKey, 'oneTime') as Promise<SimplePayResponse>\n}\n\nexport const makeSimplePayRecurringRequest = async (apiUrl: string, requestBody: SimplePayRecurringRequestBody, merchantKey: string) => {\n return makeRequest(apiUrl, requestBody, merchantKey, 'recurring') as Promise<SimplePayRecurringResponse>\n}\n\nexport const makeSimplePayTokenRequest = async (apiUrl: string, requestBody: SimplePayTokenRequestBody, merchantKey: string) => {\n return makeRequest(apiUrl, requestBody, merchantKey, 'token') as Promise<SimplePayTokenResponse>\n}\n\nconst makeRequest = async (apiUrl: string, requestBody: SimplePayRequestBody | SimplePayRecurringRequestBody | SimplePayTokenRequestBody, merchantKey: string, type: 'oneTime' | 'recurring' | 'token') => {\n const bodyString = prepareRequestBody(requestBody)\n const signature = generateSignature(bodyString, merchantKey)\n simplepayLogger({ function: `SimplePay/makeRequest/${type}`, bodyString, signature })\n\n try {\n const response = await fetch(apiUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Signature': signature,\n },\n body: bodyString,\n })\n\n simplepayLogger({ function: `SimplePay/makeRequest/${type}`, response })\n\n if (!response.ok) {\n throw new Error(`SimplePay API error: ${response.status}`)\n }\n\n const responseSignature = response.headers.get('Signature')\n simplepayLogger({ function: `SimplePay/makeRequest/${type}`, responseSignature })\n if (!responseSignature) {\n throw new Error('Missing response signature')\n }\n\n const responseText = await response.text()\n const responseJSON = JSON.parse(responseText) as { errorCodes?: string[] }\n simplepayLogger({ function: `SimplePay/makeRequest/${type}`, responseText, responseJSON })\n\n if (responseJSON.errorCodes) {\n throw new Error(`SimplePay API error: ${responseJSON.errorCodes}`)\n }\n\n if (!checkSignature(responseText, responseSignature, merchantKey)) {\n throw new Error('Invalid response signature')\n }\n\n return responseJSON\n\n } catch (error) {\n throw error\n }\n}\n\nexport const getPaymentResponse = (r: string, signature: string) => {\n simplepayLogger({ function: 'SimplePay/getPaymentResponse', r, signature })\n signature = decodeURIComponent(signature)\n const rDecoded = Buffer.from(r, 'base64').toString('utf-8')\n const rDecodedJSON = JSON.parse(rDecoded) as SimplePayAPIResult\n const currency = getCurrencyFromMerchantId(rDecodedJSON.m)\n const { MERCHANT_KEY } = getSimplePayConfig(currency as Currency)\n\n if (!checkSignature(rDecoded, signature, MERCHANT_KEY || '')) {\n simplepayLogger({ function: 'SimplePay/getPaymentResponse', rDecoded, signature })\n throw new Error('Invalid response signature')\n }\n\n const responseJson = JSON.parse(rDecoded)\n const response: SimplePayResult = {\n responseCode: responseJson.r,\n transactionId: responseJson.t,\n event: responseJson.e,\n merchantId: responseJson.m,\n orderRef: responseJson.o,\n tokens: responseJson.tokens,\n }\n\n return response\n}\n","import crypto from 'crypto'\nimport { PaymentData, SimplePayRequestBody } from './types'\nimport { simplepayLogger, getSimplePayConfig, toISO8601DateString, makeSimplePayRequest } from './utils'\n\nconst startPayment = async (paymentData: PaymentData) => {\n simplepayLogger({ function: 'SimplePay/startPayment', paymentData })\n const currency = paymentData.currency || 'HUF'\n const { MERCHANT_KEY, MERCHANT_ID, API_URL, SDK_VERSION } = getSimplePayConfig(currency)\n simplepayLogger({ function: 'SimplePay/startPayment', MERCHANT_KEY, MERCHANT_ID, API_URL })\n\n if (!MERCHANT_KEY || !MERCHANT_ID) {\n throw new Error(`Missing SimplePay configuration for ${currency}`)\n }\n\n const requestBody: SimplePayRequestBody = {\n salt: crypto.randomBytes(16).toString('hex'),\n merchant: MERCHANT_ID,\n orderRef: paymentData.orderRef,\n currency,\n customerEmail: paymentData.customerEmail,\n language: paymentData.language || 'HU',\n sdkVersion: SDK_VERSION,\n methods: [paymentData.method || 'CARD'],\n total: String(paymentData.total),\n timeout: toISO8601DateString(new Date(Date.now() + 30 * 60 * 1000)),\n url: process.env.SIMPLEPAY_REDIRECT_URL || 'http://url.to.redirect',\n invoice: paymentData.invoice,\n }\n\n return makeSimplePayRequest(API_URL, requestBody, MERCHANT_KEY)\n}\n\nexport { startPayment }\n","import crypto from 'crypto'\nimport { SimplePayRecurringRequestBody, RecurringPaymentData, TokenPaymentData, SimplePayTokenRequestBody} from './types'\nimport { getSimplePayConfig, simplepayLogger, toISO8601DateString, makeSimplePayTokenRequest, makeSimplePayRecurringRequest} from './utils'\n\nconst INTERVAL_IN_MONTHS = 6\nconst DEFAULT_UNTIL = new Date(Date.now() + INTERVAL_IN_MONTHS * 30 * 24 * 60 * 60 * 1000)\nconst DEFAULT_MAX_AMOUNT = 12000\nconst DEFAULT_TIMES = 3\n\nconst startRecurringPayment = async (paymentData: RecurringPaymentData) => {\n simplepayLogger({ function: 'SimplePay/startRecurringPayment', paymentData })\n const currency = paymentData.currency || 'HUF'\n const { MERCHANT_KEY, MERCHANT_ID, API_URL, SDK_VERSION } = getSimplePayConfig(currency)\n simplepayLogger({ function: 'SimplePay/startRecurringPayment', MERCHANT_KEY, MERCHANT_ID, API_URL })\n\n if (!MERCHANT_KEY || !MERCHANT_ID) {\n throw new Error(`Missing SimplePay configuration for ${currency}`)\n }\n\n const requestBody: SimplePayRecurringRequestBody = {\n salt: crypto.randomBytes(16).toString('hex'),\n merchant: MERCHANT_ID,\n orderRef: paymentData.orderRef,\n currency,\n customer: paymentData.customer,\n customerEmail: paymentData.customerEmail,\n language: paymentData.language || 'HU',\n sdkVersion: SDK_VERSION,\n methods: ['CARD'],\n recurring: {\n times: paymentData.recurring.times || DEFAULT_TIMES,\n until: paymentData.recurring.until || toISO8601DateString(DEFAULT_UNTIL),\n maxAmount: paymentData.recurring.maxAmount || DEFAULT_MAX_AMOUNT\n },\n threeDSReqAuthMethod: '02', \n total: String(paymentData.total),\n timeout: toISO8601DateString(new Date(Date.now() + 30 * 60 * 1000)),\n url: process.env.SIMPLEPAY_REDIRECT_URL || 'http://url.to.redirect',\n invoice: paymentData.invoice,\n }\n\n return makeSimplePayRecurringRequest(API_URL, requestBody, MERCHANT_KEY)\n}\n\nconst startTokenPayment = async (paymentData: TokenPaymentData) => {\n simplepayLogger({ function: 'SimplePay/startTokenPayment', paymentData })\n const currency = paymentData.currency || 'HUF'\n const { MERCHANT_KEY, MERCHANT_ID, API_URL_RECURRING, SDK_VERSION } = getSimplePayConfig(currency)\n simplepayLogger({ function: 'SimplePay/startTokenPayment', MERCHANT_KEY, MERCHANT_ID, API_URL_RECURRING })\n\n if (!MERCHANT_KEY || !MERCHANT_ID) {\n throw new Error(`Missing SimplePay configuration for ${currency}`)\n }\n\n const requestBody: SimplePayTokenRequestBody = {\n salt: crypto.randomBytes(16).toString('hex'),\n merchant: MERCHANT_ID,\n orderRef: paymentData.orderRef,\n currency,\n customer: paymentData.customer,\n customerEmail: paymentData.customerEmail,\n language: paymentData.language || 'HU',\n sdkVersion: SDK_VERSION,\n methods: ['CARD'],\n token: paymentData.token,\n type: 'MIT',\n threeDSReqAuthMethod: '02',\n total: String(paymentData.total),\n timeout: toISO8601DateString(new Date(Date.now() + 30 * 60 * 1000)),\n url: process.env.SIMPLEPAY_REDIRECT_URL || 'http://recurring.url.to.redirect',\n invoice: paymentData.invoice,\n }\n\n return makeSimplePayTokenRequest(API_URL_RECURRING, requestBody, MERCHANT_KEY)\n}\n\nexport { startRecurringPayment, startTokenPayment }"],"names":["CURRENCIES","simplepayLogger","args","getSimplePayConfig","currency","SIMPLEPAY_API_URL","SIMPLEPAY_SANDBOX_URL","SDK_VERSION","MERCHANT_KEY","MERCHANT_ID","API_URL","API_URL_RECURRING","prepareRequestBody","body","generateSignature","merchantKey","hmac","crypto","checkSignature","responseText","signature","toISO8601DateString","date","getCurrencyFromMerchantId","merchantId","_b","_a","key","value","makeSimplePayRequest","apiUrl","requestBody","makeRequest","makeSimplePayRecurringRequest","makeSimplePayTokenRequest","type","bodyString","response","responseSignature","responseJSON","error","getPaymentResponse","r","rDecoded","rDecodedJSON","responseJson","startPayment","paymentData","INTERVAL_IN_MONTHS","DEFAULT_UNTIL","DEFAULT_MAX_AMOUNT","DEFAULT_TIMES","startRecurringPayment","startTokenPayment"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,160 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const simplepayLogger = (...args: any[]) => {
|
|
7
|
-
if (process.env.SIMPLEPAY_LOGGER !== 'true') {
|
|
8
|
-
return
|
|
9
|
-
}
|
|
10
|
-
console.log(...args)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const generateSignature = (body: string, merchantKey: string) => {
|
|
14
|
-
const hmac = crypto.createHmac('sha384', merchantKey.trim())
|
|
15
|
-
hmac.update(body, 'utf8')
|
|
16
|
-
return hmac.digest('base64')
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const checkSignature = (responseText: string, signature: string, merchantKey: string) =>
|
|
20
|
-
signature === generateSignature(responseText, merchantKey)
|
|
21
|
-
|
|
22
|
-
// escaping slashes for the request body to prevent strange SimplePay API errors (eg Missing Signature)
|
|
23
|
-
const prepareRequestBody = (body: SimplePayRequestBody) =>
|
|
24
|
-
JSON.stringify(body).replace(/\//g, '\\/')
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const getSimplePayConfig = (currency: Currency) => {
|
|
28
|
-
if (!CURRENCIES.includes(currency)) {
|
|
29
|
-
throw new Error(`Unsupported currency: ${currency}`)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const SIMPLEPAY_API_URL = 'https://secure.simplepay.hu/payment/v2'
|
|
33
|
-
const SIMPLEPAY_SANDBOX_URL = 'https://sandbox.simplepay.hu/payment/v2/start'
|
|
34
|
-
const SDK_VERSION = 'SimplePayV2.1_Rrd_0.6.1'
|
|
35
|
-
const MERCHANT_KEY = process.env[`SIMPLEPAY_MERCHANT_KEY_${currency}`]
|
|
36
|
-
const MERCHANT_ID = process.env[`SIMPLEPAY_MERCHANT_ID_${currency}`]
|
|
37
|
-
const API_URL = process.env.SIMPLEPAY_PRODUCTION === 'true' ? SIMPLEPAY_API_URL : SIMPLEPAY_SANDBOX_URL
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
MERCHANT_KEY,
|
|
41
|
-
MERCHANT_ID,
|
|
42
|
-
API_URL,
|
|
43
|
-
SDK_VERSION
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const startPayment = async (paymentData: PaymentData) => {
|
|
48
|
-
const currency = paymentData.currency || 'HUF'
|
|
49
|
-
const { MERCHANT_KEY, MERCHANT_ID, API_URL, SDK_VERSION } = getSimplePayConfig(currency)
|
|
50
|
-
simplepayLogger({ MERCHANT_KEY, MERCHANT_ID, API_URL })
|
|
51
|
-
|
|
52
|
-
if (!MERCHANT_KEY || !MERCHANT_ID) {
|
|
53
|
-
throw new Error('Missing SimplePay configuration')
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const requestBody: SimplePayRequestBody = {
|
|
57
|
-
salt: crypto.randomBytes(16).toString('hex'),
|
|
58
|
-
merchant: MERCHANT_ID,
|
|
59
|
-
orderRef: paymentData.orderRef,
|
|
60
|
-
currency,
|
|
61
|
-
customerEmail: paymentData.customerEmail,
|
|
62
|
-
language: paymentData.language || 'HU',
|
|
63
|
-
sdkVersion: SDK_VERSION,
|
|
64
|
-
methods: [paymentData.method || 'CARD'],
|
|
65
|
-
total: String(paymentData.total),
|
|
66
|
-
timeout: new Date(Date.now() + 30 * 60 * 1000)
|
|
67
|
-
.toISOString()
|
|
68
|
-
.replace(/\.\d{3}Z$/, '+00:00'),
|
|
69
|
-
url: process.env.SIMPLEPAY_REDIRECT_URL || 'http://url.to.redirect',
|
|
70
|
-
invoice: paymentData.invoice,
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const bodyString = prepareRequestBody(requestBody)
|
|
74
|
-
const signature = generateSignature(bodyString, MERCHANT_KEY)
|
|
75
|
-
simplepayLogger({ bodyString, signature })
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
const response = await fetch(API_URL, {
|
|
79
|
-
method: 'POST',
|
|
80
|
-
headers: {
|
|
81
|
-
'Content-Type': 'application/json',
|
|
82
|
-
'Signature': signature,
|
|
83
|
-
},
|
|
84
|
-
body: bodyString,
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
simplepayLogger({ response })
|
|
88
|
-
|
|
89
|
-
if (!response.ok) {
|
|
90
|
-
throw new Error(`SimplePay API error: ${response.status}`)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const responseSignature = response.headers.get('Signature')
|
|
94
|
-
simplepayLogger({ responseSignature })
|
|
95
|
-
if (!responseSignature) {
|
|
96
|
-
throw new Error('Missing response signature')
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const responseText = await response.text()
|
|
100
|
-
const responseJSON = JSON.parse(responseText) as SimplePayResponse
|
|
101
|
-
simplepayLogger({ responseText, responseJSON })
|
|
102
|
-
|
|
103
|
-
if (responseJSON.errorCodes) {
|
|
104
|
-
throw new Error(`SimplePay API error: ${responseJSON.errorCodes}`)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (!checkSignature(responseText, responseSignature, MERCHANT_KEY)) {
|
|
108
|
-
throw new Error('Invalid response signature')
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return responseJSON
|
|
112
|
-
|
|
113
|
-
} catch (error) {
|
|
114
|
-
throw error
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const getCurrencyFromMerchantId = (merchantId: string) => {
|
|
119
|
-
const currency = Object.entries(process.env)
|
|
120
|
-
.find(([key, value]) =>
|
|
121
|
-
key.startsWith('SIMPLEPAY_MERCHANT_ID_') && value === merchantId
|
|
122
|
-
)?.[0]?.replace('SIMPLEPAY_MERCHANT_ID_', '') as Currency
|
|
123
|
-
if (!currency) {
|
|
124
|
-
throw new Error(`Merchant id not found in the environment: ${merchantId}`)
|
|
125
|
-
}
|
|
126
|
-
return currency
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const getPaymentResponse = (r: string, signature: string) => {
|
|
130
|
-
signature = decodeURIComponent(signature)
|
|
131
|
-
const rDecoded = Buffer.from(r, 'base64').toString('utf-8')
|
|
132
|
-
const rDecodedJSON = JSON.parse(rDecoded)
|
|
133
|
-
const currency = getCurrencyFromMerchantId(rDecodedJSON.m)
|
|
134
|
-
const { MERCHANT_KEY } = getSimplePayConfig(currency as Currency)
|
|
135
|
-
|
|
136
|
-
if (!checkSignature(rDecoded, signature, MERCHANT_KEY || '')) {
|
|
137
|
-
simplepayLogger({ rDecoded, signature })
|
|
138
|
-
throw new Error('Invalid response signature')
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const responseJson: SimplepayResult = JSON.parse(rDecoded)
|
|
142
|
-
const response = {
|
|
143
|
-
responseCode: responseJson.r,
|
|
144
|
-
transactionId: responseJson.t,
|
|
145
|
-
event: responseJson.e,
|
|
146
|
-
merchantId: responseJson.m,
|
|
147
|
-
orderRef: responseJson.o,
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return response
|
|
151
|
-
}
|
|
1
|
+
import { startPayment } from './oneTime'
|
|
2
|
+
import { startRecurringPayment, startTokenPayment} from './recurring'
|
|
3
|
+
import type { Currency, Language, PaymentMethod } from './types'
|
|
4
|
+
import { checkSignature, generateSignature, getPaymentResponse, toISO8601DateString } from './utils'
|
|
152
5
|
|
|
153
6
|
export {
|
|
154
|
-
|
|
155
|
-
getPaymentResponse,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
checkSignature,
|
|
159
|
-
getCurrencyFromMerchantId
|
|
7
|
+
Currency, Language, PaymentMethod,
|
|
8
|
+
checkSignature, generateSignature, toISO8601DateString, getPaymentResponse,
|
|
9
|
+
startPayment,
|
|
10
|
+
startRecurringPayment, startTokenPayment,
|
|
160
11
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import { getPaymentResponse, startPayment } from './index'
|
|
3
|
+
|
|
4
|
+
const setEnv = () => {
|
|
5
|
+
process.env.SIMPLEPAY_MERCHANT_ID_HUF = 'testId'
|
|
6
|
+
process.env.SIMPLEPAY_MERCHANT_KEY_HUF = 'testKey'
|
|
7
|
+
process.env.SIMPLEPAY_MERCHANT_ID_EUR = 'merchantEuroId'
|
|
8
|
+
process.env.SIMPLEPAY_MERCHANT_KEY_EUR = 'secretEuroKey'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const paymentData = {
|
|
12
|
+
orderRef: 'TEST123',
|
|
13
|
+
customerEmail: 'test@example.com',
|
|
14
|
+
total: 1212
|
|
15
|
+
}
|
|
16
|
+
describe('SimplePay SDK Tests', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
// Clear all environment variables before each test
|
|
19
|
+
delete process.env.SIMPLEPAY_MERCHANT_ID_HUF
|
|
20
|
+
delete process.env.SIMPLEPAY_MERCHANT_KEY_HUF
|
|
21
|
+
delete process.env.SIMPLEPAY_MERCHANT_ID_EUR
|
|
22
|
+
delete process.env.SIMPLEPAY_MERCHANT_KEY_EUR
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('startPayment', () => {
|
|
26
|
+
it('should throw error when merchant configuration is missing', async () => {
|
|
27
|
+
await expect(startPayment(paymentData)).rejects.toThrow('Missing SimplePay configuration')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('should handle API errors correctly', async () => {
|
|
31
|
+
setEnv()
|
|
32
|
+
|
|
33
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
34
|
+
ok: true,
|
|
35
|
+
headers: {
|
|
36
|
+
get: vi.fn().mockReturnValue('mockSignature')
|
|
37
|
+
},
|
|
38
|
+
text: vi.fn().mockResolvedValue(JSON.stringify({
|
|
39
|
+
transactionId: '123456',
|
|
40
|
+
total: '1212',
|
|
41
|
+
merchant: 'testId'
|
|
42
|
+
}))
|
|
43
|
+
}) as unknown as typeof fetch
|
|
44
|
+
await expect(startPayment(paymentData)).rejects.toThrow('Invalid response signature')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should successfully start CARD, HUF, HU payment when API returns valid response', async () => {
|
|
48
|
+
setEnv()
|
|
49
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
50
|
+
ok: true,
|
|
51
|
+
headers: {
|
|
52
|
+
get: vi.fn().mockReturnValue('bxSwUc0qn0oABSRcq9uawF6zncFBhRk/AbO4HznYR9Pt5SjocyxAD+9Q4bE44h0J')
|
|
53
|
+
},
|
|
54
|
+
text: vi.fn().mockResolvedValue(JSON.stringify({
|
|
55
|
+
transactionId: '123456',
|
|
56
|
+
total: '1212',
|
|
57
|
+
merchant: 'testId'
|
|
58
|
+
}))
|
|
59
|
+
}) as unknown as typeof fetch
|
|
60
|
+
|
|
61
|
+
await expect(startPayment(paymentData)).resolves.toBeDefined()
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe('getPaymentResponse', () => {
|
|
66
|
+
it('should correctly decode and parse valid response', () => {
|
|
67
|
+
setEnv()
|
|
68
|
+
const r = 'eyJyIjowLCJ0Ijo1MDQyMzM4ODEsImUiOiJTVUNDRVNTIiwibSI6Im1lcmNoYW50RXVyb0lkIiwibyI6ImMtMS1ldXIifQ=='
|
|
69
|
+
// { r: 0, t: 504233881, e: 'SUCCESS', m: 'merchantEuroId', o: 'c-1-eur' }
|
|
70
|
+
const s = 'YlxrpDa8jF/xVB/rNsEJoFxOoHQ+CiziBmWSMIhYWHSCJmvLoc6kzAiZPmYDU8z6'
|
|
71
|
+
const result = getPaymentResponse(r, s)
|
|
72
|
+
expect(result).toEqual({
|
|
73
|
+
responseCode: 0,
|
|
74
|
+
transactionId: 504233881,
|
|
75
|
+
event: 'SUCCESS',
|
|
76
|
+
merchantId: 'merchantEuroId',
|
|
77
|
+
orderRef: 'c-1-eur'
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should throw error for invalid signature', () => {
|
|
82
|
+
setEnv()
|
|
83
|
+
const mockResponse = {
|
|
84
|
+
r: 'SUCCESS',
|
|
85
|
+
t: '123456789',
|
|
86
|
+
e: 'PAYMENT',
|
|
87
|
+
m: 'testId',
|
|
88
|
+
o: 'ORDER123'
|
|
89
|
+
}
|
|
90
|
+
const encodedResponse = Buffer.from(JSON.stringify(mockResponse)).toString('base64')
|
|
91
|
+
|
|
92
|
+
expect(() =>
|
|
93
|
+
getPaymentResponse(encodedResponse, 'invalid-signature')
|
|
94
|
+
).toThrow('Invalid response signature')
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
})
|
package/src/oneTime.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { PaymentData, SimplePayRequestBody } from './types'
|
|
3
|
+
import { simplepayLogger, getSimplePayConfig, toISO8601DateString, makeSimplePayRequest } from './utils'
|
|
4
|
+
|
|
5
|
+
const startPayment = async (paymentData: PaymentData) => {
|
|
6
|
+
simplepayLogger({ function: 'SimplePay/startPayment', paymentData })
|
|
7
|
+
const currency = paymentData.currency || 'HUF'
|
|
8
|
+
const { MERCHANT_KEY, MERCHANT_ID, API_URL, SDK_VERSION } = getSimplePayConfig(currency)
|
|
9
|
+
simplepayLogger({ function: 'SimplePay/startPayment', MERCHANT_KEY, MERCHANT_ID, API_URL })
|
|
10
|
+
|
|
11
|
+
if (!MERCHANT_KEY || !MERCHANT_ID) {
|
|
12
|
+
throw new Error(`Missing SimplePay configuration for ${currency}`)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const requestBody: SimplePayRequestBody = {
|
|
16
|
+
salt: crypto.randomBytes(16).toString('hex'),
|
|
17
|
+
merchant: MERCHANT_ID,
|
|
18
|
+
orderRef: paymentData.orderRef,
|
|
19
|
+
currency,
|
|
20
|
+
customerEmail: paymentData.customerEmail,
|
|
21
|
+
language: paymentData.language || 'HU',
|
|
22
|
+
sdkVersion: SDK_VERSION,
|
|
23
|
+
methods: [paymentData.method || 'CARD'],
|
|
24
|
+
total: String(paymentData.total),
|
|
25
|
+
timeout: toISO8601DateString(new Date(Date.now() + 30 * 60 * 1000)),
|
|
26
|
+
url: process.env.SIMPLEPAY_REDIRECT_URL || 'http://url.to.redirect',
|
|
27
|
+
invoice: paymentData.invoice,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return makeSimplePayRequest(API_URL, requestBody, MERCHANT_KEY)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { startPayment }
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import { startRecurringPayment } from './recurring'
|
|
3
|
+
import { toISO8601DateString } from './utils'
|
|
4
|
+
|
|
5
|
+
const setEnv = () => {
|
|
6
|
+
process.env.SIMPLEPAY_MERCHANT_ID_HUF = 'testId'
|
|
7
|
+
process.env.SIMPLEPAY_MERCHANT_KEY_HUF = 'testKey'
|
|
8
|
+
process.env.SIMPLEPAY_MERCHANT_ID_EUR = 'merchantEuroId'
|
|
9
|
+
process.env.SIMPLEPAY_MERCHANT_KEY_EUR = 'secretEuroKey'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const paymentData = {
|
|
13
|
+
orderRef: 'TEST123',
|
|
14
|
+
customer: 'Radharadhya Dasa',
|
|
15
|
+
customerEmail: 'test@example.com',
|
|
16
|
+
total: 1212,
|
|
17
|
+
recurring: {
|
|
18
|
+
times: 3,
|
|
19
|
+
until: toISO8601DateString(new Date(Date.now() + 6 * 30 * 24 * 60 * 60 * 1000)),
|
|
20
|
+
maxAmount: 12000
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
describe('SimplePay Recurring Tests', () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
// Clear all environment variables before each test
|
|
26
|
+
delete process.env.SIMPLEPAY_MERCHANT_ID_HUF
|
|
27
|
+
delete process.env.SIMPLEPAY_MERCHANT_KEY_HUF
|
|
28
|
+
delete process.env.SIMPLEPAY_MERCHANT_ID_EUR
|
|
29
|
+
delete process.env.SIMPLEPAY_MERCHANT_KEY_EUR
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('startRecurringPayment', () => {
|
|
33
|
+
it('should throw error when merchant configuration is missing', async () => {
|
|
34
|
+
await expect(startRecurringPayment(paymentData)).rejects.toThrow('Missing SimplePay configuration')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should handle API errors correctly', async () => {
|
|
38
|
+
setEnv()
|
|
39
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
40
|
+
ok: true,
|
|
41
|
+
headers: {
|
|
42
|
+
get: vi.fn().mockReturnValue('mockSignature')
|
|
43
|
+
},
|
|
44
|
+
text: vi.fn().mockResolvedValue(JSON.stringify({
|
|
45
|
+
transactionId: '123456',
|
|
46
|
+
total: '1212',
|
|
47
|
+
merchant: 'testId'
|
|
48
|
+
}))
|
|
49
|
+
}) as unknown as typeof fetch
|
|
50
|
+
await expect(startRecurringPayment(paymentData)).rejects.toThrow('Invalid response signature')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should successfully start recurring payment and card registration when API returns valid response', async () => {
|
|
54
|
+
setEnv()
|
|
55
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
56
|
+
ok: true,
|
|
57
|
+
headers: {
|
|
58
|
+
get: vi.fn().mockReturnValue('bxSwUc0qn0oABSRcq9uawF6zncFBhRk/AbO4HznYR9Pt5SjocyxAD+9Q4bE44h0J')
|
|
59
|
+
},
|
|
60
|
+
text: vi.fn().mockResolvedValue(JSON.stringify({
|
|
61
|
+
transactionId: '123456',
|
|
62
|
+
total: '1212',
|
|
63
|
+
merchant: 'testId'
|
|
64
|
+
}))
|
|
65
|
+
}) as unknown as typeof fetch
|
|
66
|
+
|
|
67
|
+
await expect(startRecurringPayment(paymentData)).resolves.toBeDefined()
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
})
|
package/src/recurring.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { SimplePayRecurringRequestBody, RecurringPaymentData, TokenPaymentData, SimplePayTokenRequestBody} from './types'
|
|
3
|
+
import { getSimplePayConfig, simplepayLogger, toISO8601DateString, makeSimplePayTokenRequest, makeSimplePayRecurringRequest} from './utils'
|
|
4
|
+
|
|
5
|
+
const INTERVAL_IN_MONTHS = 6
|
|
6
|
+
const DEFAULT_UNTIL = new Date(Date.now() + INTERVAL_IN_MONTHS * 30 * 24 * 60 * 60 * 1000)
|
|
7
|
+
const DEFAULT_MAX_AMOUNT = 12000
|
|
8
|
+
const DEFAULT_TIMES = 3
|
|
9
|
+
|
|
10
|
+
const startRecurringPayment = async (paymentData: RecurringPaymentData) => {
|
|
11
|
+
simplepayLogger({ function: 'SimplePay/startRecurringPayment', paymentData })
|
|
12
|
+
const currency = paymentData.currency || 'HUF'
|
|
13
|
+
const { MERCHANT_KEY, MERCHANT_ID, API_URL, SDK_VERSION } = getSimplePayConfig(currency)
|
|
14
|
+
simplepayLogger({ function: 'SimplePay/startRecurringPayment', MERCHANT_KEY, MERCHANT_ID, API_URL })
|
|
15
|
+
|
|
16
|
+
if (!MERCHANT_KEY || !MERCHANT_ID) {
|
|
17
|
+
throw new Error(`Missing SimplePay configuration for ${currency}`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const requestBody: SimplePayRecurringRequestBody = {
|
|
21
|
+
salt: crypto.randomBytes(16).toString('hex'),
|
|
22
|
+
merchant: MERCHANT_ID,
|
|
23
|
+
orderRef: paymentData.orderRef,
|
|
24
|
+
currency,
|
|
25
|
+
customer: paymentData.customer,
|
|
26
|
+
customerEmail: paymentData.customerEmail,
|
|
27
|
+
language: paymentData.language || 'HU',
|
|
28
|
+
sdkVersion: SDK_VERSION,
|
|
29
|
+
methods: ['CARD'],
|
|
30
|
+
recurring: {
|
|
31
|
+
times: paymentData.recurring.times || DEFAULT_TIMES,
|
|
32
|
+
until: paymentData.recurring.until || toISO8601DateString(DEFAULT_UNTIL),
|
|
33
|
+
maxAmount: paymentData.recurring.maxAmount || DEFAULT_MAX_AMOUNT
|
|
34
|
+
},
|
|
35
|
+
threeDSReqAuthMethod: '02',
|
|
36
|
+
total: String(paymentData.total),
|
|
37
|
+
timeout: toISO8601DateString(new Date(Date.now() + 30 * 60 * 1000)),
|
|
38
|
+
url: process.env.SIMPLEPAY_REDIRECT_URL || 'http://url.to.redirect',
|
|
39
|
+
invoice: paymentData.invoice,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return makeSimplePayRecurringRequest(API_URL, requestBody, MERCHANT_KEY)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const startTokenPayment = async (paymentData: TokenPaymentData) => {
|
|
46
|
+
simplepayLogger({ function: 'SimplePay/startTokenPayment', paymentData })
|
|
47
|
+
const currency = paymentData.currency || 'HUF'
|
|
48
|
+
const { MERCHANT_KEY, MERCHANT_ID, API_URL_RECURRING, SDK_VERSION } = getSimplePayConfig(currency)
|
|
49
|
+
simplepayLogger({ function: 'SimplePay/startTokenPayment', MERCHANT_KEY, MERCHANT_ID, API_URL_RECURRING })
|
|
50
|
+
|
|
51
|
+
if (!MERCHANT_KEY || !MERCHANT_ID) {
|
|
52
|
+
throw new Error(`Missing SimplePay configuration for ${currency}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const requestBody: SimplePayTokenRequestBody = {
|
|
56
|
+
salt: crypto.randomBytes(16).toString('hex'),
|
|
57
|
+
merchant: MERCHANT_ID,
|
|
58
|
+
orderRef: paymentData.orderRef,
|
|
59
|
+
currency,
|
|
60
|
+
customer: paymentData.customer,
|
|
61
|
+
customerEmail: paymentData.customerEmail,
|
|
62
|
+
language: paymentData.language || 'HU',
|
|
63
|
+
sdkVersion: SDK_VERSION,
|
|
64
|
+
methods: ['CARD'],
|
|
65
|
+
token: paymentData.token,
|
|
66
|
+
type: 'MIT',
|
|
67
|
+
threeDSReqAuthMethod: '02',
|
|
68
|
+
total: String(paymentData.total),
|
|
69
|
+
timeout: toISO8601DateString(new Date(Date.now() + 30 * 60 * 1000)),
|
|
70
|
+
url: process.env.SIMPLEPAY_REDIRECT_URL || 'http://recurring.url.to.redirect',
|
|
71
|
+
invoice: paymentData.invoice,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return makeSimplePayTokenRequest(API_URL_RECURRING, requestBody, MERCHANT_KEY)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { startRecurringPayment, startTokenPayment }
|
package/src/types.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
type PaymentMethod = 'CARD' | 'WIRE'
|
|
1
|
+
export type PaymentMethod = 'CARD' | 'WIRE'
|
|
2
2
|
|
|
3
|
-
const CURRENCIES = ['HUF', 'EUR', 'USD'] as const
|
|
4
|
-
type Currency = typeof CURRENCIES[number]
|
|
3
|
+
export const CURRENCIES = ['HUF', 'EUR', 'USD'] as const
|
|
4
|
+
export type Currency = typeof CURRENCIES[number]
|
|
5
5
|
|
|
6
|
-
const LANGUAGES = [
|
|
6
|
+
export const LANGUAGES = [
|
|
7
7
|
'AR', // Arabic
|
|
8
8
|
'BG', // Bulgarian
|
|
9
9
|
'CS', // Czech
|
|
@@ -21,9 +21,9 @@ const LANGUAGES = [
|
|
|
21
21
|
'TR', // Turkish
|
|
22
22
|
'ZH', // Chinese
|
|
23
23
|
] as const
|
|
24
|
-
type Language = typeof LANGUAGES[number]
|
|
24
|
+
export type Language = typeof LANGUAGES[number]
|
|
25
25
|
|
|
26
|
-
interface PaymentData {
|
|
26
|
+
export interface PaymentData {
|
|
27
27
|
orderRef: string
|
|
28
28
|
total: number | string
|
|
29
29
|
customerEmail: string
|
|
@@ -42,7 +42,24 @@ interface PaymentData {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
export type ISO8601DateString = string
|
|
46
|
+
export interface Recurring {
|
|
47
|
+
times: number,
|
|
48
|
+
until: ISO8601DateString,
|
|
49
|
+
maxAmount: number
|
|
50
|
+
}
|
|
51
|
+
export interface RecurringPaymentData extends PaymentData {
|
|
52
|
+
customer: string,
|
|
53
|
+
recurring: Recurring
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface TokenPaymentData extends Omit<PaymentData, 'method'> {
|
|
57
|
+
method: 'CARD',
|
|
58
|
+
customer: string,
|
|
59
|
+
token: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface SimplePayRequestBody extends Omit<PaymentData, 'total'> {
|
|
46
63
|
total: string
|
|
47
64
|
salt: string
|
|
48
65
|
merchant: string
|
|
@@ -52,24 +69,52 @@ interface SimplePayRequestBody extends Omit<PaymentData, 'total'> {
|
|
|
52
69
|
url: string
|
|
53
70
|
}
|
|
54
71
|
|
|
55
|
-
interface
|
|
72
|
+
export interface SimplePayRecurringRequestBody extends SimplePayRequestBody {
|
|
73
|
+
customer: string
|
|
74
|
+
recurring: Recurring
|
|
75
|
+
threeDSReqAuthMethod: '02' // only registered users can use this
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface SimplePayTokenRequestBody extends SimplePayRequestBody {
|
|
79
|
+
customer: string
|
|
80
|
+
token: string
|
|
81
|
+
threeDSReqAuthMethod: '02' // only registered users can use this
|
|
82
|
+
type: 'MIT' // Merchant Initiated Transaction
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface SimplePayResponse {
|
|
56
86
|
salt: string
|
|
57
87
|
merchant: string
|
|
58
88
|
orderRef: string
|
|
59
89
|
currency: Currency
|
|
60
90
|
transactionId: string
|
|
61
|
-
timeout:
|
|
91
|
+
timeout: ISO8601DateString
|
|
62
92
|
total: string
|
|
63
93
|
paymentUrl: string
|
|
64
94
|
errorCodes?: string[]
|
|
65
95
|
}
|
|
66
96
|
|
|
67
|
-
interface
|
|
97
|
+
export interface SimplePayRecurringResponse extends SimplePayResponse {
|
|
98
|
+
tokens: string[]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface SimplePayTokenResponse extends Omit<SimplePayResponse, 'paymentUrl' | 'timeout'> { }
|
|
102
|
+
|
|
103
|
+
export type SimplePayEvents = 'SUCCESS' | 'FAIL' | 'TIMEOUT' | 'CANCEL'
|
|
104
|
+
|
|
105
|
+
export interface SimplePayAPIResult {
|
|
68
106
|
r: number // response code
|
|
69
107
|
t: string // transaction id
|
|
70
|
-
e:
|
|
108
|
+
e: SimplePayEvents // event
|
|
71
109
|
m: string // merchant id
|
|
72
110
|
o: string // order id
|
|
73
111
|
}
|
|
74
112
|
|
|
75
|
-
export
|
|
113
|
+
export interface SimplePayResult {
|
|
114
|
+
responseCode: number,
|
|
115
|
+
transactionId: string,
|
|
116
|
+
event: SimplePayEvents,
|
|
117
|
+
merchantId: string,
|
|
118
|
+
orderRef: string,
|
|
119
|
+
tokens?: string[],
|
|
120
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { describe, it, expect,
|
|
2
|
-
import { checkSignature, generateSignature, getPaymentResponse, getSimplePayConfig, startPayment, getCurrencyFromMerchantId } from './index'
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
3
2
|
import { Currency } from './types'
|
|
3
|
+
import { checkSignature, generateSignature, getCurrencyFromMerchantId, getSimplePayConfig } from './utils'
|
|
4
4
|
|
|
5
5
|
const setEnv = () => {
|
|
6
6
|
process.env.SIMPLEPAY_MERCHANT_ID_HUF = 'testId'
|
|
@@ -14,11 +14,13 @@ const paymentData = {
|
|
|
14
14
|
customerEmail: 'test@example.com',
|
|
15
15
|
total: 1212
|
|
16
16
|
}
|
|
17
|
-
describe('SimplePay
|
|
17
|
+
describe('SimplePay Utils Tests', () => {
|
|
18
18
|
beforeEach(() => {
|
|
19
19
|
// Clear all environment variables before each test
|
|
20
|
-
delete process.env.SIMPLEPAY_MERCHANT_KEY_HUF
|
|
21
20
|
delete process.env.SIMPLEPAY_MERCHANT_ID_HUF
|
|
21
|
+
delete process.env.SIMPLEPAY_MERCHANT_KEY_HUF
|
|
22
|
+
delete process.env.SIMPLEPAY_MERCHANT_ID_EUR
|
|
23
|
+
delete process.env.SIMPLEPAY_MERCHANT_KEY_EUR
|
|
22
24
|
})
|
|
23
25
|
describe('generateSignature', () => {
|
|
24
26
|
it('should generate correct signature for sample payload from documentation', () => {
|
|
@@ -101,7 +103,6 @@ describe('SimplePay SDK Tests', () => {
|
|
|
101
103
|
m: 'testId',
|
|
102
104
|
o: 'ORDER123'
|
|
103
105
|
}
|
|
104
|
-
const encodedResponse = Buffer.from(JSON.stringify(mockResponse)).toString('base64')
|
|
105
106
|
|
|
106
107
|
describe('getCurrencyFromMerchantId', () => {
|
|
107
108
|
it('should return correct currency for merchantId', () => {
|
|
@@ -110,77 +111,4 @@ describe('SimplePay SDK Tests', () => {
|
|
|
110
111
|
expect(currency).toBe('HUF')
|
|
111
112
|
})
|
|
112
113
|
})
|
|
113
|
-
|
|
114
|
-
describe('getPaymentResponse', () => {
|
|
115
|
-
it.only('TODO: test for invalid response', () => {
|
|
116
|
-
process.env.SIMPLEPAY_MERCHANT_ID_HUF = 'P085602'
|
|
117
|
-
process.env.SIMPLEPAY_MERCHANT_KEY_HUF = 'QnJvDEEj51jdDEa1P125258p8g5gU383'
|
|
118
|
-
const r = 'eyJyIjowLCJ0Ijo1MDQyNDU5MDIsImUiOiJTVUNDRVNTIiwibSI6IlAwODU2MDIiLCJvIjoiMTczMzQ5MzQxMjA1NyJ9'
|
|
119
|
-
const s = 'apIUA%2B8cislIlgYUB7lSpoasi%2BCIRg1SMS5zVbEytnEnxcvdx%2BWDXkAznnseADiI'
|
|
120
|
-
const result = getPaymentResponse(r, s)
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
it('should correctly decode and parse valid response', () => {
|
|
124
|
-
setEnv()
|
|
125
|
-
const r = 'eyJyIjowLCJ0Ijo1MDQyMzM4ODEsImUiOiJTVUNDRVNTIiwibSI6Im1lcmNoYW50RXVyb0lkIiwibyI6ImMtMS1ldXIifQ=='
|
|
126
|
-
// { r: 0, t: 504233881, e: 'SUCCESS', m: 'merchantEuroId', o: 'c-1-eur' }
|
|
127
|
-
const s = 'WWx4cnBEYThqRi94VkIvck5zRUpvRnhPb0hRK0NpemlCbVdTTUloWVdIU0NKbXZMb2M2a3pBaVpQbVlEVTh6Ng=='
|
|
128
|
-
const result = getPaymentResponse(r, s)
|
|
129
|
-
expect(result).toEqual({
|
|
130
|
-
responseCode: 0,
|
|
131
|
-
transactionId: 504233881,
|
|
132
|
-
event: 'SUCCESS',
|
|
133
|
-
merchantId: 'merchantEuroId',
|
|
134
|
-
orderRef: 'c-1-eur'
|
|
135
|
-
})
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('should throw error for invalid signature', () => {
|
|
139
|
-
setEnv()
|
|
140
|
-
expect(() =>
|
|
141
|
-
getPaymentResponse(encodedResponse, 'invalid-signature')
|
|
142
|
-
).toThrow('Invalid response signature')
|
|
143
|
-
})
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
describe('startPayment', () => {
|
|
147
|
-
it('should throw error when merchant configuration is missing', async () => {
|
|
148
|
-
await expect(startPayment(paymentData)).rejects.toThrow('Missing SimplePay configuration')
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
it('should handle API errors correctly', async () => {
|
|
152
|
-
setEnv()
|
|
153
|
-
|
|
154
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
155
|
-
ok: true,
|
|
156
|
-
headers: {
|
|
157
|
-
get: vi.fn().mockReturnValue('mockSignature')
|
|
158
|
-
},
|
|
159
|
-
text: vi.fn().mockResolvedValue(JSON.stringify({
|
|
160
|
-
transactionId: '123456',
|
|
161
|
-
total: '1212',
|
|
162
|
-
merchant: 'testId'
|
|
163
|
-
}))
|
|
164
|
-
}) as unknown as typeof fetch
|
|
165
|
-
await expect(startPayment(paymentData)).rejects.toThrow('Invalid response signature')
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
it('should successfully start CARD, HUF, HU payment when API returns valid response', async () => {
|
|
169
|
-
setEnv()
|
|
170
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
171
|
-
ok: true,
|
|
172
|
-
headers: {
|
|
173
|
-
get: vi.fn().mockReturnValue('bxSwUc0qn0oABSRcq9uawF6zncFBhRk/AbO4HznYR9Pt5SjocyxAD+9Q4bE44h0J')
|
|
174
|
-
},
|
|
175
|
-
text: vi.fn().mockResolvedValue(JSON.stringify({
|
|
176
|
-
transactionId: '123456',
|
|
177
|
-
total: '1212',
|
|
178
|
-
merchant: 'testId'
|
|
179
|
-
}))
|
|
180
|
-
}) as unknown as typeof fetch
|
|
181
|
-
|
|
182
|
-
await expect(startPayment(paymentData)).resolves.toBeDefined()
|
|
183
|
-
})
|
|
184
|
-
})
|
|
185
|
-
|
|
186
114
|
})
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { CURRENCIES, Currency, ISO8601DateString, SimplePayAPIResult, SimplePayRecurringRequestBody, SimplePayRecurringResponse, SimplePayRequestBody, SimplePayResponse, SimplePayResult, SimplePayTokenRequestBody, SimplePayTokenResponse } from "./types"
|
|
3
|
+
|
|
4
|
+
export const simplepayLogger = (...args: any[]) => {
|
|
5
|
+
if (process.env.SIMPLEPAY_LOGGER !== 'true') {
|
|
6
|
+
return
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
console.log('👉 ', ...args)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const getSimplePayConfig = (currency: Currency) => {
|
|
13
|
+
if (!CURRENCIES.includes(currency)) {
|
|
14
|
+
throw new Error(`Unsupported currency: ${currency}`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const SIMPLEPAY_API_URL = 'https://secure.simplepay.hu/payment/v2'
|
|
18
|
+
const SIMPLEPAY_SANDBOX_URL = 'https://sandbox.simplepay.hu/payment/v2/start'
|
|
19
|
+
const SDK_VERSION = 'SimplePayV2.1_Rrd_0.6.1'
|
|
20
|
+
const MERCHANT_KEY = process.env[`SIMPLEPAY_MERCHANT_KEY_${currency}`]
|
|
21
|
+
const MERCHANT_ID = process.env[`SIMPLEPAY_MERCHANT_ID_${currency}`]
|
|
22
|
+
const API_URL = process.env.SIMPLEPAY_PRODUCTION === 'true' ? SIMPLEPAY_API_URL : SIMPLEPAY_SANDBOX_URL
|
|
23
|
+
const API_URL_RECURRING = process.env.SIMPLEPAY_PRODUCTION === 'true' ? SIMPLEPAY_API_URL : 'https://sandbox.simplepay.hu/payment/v2/dorecurring'
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
MERCHANT_KEY,
|
|
27
|
+
MERCHANT_ID,
|
|
28
|
+
API_URL,
|
|
29
|
+
API_URL_RECURRING,
|
|
30
|
+
SDK_VERSION
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// escaping slashes for the request body to prevent strange SimplePay API errors (eg Missing Signature)
|
|
35
|
+
export const prepareRequestBody = (body: any) =>
|
|
36
|
+
JSON.stringify(body).replace(/\//g, '\\/')
|
|
37
|
+
|
|
38
|
+
export const generateSignature = (body: string, merchantKey: string) => {
|
|
39
|
+
const hmac = crypto.createHmac('sha384', merchantKey.trim())
|
|
40
|
+
hmac.update(body, 'utf8')
|
|
41
|
+
return hmac.digest('base64')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const checkSignature = (responseText: string, signature: string, merchantKey: string) =>
|
|
45
|
+
signature === generateSignature(responseText, merchantKey)
|
|
46
|
+
|
|
47
|
+
export const toISO8601DateString = (date: Date): ISO8601DateString => date.toISOString().replace(/\.\d{3}Z$/, '+00:00')
|
|
48
|
+
|
|
49
|
+
export const getCurrencyFromMerchantId = (merchantId: string) => {
|
|
50
|
+
const currency = Object.entries(process.env)
|
|
51
|
+
.find(([key, value]) =>
|
|
52
|
+
key.startsWith('SIMPLEPAY_MERCHANT_ID_') && value === merchantId
|
|
53
|
+
)?.[0]?.replace('SIMPLEPAY_MERCHANT_ID_', '') as Currency
|
|
54
|
+
if (!currency) {
|
|
55
|
+
throw new Error(`Merchant id not found in the environment: ${merchantId}`)
|
|
56
|
+
}
|
|
57
|
+
return currency
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const makeSimplePayRequest = async (apiUrl: string, requestBody: SimplePayRequestBody, merchantKey: string) => {
|
|
61
|
+
return makeRequest(apiUrl, requestBody, merchantKey, 'oneTime') as Promise<SimplePayResponse>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const makeSimplePayRecurringRequest = async (apiUrl: string, requestBody: SimplePayRecurringRequestBody, merchantKey: string) => {
|
|
65
|
+
return makeRequest(apiUrl, requestBody, merchantKey, 'recurring') as Promise<SimplePayRecurringResponse>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const makeSimplePayTokenRequest = async (apiUrl: string, requestBody: SimplePayTokenRequestBody, merchantKey: string) => {
|
|
69
|
+
return makeRequest(apiUrl, requestBody, merchantKey, 'token') as Promise<SimplePayTokenResponse>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const makeRequest = async (apiUrl: string, requestBody: SimplePayRequestBody | SimplePayRecurringRequestBody | SimplePayTokenRequestBody, merchantKey: string, type: 'oneTime' | 'recurring' | 'token') => {
|
|
73
|
+
const bodyString = prepareRequestBody(requestBody)
|
|
74
|
+
const signature = generateSignature(bodyString, merchantKey)
|
|
75
|
+
simplepayLogger({ function: `SimplePay/makeRequest/${type}`, bodyString, signature })
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const response = await fetch(apiUrl, {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: {
|
|
81
|
+
'Content-Type': 'application/json',
|
|
82
|
+
'Signature': signature,
|
|
83
|
+
},
|
|
84
|
+
body: bodyString,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
simplepayLogger({ function: `SimplePay/makeRequest/${type}`, response })
|
|
88
|
+
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
throw new Error(`SimplePay API error: ${response.status}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const responseSignature = response.headers.get('Signature')
|
|
94
|
+
simplepayLogger({ function: `SimplePay/makeRequest/${type}`, responseSignature })
|
|
95
|
+
if (!responseSignature) {
|
|
96
|
+
throw new Error('Missing response signature')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const responseText = await response.text()
|
|
100
|
+
const responseJSON = JSON.parse(responseText) as { errorCodes?: string[] }
|
|
101
|
+
simplepayLogger({ function: `SimplePay/makeRequest/${type}`, responseText, responseJSON })
|
|
102
|
+
|
|
103
|
+
if (responseJSON.errorCodes) {
|
|
104
|
+
throw new Error(`SimplePay API error: ${responseJSON.errorCodes}`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!checkSignature(responseText, responseSignature, merchantKey)) {
|
|
108
|
+
throw new Error('Invalid response signature')
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return responseJSON
|
|
112
|
+
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw error
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const getPaymentResponse = (r: string, signature: string) => {
|
|
119
|
+
simplepayLogger({ function: 'SimplePay/getPaymentResponse', r, signature })
|
|
120
|
+
signature = decodeURIComponent(signature)
|
|
121
|
+
const rDecoded = Buffer.from(r, 'base64').toString('utf-8')
|
|
122
|
+
const rDecodedJSON = JSON.parse(rDecoded) as SimplePayAPIResult
|
|
123
|
+
const currency = getCurrencyFromMerchantId(rDecodedJSON.m)
|
|
124
|
+
const { MERCHANT_KEY } = getSimplePayConfig(currency as Currency)
|
|
125
|
+
|
|
126
|
+
if (!checkSignature(rDecoded, signature, MERCHANT_KEY || '')) {
|
|
127
|
+
simplepayLogger({ function: 'SimplePay/getPaymentResponse', rDecoded, signature })
|
|
128
|
+
throw new Error('Invalid response signature')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const responseJson = JSON.parse(rDecoded)
|
|
132
|
+
const response: SimplePayResult = {
|
|
133
|
+
responseCode: responseJson.r,
|
|
134
|
+
transactionId: responseJson.t,
|
|
135
|
+
event: responseJson.e,
|
|
136
|
+
merchantId: responseJson.m,
|
|
137
|
+
orderRef: responseJson.o,
|
|
138
|
+
tokens: responseJson.tokens,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return response
|
|
142
|
+
}
|