simplepay-js-sdk 0.1.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 +38 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
- package/src/index.spec.ts +51 -0
- package/src/index.ts +124 -0
- package/src/types.ts +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# SimplePay Node.js Utility
|
|
2
|
+
|
|
3
|
+
A lightweight utility for integrating SimplePay payments in Node.js applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install simplepay-js-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { startPayment, getPaymentResponse } from 'simplepay-nodejs-utility'
|
|
15
|
+
|
|
16
|
+
// Start a payment
|
|
17
|
+
const paymentResponse = await startPayment({
|
|
18
|
+
orderRef: 'unique-order-reference',
|
|
19
|
+
total: 1000,
|
|
20
|
+
customerEmail: 'customer@example.com',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// Handle payment response
|
|
24
|
+
const result = getPaymentResponse(encodedResponse, signature)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Configuration
|
|
28
|
+
|
|
29
|
+
Set the following environment variables:
|
|
30
|
+
|
|
31
|
+
- `SIMPLEPAY_MERCHANT_KEY`
|
|
32
|
+
- `SIMPLEPAY_MERCHANT_ID`
|
|
33
|
+
- `SIMPLEPAY_PRODUCTION`
|
|
34
|
+
- `SIMPLEPAY_REDIRECT_URL`
|
|
35
|
+
|
|
36
|
+
## License
|
|
37
|
+
|
|
38
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PaymentData, SimplePayResponse } from './types';
|
|
2
|
+
declare const generateSignature: (body: string, merchantKey: string) => string;
|
|
3
|
+
declare const checkSignature: (responseText: string, signature: string, merchantKey: string) => boolean;
|
|
4
|
+
declare const startPayment: (paymentData: PaymentData) => Promise<SimplePayResponse>;
|
|
5
|
+
declare const getPaymentResponse: (r: string, signature: string) => {
|
|
6
|
+
responseCode: number;
|
|
7
|
+
transactionId: string;
|
|
8
|
+
event: "success" | "fail" | "timeout" | "cancel";
|
|
9
|
+
merchantId: string;
|
|
10
|
+
orderId: string;
|
|
11
|
+
};
|
|
12
|
+
export { startPayment, generateSignature, checkSignature, getPaymentResponse };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
// Existing interfaces remain the same
|
|
3
|
+
const simplepayLogger = (...args) => {
|
|
4
|
+
if (process.env.SIMPLEPAY_LOGGER !== 'true') {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
console.log(...args);
|
|
8
|
+
};
|
|
9
|
+
const generateSignature = (body, merchantKey) => {
|
|
10
|
+
const hmac = crypto.createHmac('sha384', merchantKey.trim());
|
|
11
|
+
hmac.update(body, 'utf8');
|
|
12
|
+
return hmac.digest('base64');
|
|
13
|
+
};
|
|
14
|
+
const checkSignature = (responseText, signature, merchantKey) => signature === generateSignature(responseText, merchantKey);
|
|
15
|
+
// escaping slashes for the request body to prevent strange SimplePay API errors (eg Missing Signature)
|
|
16
|
+
const prepareRequestBody = (body) => JSON.stringify(body).replace(/\//g, '\\/');
|
|
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.1.0';
|
|
20
|
+
const startPayment = async (paymentData) => {
|
|
21
|
+
const MERCHANT_KEY = process.env.SIMPLEPAY_MERCHANT_KEY;
|
|
22
|
+
const MERCHANT_ID = process.env.SIMPLEPAY_MERCHANT_ID;
|
|
23
|
+
const API_URL = process.env.SIMPLEPAY_PRODUCTION === 'true' ? SIMPLEPAY_API_URL : SIMPLEPAY_SANDBOX_URL;
|
|
24
|
+
simplepayLogger({ MERCHANT_KEY, MERCHANT_ID, API_URL });
|
|
25
|
+
if (!MERCHANT_KEY || !MERCHANT_ID) {
|
|
26
|
+
throw new Error('Missing SimplePay configuration');
|
|
27
|
+
}
|
|
28
|
+
const requestBody = {
|
|
29
|
+
salt: crypto.randomBytes(16).toString('hex'),
|
|
30
|
+
merchant: MERCHANT_ID,
|
|
31
|
+
orderRef: paymentData.orderRef,
|
|
32
|
+
currency: paymentData.currency || 'HUF',
|
|
33
|
+
customerEmail: paymentData.customerEmail,
|
|
34
|
+
language: paymentData.language || 'HU',
|
|
35
|
+
sdkVersion: SDK_VERSION,
|
|
36
|
+
methods: ['CARD'],
|
|
37
|
+
total: String(paymentData.total),
|
|
38
|
+
timeout: new Date(Date.now() + 30 * 60 * 1000)
|
|
39
|
+
.toISOString()
|
|
40
|
+
.replace(/\.\d{3}Z$/, '+00:00'),
|
|
41
|
+
url: process.env.SIMPLEPAY_REDIRECT_URL || 'http://url.to.redirect',
|
|
42
|
+
invoice: paymentData.invoice,
|
|
43
|
+
};
|
|
44
|
+
const bodyString = prepareRequestBody(requestBody);
|
|
45
|
+
const signature = generateSignature(bodyString, MERCHANT_KEY);
|
|
46
|
+
simplepayLogger({ bodyString, signature });
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetch(API_URL, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: {
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
'Signature': signature,
|
|
53
|
+
},
|
|
54
|
+
body: bodyString,
|
|
55
|
+
});
|
|
56
|
+
simplepayLogger({ response });
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new Error(`SimplePay API error: ${response.status}`);
|
|
59
|
+
}
|
|
60
|
+
const responseSignature = response.headers.get('Signature');
|
|
61
|
+
simplepayLogger({ responseSignature });
|
|
62
|
+
if (!responseSignature) {
|
|
63
|
+
throw new Error('Missing response signature');
|
|
64
|
+
}
|
|
65
|
+
const responseText = await response.text();
|
|
66
|
+
const responseJSON = JSON.parse(responseText);
|
|
67
|
+
simplepayLogger({ responseText, responseJSON });
|
|
68
|
+
if (responseJSON.errorCodes) {
|
|
69
|
+
throw new Error(`SimplePay API error: ${responseJSON.errorCodes}`);
|
|
70
|
+
}
|
|
71
|
+
if (!checkSignature(responseText, responseSignature, MERCHANT_KEY)) {
|
|
72
|
+
throw new Error('Invalid response signature');
|
|
73
|
+
}
|
|
74
|
+
return responseJSON;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error('SimplePay payment start error:', error);
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const getPaymentResponse = (r, signature) => {
|
|
82
|
+
// Note: Replaced atob with Buffer for ESM
|
|
83
|
+
const rDecoded = Buffer.from(r, 'base64').toString('utf-8');
|
|
84
|
+
if (!checkSignature(rDecoded, signature, process.env.SIMPLEPAY_MERCHANT_KEY || '')) {
|
|
85
|
+
simplepayLogger({ rDecoded, signature });
|
|
86
|
+
throw new Error('Invalid response signature');
|
|
87
|
+
}
|
|
88
|
+
const responseJson = JSON.parse(rDecoded);
|
|
89
|
+
const response = {
|
|
90
|
+
responseCode: responseJson.r,
|
|
91
|
+
transactionId: responseJson.t,
|
|
92
|
+
event: responseJson.e,
|
|
93
|
+
merchantId: responseJson.m,
|
|
94
|
+
orderId: responseJson.o,
|
|
95
|
+
};
|
|
96
|
+
return response;
|
|
97
|
+
};
|
|
98
|
+
export { startPayment, generateSignature, checkSignature, getPaymentResponse };
|
|
99
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
interface PaymentData {
|
|
2
|
+
orderRef: string;
|
|
3
|
+
total: number | string;
|
|
4
|
+
customerEmail: string;
|
|
5
|
+
currency?: string;
|
|
6
|
+
language?: string;
|
|
7
|
+
invoice?: {
|
|
8
|
+
name: string;
|
|
9
|
+
country: string;
|
|
10
|
+
state: string;
|
|
11
|
+
city: string;
|
|
12
|
+
zip: string;
|
|
13
|
+
address: string;
|
|
14
|
+
address2?: string;
|
|
15
|
+
phone?: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
interface SimplePayRequestBody extends Omit<PaymentData, 'total'> {
|
|
19
|
+
total: string;
|
|
20
|
+
salt: string;
|
|
21
|
+
merchant: string;
|
|
22
|
+
sdkVersion: string;
|
|
23
|
+
methods: ['CARD'];
|
|
24
|
+
timeout: string;
|
|
25
|
+
url: string;
|
|
26
|
+
}
|
|
27
|
+
interface SimplePayResponse {
|
|
28
|
+
salt: string;
|
|
29
|
+
merchant: string;
|
|
30
|
+
orderRef: string;
|
|
31
|
+
currency: string;
|
|
32
|
+
transactionId: string;
|
|
33
|
+
timeout: string;
|
|
34
|
+
total: string;
|
|
35
|
+
paymentUrl: string;
|
|
36
|
+
errorCodes?: string[];
|
|
37
|
+
}
|
|
38
|
+
interface SimplepayResult {
|
|
39
|
+
r: number;
|
|
40
|
+
t: string;
|
|
41
|
+
e: 'success' | 'fail' | 'timeout' | 'cancel';
|
|
42
|
+
m: string;
|
|
43
|
+
o: string;
|
|
44
|
+
}
|
|
45
|
+
export { PaymentData, SimplePayRequestBody, SimplePayResponse, SimplepayResult };
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "simplepay-js-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A Node.js utility for SimplePay payment integration",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/rrd108/simplepay-js-sdk"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./dist/index.js",
|
|
13
|
+
"./types": "./dist/types.js"
|
|
14
|
+
},
|
|
15
|
+
"types": "dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"prepare": "npm run build",
|
|
23
|
+
"test": "vitest"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"simplepay",
|
|
27
|
+
"payment",
|
|
28
|
+
"nodejs",
|
|
29
|
+
"typescript"
|
|
30
|
+
],
|
|
31
|
+
"author": "Radharadhya Dasa",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^20.0.0",
|
|
35
|
+
"typescript": "^5.0.0",
|
|
36
|
+
"vitest": "^2.1.6"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { checkSignature, generateSignature } from './index'
|
|
3
|
+
|
|
4
|
+
describe('generateSignature', () => {
|
|
5
|
+
it('should generate correct signature for sample payload from documentation', () => {
|
|
6
|
+
const merchantKey = 'FxDa5w314kLlNseq2sKuVwaqZshZT5d6'
|
|
7
|
+
const body = {
|
|
8
|
+
salt: 'c1ca1d0e9fc2323b3dda7cf145e36f5e',
|
|
9
|
+
merchant: 'PUBLICTESTHUF',
|
|
10
|
+
orderRef: '101010516348232058105',
|
|
11
|
+
currency: 'HUF',
|
|
12
|
+
customerEmail: 'sdk_test@otpmobil.com',
|
|
13
|
+
language: 'HU',
|
|
14
|
+
sdkVersion: 'SimplePayV2.1_Payment_PHP_SDK_2.0.7_190701:dd236896400d7463677a82a47f53e36e',
|
|
15
|
+
methods: ['CARD'],
|
|
16
|
+
total: '25',
|
|
17
|
+
timeout: '2021-10-30T12:30:11+00:00',
|
|
18
|
+
url: 'https://sdk.simplepay.hu/back.php',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let bodyString = JSON.stringify(body)
|
|
22
|
+
// after stringify we should insert backslashes for the url
|
|
23
|
+
bodyString = bodyString.replace(/\//g, '\\/')
|
|
24
|
+
|
|
25
|
+
const result = generateSignature(bodyString, merchantKey)
|
|
26
|
+
|
|
27
|
+
const expectedSignature = 'gcDJ8J7TyT1rC/Ygj/8CihXaLwniMWRav09QSEMQUnv5TbYaEDvQAuBE1mW3plvZ'
|
|
28
|
+
expect(result).toBe(expectedSignature)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
describe('checkSignature', () => {
|
|
34
|
+
it('should generate correct signature for the response', () => {
|
|
35
|
+
const merchantKey = 'P085602'
|
|
36
|
+
const response = {
|
|
37
|
+
transactionId: '504226393',
|
|
38
|
+
orderRef: 'ORDER124',
|
|
39
|
+
merchant: merchantKey,
|
|
40
|
+
timeout: '2024-11-29T13:29:17+01:00',
|
|
41
|
+
total: '1000',
|
|
42
|
+
paymentUrl: 'https://sb-checkout.simplepay.hu/trx/cJZs3UUc48FlGu7Mfa1M0tcTWO53oA5RxS0OUMPqA17Fe3HGBr',
|
|
43
|
+
currency: 'HUF',
|
|
44
|
+
salt: 'R0ZBm2gdCXuTmxpOkqB4s0aAhZxZwSWG'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const expectedSignature = 'W98O/EdHobsWJTw2U1xUuUWXtCaJnTnzq5Na8ddCKE4gm2IW7vro33tGAW55YPf6'
|
|
48
|
+
const result = checkSignature(JSON.stringify(response).replace(/\//g, '\\/'), expectedSignature, merchantKey)
|
|
49
|
+
expect(result).toBeTruthy()
|
|
50
|
+
})
|
|
51
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { PaymentData, SimplePayRequestBody, SimplePayResponse, SimplepayResult } from './types'
|
|
3
|
+
|
|
4
|
+
// Existing interfaces remain the same
|
|
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
|
+
const SIMPLEPAY_API_URL = 'https://secure.simplepay.hu/payment/v2'
|
|
27
|
+
const SIMPLEPAY_SANDBOX_URL = 'https://sandbox.simplepay.hu/payment/v2/start'
|
|
28
|
+
const SDK_VERSION = 'SimplePayV2.1_Rrd_0.1.0'
|
|
29
|
+
|
|
30
|
+
const startPayment = async (paymentData: PaymentData) => {
|
|
31
|
+
const MERCHANT_KEY = process.env.SIMPLEPAY_MERCHANT_KEY
|
|
32
|
+
const MERCHANT_ID = process.env.SIMPLEPAY_MERCHANT_ID
|
|
33
|
+
const API_URL = process.env.SIMPLEPAY_PRODUCTION === 'true' ? SIMPLEPAY_API_URL : SIMPLEPAY_SANDBOX_URL
|
|
34
|
+
simplepayLogger({ MERCHANT_KEY, MERCHANT_ID, API_URL })
|
|
35
|
+
|
|
36
|
+
if (!MERCHANT_KEY || !MERCHANT_ID) {
|
|
37
|
+
throw new Error('Missing SimplePay configuration')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const requestBody: SimplePayRequestBody = {
|
|
41
|
+
salt: crypto.randomBytes(16).toString('hex'),
|
|
42
|
+
merchant: MERCHANT_ID,
|
|
43
|
+
orderRef: paymentData.orderRef,
|
|
44
|
+
currency: paymentData.currency || 'HUF',
|
|
45
|
+
customerEmail: paymentData.customerEmail,
|
|
46
|
+
language: paymentData.language || 'HU',
|
|
47
|
+
sdkVersion: SDK_VERSION,
|
|
48
|
+
methods: ['CARD'],
|
|
49
|
+
total: String(paymentData.total),
|
|
50
|
+
timeout: new Date(Date.now() + 30 * 60 * 1000)
|
|
51
|
+
.toISOString()
|
|
52
|
+
.replace(/\.\d{3}Z$/, '+00:00'),
|
|
53
|
+
url: process.env.SIMPLEPAY_REDIRECT_URL || 'http://url.to.redirect',
|
|
54
|
+
invoice: paymentData.invoice,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const bodyString = prepareRequestBody(requestBody)
|
|
58
|
+
const signature = generateSignature(bodyString, MERCHANT_KEY)
|
|
59
|
+
simplepayLogger({ bodyString, signature })
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetch(API_URL, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: {
|
|
65
|
+
'Content-Type': 'application/json',
|
|
66
|
+
'Signature': signature,
|
|
67
|
+
},
|
|
68
|
+
body: bodyString,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
simplepayLogger({ response })
|
|
72
|
+
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(`SimplePay API error: ${response.status}`)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const responseSignature = response.headers.get('Signature')
|
|
78
|
+
simplepayLogger({ responseSignature })
|
|
79
|
+
if (!responseSignature) {
|
|
80
|
+
throw new Error('Missing response signature')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const responseText = await response.text()
|
|
84
|
+
const responseJSON = JSON.parse(responseText) as SimplePayResponse
|
|
85
|
+
simplepayLogger({ responseText, responseJSON })
|
|
86
|
+
|
|
87
|
+
if (responseJSON.errorCodes) {
|
|
88
|
+
throw new Error(`SimplePay API error: ${responseJSON.errorCodes}`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!checkSignature(responseText, responseSignature, MERCHANT_KEY)) {
|
|
92
|
+
throw new Error('Invalid response signature')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return responseJSON
|
|
96
|
+
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('SimplePay payment start error:', error)
|
|
99
|
+
throw error
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const getPaymentResponse = (r: string, signature: string) => {
|
|
104
|
+
// Note: Replaced atob with Buffer for ESM
|
|
105
|
+
const rDecoded = Buffer.from(r, 'base64').toString('utf-8')
|
|
106
|
+
|
|
107
|
+
if (!checkSignature(rDecoded, signature, process.env.SIMPLEPAY_MERCHANT_KEY || '')) {
|
|
108
|
+
simplepayLogger({ rDecoded, signature })
|
|
109
|
+
throw new Error('Invalid response signature')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const responseJson: SimplepayResult = JSON.parse(rDecoded)
|
|
113
|
+
const response = {
|
|
114
|
+
responseCode: responseJson.r,
|
|
115
|
+
transactionId: responseJson.t,
|
|
116
|
+
event: responseJson.e,
|
|
117
|
+
merchantId: responseJson.m,
|
|
118
|
+
orderId: responseJson.o,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return response
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { startPayment, generateSignature, checkSignature, getPaymentResponse }
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
interface PaymentData {
|
|
2
|
+
orderRef: string
|
|
3
|
+
total: number | string
|
|
4
|
+
customerEmail: string
|
|
5
|
+
currency?: string
|
|
6
|
+
language?: string
|
|
7
|
+
invoice?: {
|
|
8
|
+
name: string
|
|
9
|
+
country: string
|
|
10
|
+
state: string
|
|
11
|
+
city: string
|
|
12
|
+
zip: string
|
|
13
|
+
address: string
|
|
14
|
+
address2?: string
|
|
15
|
+
phone?: string
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface SimplePayRequestBody extends Omit<PaymentData, 'total'> {
|
|
20
|
+
total: string
|
|
21
|
+
salt: string
|
|
22
|
+
merchant: string
|
|
23
|
+
sdkVersion: string
|
|
24
|
+
methods: ['CARD']
|
|
25
|
+
timeout: string
|
|
26
|
+
url: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface SimplePayResponse {
|
|
30
|
+
salt: string
|
|
31
|
+
merchant: string
|
|
32
|
+
orderRef: string
|
|
33
|
+
currency: string
|
|
34
|
+
transactionId: string
|
|
35
|
+
timeout: string
|
|
36
|
+
total: string
|
|
37
|
+
paymentUrl: string
|
|
38
|
+
errorCodes?: string[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface SimplepayResult {
|
|
42
|
+
r: number // response code
|
|
43
|
+
t: string // transaction id
|
|
44
|
+
e: 'success' | 'fail' | 'timeout' | 'cancel' // event
|
|
45
|
+
m: string // merchant id
|
|
46
|
+
o: string // order id
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { PaymentData, SimplePayRequestBody, SimplePayResponse, SimplepayResult }
|