simplepay-js-sdk 0.1.0 → 0.3.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 +76 -17
- package/dist/index.js +18 -8
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -1
- package/package.json +5 -3
- package/src/index.spec.ts +102 -2
- package/src/index.ts +20 -8
- package/src/types.ts +4 -1
package/README.md
CHANGED
|
@@ -1,37 +1,96 @@
|
|
|
1
|
-
# SimplePay
|
|
1
|
+
# SimplePay JS SDK
|
|
2
2
|
|
|
3
|
-
A lightweight utility for integrating SimplePay payments in Node.js applications.
|
|
3
|
+
A lightweight utility for integrating Hungary's SimplePay payments in Node.js applications.
|
|
4
|
+
|
|
5
|
+

|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
10
|
+
# npm
|
|
8
11
|
npm install simplepay-js-sdk
|
|
12
|
+
|
|
13
|
+
# yarn
|
|
14
|
+
yarn add simplepay-js-sdk
|
|
15
|
+
|
|
16
|
+
# pnpm
|
|
17
|
+
pnpm add simplepay-js-sdk
|
|
9
18
|
```
|
|
10
19
|
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
Set the following environment variables in your `.env` file:
|
|
23
|
+
|
|
24
|
+
- `SIMPLEPAY_LOGGER` If it set to `true`, it will log varibles - useful only for debugging.
|
|
25
|
+
- `SIMPLEPAY_MERCHANT_KEY_HUF` Your Simplepay secret merchant key.
|
|
26
|
+
- `SIMPLEPAY_MERCHANT_ID_HUF` Your Simplepay merchant id.
|
|
27
|
+
- `SIMPLEPAY_PRODUCTION` If it set to `true`, it will use production environment, otherwise it will use sandbox environment.
|
|
28
|
+
- `SIMPLEPAY_REDIRECT_URL` The URL of your site, where the customer will be redirected after the payment.
|
|
29
|
+
|
|
11
30
|
## Usage
|
|
12
31
|
|
|
32
|
+
You should create 3 endpoints, to start the payment, get the payment response and handle the IPN.
|
|
33
|
+
|
|
34
|
+
### Start Payment Endpoint
|
|
35
|
+
|
|
13
36
|
```typescript
|
|
14
|
-
import { startPayment
|
|
37
|
+
import { startPayment } from 'simplepay-js-sdk'
|
|
15
38
|
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
39
|
+
try {
|
|
40
|
+
const response = await startPayment({
|
|
41
|
+
orderRef: 'order-12',
|
|
42
|
+
total: 1212,
|
|
43
|
+
currency: 'HUF', // optional, defaults to HUF
|
|
44
|
+
customerEmail: 'rrd@webmania.cc',
|
|
45
|
+
language: 'HU', // optional, defaults to HU
|
|
46
|
+
method: 'CARD', // optional, CARD | WIRE, defaults to CARD
|
|
47
|
+
invoice: {
|
|
48
|
+
name: 'Radharadhya Dasa',
|
|
49
|
+
country: 'HU',
|
|
50
|
+
state: 'Budapest',
|
|
51
|
+
city: 'Budapest',
|
|
52
|
+
zip: '1234',
|
|
53
|
+
address: 'Sehol u. 0',
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
return response
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Payment initiation failed:', error)
|
|
59
|
+
return error
|
|
60
|
+
}
|
|
61
|
+
```
|
|
22
62
|
|
|
23
|
-
|
|
24
|
-
|
|
63
|
+
`response.paymentUrl` will contain the Simplepay payment URL, which you can redirect the customer to.
|
|
64
|
+
|
|
65
|
+
### Get Payment Response Endpoint
|
|
66
|
+
|
|
67
|
+
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`.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { getPaymentResponse } from 'simplepay-js-sdk'
|
|
71
|
+
|
|
72
|
+
// get "r" and "s" from the url the way you do it on your app and framework
|
|
73
|
+
|
|
74
|
+
const response = getPaymentResponse(r, s)
|
|
25
75
|
```
|
|
26
76
|
|
|
27
|
-
|
|
77
|
+
`response` will have the following properties:
|
|
78
|
+
|
|
79
|
+
- `responseCode`: `0` on success, or an error code
|
|
80
|
+
- `transactionId`: the transaction id
|
|
81
|
+
- `event`: the event type: `success` | `fail` | `timeout` | `cancel`
|
|
82
|
+
- `merchantId`: the merchant id
|
|
83
|
+
- `orderId`: the order id
|
|
84
|
+
|
|
85
|
+
### IPN Endpoint
|
|
28
86
|
|
|
29
|
-
|
|
87
|
+
Simplepay will send a `POST` request to the IPN url and you should send a response back.
|
|
88
|
+
At this endpoint you should
|
|
30
89
|
|
|
31
|
-
- `
|
|
32
|
-
- `
|
|
33
|
-
- `
|
|
34
|
-
- `
|
|
90
|
+
- check if the signature is valid - use `checkSignature(ipnBody, signatureHeader, SIMPLEPAY_MERCHANT_KEY_HUF)`
|
|
91
|
+
- add a `receiveDate` property to the received JSON
|
|
92
|
+
- calculate the new signature - use `generateSignature(responseText, SIMPLEPAY_MERCHANT_KEY_HUF)`
|
|
93
|
+
- send the `response` with the new `signature`
|
|
35
94
|
|
|
36
95
|
## License
|
|
37
96
|
|
package/dist/index.js
CHANGED
|
@@ -14,13 +14,22 @@ const generateSignature = (body, merchantKey) => {
|
|
|
14
14
|
const checkSignature = (responseText, signature, merchantKey) => signature === generateSignature(responseText, merchantKey);
|
|
15
15
|
// escaping slashes for the request body to prevent strange SimplePay API errors (eg Missing Signature)
|
|
16
16
|
const prepareRequestBody = (body) => JSON.stringify(body).replace(/\//g, '\\/');
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const MERCHANT_KEY = process.env.
|
|
22
|
-
const MERCHANT_ID = process.env.
|
|
17
|
+
const getSimplePayConfig = () => {
|
|
18
|
+
const SIMPLEPAY_API_URL = 'https://secure.simplepay.hu/payment/v2';
|
|
19
|
+
const SIMPLEPAY_SANDBOX_URL = 'https://sandbox.simplepay.hu/payment/v2/start';
|
|
20
|
+
const SDK_VERSION = '0.3.0';
|
|
21
|
+
const MERCHANT_KEY = process.env.SIMPLEPAY_MERCHANT_KEY_HUF;
|
|
22
|
+
const MERCHANT_ID = process.env.SIMPLEPAY_MERCHANT_ID_HUF;
|
|
23
23
|
const API_URL = process.env.SIMPLEPAY_PRODUCTION === 'true' ? SIMPLEPAY_API_URL : SIMPLEPAY_SANDBOX_URL;
|
|
24
|
+
return {
|
|
25
|
+
MERCHANT_KEY,
|
|
26
|
+
MERCHANT_ID,
|
|
27
|
+
API_URL,
|
|
28
|
+
SDK_VERSION
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const startPayment = async (paymentData) => {
|
|
32
|
+
const { MERCHANT_KEY, MERCHANT_ID, API_URL, SDK_VERSION } = getSimplePayConfig();
|
|
24
33
|
simplepayLogger({ MERCHANT_KEY, MERCHANT_ID, API_URL });
|
|
25
34
|
if (!MERCHANT_KEY || !MERCHANT_ID) {
|
|
26
35
|
throw new Error('Missing SimplePay configuration');
|
|
@@ -33,7 +42,7 @@ const startPayment = async (paymentData) => {
|
|
|
33
42
|
customerEmail: paymentData.customerEmail,
|
|
34
43
|
language: paymentData.language || 'HU',
|
|
35
44
|
sdkVersion: SDK_VERSION,
|
|
36
|
-
methods: ['CARD'],
|
|
45
|
+
methods: [paymentData.method || 'CARD'],
|
|
37
46
|
total: String(paymentData.total),
|
|
38
47
|
timeout: new Date(Date.now() + 30 * 60 * 1000)
|
|
39
48
|
.toISOString()
|
|
@@ -79,9 +88,10 @@ const startPayment = async (paymentData) => {
|
|
|
79
88
|
}
|
|
80
89
|
};
|
|
81
90
|
const getPaymentResponse = (r, signature) => {
|
|
91
|
+
const { MERCHANT_KEY } = getSimplePayConfig();
|
|
82
92
|
// Note: Replaced atob with Buffer for ESM
|
|
83
93
|
const rDecoded = Buffer.from(r, 'base64').toString('utf-8');
|
|
84
|
-
if (!checkSignature(rDecoded, signature,
|
|
94
|
+
if (!checkSignature(rDecoded, signature, MERCHANT_KEY || '')) {
|
|
85
95
|
simplepayLogger({ rDecoded, signature });
|
|
86
96
|
throw new Error('Invalid response signature');
|
|
87
97
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAG3B,sCAAsC;AAEtC,MAAM,eAAe,GAAG,CAAC,GAAG,IAAW,EAAE,EAAE;IACvC,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,MAAM,EAAE,CAAC;QAC1C,OAAM;IACV,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;AACxB,CAAC,CAAA;AAED,MAAM,iBAAiB,GAAG,CAAC,IAAY,EAAE,WAAmB,EAAE,EAAE;IAC5D,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,WAAW,CAAC,IAAI,EAAE,CAAC,CAAA;IAC5D,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACzB,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;AAChC,CAAC,CAAA;AAED,MAAM,cAAc,GAAG,CAAC,YAAoB,EAAE,SAAiB,EAAE,WAAmB,EAAE,EAAE,CACpF,SAAS,KAAK,iBAAiB,CAAC,YAAY,EAAE,WAAW,CAAC,CAAA;AAE9D,uGAAuG;AACvG,MAAM,kBAAkB,GAAG,CAAC,IAA0B,EAAE,EAAE,CACtD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
type PaymentMethod = 'CARD' | 'WIRE';
|
|
1
2
|
interface PaymentData {
|
|
2
3
|
orderRef: string;
|
|
3
4
|
total: number | string;
|
|
4
5
|
customerEmail: string;
|
|
5
6
|
currency?: string;
|
|
6
7
|
language?: string;
|
|
8
|
+
method?: PaymentMethod;
|
|
7
9
|
invoice?: {
|
|
8
10
|
name: string;
|
|
9
11
|
country: string;
|
|
@@ -20,7 +22,7 @@ interface SimplePayRequestBody extends Omit<PaymentData, 'total'> {
|
|
|
20
22
|
salt: string;
|
|
21
23
|
merchant: string;
|
|
22
24
|
sdkVersion: string;
|
|
23
|
-
methods: [
|
|
25
|
+
methods: PaymentMethod[];
|
|
24
26
|
timeout: string;
|
|
25
27
|
url: string;
|
|
26
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simplepay-js-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A Node.js utility for SimplePay payment integration",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "tsc",
|
|
22
22
|
"prepare": "npm run build",
|
|
23
|
-
"test": "vitest"
|
|
23
|
+
"test": "vitest --run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"release": "bash ./release.sh"
|
|
24
26
|
},
|
|
25
27
|
"keywords": [
|
|
26
28
|
"simplepay",
|
|
@@ -31,7 +33,7 @@
|
|
|
31
33
|
"author": "Radharadhya Dasa",
|
|
32
34
|
"license": "MIT",
|
|
33
35
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^
|
|
36
|
+
"@types/node": "^22.0.0",
|
|
35
37
|
"typescript": "^5.0.0",
|
|
36
38
|
"vitest": "^2.1.6"
|
|
37
39
|
},
|
package/src/index.spec.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { checkSignature, generateSignature } from './index'
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { checkSignature, generateSignature, getPaymentResponse, startPayment } from './index'
|
|
3
|
+
import { PaymentData } from './types'
|
|
4
|
+
|
|
5
|
+
const setEnv = () => {
|
|
6
|
+
process.env.SIMPLEPAY_MERCHANT_KEY_HUF = 'testKey'
|
|
7
|
+
process.env.SIMPLEPAY_MERCHANT_ID_HUF = 'testId'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const paymentData = {
|
|
11
|
+
orderRef: 'TEST123',
|
|
12
|
+
customerEmail: 'test@example.com',
|
|
13
|
+
total: 1212
|
|
14
|
+
}
|
|
3
15
|
|
|
4
16
|
describe('generateSignature', () => {
|
|
5
17
|
it('should generate correct signature for sample payload from documentation', () => {
|
|
@@ -48,4 +60,92 @@ describe('checkSignature', () => {
|
|
|
48
60
|
const result = checkSignature(JSON.stringify(response).replace(/\//g, '\\/'), expectedSignature, merchantKey)
|
|
49
61
|
expect(result).toBeTruthy()
|
|
50
62
|
})
|
|
63
|
+
|
|
64
|
+
it('should return false for invalid signature', () => {
|
|
65
|
+
const merchantKey = 'testKey'
|
|
66
|
+
const response = { test: 'data' }
|
|
67
|
+
const invalidSignature = 'invalid-signature'
|
|
68
|
+
|
|
69
|
+
const result = checkSignature(
|
|
70
|
+
JSON.stringify(response),
|
|
71
|
+
invalidSignature,
|
|
72
|
+
merchantKey
|
|
73
|
+
)
|
|
74
|
+
expect(result).toBeFalsy()
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('getPaymentResponse', () => {
|
|
79
|
+
it('should correctly decode and parse valid response', () => {
|
|
80
|
+
// Create a base64 encoded response similar to what SimplePay returns
|
|
81
|
+
const mockResponse = {
|
|
82
|
+
r: 'SUCCESS',
|
|
83
|
+
t: '123456789',
|
|
84
|
+
e: 'PAYMENT',
|
|
85
|
+
m: 'MERCHANT123',
|
|
86
|
+
o: 'ORDER123'
|
|
87
|
+
}
|
|
88
|
+
const encodedResponse = Buffer.from(JSON.stringify(mockResponse)).toString('base64')
|
|
89
|
+
const validSignature = generateSignature(JSON.stringify(mockResponse), process.env.SIMPLEPAY_MERCHANT_KEY_HUF || '')
|
|
90
|
+
|
|
91
|
+
const result = getPaymentResponse(encodedResponse, validSignature)
|
|
92
|
+
|
|
93
|
+
expect(result).toEqual({
|
|
94
|
+
responseCode: 'SUCCESS',
|
|
95
|
+
transactionId: '123456789',
|
|
96
|
+
event: 'PAYMENT',
|
|
97
|
+
merchantId: 'MERCHANT123',
|
|
98
|
+
orderId: 'ORDER123'
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('should throw error for invalid signature', () => {
|
|
103
|
+
const mockResponse = { test: 'data' }
|
|
104
|
+
const encodedResponse = Buffer.from(JSON.stringify(mockResponse)).toString('base64')
|
|
105
|
+
|
|
106
|
+
expect(() =>
|
|
107
|
+
getPaymentResponse(encodedResponse, 'invalid-signature')
|
|
108
|
+
).toThrow('Invalid response signature')
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('startPayment', () => {
|
|
113
|
+
it('should throw error when merchant configuration is missing', async () => {
|
|
114
|
+
await expect(startPayment(paymentData)).rejects.toThrow('Missing SimplePay configuration')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('should handle API errors correctly', async () => {
|
|
118
|
+
setEnv()
|
|
119
|
+
|
|
120
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
121
|
+
ok: true,
|
|
122
|
+
headers: {
|
|
123
|
+
get: vi.fn().mockReturnValue('mockSignature')
|
|
124
|
+
},
|
|
125
|
+
text: vi.fn().mockResolvedValue(JSON.stringify({
|
|
126
|
+
transactionId: '123456',
|
|
127
|
+
total: '1212',
|
|
128
|
+
merchant: 'testId'
|
|
129
|
+
}))
|
|
130
|
+
}) as unknown as typeof fetch
|
|
131
|
+
await expect(startPayment(paymentData)).rejects.toThrow('Invalid response signature')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should successfully start CARD, HUF, HU payment when API returns valid response', async () => {
|
|
135
|
+
setEnv()
|
|
136
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
137
|
+
ok: true,
|
|
138
|
+
headers: {
|
|
139
|
+
get: vi.fn().mockReturnValue('bxSwUc0qn0oABSRcq9uawF6zncFBhRk/AbO4HznYR9Pt5SjocyxAD+9Q4bE44h0J')
|
|
140
|
+
},
|
|
141
|
+
text: vi.fn().mockResolvedValue(JSON.stringify({
|
|
142
|
+
transactionId: '123456',
|
|
143
|
+
total: '1212',
|
|
144
|
+
merchant: 'testId'
|
|
145
|
+
}))
|
|
146
|
+
}) as unknown as typeof fetch
|
|
147
|
+
|
|
148
|
+
await expect(startPayment(paymentData)).resolves.toBeDefined()
|
|
149
|
+
})
|
|
51
150
|
})
|
|
151
|
+
|
package/src/index.ts
CHANGED
|
@@ -23,14 +23,25 @@ const checkSignature = (responseText: string, signature: string, merchantKey: st
|
|
|
23
23
|
const prepareRequestBody = (body: SimplePayRequestBody) =>
|
|
24
24
|
JSON.stringify(body).replace(/\//g, '\\/')
|
|
25
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
26
|
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
27
|
+
const getSimplePayConfig = () => {
|
|
28
|
+
const SIMPLEPAY_API_URL = 'https://secure.simplepay.hu/payment/v2'
|
|
29
|
+
const SIMPLEPAY_SANDBOX_URL = 'https://sandbox.simplepay.hu/payment/v2/start'
|
|
30
|
+
const SDK_VERSION = '0.3.0'
|
|
31
|
+
const MERCHANT_KEY = process.env.SIMPLEPAY_MERCHANT_KEY_HUF
|
|
32
|
+
const MERCHANT_ID = process.env.SIMPLEPAY_MERCHANT_ID_HUF
|
|
33
33
|
const API_URL = process.env.SIMPLEPAY_PRODUCTION === 'true' ? SIMPLEPAY_API_URL : SIMPLEPAY_SANDBOX_URL
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
MERCHANT_KEY,
|
|
37
|
+
MERCHANT_ID,
|
|
38
|
+
API_URL,
|
|
39
|
+
SDK_VERSION
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const startPayment = async (paymentData: PaymentData) => {
|
|
44
|
+
const { MERCHANT_KEY, MERCHANT_ID, API_URL, SDK_VERSION } = getSimplePayConfig()
|
|
34
45
|
simplepayLogger({ MERCHANT_KEY, MERCHANT_ID, API_URL })
|
|
35
46
|
|
|
36
47
|
if (!MERCHANT_KEY || !MERCHANT_ID) {
|
|
@@ -45,7 +56,7 @@ const startPayment = async (paymentData: PaymentData) => {
|
|
|
45
56
|
customerEmail: paymentData.customerEmail,
|
|
46
57
|
language: paymentData.language || 'HU',
|
|
47
58
|
sdkVersion: SDK_VERSION,
|
|
48
|
-
methods: ['CARD'],
|
|
59
|
+
methods: [paymentData.method || 'CARD'],
|
|
49
60
|
total: String(paymentData.total),
|
|
50
61
|
timeout: new Date(Date.now() + 30 * 60 * 1000)
|
|
51
62
|
.toISOString()
|
|
@@ -101,10 +112,11 @@ const startPayment = async (paymentData: PaymentData) => {
|
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
const getPaymentResponse = (r: string, signature: string) => {
|
|
115
|
+
const { MERCHANT_KEY } = getSimplePayConfig()
|
|
104
116
|
// Note: Replaced atob with Buffer for ESM
|
|
105
117
|
const rDecoded = Buffer.from(r, 'base64').toString('utf-8')
|
|
106
118
|
|
|
107
|
-
if (!checkSignature(rDecoded, signature,
|
|
119
|
+
if (!checkSignature(rDecoded, signature, MERCHANT_KEY || '')) {
|
|
108
120
|
simplepayLogger({ rDecoded, signature })
|
|
109
121
|
throw new Error('Invalid response signature')
|
|
110
122
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
type PaymentMethod = 'CARD' | 'WIRE'
|
|
2
|
+
|
|
1
3
|
interface PaymentData {
|
|
2
4
|
orderRef: string
|
|
3
5
|
total: number | string
|
|
4
6
|
customerEmail: string
|
|
5
7
|
currency?: string
|
|
6
8
|
language?: string
|
|
9
|
+
method?: PaymentMethod
|
|
7
10
|
invoice?: {
|
|
8
11
|
name: string
|
|
9
12
|
country: string
|
|
@@ -21,7 +24,7 @@ interface SimplePayRequestBody extends Omit<PaymentData, 'total'> {
|
|
|
21
24
|
salt: string
|
|
22
25
|
merchant: string
|
|
23
26
|
sdkVersion: string
|
|
24
|
-
methods: [
|
|
27
|
+
methods: PaymentMethod[]
|
|
25
28
|
timeout: string
|
|
26
29
|
url: string
|
|
27
30
|
}
|