@kuvarpay/sdk 1.0.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.
Files changed (5) hide show
  1. package/README.md +162 -0
  2. package/index.d.ts +134 -0
  3. package/index.js +494 -0
  4. package/index.mjs +344 -0
  5. package/package.json +30 -0
package/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # KuvarPay Server SDK
2
+
3
+ Backend-focused SDK for creating and verifying checkout sessions, subscription sessions, invoices, and transactions using your Business SECRET API key.
4
+
5
+ Works with Node 18+ (global `fetch`) or any environment where you can supply a `fetch` implementation. Also includes quick PHP cURL examples.
6
+
7
+ ## Installation
8
+
9
+ This repository includes the SDK source files. You can copy `server-sdk/index.js` (CommonJS) or `server-sdk/index.mjs` (ESM) into your backend project, or import them via your build tooling.
10
+
11
+ > Requirements:
12
+ > - Business ID
13
+ > - SECRET API Key (server-only)
14
+ > - Payment API Base URL (e.g., `https://pay.kuvarpay.com` or your payment host)
15
+
16
+ ## Node (CommonJS) Usage
17
+
18
+ ```js
19
+ // index.js (Node 18+)
20
+ const { KuvarPayServer } = require('./server-sdk/index.js');
21
+
22
+ const kv = new KuvarPayServer({
23
+ baseUrl: process.env.PAYMENT_API_BASE_URL, // e.g., https://pay.kuvarpay.com
24
+ businessId: process.env.KUVARPAY_BUSINESS_ID,
25
+ secretApiKey: process.env.KUVARPAY_SECRET_API_KEY,
26
+ });
27
+
28
+ async function verify(sessionId) {
29
+ const result = await kv.verifyPayment(sessionId);
30
+ console.log('Verified?', result.verified, 'status:', result.status);
31
+ }
32
+
33
+ async function session(sessionId) {
34
+ const s = await kv.getSessionStatus(sessionId);
35
+ console.log('Session:', s);
36
+ }
37
+
38
+ async function txn(transactionId) {
39
+ const t = await kv.getTransactionStatus(transactionId);
40
+ console.log('Transaction:', t);
41
+ }
42
+ ```
43
+
44
+ ## Node (ESM) Usage
45
+
46
+ ```js
47
+ // index.mjs (Node 18+)
48
+ import { KuvarPayServer } from './server-sdk/index.mjs';
49
+
50
+ const kv = new KuvarPayServer({
51
+ baseUrl: process.env.PAYMENT_API_BASE_URL,
52
+ businessId: process.env.KUVARPAY_BUSINESS_ID,
53
+ secretApiKey: process.env.KUVARPAY_SECRET_API_KEY,
54
+ });
55
+
56
+ const result = await kv.verifyPayment('sess_123');
57
+ console.log(result);
58
+ ```
59
+
60
+ ### Node < 18
61
+
62
+ Provide your own `fetchImpl` (e.g., from `node-fetch`) when constructing:
63
+
64
+ ```js
65
+ const fetch = (...args) => import('node-fetch').then(mod => mod.default(...args));
66
+ const kv = new KuvarPayServer({ baseUrl, businessId, secretApiKey, fetchImpl: fetch });
67
+ ```
68
+
69
+ ## PHP cURL Examples
70
+
71
+ Replace `PAYMENT_API_BASE_URL`, `BUSINESS_ID`, and `SECRET_API_KEY` with your values.
72
+
73
+ ### Verify Payment (Checkout Session)
74
+
75
+ ```php
76
+ $base = getenv('PAYMENT_API_BASE_URL');
77
+ $businessId = getenv('KUVARPAY_BUSINESS_ID');
78
+ $secret = getenv('KUVARPAY_SECRET_API_KEY');
79
+ $sessionId = 'sess_123';
80
+
81
+ $ch = curl_init("$base/api/v1/checkout-sessions/" . urlencode($sessionId));
82
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
83
+ "Accept: application/json",
84
+ "X-API-Key: $secret",
85
+ "X-Business-ID: $businessId"
86
+ ]);
87
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
88
+ $resp = curl_exec($ch);
89
+ $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
90
+ curl_close($ch);
91
+
92
+ if ($statusCode >= 200 && $statusCode < 300) {
93
+ $data = json_decode($resp, true);
94
+ $status = $data['status'] ?? ($data['data']['status'] ?? null);
95
+ $verified = ($status === 'COMPLETED');
96
+ // handle $verified
97
+ } else {
98
+ // handle error
99
+ }
100
+ ```
101
+
102
+ ### Transaction Status
103
+
104
+ ```php
105
+ $transactionId = 'tx_123';
106
+ $ch = curl_init("$base/api/v1/transactions/" . urlencode($transactionId));
107
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
108
+ "Accept: application/json",
109
+ "X-API-Key: $secret",
110
+ "X-Business-ID: $businessId"
111
+ ]);
112
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
113
+ $resp = curl_exec($ch);
114
+ $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
115
+ curl_close($ch);
116
+
117
+ if ($statusCode >= 200 && $statusCode < 300) {
118
+ $data = json_decode($resp, true);
119
+ $status = $data['status'] ?? null;
120
+ // handle $status
121
+ } else {
122
+ // handle error
123
+ }
124
+ ```
125
+
126
+ ## API Reference
127
+
128
+ Payments:
129
+ - `createCheckoutSession(data)` → `{ sessionId, authToken?, approvalUrl?, raw }`
130
+ - `getSessionStatus(sessionId)` → `{ sessionId, status, transactionId?, raw }`
131
+ - `getTransactionStatus(transactionId)` → `{ transactionId, status, raw }`
132
+ - `verifyPayment(sessionId)` → `{ verified: boolean, status, sessionId, transactionId?, raw }`
133
+
134
+ Subscriptions:
135
+ - `createSubscriptionCheckoutSession(data)` → `{ sessionId, approvalUrl?, raw }`
136
+ - `getSubscriptionCheckoutSession(id)` → full session details
137
+ - `confirmSubscriptionCheckoutSession(id, body?)` → confirmation response
138
+ - `getSubscription(subscriptionId)` → full subscription details
139
+ - `cancelSubscription(subscriptionId, body?)` → cancellation response
140
+ - `createMeteredInvoice(subscriptionId, invoiceData)` → invoice response
141
+ - `createSubscriptionInvoice(subscriptionId, invoiceData)` → alias to metered invoice
142
+ - `scheduleSubscriptionInvoice(subscriptionId, invoiceData)` → enforces `chargeSchedule: 'SCHEDULED'`
143
+ - `createRenewalCheckoutSession(body)` → renewal checkout creation
144
+ - `renewSubscription(subscriptionId, body?)` → direct renew trigger
145
+
146
+ Relay Authorization (metered):
147
+ - `createRelayAuthorization(body)`
148
+ - `getRelayAuthorizationStatus(body)`
149
+ - `revokeRelayAuthorization(body)` (supports legacy path via `{ useLegacyPath: true }`)
150
+
151
+ Notes:
152
+ - Status values commonly include `PENDING`, `ARMED`, `PROCESSING`, `COMPLETED`, `FAILED`, `EXPIRED`, `CANCELLED`, etc.
153
+ - `verifyPayment` returns `verified = true` when `status === 'COMPLETED'`.
154
+
155
+ ## Security
156
+
157
+ - Always keep your SECRET API key in server-side environment variables. Never expose it to the browser.
158
+ - Use HTTPS for all API calls.
159
+
160
+ ## Parity with Client SDK
161
+
162
+ The Server SDK provides server-side equivalents of the Client SDK’s network operations (creating sessions, fetching statuses, managing subscriptions, and invoices). It does not implement browser UI features like `openPayment(...)` / `openSubscription(...)` modals, postMessage callbacks, or iframe overlays. Use the Client SDK in the browser for UI, and the Server SDK on your backend for secure API operations.
package/index.d.ts ADDED
@@ -0,0 +1,134 @@
1
+ export interface KuvarPayServerConfig {
2
+ baseUrl?: string;
3
+ businessId?: string;
4
+ secretApiKey?: string;
5
+ timeoutMs?: number;
6
+ fetchImpl?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
7
+ }
8
+
9
+ export interface SessionStatusResult {
10
+ sessionId: string;
11
+ status?: string;
12
+ transactionId: string | null;
13
+ metadata?: Record<string, any>;
14
+ raw: any;
15
+ }
16
+
17
+ export interface TransactionStatusResult {
18
+ transactionId: string;
19
+ status?: string;
20
+ metadata?: Record<string, any>;
21
+ raw: any;
22
+ }
23
+
24
+ export interface VerifyPaymentResult {
25
+ verified: boolean;
26
+ status?: string;
27
+ sessionId: string;
28
+ transactionId: string | null;
29
+ metadata?: Record<string, any>;
30
+ raw: any;
31
+ }
32
+
33
+ export class KuvarPayServer {
34
+ constructor(config?: KuvarPayServerConfig);
35
+ getSessionStatus(sessionId: string): Promise<SessionStatusResult>;
36
+ getTransactionStatus(transactionId: string): Promise<TransactionStatusResult>;
37
+ getTransactionStatusByReference(reference: string): Promise<any>;
38
+ verifyPayment(sessionId: string): Promise<VerifyPaymentResult>;
39
+ verifyWebhookSignature(payload: string | Buffer, signature: string, secret: string): boolean;
40
+
41
+ // Payments
42
+ getCurrencies(params?: Record<string, any>): Promise<any>;
43
+ calculatePayment(data: { fromCurrency: string; fromNetwork: string; toCurrency: string; toAmount: number }): Promise<any>;
44
+ createCheckoutSession(checkoutSessionData: {
45
+ amount: number;
46
+ currency: string;
47
+ description?: string;
48
+ redirectUrl?: string;
49
+ callbackUrl?: string;
50
+ customerEmail?: string;
51
+ customerName?: string;
52
+ expiresIn?: number;
53
+ subaccount?: string;
54
+ split_code?: string;
55
+ metadata?: Record<string, any>;
56
+ [key: string]: any;
57
+ }): Promise<{ sessionId: string; authToken?: string; approvalUrl?: string; raw: any }>;
58
+
59
+ createTransaction(transactionData: {
60
+ amount: number;
61
+ currency: string;
62
+ fromCurrency: string;
63
+ fromNetwork: string;
64
+ toCurrency: string;
65
+ subaccountCode?: string;
66
+ splitCode?: string;
67
+ description?: string;
68
+ callbackUrl?: string;
69
+ metadata?: Record<string, any>;
70
+ [key: string]: any;
71
+ }): Promise<any>;
72
+
73
+ listTransactions(params?: {
74
+ page?: number;
75
+ perPage?: number;
76
+ status?: string;
77
+ fromDate?: string;
78
+ toDate?: string;
79
+ }): Promise<any>;
80
+
81
+ refundTransaction(transactionId: string, data?: { amount?: number; reason?: string }): Promise<any>;
82
+ getExpiredTransactions(): Promise<any>;
83
+ getOptimalTransferFee(amount: number, currency?: string): Promise<any>;
84
+ sendInvoiceEmail(sessionId: string): Promise<any>;
85
+
86
+ // Subaccounts
87
+ createSubaccount(subaccountData: Record<string, any>): Promise<any>;
88
+ listSubaccounts(params?: { page?: number; perPage?: number }): Promise<any>;
89
+ getSubaccount(code: string): Promise<any>;
90
+ updateSubaccount(code: string, subaccountData: Record<string, any>): Promise<any>;
91
+ deleteSubaccount(code: string): Promise<any>;
92
+
93
+ // Split Groups
94
+ createSplitGroup(splitGroupData: Record<string, any>): Promise<any>;
95
+ listSplitGroups(): Promise<any>;
96
+ getSplitGroup(code: string): Promise<any>;
97
+ updateSplitGroup(code: string, splitGroupData: Record<string, any>): Promise<any>;
98
+ deleteSplitGroup(code: string): Promise<any>;
99
+
100
+
101
+ // Subscriptions
102
+ createSubscriptionCheckoutSession(subscriptionData: Record<string, any>): Promise<{ sessionId: string; approvalUrl?: string; raw: any }>;
103
+ getSubscriptionCheckoutSession(id: string): Promise<any>;
104
+ confirmSubscriptionCheckoutSession(id: string, body?: Record<string, any>): Promise<any>;
105
+
106
+ // Invoices
107
+ createMeteredInvoice(subscriptionId: string, invoiceData: Record<string, any>): Promise<any>;
108
+ createSubscriptionInvoice(subscriptionId: string, invoiceData: Record<string, any>): Promise<any>;
109
+ scheduleSubscriptionInvoice(subscriptionId: string, invoiceData: Record<string, any>): Promise<any>;
110
+
111
+ // Subscription details & lifecycle
112
+ getSubscription(subscriptionId: string): Promise<any>;
113
+ cancelSubscription(subscriptionId: string, body?: Record<string, any>): Promise<any>;
114
+
115
+ // Relay Authorization
116
+ createRelayAuthorization(body: Record<string, any>): Promise<any>;
117
+ getRelayAuthorizationStatus(body: Record<string, any>): Promise<any>;
118
+ revokeRelayAuthorization(body: Record<string, any>): Promise<any>;
119
+
120
+ // Renewal
121
+ createRenewalCheckoutSession(body: Record<string, any>): Promise<any>;
122
+ renewSubscription(subscriptionId: string, body?: Record<string, any>): Promise<any>;
123
+
124
+ // Banks
125
+ getBanks(params?: { currency?: string; type?: 'bank' | 'mobile_money'; country?: string }): Promise<any>;
126
+ resolveBankAccount(data: { bank_code: string; account_number: string; country_code?: string }): Promise<any>;
127
+
128
+ // Sandbox
129
+ startSandboxSimulator(): Promise<any>;
130
+ stopSandboxSimulator(): Promise<any>;
131
+ getSandboxSimulatorStatus(): Promise<any>;
132
+ }
133
+
134
+ export default KuvarPayServer;
package/index.js ADDED
@@ -0,0 +1,494 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * KuvarPay Server SDK (CommonJS)
5
+ *
6
+ * For backend/server usage only. Requires a Business ID and SECRET API key.
7
+ * Works with Node 18+ (global fetch). If using older Node, pass a custom fetch
8
+ * implementation in the constructor (e.g., node-fetch) via { fetchImpl }.
9
+ */
10
+
11
+ const crypto = require('crypto');
12
+
13
+ const DEFAULT_BASE = (process.env.PAYMENT_API_BASE_URL
14
+ || process.env.PAYMENT_SERVER_URL
15
+ || process.env.NEXT_PUBLIC_PAYMENT_API_BASE_URL
16
+ || 'http://localhost:3002').replace(/\/+$/, '');
17
+
18
+ class KuvarPayServer {
19
+ /**
20
+ * @param {Object} config
21
+ * @param {string} [config.baseUrl] - Payment API Base URL (e.g., https://pay.kuvarpay.com or https://api.yourpaymenthost.com)
22
+ * @param {string} [config.businessId] - Business ID
23
+ * @param {string} [config.secretApiKey] - Secret API key for server authentication
24
+ * @param {number} [config.timeoutMs] - Request timeout in ms (default 60000)
25
+ * @param {Function} [config.fetchImpl] - Optional fetch implementation for Node < 18
26
+ */
27
+ constructor({ baseUrl, businessId, secretApiKey, timeoutMs, fetchImpl } = {}) {
28
+ this.baseUrl = (baseUrl || DEFAULT_BASE).replace(/\/+$/, '');
29
+ this.businessId = businessId || process.env.KUVARPAY_BUSINESS_ID;
30
+ this.secretApiKey = secretApiKey || process.env.KUVARPAY_SECRET_API_KEY || process.env.PAYMENT_SECRET_API_KEY;
31
+ this.timeoutMs = timeoutMs || 60000;
32
+ this.fetchImpl = fetchImpl || (typeof fetch !== 'undefined' ? fetch.bind(globalThis) : null);
33
+
34
+ if (!this.secretApiKey) throw new Error('secretApiKey is required');
35
+ if (!this.businessId) throw new Error('businessId is required');
36
+ if (!this.fetchImpl) throw new Error('A fetch implementation is required (Node 18+ or provide fetchImpl)');
37
+ }
38
+
39
+ _headers(extra = {}) {
40
+ return Object.assign({
41
+ 'Accept': 'application/json',
42
+ 'Authorization': this.secretApiKey,
43
+ 'X-API-Key': this.secretApiKey,
44
+ 'X-Business-ID': this.businessId,
45
+ }, extra);
46
+ }
47
+
48
+ _buildUrl(path) {
49
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
50
+ return `${this.baseUrl}${normalizedPath}`;
51
+ }
52
+
53
+ async _request(method, path, body, extraHeaders = {}) {
54
+ const url = this._buildUrl(path);
55
+ const controller = new AbortController();
56
+ const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
57
+ try {
58
+ const res = await this.fetchImpl(url, {
59
+ method,
60
+ headers: this._headers(Object.assign({
61
+ 'Content-Type': 'application/json',
62
+ }, extraHeaders)),
63
+ body: body != null ? JSON.stringify(body) : undefined,
64
+ signal: controller.signal,
65
+ });
66
+ clearTimeout(timeoutId);
67
+ const text = await res.text();
68
+ let json;
69
+ try { json = text ? JSON.parse(text) : null; } catch (e) { json = null; }
70
+ if (!res.ok) {
71
+ const msg = (json && (json.error || json.message)) || `HTTP ${res.status}`;
72
+ const err = new Error(msg);
73
+ err.status = res.status;
74
+ err.body = json || text;
75
+ throw err;
76
+ }
77
+ return json;
78
+ } catch (err) {
79
+ clearTimeout(timeoutId);
80
+ throw err;
81
+ }
82
+ }
83
+
84
+ async _get(path) {
85
+ return this._request('GET', path);
86
+ }
87
+
88
+ async _post(path, body, extraHeaders = {}) {
89
+ return this._request('POST', path, body, extraHeaders);
90
+ }
91
+
92
+ /**
93
+ * Fetch checkout session status by sessionId
94
+ * GET {Payment API Base}/api/v1/checkout-sessions/{sessionId}
95
+ * @param {string} sessionId
96
+ * @returns {Promise<{ sessionId: string, status: string | undefined, transactionId: string | null, raw: any }>}
97
+ */
98
+ /**
99
+ * Signature Verification for Webhooks
100
+ * @param {string|Buffer} payload - Raw request body
101
+ * @param {string} signature - Value from X-KuvarPay-Signature header
102
+ * @param {string} secret - Your Webhook Secret from the dashboard
103
+ */
104
+ verifyWebhookSignature(payload, signature, secret) {
105
+ if (!signature || !secret) return false;
106
+ const hmac = crypto.createHmac('sha256', secret);
107
+ const digest = hmac.update(payload).digest('hex');
108
+ return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature));
109
+ }
110
+
111
+ async getSessionStatus(sessionId) {
112
+ if (!sessionId || typeof sessionId !== 'string') {
113
+ throw new Error('sessionId is required');
114
+ }
115
+ const data = await this._get(`/api/v1/checkout-sessions/${encodeURIComponent(sessionId)}`);
116
+ const status = data?.status || data?.checkoutSession?.status || data?.data?.status;
117
+ const transactionId = data?.transactionId || data?.data?.transactionId || null;
118
+ return {
119
+ sessionId: data?.id || data?.sessionId || sessionId,
120
+ status,
121
+ transactionId,
122
+ metadata: data?.metadata || data?.checkoutSession?.metadata || data?.data?.metadata || null,
123
+ raw: data,
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Fetch transaction status by transactionId
129
+ * GET {Payment API Base}/api/v1/transactions/{transactionId}
130
+ * @param {string} transactionId
131
+ * @returns {Promise<{ transactionId: string, status: string | undefined, raw: any }>}
132
+ */
133
+ async getTransactionStatus(transactionId) {
134
+ if (!transactionId || typeof transactionId !== 'string') {
135
+ throw new Error('transactionId is required');
136
+ }
137
+ const data = await this._get(`/api/v1/transactions/${encodeURIComponent(transactionId)}`);
138
+ return {
139
+ transactionId: data?.id || transactionId,
140
+ status: data?.status,
141
+ metadata: data?.metadata || data?.data?.metadata || null,
142
+ raw: data,
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Get transaction status by merchant reference
148
+ * GET /api/v1/transactions/status/{reference}
149
+ */
150
+ async getTransactionStatusByReference(reference) {
151
+ if (!reference) throw new Error('reference is required');
152
+ return this._get(`/api/v1/transactions/status/${encodeURIComponent(reference)}`);
153
+ }
154
+
155
+ /**
156
+ * List all transactions
157
+ * @param {Object} params - { page, perPage, status, fromDate, toDate }
158
+ */
159
+ async listTransactions(params = {}) {
160
+ const query = new URLSearchParams(params).toString();
161
+ const path = query ? `/api/v1/transactions?${query}` : '/api/v1/transactions';
162
+ return this._get(path);
163
+ }
164
+
165
+ /**
166
+ * Refund a transaction
167
+ * @param {string} transactionId
168
+ * @param {Object} data - { amount?, reason? }
169
+ */
170
+ async refundTransaction(transactionId, data = {}) {
171
+ return this._post(`/api/v1/transactions/${encodeURIComponent(transactionId)}/refund`, data);
172
+ }
173
+
174
+ /**
175
+ * Get expired transactions
176
+ */
177
+ async getExpiredTransactions() {
178
+ return this._get('/api/v1/transactions/expired');
179
+ }
180
+
181
+ /**
182
+ * Calculate fees
183
+ */
184
+ async getOptimalTransferFee(amount, currency = 'NGN') {
185
+ return this._get(`/api/v1/optimal-transfer-fee?amount=${amount}&currency=${currency}`);
186
+ }
187
+
188
+ /**
189
+ * Calculate required crypto payment amount
190
+ * POST /api/v1/transactions/calculate-payment
191
+ * @param {Object} data - { fromCurrency, fromNetwork, toCurrency, toAmount }
192
+ */
193
+ async calculatePayment(data) {
194
+ return this._post('/api/v1/transactions/calculate-payment', data);
195
+ }
196
+
197
+ /**
198
+ * Convenience wrapper to verify a payment session reached a final successful state
199
+ * @param {string} sessionId
200
+ * @returns {Promise<{ verified: boolean, status: string | undefined, sessionId: string, transactionId: string | null, raw: any }>}
201
+ */
202
+ async verifyPayment(sessionId) {
203
+ const s = await this.getSessionStatus(sessionId);
204
+ const verified = s.status === 'COMPLETED';
205
+ return {
206
+ verified,
207
+ status: s.status,
208
+ sessionId: s.sessionId,
209
+ transactionId: s.transactionId,
210
+ metadata: s.metadata,
211
+ raw: s.raw,
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Create a one-time checkout session
217
+ * POST {Payment API Base}/api/v1/checkout-sessions
218
+ * @param {Object} checkoutSessionData - { amount, currency, description?, redirectUrl?, callbackUrl?, customerEmail?, customerName?, expiresIn?, metadata? }
219
+ * @returns {Promise<{ sessionId: string, authToken?: string, approvalUrl?: string, raw: any }>} // approvalUrl may be present when using redirect flows
220
+ */
221
+ async createCheckoutSession(checkoutSessionData) {
222
+ const payload = Object.assign({}, checkoutSessionData, {
223
+ businessId: checkoutSessionData.businessId || this.businessId,
224
+ });
225
+ // payload can include 'subaccount' (string) or 'split_code' (string)
226
+ // Guide says /api/v1/checkout-sessions
227
+ const data = await this._post('/api/v1/checkout-sessions', payload);
228
+ return {
229
+ sessionId: data?.sessionId || data?.id || data?.checkoutSession?.id || data?.data?.sessionId,
230
+ authToken: data?.authToken || data?.data?.authToken,
231
+ approvalUrl: data?.approvalUrl || data?.approval_url || data?.checkoutSession?.approval_url || data?.data?.approval_url,
232
+ raw: data,
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Create a manual transaction (SDK / Server-to-Server)
238
+ * POST {Payment API Base}/api/v1/transactions/create
239
+ * @param {Object} transactionData - { amount, currency, fromCurrency, fromNetwork, toCurrency, subaccountCode?, splitCode?, description?, callbackUrl?, metadata? }
240
+ * @returns {Promise<any>}
241
+ */
242
+ async getCurrencies(params = {}) {
243
+ const query = new URLSearchParams(params).toString();
244
+ const path = query ? `/api/v1/currencies?${query}` : '/api/v1/currencies';
245
+ return this._get(path);
246
+ }
247
+
248
+ async createTransaction(transactionData) {
249
+ const payload = Object.assign({}, transactionData, {
250
+ businessId: transactionData.businessId || this.businessId,
251
+ });
252
+ return this._post('/api/v1/transactions', payload);
253
+ }
254
+
255
+ /**
256
+ * Email an invoice to a customer
257
+ */
258
+ async sendInvoiceEmail(sessionId) {
259
+ return this._post(`/api/v1/invoices/${encodeURIComponent(sessionId)}/send-email`, {});
260
+ }
261
+
262
+ // --- Subaccount Management ---
263
+
264
+ /**
265
+ * Create a new subaccount
266
+ * @param {Object} subaccountData - { business_name, percentage_charge, ... }
267
+ */
268
+ async createSubaccount(subaccountData) {
269
+ return this._post('/api/v1/subaccounts', subaccountData);
270
+ }
271
+
272
+ /**
273
+ * List all subaccounts
274
+ * @param {Object} [params] - { page, perPage }
275
+ */
276
+ async listSubaccounts(params = {}) {
277
+ const query = new URLSearchParams(params).toString();
278
+ const path = query ? `/api/v1/subaccounts?${query}` : '/api/v1/subaccounts';
279
+ return this._get(path);
280
+ }
281
+
282
+ /**
283
+ * Get subaccount details
284
+ * @param {string} code - SUB_xxx
285
+ */
286
+ async getSubaccount(code) {
287
+ return this._get(`/api/v1/subaccounts/${encodeURIComponent(code)}`);
288
+ }
289
+
290
+ /**
291
+ * Update subaccount
292
+ */
293
+ async updateSubaccount(code, subaccountData) {
294
+ return this._request('PATCH', `/api/v1/subaccounts/${encodeURIComponent(code)}`, subaccountData);
295
+ }
296
+
297
+ /**
298
+ * Delete subaccount (soft delete)
299
+ */
300
+ async deleteSubaccount(code) {
301
+ return this._request('DELETE', `/api/v1/subaccounts/${encodeURIComponent(code)}`);
302
+ }
303
+
304
+ // --- Split Group Management ---
305
+
306
+ /**
307
+ * Create a new split group
308
+ * @param {Object} splitGroupData - { name, bearer_type, subaccounts: [{ subaccount, share }] }
309
+ */
310
+ async createSplitGroup(splitGroupData) {
311
+ return this._post('/api/v1/split', splitGroupData);
312
+ }
313
+
314
+ /**
315
+ * List all split groups
316
+ */
317
+ async listSplitGroups() {
318
+ return this._get('/api/v1/split');
319
+ }
320
+
321
+ /**
322
+ * Get split group details
323
+ * @param {string} code - SPL_xxx
324
+ */
325
+ async getSplitGroup(code) {
326
+ return this._get(`/api/v1/split/${encodeURIComponent(code)}`);
327
+ }
328
+
329
+ /**
330
+ * Update split group
331
+ */
332
+ async updateSplitGroup(code, splitGroupData) {
333
+ return this._request('PATCH', `/api/v1/split/${encodeURIComponent(code)}`, splitGroupData);
334
+ }
335
+
336
+ /**
337
+ * Delete split group
338
+ */
339
+ async deleteSplitGroup(code) {
340
+ return this._request('DELETE', `/api/v1/split/${encodeURIComponent(code)}`);
341
+ }
342
+
343
+
344
+ /**
345
+ * Create a subscription checkout session (Metered or Fixed, depending on your Payment API config)
346
+ * POST {Payment API Base}/api/v1/subscriptions/checkout-sessions
347
+ * @param {Object} subscriptionData - { billingMode?, customer?, expectedUsage?, strategy?, customMultiplier?, metadata?, businessId? }
348
+ * @returns {Promise<{ sessionId: string, approvalUrl?: string, raw: any }>}
349
+ */
350
+ async createSubscriptionCheckoutSession(subscriptionData) {
351
+ const payload = Object.assign({
352
+ billingMode: subscriptionData?.billingMode || 'METERED',
353
+ businessId: subscriptionData?.businessId || this.businessId,
354
+ }, subscriptionData);
355
+ const data = await this._post('/api/v1/subscriptions/checkout-sessions', payload);
356
+ return {
357
+ sessionId: data?.sessionId || data?.id || data?.checkoutSession?.id || data?.checkoutSession?.uid,
358
+ approvalUrl: data?.approvalUrl || data?.approval_url || data?.checkoutSession?.approval_url,
359
+ raw: data,
360
+ };
361
+ }
362
+
363
+ /**
364
+ * Get a subscription checkout session by id
365
+ * GET {Payment API Base}/api/v1/subscriptions/checkout-sessions/{id}
366
+ */
367
+ async getSubscriptionCheckoutSession(id) {
368
+ return this._get(`/api/v1/subscriptions/checkout-sessions/${encodeURIComponent(id)}`);
369
+ }
370
+
371
+ /**
372
+ * Confirm a subscription checkout session
373
+ * POST {Payment API Base}/api/v1/subscriptions/checkout-sessions/{id}/confirm
374
+ */
375
+ async confirmSubscriptionCheckoutSession(id, body = {}) {
376
+ return this._post(`/api/v1/subscriptions/checkout-sessions/${encodeURIComponent(id)}/confirm`, body);
377
+ }
378
+
379
+ /**
380
+ * Create a metered invoice for a subscription
381
+ * POST {Payment API Base}/api/v1/subscriptions/{subscriptionId}/metered-invoices
382
+ */
383
+ async createMeteredInvoice(subscriptionId, invoiceData) {
384
+ if (!subscriptionId) throw new Error('subscriptionId is required');
385
+ const payload = Object.assign({}, invoiceData);
386
+ const data = await this._post(`/api/v1/subscriptions/${encodeURIComponent(subscriptionId)}/metered-invoices`, payload);
387
+ return data;
388
+ }
389
+
390
+ // --- Banks & Account Verification ---
391
+
392
+ /**
393
+ * List supported banks
394
+ * GET /api/v1/banks
395
+ * @param {Object} params - { currency, type, country }
396
+ */
397
+ async getBanks(params = {}) {
398
+ const query = new URLSearchParams(params).toString();
399
+ const path = query ? `/api/v1/banks?${query}` : '/api/v1/banks';
400
+ return this._get(path);
401
+ }
402
+
403
+ /**
404
+ * Resolve bank account name
405
+ * POST /api/v1/banks/resolve
406
+ * @param {Object} data - { bank_code, account_number, country_code? }
407
+ */
408
+ async resolveBankAccount(data) {
409
+ return this._post('/api/v1/banks/resolve', data);
410
+ }
411
+
412
+ // --- Sandbox Simulator ---
413
+
414
+ async startSandboxSimulator() {
415
+ return this._post('/api/v1/sandbox-simulator/start', {});
416
+ }
417
+
418
+ async stopSandboxSimulator() {
419
+ return this._post('/api/v1/sandbox-simulator/stop', {});
420
+ }
421
+
422
+ async getSandboxSimulatorStatus() {
423
+ return this._get('/api/v1/sandbox-simulator/status');
424
+ }
425
+
426
+ /**
427
+ * Alias for metered invoice creation
428
+ */
429
+ async createSubscriptionInvoice(subscriptionId, invoiceData) {
430
+ return this.createMeteredInvoice(subscriptionId, invoiceData);
431
+ }
432
+
433
+ /**
434
+ * Schedule a subscription invoice (convenience wrapper)
435
+ */
436
+ async scheduleSubscriptionInvoice(subscriptionId, invoiceData) {
437
+ if (!invoiceData?.dueDate) throw new Error('dueDate is required to schedule an invoice');
438
+ const payload = Object.assign({}, invoiceData, { chargeSchedule: 'SCHEDULED' });
439
+ return this.createSubscriptionInvoice(subscriptionId, payload);
440
+ }
441
+
442
+ /**
443
+ * Get subscription details by id
444
+ * GET {Payment API Base}/api/v1/subscriptions/{subscriptionId}
445
+ */
446
+ async getSubscription(subscriptionId) {
447
+ if (!subscriptionId) throw new Error('subscriptionId is required');
448
+ return this._get(`/api/v1/subscriptions/${encodeURIComponent(subscriptionId)}`);
449
+ }
450
+
451
+ /**
452
+ * Cancel a subscription (confirm cancellation)
453
+ * POST {Payment API Base}/api/v1/subscriptions/{subscriptionId}/cancel
454
+ */
455
+ async cancelSubscription(subscriptionId, body = {}) {
456
+ if (!subscriptionId) throw new Error('subscriptionId is required');
457
+ return this._post(`/api/v1/subscriptions/${encodeURIComponent(subscriptionId)}/cancel`, body);
458
+ }
459
+
460
+ /**
461
+ * Relay Authorization APIs (for metered subscriptions)
462
+ */
463
+ async createRelayAuthorization(body) {
464
+ return this._post('/api/v1/subscriptions/relay/authorizations', body);
465
+ }
466
+
467
+ async getRelayAuthorizationStatus(body) {
468
+ return this._post('/api/v1/subscriptions/relay/authorization-status', body);
469
+ }
470
+
471
+ async revokeRelayAuthorization(body) {
472
+ // Some environments use '/relay/revokeAuth'; support both via path param
473
+ const path = body?.useLegacyPath ? '/api/v1/subscriptions/relay/revokeAuth' : '/api/v1/subscriptions/revoke-authorization';
474
+ return this._post(path, body);
475
+ }
476
+
477
+ /**
478
+ * Create a renewal checkout session for an existing subscription
479
+ * POST {Payment API Base}/api/v1/subscriptions/renewal-checkout-sessions
480
+ */
481
+ async createRenewalCheckoutSession(body) {
482
+ return this._post('/api/v1/subscriptions/renewal-checkout-sessions', body);
483
+ }
484
+
485
+ /**
486
+ * Trigger subscription renew
487
+ * POST {Payment API Base}/api/v1/subscriptions/{subscriptionId}/renew
488
+ */
489
+ async renewSubscription(subscriptionId, body = {}) {
490
+ return this._post(`/api/v1/subscriptions/${encodeURIComponent(subscriptionId)}/renew`, body);
491
+ }
492
+ }
493
+
494
+ module.exports = { KuvarPayServer };
package/index.mjs ADDED
@@ -0,0 +1,344 @@
1
+ /**
2
+ * KuvarPay Server SDK (ESM)
3
+ *
4
+ * For backend/server usage only. Requires a Business ID and SECRET API key.
5
+ * Works with Node 18+ (global fetch). If using older Node, pass a custom fetch
6
+ * implementation in the constructor (e.g., node-fetch) via { fetchImpl }.
7
+ */
8
+
9
+ import crypto from 'node:crypto';
10
+
11
+ const DEFAULT_BASE = (process.env.PAYMENT_API_BASE_URL
12
+ || process.env.PAYMENT_SERVER_URL
13
+ || process.env.NEXT_PUBLIC_PAYMENT_API_BASE_URL
14
+ || 'http://localhost:3002').replace(/\/+$/, '');
15
+
16
+ export class KuvarPayServer {
17
+ /**
18
+ * @param {Object} config
19
+ * @param {string} [config.baseUrl] - Payment API Base URL (e.g., https://pay.kuvarpay.com or https://api.yourpaymenthost.com)
20
+ * @param {string} [config.businessId] - Business ID
21
+ * @param {string} [config.secretApiKey] - Secret API key for server authentication
22
+ * @param {number} [config.timeoutMs] - Request timeout in ms (default 60000)
23
+ * @param {Function} [config.fetchImpl] - Optional fetch implementation for Node < 18
24
+ */
25
+ constructor({ baseUrl, businessId, secretApiKey, timeoutMs, fetchImpl } = {}) {
26
+ this.baseUrl = (baseUrl || DEFAULT_BASE).replace(/\/+$/, '');
27
+ this.businessId = businessId || process.env.KUVARPAY_BUSINESS_ID;
28
+ this.secretApiKey = secretApiKey || process.env.KUVARPAY_SECRET_API_KEY || process.env.PAYMENT_SECRET_API_KEY;
29
+ this.timeoutMs = timeoutMs || 60000;
30
+ this.fetchImpl = fetchImpl || (typeof fetch !== 'undefined' ? fetch.bind(globalThis) : null);
31
+
32
+ if (!this.secretApiKey) throw new Error('secretApiKey is required');
33
+ if (!this.businessId) throw new Error('businessId is required');
34
+ if (!this.fetchImpl) throw new Error('A fetch implementation is required (Node 18+ or provide fetchImpl)');
35
+ }
36
+
37
+ _headers(extra = {}) {
38
+ return Object.assign({
39
+ 'Accept': 'application/json',
40
+ 'Authorization': this.secretApiKey,
41
+ 'X-API-Key': this.secretApiKey,
42
+ 'X-Business-ID': this.businessId,
43
+ }, extra);
44
+ }
45
+
46
+ _buildUrl(path) {
47
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
48
+ return `${this.baseUrl}${normalizedPath}`;
49
+ }
50
+
51
+ async _request(method, path, body, extraHeaders = {}) {
52
+ const url = this._buildUrl(path);
53
+ const controller = new AbortController();
54
+ const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
55
+ try {
56
+ const res = await this.fetchImpl(url, {
57
+ method,
58
+ headers: this._headers(Object.assign({
59
+ 'Content-Type': 'application/json',
60
+ }, extraHeaders)),
61
+ body: body != null ? JSON.stringify(body) : undefined,
62
+ signal: controller.signal,
63
+ });
64
+ clearTimeout(timeoutId);
65
+ const text = await res.text();
66
+ let json;
67
+ try { json = text ? JSON.parse(text) : null; } catch (e) { json = null; }
68
+ if (!res.ok) {
69
+ const msg = (json && (json.error || json.message)) || `HTTP ${res.status}`;
70
+ const err = new Error(msg);
71
+ err.status = res.status;
72
+ err.body = json || text;
73
+ throw err;
74
+ }
75
+ return json;
76
+ } catch (err) {
77
+ clearTimeout(timeoutId);
78
+ throw err;
79
+ }
80
+ }
81
+
82
+ async _get(path) {
83
+ return this._request('GET', path);
84
+ }
85
+
86
+ async _post(path, body, extraHeaders = {}) {
87
+ return this._request('POST', path, body, extraHeaders);
88
+ }
89
+
90
+ /**
91
+ * Signature Verification for Webhooks
92
+ * @param {string|Buffer} payload - Raw request body
93
+ * @param {string} signature - Value from X-KuvarPay-Signature header
94
+ * @param {string} secret - Your Webhook Secret from the dashboard
95
+ */
96
+ verifyWebhookSignature(payload, signature, secret) {
97
+ if (!signature || !secret) return false;
98
+ const hmac = crypto.createHmac('sha256', secret);
99
+ const digest = hmac.update(payload).digest('hex');
100
+ return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature));
101
+ }
102
+
103
+ async getSessionStatus(sessionId) {
104
+ if (!sessionId || typeof sessionId !== 'string') {
105
+ throw new Error('sessionId is required');
106
+ }
107
+ const data = await this._get(`/api/v1/checkout-sessions/${encodeURIComponent(sessionId)}`);
108
+ const status = data?.status || data?.checkoutSession?.status || data?.data?.status;
109
+ const transactionId = data?.transactionId || data?.data?.transactionId || null;
110
+ return {
111
+ sessionId: data?.id || data?.sessionId || sessionId,
112
+ status,
113
+ transactionId,
114
+ metadata: data?.metadata || data?.checkoutSession?.metadata || data?.data?.metadata || null,
115
+ raw: data,
116
+ };
117
+ }
118
+
119
+ async getTransactionStatus(transactionId) {
120
+ if (!transactionId || typeof transactionId !== 'string') {
121
+ throw new Error('transactionId is required');
122
+ }
123
+ const data = await this._get(`/api/v1/transactions/${encodeURIComponent(transactionId)}`);
124
+ return {
125
+ transactionId: data?.id || transactionId,
126
+ status: data?.status,
127
+ metadata: data?.metadata || data?.data?.metadata || null,
128
+ raw: data,
129
+ };
130
+ }
131
+
132
+ async getTransactionStatusByReference(reference) {
133
+ if (!reference) throw new Error('reference is required');
134
+ return this._get(`/api/v1/transactions/status/${encodeURIComponent(reference)}`);
135
+ }
136
+
137
+ async listTransactions(params = {}) {
138
+ const query = new URLSearchParams(params).toString();
139
+ const path = query ? `/api/v1/transactions?${query}` : '/api/v1/transactions';
140
+ return this._get(path);
141
+ }
142
+
143
+ async refundTransaction(transactionId, data = {}) {
144
+ return this._post(`/api/v1/transactions/${encodeURIComponent(transactionId)}/refund`, data);
145
+ }
146
+
147
+ async getExpiredTransactions() {
148
+ return this._get('/api/v1/transactions/expired');
149
+ }
150
+
151
+ async getOptimalTransferFee(amount, currency = 'NGN') {
152
+ return this._get(`/api/v1/optimal-transfer-fee?amount=${amount}&currency=${currency}`);
153
+ }
154
+
155
+ async calculatePayment(data) {
156
+ return this._post('/api/v1/transactions/calculate-payment', data);
157
+ }
158
+
159
+ async verifyPayment(sessionId) {
160
+ const s = await this.getSessionStatus(sessionId);
161
+ const verified = s.status === 'COMPLETED';
162
+ return {
163
+ verified,
164
+ status: s.status,
165
+ sessionId: s.sessionId,
166
+ transactionId: s.transactionId,
167
+ metadata: s.metadata,
168
+ raw: s.raw,
169
+ };
170
+ }
171
+
172
+ async createCheckoutSession(checkoutSessionData) {
173
+ const payload = Object.assign({}, checkoutSessionData, {
174
+ businessId: checkoutSessionData.businessId || this.businessId,
175
+ });
176
+ // payload can include 'subaccount' (string) or 'split_code' (string)
177
+ const data = await this._post('/api/v1/checkout-sessions', payload);
178
+ return {
179
+ sessionId: data?.sessionId || data?.id || data?.checkoutSession?.id || data?.data?.sessionId,
180
+ authToken: data?.authToken || data?.data?.authToken,
181
+ approvalUrl: data?.approvalUrl || data?.approval_url || data?.checkoutSession?.approval_url || data?.data?.approval_url,
182
+ raw: data,
183
+ };
184
+ }
185
+
186
+ async getCurrencies(params = {}) {
187
+ const query = new URLSearchParams(params).toString();
188
+ const path = query ? `/api/v1/currencies?${query}` : '/api/v1/currencies';
189
+ return this._get(path);
190
+ }
191
+
192
+ async createTransaction(transactionData) {
193
+ const payload = Object.assign({}, transactionData, {
194
+ businessId: transactionData.businessId || this.businessId,
195
+ });
196
+ return this._post('/api/v1/transactions', payload);
197
+ }
198
+
199
+ async sendInvoiceEmail(sessionId) {
200
+ return this._post(`/api/v1/invoices/${encodeURIComponent(sessionId)}/send-email`, {});
201
+ }
202
+
203
+ // --- Subaccount Management ---
204
+
205
+ async createSubaccount(subaccountData) {
206
+ return this._post('/api/v1/subaccounts', subaccountData);
207
+ }
208
+
209
+ async listSubaccounts(params = {}) {
210
+ const query = new URLSearchParams(params).toString();
211
+ const path = query ? `/api/v1/subaccounts?${query}` : '/api/v1/subaccounts';
212
+ return this._get(path);
213
+ }
214
+
215
+ async getSubaccount(code) {
216
+ return this._get(`/api/v1/subaccounts/${encodeURIComponent(code)}`);
217
+ }
218
+
219
+ async updateSubaccount(code, subaccountData) {
220
+ return this._request('PATCH', `/api/v1/subaccounts/${encodeURIComponent(code)}`, subaccountData);
221
+ }
222
+
223
+ async deleteSubaccount(code) {
224
+ return this._request('DELETE', `/api/v1/subaccounts/${encodeURIComponent(code)}`);
225
+ }
226
+
227
+ // --- Split Group Management ---
228
+
229
+ async createSplitGroup(splitGroupData) {
230
+ return this._post('/api/v1/split', splitGroupData);
231
+ }
232
+
233
+ async listSplitGroups() {
234
+ return this._get('/api/v1/split');
235
+ }
236
+
237
+ async getSplitGroup(code) {
238
+ return this._get(`/api/v1/split/${encodeURIComponent(code)}`);
239
+ }
240
+
241
+ async updateSplitGroup(code, splitGroupData) {
242
+ return this._request('PATCH', `/api/v1/split/${encodeURIComponent(code)}`, splitGroupData);
243
+ }
244
+
245
+ async deleteSplitGroup(code) {
246
+ return this._request('DELETE', `/api/v1/split/${encodeURIComponent(code)}`);
247
+ }
248
+
249
+
250
+ async createSubscriptionCheckoutSession(subscriptionData) {
251
+ const payload = Object.assign({
252
+ billingMode: subscriptionData?.billingMode || 'METERED',
253
+ businessId: subscriptionData?.businessId || this.businessId,
254
+ }, subscriptionData);
255
+ const data = await this._post('/api/v1/subscriptions/checkout-sessions', payload);
256
+ return {
257
+ sessionId: data?.sessionId || data?.id || data?.checkoutSession?.id || data?.checkoutSession?.uid,
258
+ approvalUrl: data?.approvalUrl || data?.approval_url || data?.checkoutSession?.approval_url,
259
+ raw: data,
260
+ };
261
+ }
262
+
263
+ async getSubscriptionCheckoutSession(id) {
264
+ return this._get(`/api/v1/subscriptions/checkout-sessions/${encodeURIComponent(id)}`);
265
+ }
266
+
267
+ async confirmSubscriptionCheckoutSession(id, body = {}) {
268
+ return this._post(`/api/v1/subscriptions/checkout-sessions/${encodeURIComponent(id)}/confirm`, body);
269
+ }
270
+
271
+ async createMeteredInvoice(subscriptionId, invoiceData) {
272
+ if (!subscriptionId) throw new Error('subscriptionId is required');
273
+ const payload = Object.assign({}, invoiceData);
274
+ const data = await this._post(`/api/v1/subscriptions/${encodeURIComponent(subscriptionId)}/metered-invoices`, payload);
275
+ return data;
276
+ }
277
+
278
+ // --- Banks & Account Verification ---
279
+
280
+ async getBanks(params = {}) {
281
+ const query = new URLSearchParams(params).toString();
282
+ const path = query ? `/api/v1/banks?${query}` : '/api/v1/banks';
283
+ return this._get(path);
284
+ }
285
+
286
+ async resolveBankAccount(data) {
287
+ return this._post('/api/v1/banks/resolve', data);
288
+ }
289
+
290
+ // --- Sandbox Simulator ---
291
+
292
+ async startSandboxSimulator() {
293
+ return this._post('/api/v1/sandbox-simulator/start', {});
294
+ }
295
+
296
+ async stopSandboxSimulator() {
297
+ return this._post('/api/v1/sandbox-simulator/stop', {});
298
+ }
299
+
300
+ async getSandboxSimulatorStatus() {
301
+ return this._get('/api/v1/sandbox-simulator/status');
302
+ }
303
+
304
+ async createSubscriptionInvoice(subscriptionId, invoiceData) {
305
+ return this.createMeteredInvoice(subscriptionId, invoiceData);
306
+ }
307
+
308
+ async scheduleSubscriptionInvoice(subscriptionId, invoiceData) {
309
+ if (!invoiceData?.dueDate) throw new Error('dueDate is required to schedule an invoice');
310
+ const payload = Object.assign({}, invoiceData, { chargeSchedule: 'SCHEDULED' });
311
+ return this.createSubscriptionInvoice(subscriptionId, payload);
312
+ }
313
+
314
+ async getSubscription(subscriptionId) {
315
+ if (!subscriptionId) throw new Error('subscriptionId is required');
316
+ return this._get(`/api/v1/subscriptions/${encodeURIComponent(subscriptionId)}`);
317
+ }
318
+
319
+ async cancelSubscription(subscriptionId, body = {}) {
320
+ if (!subscriptionId) throw new Error('subscriptionId is required');
321
+ return this._post(`/api/v1/subscriptions/${encodeURIComponent(subscriptionId)}/cancel`, body);
322
+ }
323
+
324
+ async createRelayAuthorization(body) {
325
+ return this._post('/api/v1/subscriptions/relay/authorizations', body);
326
+ }
327
+
328
+ async getRelayAuthorizationStatus(body) {
329
+ return this._post('/api/v1/subscriptions/relay/authorization-status', body);
330
+ }
331
+
332
+ async revokeRelayAuthorization(body) {
333
+ const path = body?.useLegacyPath ? '/api/v1/subscriptions/relay/revokeAuth' : '/api/v1/subscriptions/revoke-authorization';
334
+ return this._post(path, body);
335
+ }
336
+
337
+ async createRenewalCheckoutSession(body) {
338
+ return this._post('/api/v1/subscriptions/renewal-checkout-sessions', body);
339
+ }
340
+
341
+ async renewSubscription(subscriptionId, body = {}) {
342
+ return this._post(`/api/v1/subscriptions/${encodeURIComponent(subscriptionId)}/renew`, body);
343
+ }
344
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@kuvarpay/sdk",
3
+ "version": "1.0.0",
4
+ "description": "Official KuvarPay Server SDK for Node.js",
5
+ "main": "index.js",
6
+ "module": "index.mjs",
7
+ "types": "index.d.ts",
8
+ "files": [
9
+ "index.js",
10
+ "index.mjs",
11
+ "index.d.ts",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18.0.0"
16
+ },
17
+ "keywords": [
18
+ "payments",
19
+ "crypto",
20
+ "kuvarpay",
21
+ "blockchain",
22
+ "fiat-payout",
23
+ "split-payments"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "author": "KuvarPay Team",
29
+ "license": "MIT"
30
+ }