clink-typescript-sdk-test 0.0.3
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 +189 -0
- package/dist/client/index.d.ts +89 -0
- package/dist/client/index.js +171 -0
- package/dist/client/model.d.ts +399 -0
- package/dist/client/model.js +2 -0
- package/dist/client/request.d.ts +2 -0
- package/dist/client/request.js +83 -0
- package/dist/exceptions/index.d.ts +7 -0
- package/dist/exceptions/index.js +16 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +19 -0
- package/dist/webhook/index.d.ts +31 -0
- package/dist/webhook/index.js +59 -0
- package/dist/webhook/model.d.ts +34 -0
- package/dist/webhook/model.js +2 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# ClinkPay Client SDK Guide
|
|
2
|
+
|
|
3
|
+
Lightweight client for Clink billing APIs, It supports `production` and `sandbox` environments and handles authentication and errors for you.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
- Runtime with `fetch` support (e.g., Node.js 18+).
|
|
7
|
+
- Only `env` values `production` and `sandbox` are supported; any other value throws. the default value is `production`.
|
|
8
|
+
|
|
9
|
+
## Import
|
|
10
|
+
Choose your package management tool (such as npm, yarn, or pnpm) and import it according to your project configuration.
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install clink-typescript-sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
```ts
|
|
18
|
+
import { ClinkPayClient } from 'clink-typescript-sdk';
|
|
19
|
+
|
|
20
|
+
const client = new ClinkPayClient({
|
|
21
|
+
apiKey: 'YOUR_API_KEY',
|
|
22
|
+
env: 'sandbox',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
const session = await client.createCheckoutSession({
|
|
27
|
+
originalAmount: 1999,
|
|
28
|
+
originalCurrency: 'USD',
|
|
29
|
+
successUrl: 'https://merchant.example.com/success',
|
|
30
|
+
cancelUrl: 'https://merchant.example.com/cancel',
|
|
31
|
+
allowPromotionCodes: true,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
console.log(session.sessionId);
|
|
35
|
+
console.log(session.url);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
main();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## API Overview
|
|
42
|
+
- `createCheckoutSession(options)`: Create a checkout session and get the redirect `url`.
|
|
43
|
+
- `getCheckoutSession(sessionId)`: Retrieve checkout session details.
|
|
44
|
+
- `getOrder(orderId)`: Retrieve order details.
|
|
45
|
+
- `getRefund(refundId)`: Retrieve refund details.
|
|
46
|
+
- `getSubscription(subscriptionId)`: Retrieve subscription details.
|
|
47
|
+
- `getInvoice(invoiceId)`: Retrieve subscription invoice details.
|
|
48
|
+
- `customerPortalSession(options)`: Create a customer portal session and get the access link.
|
|
49
|
+
|
|
50
|
+
All methods are asynchronous and throw on errors.
|
|
51
|
+
|
|
52
|
+
## Error Handling
|
|
53
|
+
On non-successful responses or network errors, methods throw exceptions:
|
|
54
|
+
- Business errors throw `ClinkApiError`.
|
|
55
|
+
- Non-business errors throw standard `Error`.
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
```ts
|
|
59
|
+
import { ClinkPayClient } from 'clink-typescript-sdk';
|
|
60
|
+
|
|
61
|
+
const client = new ClinkPayClient({ apiKey: 'YOUR_API_KEY', env: 'sandbox' });
|
|
62
|
+
|
|
63
|
+
async function demo() {
|
|
64
|
+
try {
|
|
65
|
+
const order = await client.getOrder('ord_123');
|
|
66
|
+
console.log(order.status);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
if (e instanceof ClinkApiError) {
|
|
69
|
+
const { code, message } = e;
|
|
70
|
+
// your code here
|
|
71
|
+
}
|
|
72
|
+
// handle other errors
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
demo();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Detailed Examples
|
|
80
|
+
|
|
81
|
+
### Create a Checkout Session
|
|
82
|
+
```ts
|
|
83
|
+
const client = new ClinkPayClient({ apiKey: 'YOUR_API_KEY', env: 'sandbox' });
|
|
84
|
+
const session = await client.createCheckoutSession({
|
|
85
|
+
originalAmount: 4999,
|
|
86
|
+
originalCurrency: 'USD',
|
|
87
|
+
successUrl: 'https://merchant.example.com/success',
|
|
88
|
+
cancelUrl: 'https://merchant.example.com/cancel',
|
|
89
|
+
});
|
|
90
|
+
console.log(session.sessionId);
|
|
91
|
+
console.log(session.url);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Get Checkout Session
|
|
95
|
+
```ts
|
|
96
|
+
const info = await client.getCheckoutSession('sess_123');
|
|
97
|
+
console.log(info.status);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Get Order
|
|
101
|
+
```ts
|
|
102
|
+
const order = await client.getOrder('ord_123');
|
|
103
|
+
console.log(order.amountTotal);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
### Get Refund
|
|
108
|
+
```ts
|
|
109
|
+
const refund = await client.getRefund('rfd_123');
|
|
110
|
+
console.log(refund.status);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Get Subscription
|
|
114
|
+
```ts
|
|
115
|
+
const sub = await client.getSubscription('sub_123');
|
|
116
|
+
console.log(sub.status);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Get Invoice
|
|
120
|
+
```ts
|
|
121
|
+
const invoice = await client.getInvoice('inv_123');
|
|
122
|
+
console.log(invoice.status);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Customer Portal Session
|
|
126
|
+
```ts
|
|
127
|
+
const portal = await client.customerPortalSession({
|
|
128
|
+
customerId: 'cus_123',
|
|
129
|
+
returnUrl: 'https://merchant.example.com/return',
|
|
130
|
+
});
|
|
131
|
+
console.log(portal.url);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Get Product Info
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const product = await client.getProduct('prod_123');
|
|
138
|
+
console.log(product);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Get Product List
|
|
142
|
+
```ts
|
|
143
|
+
const products = await client.getProductList({
|
|
144
|
+
page: 1,
|
|
145
|
+
pageSize: 10,
|
|
146
|
+
});
|
|
147
|
+
console.log(products);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Get Price Info
|
|
151
|
+
```ts
|
|
152
|
+
const price = await client.getPrice('price_123');
|
|
153
|
+
console.log(price);
|
|
154
|
+
```
|
|
155
|
+
### Get Price List
|
|
156
|
+
```ts
|
|
157
|
+
const prices = await client.getPriceList({
|
|
158
|
+
page: 1,
|
|
159
|
+
pageSize: 10,
|
|
160
|
+
});
|
|
161
|
+
console.log(prices);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Get Payment Instrument List
|
|
165
|
+
```ts
|
|
166
|
+
const paymentInstruments = await client.getPaymentInstrumentList({
|
|
167
|
+
customerId: 'cus_123',
|
|
168
|
+
type: 'card',
|
|
169
|
+
});
|
|
170
|
+
console.log(paymentInstruments);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
## webhook
|
|
176
|
+
|
|
177
|
+
### Verify and Get Webhook Event
|
|
178
|
+
```ts
|
|
179
|
+
import { ClinkWebhook } from 'clink-typescript-sdk';
|
|
180
|
+
|
|
181
|
+
const clinkWebhook = new ClinkWebhook({ signatureKey: 'YOUR_SIGNATURE_KEY' });
|
|
182
|
+
// if verify error, the verifyAndGet method will throw error
|
|
183
|
+
const event = clinkWebhook.verifyAndGet({
|
|
184
|
+
timestamp: 'timestamp from request header',
|
|
185
|
+
body: 'body(jsonString) from request body',
|
|
186
|
+
headerSignature: 'signature from request header',
|
|
187
|
+
});
|
|
188
|
+
console.log(event);
|
|
189
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ClinkPayClientConstructorOptions, CreateCheckoutSessionRequest, CreateCheckoutSessionResponse, CustomerPortalSessionRequest, CustomerPortalSessionResponse, GetInvoiceResponse, GetOrderResponse, GetPriceRequest, GetPriceResponse, GetProductResponse, GetSessionResponse, GetSubscriptionResponse, ListResult, PageQuery, PaymentInstrumentRequest, PaymentInstrumentResponse, RefundResponse } from './model';
|
|
2
|
+
export declare class ClinkPayClient {
|
|
3
|
+
private apiKey;
|
|
4
|
+
private baseUrl;
|
|
5
|
+
private requestClient;
|
|
6
|
+
constructor(options: ClinkPayClientConstructorOptions);
|
|
7
|
+
/**
|
|
8
|
+
* create checkout session
|
|
9
|
+
*
|
|
10
|
+
* @param options CreateCheckoutSessionRequest
|
|
11
|
+
* @returns sessionResponse
|
|
12
|
+
*/
|
|
13
|
+
createCheckoutSession(options: CreateCheckoutSessionRequest): Promise<CreateCheckoutSessionResponse>;
|
|
14
|
+
/**
|
|
15
|
+
* get checkout session
|
|
16
|
+
*
|
|
17
|
+
* @param sessionId checkout session id
|
|
18
|
+
* @returns checkout session response
|
|
19
|
+
*/
|
|
20
|
+
getCheckoutSession(sessionId: string): Promise<GetSessionResponse.SessionInfo>;
|
|
21
|
+
/**
|
|
22
|
+
* get order info
|
|
23
|
+
*
|
|
24
|
+
* @param orderId order id
|
|
25
|
+
* @returns order info response
|
|
26
|
+
*/
|
|
27
|
+
getOrder(orderId: string): Promise<GetOrderResponse>;
|
|
28
|
+
/**
|
|
29
|
+
* get refund info
|
|
30
|
+
*
|
|
31
|
+
* @param refundId refund id
|
|
32
|
+
* @returns refund info response
|
|
33
|
+
*/
|
|
34
|
+
getRefund(refundId: string): Promise<RefundResponse>;
|
|
35
|
+
/**
|
|
36
|
+
* get subscription info
|
|
37
|
+
*
|
|
38
|
+
* @param subscriptionId subscription id
|
|
39
|
+
* @returns subscription info response
|
|
40
|
+
*/
|
|
41
|
+
getSubscription(subscriptionId: string): Promise<GetSubscriptionResponse.Data>;
|
|
42
|
+
/**
|
|
43
|
+
* get invoice info
|
|
44
|
+
*
|
|
45
|
+
* @param invoiceId invoice id
|
|
46
|
+
* @returns invoice info response
|
|
47
|
+
*/
|
|
48
|
+
getInvoice(invoiceId: string): Promise<GetInvoiceResponse.Data>;
|
|
49
|
+
/**
|
|
50
|
+
* create customer portal session
|
|
51
|
+
*
|
|
52
|
+
* @param options CustomerPortalSessionRequest
|
|
53
|
+
* @returns customer portal session response
|
|
54
|
+
*/
|
|
55
|
+
customerPortalSession(options: CustomerPortalSessionRequest): Promise<CustomerPortalSessionResponse>;
|
|
56
|
+
/**
|
|
57
|
+
* get product info
|
|
58
|
+
*
|
|
59
|
+
* @param productId product id
|
|
60
|
+
* @returns product info response
|
|
61
|
+
*/
|
|
62
|
+
getProduct(productId: string): Promise<GetProductResponse>;
|
|
63
|
+
/**
|
|
64
|
+
* get products list
|
|
65
|
+
*
|
|
66
|
+
* @returns list of product info response
|
|
67
|
+
*/
|
|
68
|
+
getProductList(params?: PageQuery): Promise<ListResult<GetProductResponse>>;
|
|
69
|
+
/**
|
|
70
|
+
* get price info
|
|
71
|
+
*
|
|
72
|
+
* @param priceId price id
|
|
73
|
+
* @returns price info response
|
|
74
|
+
*/
|
|
75
|
+
getPrice(priceId: string): Promise<GetPriceResponse>;
|
|
76
|
+
/**
|
|
77
|
+
* get all prices
|
|
78
|
+
*
|
|
79
|
+
* @returns list of price info response
|
|
80
|
+
*/
|
|
81
|
+
getPriceList(params: GetPriceRequest): Promise<ListResult<GetPriceResponse>>;
|
|
82
|
+
/**
|
|
83
|
+
* get payment instrument list
|
|
84
|
+
*
|
|
85
|
+
* @param params PaymentInstrumentRequest
|
|
86
|
+
* @returns list of payment instrument response. if not found, return empty list
|
|
87
|
+
*/
|
|
88
|
+
getPaymentInstrumentList(params: PaymentInstrumentRequest): Promise<PaymentInstrumentResponse[]>;
|
|
89
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ClinkPayClient = void 0;
|
|
13
|
+
const request_1 = require("./request");
|
|
14
|
+
const supportedEnvs = ['production', 'sandbox'];
|
|
15
|
+
class ClinkPayClient {
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.baseUrl = '';
|
|
18
|
+
const { apiKey, env = 'production' } = options;
|
|
19
|
+
if (!supportedEnvs.includes(env)) {
|
|
20
|
+
throw new Error(`env ${env} is not supported. supported envs are ${supportedEnvs}`);
|
|
21
|
+
}
|
|
22
|
+
this.apiKey = apiKey;
|
|
23
|
+
switch (env) {
|
|
24
|
+
case 'production': {
|
|
25
|
+
this.baseUrl = 'https://api.clinkbill.com/api';
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case 'sandbox': {
|
|
29
|
+
this.baseUrl = 'https://uat-api.clinkbill.com/api';
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
this.requestClient = (0, request_1.createRequestClient)(this.baseUrl, this.apiKey);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* create checkout session
|
|
37
|
+
*
|
|
38
|
+
* @param options CreateCheckoutSessionRequest
|
|
39
|
+
* @returns sessionResponse
|
|
40
|
+
*/
|
|
41
|
+
createCheckoutSession(options) {
|
|
42
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
return this.requestClient.Post('/checkout/session', options);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* get checkout session
|
|
48
|
+
*
|
|
49
|
+
* @param sessionId checkout session id
|
|
50
|
+
* @returns checkout session response
|
|
51
|
+
*/
|
|
52
|
+
getCheckoutSession(sessionId) {
|
|
53
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
return this.requestClient.Get(`/checkout/session/${sessionId}`);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* get order info
|
|
59
|
+
*
|
|
60
|
+
* @param orderId order id
|
|
61
|
+
* @returns order info response
|
|
62
|
+
*/
|
|
63
|
+
getOrder(orderId) {
|
|
64
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
return this.requestClient.Get(`/order/${orderId}`);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* get refund info
|
|
70
|
+
*
|
|
71
|
+
* @param refundId refund id
|
|
72
|
+
* @returns refund info response
|
|
73
|
+
*/
|
|
74
|
+
getRefund(refundId) {
|
|
75
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
return this.requestClient.Get(`/refund/${refundId}`);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* get subscription info
|
|
81
|
+
*
|
|
82
|
+
* @param subscriptionId subscription id
|
|
83
|
+
* @returns subscription info response
|
|
84
|
+
*/
|
|
85
|
+
getSubscription(subscriptionId) {
|
|
86
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
87
|
+
return this.requestClient.Get(`/subscription/${subscriptionId}`);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* get invoice info
|
|
92
|
+
*
|
|
93
|
+
* @param invoiceId invoice id
|
|
94
|
+
* @returns invoice info response
|
|
95
|
+
*/
|
|
96
|
+
getInvoice(invoiceId) {
|
|
97
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
98
|
+
return this.requestClient.Get(`/subscription/invoice/${invoiceId}`);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* create customer portal session
|
|
103
|
+
*
|
|
104
|
+
* @param options CustomerPortalSessionRequest
|
|
105
|
+
* @returns customer portal session response
|
|
106
|
+
*/
|
|
107
|
+
customerPortalSession(options) {
|
|
108
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
109
|
+
return this.requestClient.Post('/billing/session', options);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* get product info
|
|
114
|
+
*
|
|
115
|
+
* @param productId product id
|
|
116
|
+
* @returns product info response
|
|
117
|
+
*/
|
|
118
|
+
getProduct(productId) {
|
|
119
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
120
|
+
return this.requestClient.Get(`/product/${productId}`);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* get products list
|
|
125
|
+
*
|
|
126
|
+
* @returns list of product info response
|
|
127
|
+
*/
|
|
128
|
+
getProductList(params) {
|
|
129
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
130
|
+
return this.requestClient.Get(`/product`, {
|
|
131
|
+
params,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* get price info
|
|
137
|
+
*
|
|
138
|
+
* @param priceId price id
|
|
139
|
+
* @returns price info response
|
|
140
|
+
*/
|
|
141
|
+
getPrice(priceId) {
|
|
142
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
143
|
+
return this.requestClient.Get(`/price/${priceId}`);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* get all prices
|
|
148
|
+
*
|
|
149
|
+
* @returns list of price info response
|
|
150
|
+
*/
|
|
151
|
+
getPriceList(params) {
|
|
152
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
153
|
+
return this.requestClient.Get(`/price`, {
|
|
154
|
+
params,
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* get payment instrument list
|
|
160
|
+
*
|
|
161
|
+
* @param params PaymentInstrumentRequest
|
|
162
|
+
* @returns list of payment instrument response. if not found, return empty list
|
|
163
|
+
*/
|
|
164
|
+
getPaymentInstrumentList(params) {
|
|
165
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
166
|
+
const { customerId, type } = params;
|
|
167
|
+
return this.requestClient.Get(`/payment_instrument/list?customerId=${customerId}&type=${type}`);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.ClinkPayClient = ClinkPayClient;
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
export type ClinkEnv = 'sandbox' | 'production';
|
|
2
|
+
export interface ClinkPayClientConstructorOptions {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/**
|
|
5
|
+
* default to production
|
|
6
|
+
*/
|
|
7
|
+
env?: ClinkEnv;
|
|
8
|
+
}
|
|
9
|
+
export interface CommonResponse<T = any> {
|
|
10
|
+
code: number;
|
|
11
|
+
msg: string;
|
|
12
|
+
data: T;
|
|
13
|
+
}
|
|
14
|
+
export interface CustomerPortalSessionRequest {
|
|
15
|
+
/**
|
|
16
|
+
* Existing customer's unique identifier
|
|
17
|
+
*/
|
|
18
|
+
customerId: string;
|
|
19
|
+
/**
|
|
20
|
+
* Redirect URL when customer click on 'Return to Merchant'
|
|
21
|
+
*/
|
|
22
|
+
returnUrl?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface CustomerPortalSessionResponse {
|
|
25
|
+
/**
|
|
26
|
+
* The magic link for customer to access the portal
|
|
27
|
+
*/
|
|
28
|
+
url: string;
|
|
29
|
+
/**
|
|
30
|
+
* Link expiry time
|
|
31
|
+
*/
|
|
32
|
+
expiresAt: number;
|
|
33
|
+
}
|
|
34
|
+
export interface SessionProduct {
|
|
35
|
+
/**
|
|
36
|
+
* Display name of the product shown to customers during checkout
|
|
37
|
+
*/
|
|
38
|
+
name: string;
|
|
39
|
+
/**
|
|
40
|
+
* Price per unit in the specified currency (must be greater than 0)
|
|
41
|
+
*/
|
|
42
|
+
unitAmount: number;
|
|
43
|
+
/**
|
|
44
|
+
* Number of units to be purchased (minimum 1)
|
|
45
|
+
*/
|
|
46
|
+
quantity?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Three-letter ISO currency code for the price (e.g., USD, EUR, GBP)
|
|
49
|
+
*/
|
|
50
|
+
currency?: string;
|
|
51
|
+
/**
|
|
52
|
+
* URL of the product image to display during checkout
|
|
53
|
+
*/
|
|
54
|
+
imageUrl?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface CreateCheckoutSessionRequest {
|
|
57
|
+
/**
|
|
58
|
+
* Total transaction amount in the specified currency (must be greater than 0)
|
|
59
|
+
*/
|
|
60
|
+
originalAmount: number;
|
|
61
|
+
/**
|
|
62
|
+
* Three-letter ISO currency code for the transaction (e.g., USD, EUR, GBP)
|
|
63
|
+
*/
|
|
64
|
+
originalCurrency: string;
|
|
65
|
+
/**
|
|
66
|
+
* Existing customer's unique identifier (required if customerEmail is not provided)
|
|
67
|
+
*/
|
|
68
|
+
customerId?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Customer's email address. A new customer account will be created if the email is not associated with an existing customer
|
|
71
|
+
*/
|
|
72
|
+
customerEmail?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Your internal reference ID for tracking (non-unique identifier, does not guarantee idempotency)
|
|
75
|
+
*/
|
|
76
|
+
merchantReferenceId?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Unique identifier of the predefined price configuration from your dashboard
|
|
79
|
+
*/
|
|
80
|
+
priceId?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Unique identifier of the product configured in your dashboard
|
|
83
|
+
*/
|
|
84
|
+
productId?: string;
|
|
85
|
+
/**
|
|
86
|
+
* URL where customers will be redirected after successful payment completion
|
|
87
|
+
*/
|
|
88
|
+
successUrl?: string;
|
|
89
|
+
/**
|
|
90
|
+
* URL where customers will be redirected if they cancel the checkout process
|
|
91
|
+
*/
|
|
92
|
+
cancelUrl?: string;
|
|
93
|
+
/**
|
|
94
|
+
* List of product pricing details for one-time purchases. Use this when creating transactions without pre-configured products
|
|
95
|
+
*/
|
|
96
|
+
priceDataList?: SessionProduct[];
|
|
97
|
+
/**
|
|
98
|
+
* Whether to allow promotion codes to be applied to the transaction (default: false)
|
|
99
|
+
*/
|
|
100
|
+
allowPromotionCodes?: boolean;
|
|
101
|
+
}
|
|
102
|
+
export interface CreateCheckoutSessionResponse {
|
|
103
|
+
/**
|
|
104
|
+
* Unique identifier for the created checkout session
|
|
105
|
+
*/
|
|
106
|
+
sessionId: string;
|
|
107
|
+
/**
|
|
108
|
+
* Token value required for payment processing. This token is used to verify the payment status and complete the transaction
|
|
109
|
+
*/
|
|
110
|
+
tokenValue: string;
|
|
111
|
+
/**
|
|
112
|
+
* Customer's unique identifier associated with the checkout session
|
|
113
|
+
*/
|
|
114
|
+
customerId: string;
|
|
115
|
+
/**
|
|
116
|
+
* Original transaction amount in the specified currency
|
|
117
|
+
*/
|
|
118
|
+
originalAmount: number;
|
|
119
|
+
/**
|
|
120
|
+
* Three-letter ISO currency code for the transaction (e.g., USD, EUR, GBP)
|
|
121
|
+
*/
|
|
122
|
+
originalCurrency: string;
|
|
123
|
+
/**
|
|
124
|
+
* Symbol of the currency (e.g., $, €, £)
|
|
125
|
+
*/
|
|
126
|
+
originalCurrencySymbol: string;
|
|
127
|
+
/**
|
|
128
|
+
* checkout url
|
|
129
|
+
*/
|
|
130
|
+
url: string;
|
|
131
|
+
merchantReferenceId: string;
|
|
132
|
+
/**
|
|
133
|
+
* Expiration time
|
|
134
|
+
*/
|
|
135
|
+
expireTime: string;
|
|
136
|
+
}
|
|
137
|
+
export declare namespace GetSessionResponse {
|
|
138
|
+
interface PriceList {
|
|
139
|
+
amount: number;
|
|
140
|
+
currency: string;
|
|
141
|
+
exchangeRate: number;
|
|
142
|
+
}
|
|
143
|
+
interface Recurring {
|
|
144
|
+
freeTrialDays: number;
|
|
145
|
+
interval: string;
|
|
146
|
+
}
|
|
147
|
+
interface Price {
|
|
148
|
+
priceId: string;
|
|
149
|
+
priceList: PriceList[];
|
|
150
|
+
recurring: Recurring;
|
|
151
|
+
}
|
|
152
|
+
interface Customer {
|
|
153
|
+
customerId: string;
|
|
154
|
+
email: string;
|
|
155
|
+
}
|
|
156
|
+
interface Product {
|
|
157
|
+
productId: string;
|
|
158
|
+
productName: string;
|
|
159
|
+
}
|
|
160
|
+
interface SessionInfo {
|
|
161
|
+
sessionId: string;
|
|
162
|
+
token: string;
|
|
163
|
+
status: string;
|
|
164
|
+
paymentStatus: string;
|
|
165
|
+
amountSubtotal: number;
|
|
166
|
+
amountTotal?: number;
|
|
167
|
+
originalCurrency: string;
|
|
168
|
+
paymentCurrency?: string;
|
|
169
|
+
subscriptionId?: string;
|
|
170
|
+
invoiceId?: string;
|
|
171
|
+
orderId?: string;
|
|
172
|
+
price: Price;
|
|
173
|
+
merchantReferenceId?: string;
|
|
174
|
+
customer: Customer;
|
|
175
|
+
locale: string;
|
|
176
|
+
cancelUrl?: string;
|
|
177
|
+
successUrl?: string;
|
|
178
|
+
created: string;
|
|
179
|
+
expire: string;
|
|
180
|
+
product: Product;
|
|
181
|
+
priceDataList?: SessionProduct[];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
export interface GetOrderResponse {
|
|
185
|
+
orderId: string;
|
|
186
|
+
merchantReferenceId?: string;
|
|
187
|
+
invoiceId: string;
|
|
188
|
+
customerId: string;
|
|
189
|
+
productId: string;
|
|
190
|
+
priceId: string;
|
|
191
|
+
priceDataList?: SessionProduct[];
|
|
192
|
+
paymentMethod: {
|
|
193
|
+
paymentMethodType: string;
|
|
194
|
+
paymentInstrumentId: string;
|
|
195
|
+
};
|
|
196
|
+
amountSubtotal: number;
|
|
197
|
+
amountTotal: number;
|
|
198
|
+
paymentCurrency: string;
|
|
199
|
+
originalCurrency: string;
|
|
200
|
+
status: string;
|
|
201
|
+
}
|
|
202
|
+
export declare namespace GetSubscriptionResponse {
|
|
203
|
+
interface Recurring {
|
|
204
|
+
interval: string;
|
|
205
|
+
trialPeriodDays: number;
|
|
206
|
+
pricingModel: string;
|
|
207
|
+
tiersMode?: any;
|
|
208
|
+
}
|
|
209
|
+
interface Price {
|
|
210
|
+
productId: string;
|
|
211
|
+
productName: string;
|
|
212
|
+
priceId: string;
|
|
213
|
+
priceSnapshotId: string;
|
|
214
|
+
unitAmount: string;
|
|
215
|
+
quantity: number;
|
|
216
|
+
recurring: Recurring;
|
|
217
|
+
}
|
|
218
|
+
interface RecurringInvoiceItem {
|
|
219
|
+
invoiceItemId: string;
|
|
220
|
+
amount: string;
|
|
221
|
+
currency: string;
|
|
222
|
+
description: string;
|
|
223
|
+
periodStart: number;
|
|
224
|
+
periodEnd: number;
|
|
225
|
+
proration?: number;
|
|
226
|
+
price: Price;
|
|
227
|
+
}
|
|
228
|
+
interface Data {
|
|
229
|
+
merchantReference?: any;
|
|
230
|
+
subscriptionId: string;
|
|
231
|
+
customerId: string;
|
|
232
|
+
productId: string;
|
|
233
|
+
priceId: string;
|
|
234
|
+
priceSnapshotId: string;
|
|
235
|
+
createTime: number;
|
|
236
|
+
quantity: number;
|
|
237
|
+
paymentMethodType: string;
|
|
238
|
+
paymentInstrumentId: string;
|
|
239
|
+
trialStart?: string;
|
|
240
|
+
trialEnd?: string;
|
|
241
|
+
currentPeriodStart: number;
|
|
242
|
+
currentPeriodEnd: number;
|
|
243
|
+
cancelAt?: string;
|
|
244
|
+
cancelAtPeriodEnd?: string;
|
|
245
|
+
canceledAt?: string;
|
|
246
|
+
cancelReason?: string;
|
|
247
|
+
status: string;
|
|
248
|
+
billing: string;
|
|
249
|
+
currency: string;
|
|
250
|
+
recurringInvoiceItem: RecurringInvoiceItem;
|
|
251
|
+
upcomingInvoiceItem?: RecurringInvoiceItem;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
export declare namespace GetInvoiceResponse {
|
|
255
|
+
interface Recurring {
|
|
256
|
+
interval: string;
|
|
257
|
+
trialPeriodDays: number;
|
|
258
|
+
pricingModel: string;
|
|
259
|
+
tiersMode?: any;
|
|
260
|
+
}
|
|
261
|
+
interface Price {
|
|
262
|
+
productId: string;
|
|
263
|
+
productName: string;
|
|
264
|
+
priceId: string;
|
|
265
|
+
priceSnapshotId: string;
|
|
266
|
+
unitAmount: string;
|
|
267
|
+
quantity: number;
|
|
268
|
+
recurring: Recurring;
|
|
269
|
+
}
|
|
270
|
+
interface Item {
|
|
271
|
+
invoiceItemId: string;
|
|
272
|
+
amount: string;
|
|
273
|
+
currency: string;
|
|
274
|
+
description: string;
|
|
275
|
+
periodStart: number;
|
|
276
|
+
periodEnd: number;
|
|
277
|
+
proration?: any;
|
|
278
|
+
price: Price;
|
|
279
|
+
}
|
|
280
|
+
interface Data {
|
|
281
|
+
invoiceId: string;
|
|
282
|
+
subscriptionId: string;
|
|
283
|
+
orderId: string;
|
|
284
|
+
customerId: string;
|
|
285
|
+
merchantId: string;
|
|
286
|
+
status: string;
|
|
287
|
+
createTime: number;
|
|
288
|
+
currentPeriodStart: number;
|
|
289
|
+
currentPeriodEnd: number;
|
|
290
|
+
originalAmount: string;
|
|
291
|
+
paymentAmount: string;
|
|
292
|
+
originalCurrency: string;
|
|
293
|
+
billing: string;
|
|
294
|
+
items: Item[];
|
|
295
|
+
discount?: number;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
export interface RefundResponse {
|
|
299
|
+
createTime: number;
|
|
300
|
+
refundId: string;
|
|
301
|
+
orderId: string;
|
|
302
|
+
customerId: string;
|
|
303
|
+
refundAmount: number;
|
|
304
|
+
refundCurrency: string;
|
|
305
|
+
status: string;
|
|
306
|
+
refundReason: string;
|
|
307
|
+
paymentInstrumentId: string;
|
|
308
|
+
metadata?: string;
|
|
309
|
+
}
|
|
310
|
+
export interface ListResult<T> {
|
|
311
|
+
total: number;
|
|
312
|
+
rows: T[];
|
|
313
|
+
}
|
|
314
|
+
export interface PageQuery {
|
|
315
|
+
/**
|
|
316
|
+
* Page number
|
|
317
|
+
*/
|
|
318
|
+
page?: number;
|
|
319
|
+
/**
|
|
320
|
+
* Number of items per page
|
|
321
|
+
*/
|
|
322
|
+
pageSize?: number;
|
|
323
|
+
}
|
|
324
|
+
export interface GetProductResponse {
|
|
325
|
+
productId: string;
|
|
326
|
+
name: string;
|
|
327
|
+
active: boolean;
|
|
328
|
+
description: string;
|
|
329
|
+
defaultPrice: string;
|
|
330
|
+
image: string;
|
|
331
|
+
taxCategory: string;
|
|
332
|
+
createTime: number;
|
|
333
|
+
updateTime?: string;
|
|
334
|
+
}
|
|
335
|
+
export interface GetPriceRequest extends PageQuery {
|
|
336
|
+
/**
|
|
337
|
+
* Product ID
|
|
338
|
+
*/
|
|
339
|
+
productId: string;
|
|
340
|
+
/**
|
|
341
|
+
* Is it available for purchase
|
|
342
|
+
* @requires active
|
|
343
|
+
*/
|
|
344
|
+
active: boolean;
|
|
345
|
+
}
|
|
346
|
+
export interface GetPriceResponse {
|
|
347
|
+
priceId: string;
|
|
348
|
+
productId: string;
|
|
349
|
+
currency: string;
|
|
350
|
+
unitAmount: number;
|
|
351
|
+
priceType: string;
|
|
352
|
+
recurring: {
|
|
353
|
+
interval: string;
|
|
354
|
+
tiers?: any;
|
|
355
|
+
interval_count?: any;
|
|
356
|
+
trial_period_days: number;
|
|
357
|
+
pricing_model: string;
|
|
358
|
+
tiers_mode?: any;
|
|
359
|
+
transform_quantity?: any;
|
|
360
|
+
[key: string]: any;
|
|
361
|
+
};
|
|
362
|
+
active: boolean;
|
|
363
|
+
createTime: number;
|
|
364
|
+
}
|
|
365
|
+
export type PaymentType = 'CARD' | 'CASHAPP' | 'KAKAO' | 'WECHAT' | string;
|
|
366
|
+
export interface PaymentInstrumentRequest {
|
|
367
|
+
customerId: string;
|
|
368
|
+
type: PaymentType;
|
|
369
|
+
}
|
|
370
|
+
export interface PaymentInstrumentResponse {
|
|
371
|
+
id: string;
|
|
372
|
+
customerId: string;
|
|
373
|
+
type: string;
|
|
374
|
+
card?: Card;
|
|
375
|
+
wallet?: Wallet;
|
|
376
|
+
created: number;
|
|
377
|
+
}
|
|
378
|
+
export interface Wallet {
|
|
379
|
+
accountTag: string;
|
|
380
|
+
}
|
|
381
|
+
export interface BillingAddress {
|
|
382
|
+
city?: string;
|
|
383
|
+
country: string;
|
|
384
|
+
line1?: string;
|
|
385
|
+
line2?: string;
|
|
386
|
+
postalCode?: string;
|
|
387
|
+
state?: string;
|
|
388
|
+
}
|
|
389
|
+
export interface Card {
|
|
390
|
+
last4: string;
|
|
391
|
+
name: string;
|
|
392
|
+
expiryYear: string;
|
|
393
|
+
expiryMonth: string;
|
|
394
|
+
scheme: string;
|
|
395
|
+
funding: string;
|
|
396
|
+
issuerRegion: string;
|
|
397
|
+
issuerBank: string;
|
|
398
|
+
billingAddress: BillingAddress;
|
|
399
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare function createRequestClient(baseURL: string, apiKey: string): import("alova").Alova<import("alova").AlovaGenerics<any, any, import("alova/fetch").FetchRequestInit, Response, Headers, import("alova").AlovaDefaultCacheAdapter, import("alova").AlovaDefaultCacheAdapter, import("alova").StatesExport<any>>>;
|
|
2
|
+
export type RequestClient = ReturnType<typeof createRequestClient>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
22
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.createRequestClient = createRequestClient;
|
|
27
|
+
const alova_1 = require("alova");
|
|
28
|
+
const fetch_1 = __importDefault(require("alova/fetch"));
|
|
29
|
+
const exceptions_1 = require("../exceptions");
|
|
30
|
+
/**
|
|
31
|
+
* Success code for API requests
|
|
32
|
+
*/
|
|
33
|
+
const SUCCESS_CODE = 200;
|
|
34
|
+
function createRequestClient(baseURL, apiKey) {
|
|
35
|
+
const alovaInstance = (0, alova_1.createAlova)({
|
|
36
|
+
baseURL,
|
|
37
|
+
requestAdapter: (0, fetch_1.default)(),
|
|
38
|
+
responded: {
|
|
39
|
+
onError: (error) => {
|
|
40
|
+
throw error;
|
|
41
|
+
},
|
|
42
|
+
onSuccess: (response) => __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
// Most likely due to network issues causing errors
|
|
44
|
+
if (response.status >= 400) {
|
|
45
|
+
try {
|
|
46
|
+
const json = (yield response.json());
|
|
47
|
+
throw new exceptions_1.ClinkApiError(json.code, json.msg);
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
if (e instanceof exceptions_1.ClinkApiError) {
|
|
51
|
+
throw e;
|
|
52
|
+
}
|
|
53
|
+
// throw if response.json failed
|
|
54
|
+
throw new Error(response.statusText);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const json = (yield response.json());
|
|
58
|
+
if (json.code !== SUCCESS_CODE) {
|
|
59
|
+
throw new exceptions_1.ClinkApiError(json.code, json.msg);
|
|
60
|
+
}
|
|
61
|
+
const { code, msg, data } = json, rest = __rest(json, ["code", "msg", "data"]);
|
|
62
|
+
// Compatible with non-standard return formats for lists
|
|
63
|
+
if (data !== undefined) {
|
|
64
|
+
return data;
|
|
65
|
+
}
|
|
66
|
+
return rest;
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
beforeRequest(method) {
|
|
70
|
+
method.config.headers['Content-Type'] = 'application/json';
|
|
71
|
+
// locale en
|
|
72
|
+
method.config.headers['Accept-Language'] = 'en_US';
|
|
73
|
+
method.config.headers['Content-Language'] = 'en_US';
|
|
74
|
+
// add secret key to header
|
|
75
|
+
method.config.headers['X-API-Key'] = apiKey;
|
|
76
|
+
method.config.headers['X-TIMESTAMP'] = Date.now().toString();
|
|
77
|
+
},
|
|
78
|
+
cacheFor: null,
|
|
79
|
+
shareRequest: false,
|
|
80
|
+
timeout: 10000,
|
|
81
|
+
});
|
|
82
|
+
return alovaInstance;
|
|
83
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClinkWebhookSignatureError = exports.ClinkApiError = void 0;
|
|
4
|
+
class ClinkApiError extends Error {
|
|
5
|
+
constructor(code, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.ClinkApiError = ClinkApiError;
|
|
11
|
+
class ClinkWebhookSignatureError extends Error {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.ClinkWebhookSignatureError = ClinkWebhookSignatureError;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./client"), exports);
|
|
18
|
+
__exportStar(require("./exceptions"), exports);
|
|
19
|
+
__exportStar(require("./webhook"), exports);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ClinkWebhookConstructorOptions, ClinkWebhookEvent, ClinkWebhookSignatureOptions, WithHeaderSignature } from './model';
|
|
2
|
+
export declare class ClinkWebhook {
|
|
3
|
+
private signatureKey;
|
|
4
|
+
constructor(options: ClinkWebhookConstructorOptions);
|
|
5
|
+
/**
|
|
6
|
+
* generate webhook signature
|
|
7
|
+
*
|
|
8
|
+
* @param options { timestamp: string, body: string }
|
|
9
|
+
* @returns string
|
|
10
|
+
*/
|
|
11
|
+
signature(options: ClinkWebhookSignatureOptions): string;
|
|
12
|
+
/**
|
|
13
|
+
* verify webhook signature
|
|
14
|
+
*
|
|
15
|
+
* @param options { timestamp: string, body: string, headerSignature: string }
|
|
16
|
+
* @returns boolean
|
|
17
|
+
*/
|
|
18
|
+
verifySignature(options: WithHeaderSignature): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* verify webhook signature and return event data
|
|
21
|
+
*
|
|
22
|
+
* @param options { timestamp: string, body: string, headerSignature: string }
|
|
23
|
+
* @throws ClinkWebhookSignatureError when signature verify failed
|
|
24
|
+
* @returns ClinkWebhookEvent<T>
|
|
25
|
+
*/
|
|
26
|
+
verifyAndGet<T = any>(options: {
|
|
27
|
+
timestamp: string;
|
|
28
|
+
body: string;
|
|
29
|
+
headerSignature: string;
|
|
30
|
+
}): ClinkWebhookEvent<T>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ClinkWebhook = void 0;
|
|
7
|
+
const crypto_js_1 = __importDefault(require("crypto-js"));
|
|
8
|
+
const exceptions_1 = require("../exceptions");
|
|
9
|
+
class ClinkWebhook {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
const { signatureKey } = options;
|
|
12
|
+
this.signatureKey = signatureKey;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* generate webhook signature
|
|
16
|
+
*
|
|
17
|
+
* @param options { timestamp: string, body: string }
|
|
18
|
+
* @returns string
|
|
19
|
+
*/
|
|
20
|
+
signature(options) {
|
|
21
|
+
const { timestamp, body } = options;
|
|
22
|
+
const payload = `${timestamp}.${body}`;
|
|
23
|
+
const hash = crypto_js_1.default.HmacSHA256(payload, this.signatureKey);
|
|
24
|
+
return crypto_js_1.default.enc.Hex.stringify(hash);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* verify webhook signature
|
|
28
|
+
*
|
|
29
|
+
* @param options { timestamp: string, body: string, headerSignature: string }
|
|
30
|
+
* @returns boolean
|
|
31
|
+
*/
|
|
32
|
+
verifySignature(options) {
|
|
33
|
+
const { timestamp, body, headerSignature } = options;
|
|
34
|
+
const payload = `${timestamp}.${body}`;
|
|
35
|
+
const hash = crypto_js_1.default.HmacSHA256(payload, this.signatureKey);
|
|
36
|
+
const computedSignature = crypto_js_1.default.enc.Hex.stringify(hash);
|
|
37
|
+
return computedSignature === headerSignature;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* verify webhook signature and return event data
|
|
41
|
+
*
|
|
42
|
+
* @param options { timestamp: string, body: string, headerSignature: string }
|
|
43
|
+
* @throws ClinkWebhookSignatureError when signature verify failed
|
|
44
|
+
* @returns ClinkWebhookEvent<T>
|
|
45
|
+
*/
|
|
46
|
+
verifyAndGet(options) {
|
|
47
|
+
const { timestamp, body, headerSignature } = options;
|
|
48
|
+
const isSignatureValid = this.verifySignature({
|
|
49
|
+
timestamp,
|
|
50
|
+
body,
|
|
51
|
+
headerSignature,
|
|
52
|
+
});
|
|
53
|
+
if (!isSignatureValid) {
|
|
54
|
+
throw new exceptions_1.ClinkWebhookSignatureError('webhook signature verify failed');
|
|
55
|
+
}
|
|
56
|
+
return JSON.parse(body);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.ClinkWebhook = ClinkWebhook;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface ClinkWebhookConstructorOptions {
|
|
2
|
+
/**
|
|
3
|
+
* you can get signatureKey from dashboard -> Developers -> webhook
|
|
4
|
+
*/
|
|
5
|
+
signatureKey: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* common webhook event
|
|
9
|
+
*/
|
|
10
|
+
export interface ClinkWebhookEvent<T = any> {
|
|
11
|
+
id: string;
|
|
12
|
+
type: string;
|
|
13
|
+
object: string;
|
|
14
|
+
created: string;
|
|
15
|
+
data: {
|
|
16
|
+
object: T;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface ClinkWebhookSignatureOptions {
|
|
20
|
+
/**
|
|
21
|
+
* timestamp from header X-Clink-Timestamp
|
|
22
|
+
*/
|
|
23
|
+
timestamp: string;
|
|
24
|
+
/**
|
|
25
|
+
* body from request body
|
|
26
|
+
*/
|
|
27
|
+
body: string;
|
|
28
|
+
}
|
|
29
|
+
export interface WithHeaderSignature extends ClinkWebhookSignatureOptions {
|
|
30
|
+
/**
|
|
31
|
+
* signature from header X-Clink-Signature
|
|
32
|
+
*/
|
|
33
|
+
headerSignature: string;
|
|
34
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clink-typescript-sdk-test",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "Clink-test",
|
|
6
|
+
"email": "support@clinkbill.com",
|
|
7
|
+
"url": "https://www.clinkbill.com/"
|
|
8
|
+
},
|
|
9
|
+
"description": "npm package for clinkbill",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"clinkbill",
|
|
12
|
+
"clinkpay"
|
|
13
|
+
],
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"types": "dist/index.d.ts",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"test:api": "vitest test/api.test.ts",
|
|
19
|
+
"test:webhook": "vitest test/webhook.test.ts",
|
|
20
|
+
"test": "vitest */*.test.ts",
|
|
21
|
+
"clean-dist": "rm -rf ./dist"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"alova": "3.3.4",
|
|
29
|
+
"crypto-js": "4.2.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/crypto-js": "^4.2.2",
|
|
33
|
+
"@types/node": "24.10.1",
|
|
34
|
+
"prettier": "3.6.2",
|
|
35
|
+
"typescript": "^5",
|
|
36
|
+
"vitest": "4.0.13"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
}
|
|
41
|
+
}
|