spaps-sdk 1.1.5 → 1.1.7
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 +1 -0
- package/dist/index.d.mts +21 -8
- package/dist/index.d.ts +21 -8
- package/dist/index.js +87 -3
- package/dist/index.mjs +85 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -153,6 +153,7 @@ NEXT_PUBLIC_SPAPS_API_URL=https://api.sweetpotato.dev
|
|
|
153
153
|
- **Auto-detects local mode** - No API key needed for localhost
|
|
154
154
|
- **Auto-refreshes tokens** - Handles expired tokens automatically
|
|
155
155
|
- **TypeScript support** - Full type definitions included
|
|
156
|
+
- **Crypto payments ready** - Create invoices, poll status, verify webhooks, trigger reconciliation
|
|
156
157
|
|
|
157
158
|
### 🔐 Authentication Methods
|
|
158
159
|
```javascript
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import * as spaps_types from 'spaps-types';
|
|
2
|
+
import { CreateProductRequest, Product, UpdateProductRequest, CreatePriceRequest, Price, ProductSyncResult, CryptoReconcileRequest, AuthResponse, User as User$1, CreateCryptoInvoiceRequest, CryptoInvoiceStatusSnapshot, CheckoutSession, Subscription, UsageBalance, VerifyCryptoWebhookSignatureOptions } from 'spaps-types';
|
|
3
|
+
export { AdminPermission, AdminRole, AdminUser, ApiResponse, AuthResponse, CheckoutSession, CreateCryptoInvoiceRequest, CreatePriceRequest, CreateProductRequest, CryptoInvoice, CryptoInvoiceResponse, CryptoInvoiceStatusSnapshot, CryptoReconcileRequest, Price, Product, ProductSyncResult, Subscription, TokenPair, UpdateProductRequest, UsageBalance, User, UserProfile, UserRole, UserWallet, VerifyCryptoWebhookSignatureOptions } from 'spaps-types';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Permission checking utilities for SPAPS SDK
|
|
@@ -84,11 +85,6 @@ declare class PermissionChecker {
|
|
|
84
85
|
declare function createPermissionChecker(customAdmins?: (string | AdminConfig)[]): PermissionChecker;
|
|
85
86
|
declare const defaultPermissionChecker: PermissionChecker;
|
|
86
87
|
|
|
87
|
-
/**
|
|
88
|
-
* @spaps/sdk - Sweet Potato Authentication & Payment Service SDK
|
|
89
|
-
* Zero-config client for SPAPS authentication and payments
|
|
90
|
-
*/
|
|
91
|
-
|
|
92
88
|
interface SPAPSConfig {
|
|
93
89
|
apiUrl?: string;
|
|
94
90
|
apiKey?: string;
|
|
@@ -102,6 +98,7 @@ declare class SPAPSClient {
|
|
|
102
98
|
private accessToken?;
|
|
103
99
|
private refreshToken?;
|
|
104
100
|
private _isLocalMode;
|
|
101
|
+
private unwrapApiResponse;
|
|
105
102
|
admin: {
|
|
106
103
|
createProduct: (productData: CreateProductRequest) => Promise<{
|
|
107
104
|
data: Product;
|
|
@@ -129,6 +126,11 @@ declare class SPAPSClient {
|
|
|
129
126
|
adminMetadata?: any;
|
|
130
127
|
};
|
|
131
128
|
}>;
|
|
129
|
+
triggerCryptoReconcile: (opts?: CryptoReconcileRequest) => Promise<{
|
|
130
|
+
job_id: string;
|
|
131
|
+
scheduled_at: string;
|
|
132
|
+
cursor?: Record<string, unknown>;
|
|
133
|
+
}>;
|
|
132
134
|
};
|
|
133
135
|
constructor(config?: SPAPSConfig);
|
|
134
136
|
/** Raw API request helper that returns an ApiResponse-like shape */
|
|
@@ -250,6 +252,16 @@ declare class SPAPSClient {
|
|
|
250
252
|
isAuthenticated: () => boolean;
|
|
251
253
|
};
|
|
252
254
|
payments: {
|
|
255
|
+
crypto: {
|
|
256
|
+
createInvoice: (payload: CreateCryptoInvoiceRequest) => Promise<spaps_types.CryptoInvoice>;
|
|
257
|
+
getInvoice: (invoiceId: string) => Promise<spaps_types.CryptoInvoice>;
|
|
258
|
+
getInvoiceStatus: (invoiceId: string) => Promise<CryptoInvoiceStatusSnapshot>;
|
|
259
|
+
reconcile: (options?: CryptoReconcileRequest) => Promise<{
|
|
260
|
+
job_id: string;
|
|
261
|
+
scheduled_at: string;
|
|
262
|
+
cursor?: Record<string, unknown>;
|
|
263
|
+
}>;
|
|
264
|
+
};
|
|
253
265
|
createCheckoutSession: (payload: any) => Promise<CheckoutSession>;
|
|
254
266
|
createPaymentCheckout: (params: {
|
|
255
267
|
price_id: string;
|
|
@@ -397,6 +409,7 @@ declare class SPAPSClient {
|
|
|
397
409
|
data: any;
|
|
398
410
|
}>;
|
|
399
411
|
}
|
|
412
|
+
declare function verifyCryptoWebhookSignature(options: VerifyCryptoWebhookSignatureOptions): boolean;
|
|
400
413
|
|
|
401
414
|
declare class TokenManager {
|
|
402
415
|
private static readonly ACCESS_TOKEN_KEY;
|
|
@@ -416,4 +429,4 @@ declare class WalletUtils {
|
|
|
416
429
|
static isValidAddress(address: string, chainType?: 'solana' | 'ethereum' | 'bitcoin' | 'base'): boolean;
|
|
417
430
|
}
|
|
418
431
|
|
|
419
|
-
export { type AdminConfig, DEFAULT_ADMIN_ACCOUNTS, type PermissionCheckResult, PermissionChecker, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, TokenManager, WalletUtils, canAccessAdmin, createPermissionChecker, SPAPSClient as default, defaultPermissionChecker, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount };
|
|
432
|
+
export { type AdminConfig, DEFAULT_ADMIN_ACCOUNTS, type PermissionCheckResult, PermissionChecker, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, TokenManager, WalletUtils, canAccessAdmin, createPermissionChecker, SPAPSClient as default, defaultPermissionChecker, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, verifyCryptoWebhookSignature };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import * as spaps_types from 'spaps-types';
|
|
2
|
+
import { CreateProductRequest, Product, UpdateProductRequest, CreatePriceRequest, Price, ProductSyncResult, CryptoReconcileRequest, AuthResponse, User as User$1, CreateCryptoInvoiceRequest, CryptoInvoiceStatusSnapshot, CheckoutSession, Subscription, UsageBalance, VerifyCryptoWebhookSignatureOptions } from 'spaps-types';
|
|
3
|
+
export { AdminPermission, AdminRole, AdminUser, ApiResponse, AuthResponse, CheckoutSession, CreateCryptoInvoiceRequest, CreatePriceRequest, CreateProductRequest, CryptoInvoice, CryptoInvoiceResponse, CryptoInvoiceStatusSnapshot, CryptoReconcileRequest, Price, Product, ProductSyncResult, Subscription, TokenPair, UpdateProductRequest, UsageBalance, User, UserProfile, UserRole, UserWallet, VerifyCryptoWebhookSignatureOptions } from 'spaps-types';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Permission checking utilities for SPAPS SDK
|
|
@@ -84,11 +85,6 @@ declare class PermissionChecker {
|
|
|
84
85
|
declare function createPermissionChecker(customAdmins?: (string | AdminConfig)[]): PermissionChecker;
|
|
85
86
|
declare const defaultPermissionChecker: PermissionChecker;
|
|
86
87
|
|
|
87
|
-
/**
|
|
88
|
-
* @spaps/sdk - Sweet Potato Authentication & Payment Service SDK
|
|
89
|
-
* Zero-config client for SPAPS authentication and payments
|
|
90
|
-
*/
|
|
91
|
-
|
|
92
88
|
interface SPAPSConfig {
|
|
93
89
|
apiUrl?: string;
|
|
94
90
|
apiKey?: string;
|
|
@@ -102,6 +98,7 @@ declare class SPAPSClient {
|
|
|
102
98
|
private accessToken?;
|
|
103
99
|
private refreshToken?;
|
|
104
100
|
private _isLocalMode;
|
|
101
|
+
private unwrapApiResponse;
|
|
105
102
|
admin: {
|
|
106
103
|
createProduct: (productData: CreateProductRequest) => Promise<{
|
|
107
104
|
data: Product;
|
|
@@ -129,6 +126,11 @@ declare class SPAPSClient {
|
|
|
129
126
|
adminMetadata?: any;
|
|
130
127
|
};
|
|
131
128
|
}>;
|
|
129
|
+
triggerCryptoReconcile: (opts?: CryptoReconcileRequest) => Promise<{
|
|
130
|
+
job_id: string;
|
|
131
|
+
scheduled_at: string;
|
|
132
|
+
cursor?: Record<string, unknown>;
|
|
133
|
+
}>;
|
|
132
134
|
};
|
|
133
135
|
constructor(config?: SPAPSConfig);
|
|
134
136
|
/** Raw API request helper that returns an ApiResponse-like shape */
|
|
@@ -250,6 +252,16 @@ declare class SPAPSClient {
|
|
|
250
252
|
isAuthenticated: () => boolean;
|
|
251
253
|
};
|
|
252
254
|
payments: {
|
|
255
|
+
crypto: {
|
|
256
|
+
createInvoice: (payload: CreateCryptoInvoiceRequest) => Promise<spaps_types.CryptoInvoice>;
|
|
257
|
+
getInvoice: (invoiceId: string) => Promise<spaps_types.CryptoInvoice>;
|
|
258
|
+
getInvoiceStatus: (invoiceId: string) => Promise<CryptoInvoiceStatusSnapshot>;
|
|
259
|
+
reconcile: (options?: CryptoReconcileRequest) => Promise<{
|
|
260
|
+
job_id: string;
|
|
261
|
+
scheduled_at: string;
|
|
262
|
+
cursor?: Record<string, unknown>;
|
|
263
|
+
}>;
|
|
264
|
+
};
|
|
253
265
|
createCheckoutSession: (payload: any) => Promise<CheckoutSession>;
|
|
254
266
|
createPaymentCheckout: (params: {
|
|
255
267
|
price_id: string;
|
|
@@ -397,6 +409,7 @@ declare class SPAPSClient {
|
|
|
397
409
|
data: any;
|
|
398
410
|
}>;
|
|
399
411
|
}
|
|
412
|
+
declare function verifyCryptoWebhookSignature(options: VerifyCryptoWebhookSignatureOptions): boolean;
|
|
400
413
|
|
|
401
414
|
declare class TokenManager {
|
|
402
415
|
private static readonly ACCESS_TOKEN_KEY;
|
|
@@ -416,4 +429,4 @@ declare class WalletUtils {
|
|
|
416
429
|
static isValidAddress(address: string, chainType?: 'solana' | 'ethereum' | 'bitcoin' | 'base'): boolean;
|
|
417
430
|
}
|
|
418
431
|
|
|
419
|
-
export { type AdminConfig, DEFAULT_ADMIN_ACCOUNTS, type PermissionCheckResult, PermissionChecker, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, TokenManager, WalletUtils, canAccessAdmin, createPermissionChecker, SPAPSClient as default, defaultPermissionChecker, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount };
|
|
432
|
+
export { type AdminConfig, DEFAULT_ADMIN_ACCOUNTS, type PermissionCheckResult, PermissionChecker, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, TokenManager, WalletUtils, canAccessAdmin, createPermissionChecker, SPAPSClient as default, defaultPermissionChecker, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, verifyCryptoWebhookSignature };
|
package/dist/index.js
CHANGED
|
@@ -208,9 +208,11 @@ __export(index_exports, {
|
|
|
208
208
|
getUserDisplay: () => getUserDisplay,
|
|
209
209
|
getUserRole: () => getUserRole,
|
|
210
210
|
hasPermission: () => hasPermission,
|
|
211
|
-
isAdminAccount: () => isAdminAccount
|
|
211
|
+
isAdminAccount: () => isAdminAccount,
|
|
212
|
+
verifyCryptoWebhookSignature: () => verifyCryptoWebhookSignature
|
|
212
213
|
});
|
|
213
214
|
module.exports = __toCommonJS(index_exports);
|
|
215
|
+
var import_crypto = __toESM(require("crypto"));
|
|
214
216
|
var import_axios = __toESM(require("axios"));
|
|
215
217
|
init_permissions();
|
|
216
218
|
if (typeof globalThis.fetch === "undefined") {
|
|
@@ -222,6 +224,13 @@ var SPAPSClient = class {
|
|
|
222
224
|
accessToken;
|
|
223
225
|
refreshToken;
|
|
224
226
|
_isLocalMode = false;
|
|
227
|
+
unwrapApiResponse(response, fallback) {
|
|
228
|
+
const body = response?.data ?? response;
|
|
229
|
+
if (body?.success === false) {
|
|
230
|
+
throw new Error(body?.error?.message || fallback);
|
|
231
|
+
}
|
|
232
|
+
return body?.data ?? body;
|
|
233
|
+
}
|
|
225
234
|
// Admin namespace for cleaner API
|
|
226
235
|
admin = {
|
|
227
236
|
createProduct: (productData) => this.createProduct(productData),
|
|
@@ -229,7 +238,8 @@ var SPAPSClient = class {
|
|
|
229
238
|
deleteProduct: (productId) => this.deleteProduct(productId),
|
|
230
239
|
createPrice: (priceData) => this.createPrice(priceData),
|
|
231
240
|
syncProducts: () => this.syncProducts(),
|
|
232
|
-
getProducts: () => this.getProducts()
|
|
241
|
+
getProducts: () => this.getProducts(),
|
|
242
|
+
triggerCryptoReconcile: (opts) => this.payments.crypto.reconcile(opts || {})
|
|
233
243
|
};
|
|
234
244
|
constructor(config = {}) {
|
|
235
245
|
const apiUrl = config.apiUrl || process.env.SPAPS_API_URL || process.env.NEXT_PUBLIC_SPAPS_API_URL;
|
|
@@ -507,6 +517,48 @@ var SPAPSClient = class {
|
|
|
507
517
|
isAuthenticated: () => !!this.accessToken
|
|
508
518
|
};
|
|
509
519
|
payments = {
|
|
520
|
+
crypto: {
|
|
521
|
+
createInvoice: async (payload) => {
|
|
522
|
+
const body = {
|
|
523
|
+
asset: payload.asset,
|
|
524
|
+
network: payload.network,
|
|
525
|
+
amount: payload.amount
|
|
526
|
+
};
|
|
527
|
+
if (typeof payload.expiresInSeconds === "number") {
|
|
528
|
+
body.expires_in_seconds = payload.expiresInSeconds;
|
|
529
|
+
}
|
|
530
|
+
if (payload.beneficiary) {
|
|
531
|
+
body.beneficiary = payload.beneficiary;
|
|
532
|
+
}
|
|
533
|
+
if (payload.metadata) {
|
|
534
|
+
body.metadata = payload.metadata;
|
|
535
|
+
}
|
|
536
|
+
const res = await this.client.post("/api/payments/crypto/invoices", body);
|
|
537
|
+
const data = this.unwrapApiResponse(res, "Failed to create crypto invoice");
|
|
538
|
+
return data.invoice;
|
|
539
|
+
},
|
|
540
|
+
getInvoice: async (invoiceId) => {
|
|
541
|
+
const res = await this.client.get(`/api/payments/crypto/invoices/${invoiceId}`);
|
|
542
|
+
const data = this.unwrapApiResponse(res, "Failed to fetch crypto invoice");
|
|
543
|
+
return data.invoice;
|
|
544
|
+
},
|
|
545
|
+
getInvoiceStatus: async (invoiceId) => {
|
|
546
|
+
const res = await this.client.get(`/api/payments/crypto/invoices/${invoiceId}/status`);
|
|
547
|
+
return this.unwrapApiResponse(res, "Failed to fetch crypto invoice status");
|
|
548
|
+
},
|
|
549
|
+
reconcile: async (options = {}) => {
|
|
550
|
+
const headers = {};
|
|
551
|
+
if (options.reconToken) {
|
|
552
|
+
headers["X-Recon-Token"] = options.reconToken;
|
|
553
|
+
}
|
|
554
|
+
const payload = {};
|
|
555
|
+
if (options.cursor) {
|
|
556
|
+
payload.cursor = options.cursor;
|
|
557
|
+
}
|
|
558
|
+
const res = await this.client.post("/api/payments/crypto/reconcile", payload, { headers });
|
|
559
|
+
return this.unwrapApiResponse(res, "Failed to trigger crypto reconciliation");
|
|
560
|
+
}
|
|
561
|
+
},
|
|
510
562
|
createCheckoutSession: async (payload) => {
|
|
511
563
|
const headers = {};
|
|
512
564
|
if (this.accessToken) headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
@@ -802,6 +854,37 @@ var SPAPSClient = class {
|
|
|
802
854
|
return this.client.get("/health");
|
|
803
855
|
}
|
|
804
856
|
};
|
|
857
|
+
function verifyCryptoWebhookSignature(options) {
|
|
858
|
+
const { body, signature, secret, toleranceSeconds = 300 } = options;
|
|
859
|
+
if (!signature) {
|
|
860
|
+
throw new Error("Missing webhook signature");
|
|
861
|
+
}
|
|
862
|
+
const parts = signature.split(",").reduce((acc, part) => {
|
|
863
|
+
const [key, value] = part.split("=");
|
|
864
|
+
if (key && value) acc[key.trim()] = value.trim();
|
|
865
|
+
return acc;
|
|
866
|
+
}, {});
|
|
867
|
+
const timestamp = parts["t"];
|
|
868
|
+
const expected = parts["v1"];
|
|
869
|
+
if (!timestamp || !expected) {
|
|
870
|
+
throw new Error("Invalid webhook signature format");
|
|
871
|
+
}
|
|
872
|
+
const rawBody = typeof body === "string" ? body : body instanceof Uint8Array ? Buffer.from(body).toString("utf8") : JSON.stringify(body ?? {});
|
|
873
|
+
const computed = import_crypto.default.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
|
|
874
|
+
const expectedBuffer = Buffer.from(expected, "hex");
|
|
875
|
+
const computedBuffer = Buffer.from(computed, "hex");
|
|
876
|
+
if (expectedBuffer.length !== computedBuffer.length || !import_crypto.default.timingSafeEqual(expectedBuffer, computedBuffer)) {
|
|
877
|
+
throw new Error("Invalid webhook signature");
|
|
878
|
+
}
|
|
879
|
+
const ts = Number(timestamp);
|
|
880
|
+
if (Number.isFinite(ts) && toleranceSeconds > 0) {
|
|
881
|
+
const ageSeconds = Math.abs(Date.now() / 1e3 - ts);
|
|
882
|
+
if (ageSeconds > toleranceSeconds) {
|
|
883
|
+
throw new Error("Webhook signature timestamp outside tolerance window");
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return true;
|
|
887
|
+
}
|
|
805
888
|
var index_default = SPAPSClient;
|
|
806
889
|
var TokenManager = class _TokenManager {
|
|
807
890
|
static ACCESS_TOKEN_KEY = "sweet_potato_access_token";
|
|
@@ -911,5 +994,6 @@ var WalletUtils = class _WalletUtils {
|
|
|
911
994
|
getUserDisplay,
|
|
912
995
|
getUserRole,
|
|
913
996
|
hasPermission,
|
|
914
|
-
isAdminAccount
|
|
997
|
+
isAdminAccount,
|
|
998
|
+
verifyCryptoWebhookSignature
|
|
915
999
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -188,6 +188,7 @@ var init_permissions = __esm({
|
|
|
188
188
|
|
|
189
189
|
// src/index.ts
|
|
190
190
|
init_permissions();
|
|
191
|
+
import crypto from "crypto";
|
|
191
192
|
import axios from "axios";
|
|
192
193
|
if (typeof globalThis.fetch === "undefined") {
|
|
193
194
|
__require("cross-fetch/polyfill");
|
|
@@ -198,6 +199,13 @@ var SPAPSClient = class {
|
|
|
198
199
|
accessToken;
|
|
199
200
|
refreshToken;
|
|
200
201
|
_isLocalMode = false;
|
|
202
|
+
unwrapApiResponse(response, fallback) {
|
|
203
|
+
const body = response?.data ?? response;
|
|
204
|
+
if (body?.success === false) {
|
|
205
|
+
throw new Error(body?.error?.message || fallback);
|
|
206
|
+
}
|
|
207
|
+
return body?.data ?? body;
|
|
208
|
+
}
|
|
201
209
|
// Admin namespace for cleaner API
|
|
202
210
|
admin = {
|
|
203
211
|
createProduct: (productData) => this.createProduct(productData),
|
|
@@ -205,7 +213,8 @@ var SPAPSClient = class {
|
|
|
205
213
|
deleteProduct: (productId) => this.deleteProduct(productId),
|
|
206
214
|
createPrice: (priceData) => this.createPrice(priceData),
|
|
207
215
|
syncProducts: () => this.syncProducts(),
|
|
208
|
-
getProducts: () => this.getProducts()
|
|
216
|
+
getProducts: () => this.getProducts(),
|
|
217
|
+
triggerCryptoReconcile: (opts) => this.payments.crypto.reconcile(opts || {})
|
|
209
218
|
};
|
|
210
219
|
constructor(config = {}) {
|
|
211
220
|
const apiUrl = config.apiUrl || process.env.SPAPS_API_URL || process.env.NEXT_PUBLIC_SPAPS_API_URL;
|
|
@@ -483,6 +492,48 @@ var SPAPSClient = class {
|
|
|
483
492
|
isAuthenticated: () => !!this.accessToken
|
|
484
493
|
};
|
|
485
494
|
payments = {
|
|
495
|
+
crypto: {
|
|
496
|
+
createInvoice: async (payload) => {
|
|
497
|
+
const body = {
|
|
498
|
+
asset: payload.asset,
|
|
499
|
+
network: payload.network,
|
|
500
|
+
amount: payload.amount
|
|
501
|
+
};
|
|
502
|
+
if (typeof payload.expiresInSeconds === "number") {
|
|
503
|
+
body.expires_in_seconds = payload.expiresInSeconds;
|
|
504
|
+
}
|
|
505
|
+
if (payload.beneficiary) {
|
|
506
|
+
body.beneficiary = payload.beneficiary;
|
|
507
|
+
}
|
|
508
|
+
if (payload.metadata) {
|
|
509
|
+
body.metadata = payload.metadata;
|
|
510
|
+
}
|
|
511
|
+
const res = await this.client.post("/api/payments/crypto/invoices", body);
|
|
512
|
+
const data = this.unwrapApiResponse(res, "Failed to create crypto invoice");
|
|
513
|
+
return data.invoice;
|
|
514
|
+
},
|
|
515
|
+
getInvoice: async (invoiceId) => {
|
|
516
|
+
const res = await this.client.get(`/api/payments/crypto/invoices/${invoiceId}`);
|
|
517
|
+
const data = this.unwrapApiResponse(res, "Failed to fetch crypto invoice");
|
|
518
|
+
return data.invoice;
|
|
519
|
+
},
|
|
520
|
+
getInvoiceStatus: async (invoiceId) => {
|
|
521
|
+
const res = await this.client.get(`/api/payments/crypto/invoices/${invoiceId}/status`);
|
|
522
|
+
return this.unwrapApiResponse(res, "Failed to fetch crypto invoice status");
|
|
523
|
+
},
|
|
524
|
+
reconcile: async (options = {}) => {
|
|
525
|
+
const headers = {};
|
|
526
|
+
if (options.reconToken) {
|
|
527
|
+
headers["X-Recon-Token"] = options.reconToken;
|
|
528
|
+
}
|
|
529
|
+
const payload = {};
|
|
530
|
+
if (options.cursor) {
|
|
531
|
+
payload.cursor = options.cursor;
|
|
532
|
+
}
|
|
533
|
+
const res = await this.client.post("/api/payments/crypto/reconcile", payload, { headers });
|
|
534
|
+
return this.unwrapApiResponse(res, "Failed to trigger crypto reconciliation");
|
|
535
|
+
}
|
|
536
|
+
},
|
|
486
537
|
createCheckoutSession: async (payload) => {
|
|
487
538
|
const headers = {};
|
|
488
539
|
if (this.accessToken) headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
@@ -778,6 +829,37 @@ var SPAPSClient = class {
|
|
|
778
829
|
return this.client.get("/health");
|
|
779
830
|
}
|
|
780
831
|
};
|
|
832
|
+
function verifyCryptoWebhookSignature(options) {
|
|
833
|
+
const { body, signature, secret, toleranceSeconds = 300 } = options;
|
|
834
|
+
if (!signature) {
|
|
835
|
+
throw new Error("Missing webhook signature");
|
|
836
|
+
}
|
|
837
|
+
const parts = signature.split(",").reduce((acc, part) => {
|
|
838
|
+
const [key, value] = part.split("=");
|
|
839
|
+
if (key && value) acc[key.trim()] = value.trim();
|
|
840
|
+
return acc;
|
|
841
|
+
}, {});
|
|
842
|
+
const timestamp = parts["t"];
|
|
843
|
+
const expected = parts["v1"];
|
|
844
|
+
if (!timestamp || !expected) {
|
|
845
|
+
throw new Error("Invalid webhook signature format");
|
|
846
|
+
}
|
|
847
|
+
const rawBody = typeof body === "string" ? body : body instanceof Uint8Array ? Buffer.from(body).toString("utf8") : JSON.stringify(body ?? {});
|
|
848
|
+
const computed = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
|
|
849
|
+
const expectedBuffer = Buffer.from(expected, "hex");
|
|
850
|
+
const computedBuffer = Buffer.from(computed, "hex");
|
|
851
|
+
if (expectedBuffer.length !== computedBuffer.length || !crypto.timingSafeEqual(expectedBuffer, computedBuffer)) {
|
|
852
|
+
throw new Error("Invalid webhook signature");
|
|
853
|
+
}
|
|
854
|
+
const ts = Number(timestamp);
|
|
855
|
+
if (Number.isFinite(ts) && toleranceSeconds > 0) {
|
|
856
|
+
const ageSeconds = Math.abs(Date.now() / 1e3 - ts);
|
|
857
|
+
if (ageSeconds > toleranceSeconds) {
|
|
858
|
+
throw new Error("Webhook signature timestamp outside tolerance window");
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return true;
|
|
862
|
+
}
|
|
781
863
|
var index_default = SPAPSClient;
|
|
782
864
|
var TokenManager = class _TokenManager {
|
|
783
865
|
static ACCESS_TOKEN_KEY = "sweet_potato_access_token";
|
|
@@ -887,5 +969,6 @@ export {
|
|
|
887
969
|
getUserDisplay,
|
|
888
970
|
getUserRole,
|
|
889
971
|
hasPermission,
|
|
890
|
-
isAdminAccount
|
|
972
|
+
isAdminAccount,
|
|
973
|
+
verifyCryptoWebhookSignature
|
|
891
974
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spaps-sdk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"description": "Sweet Potato Authentication & Payment Service SDK - Zero-config client with built-in permission checking and role-based access control",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"email": "buildooor@gmail.com"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"spaps-types": "^1.0.
|
|
47
|
+
"spaps-types": "^1.0.12",
|
|
48
48
|
"axios": "^1.6.0",
|
|
49
49
|
"cross-fetch": "^4.0.0"
|
|
50
50
|
},
|