arispay 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/users.ts ADDED
@@ -0,0 +1,67 @@
1
+ import type {
2
+ CreateUserRequest,
3
+ UserResponse,
4
+ SetupPaymentMethodResponse,
5
+ WalletPaymentMethodResponse,
6
+ WalletStatusResponse,
7
+ SetLimitsRequest,
8
+ SpendLimitsResponse,
9
+ } from '@arispay/shared';
10
+ import type { HttpClient } from './client.js';
11
+
12
+ export class UserService {
13
+ constructor(private client: HttpClient) {}
14
+
15
+ async create(params: CreateUserRequest): Promise<UserResponse> {
16
+ return this.client.post<UserResponse>('/v1/users', params);
17
+ }
18
+
19
+ async get(userId: string): Promise<UserResponse> {
20
+ return this.client.get<UserResponse>(`/v1/users/${userId}`);
21
+ }
22
+
23
+ async getByExternalId(externalId: string): Promise<UserResponse> {
24
+ return this.client.get<UserResponse>(`/v1/users/by-external/${encodeURIComponent(externalId)}`);
25
+ }
26
+
27
+ async getWalletStatus(userId: string): Promise<WalletStatusResponse> {
28
+ return this.client.get<WalletStatusResponse>(`/v1/users/${userId}/wallet-status`);
29
+ }
30
+
31
+ /** Hosted card flow — returns setupUrl for redirect */
32
+ async addPaymentMethod(
33
+ params: { userId: string; type: 'card'; returnUrl: string },
34
+ ): Promise<SetupPaymentMethodResponse>;
35
+ /** Direct card tokenization — returns user with card attached */
36
+ async addPaymentMethod(
37
+ params: {
38
+ userId: string;
39
+ type: 'card';
40
+ cardNumber: string;
41
+ expiryMonth: string;
42
+ expiryYear: string;
43
+ securityCode: string;
44
+ },
45
+ ): Promise<UserResponse>;
46
+ async addPaymentMethod(
47
+ params: { userId: string; type: 'wallet'; walletAddress: string; chain: string },
48
+ ): Promise<WalletPaymentMethodResponse>;
49
+ async addPaymentMethod(
50
+ params: { userId: string; type: string; [key: string]: unknown },
51
+ ): Promise<SetupPaymentMethodResponse | UserResponse | WalletPaymentMethodResponse> {
52
+ const { userId, ...body } = params;
53
+ return this.client.post(`/v1/users/${userId}/payment-methods`, body);
54
+ }
55
+
56
+ async completePaymentMethod(
57
+ params: { userId: string; sessionId: string },
58
+ ): Promise<UserResponse> {
59
+ const { userId, ...body } = params;
60
+ return this.client.post<UserResponse>(`/v1/users/${userId}/payment-methods/complete`, body);
61
+ }
62
+
63
+ async setLimits(params: { userId: string } & SetLimitsRequest): Promise<SpendLimitsResponse> {
64
+ const { userId, ...body } = params;
65
+ return this.client.put<SpendLimitsResponse>(`/v1/users/${userId}/limits`, body);
66
+ }
67
+ }
@@ -0,0 +1,81 @@
1
+ import { createHmac, timingSafeEqual } from 'crypto';
2
+ import type { RegisterWebhookRequest, WebhookResponse } from '@arispay/shared';
3
+ import type { HttpClient } from './client.js';
4
+
5
+ /** Maximum allowed age for webhook timestamps (5 minutes) */
6
+ const WEBHOOK_TOLERANCE_SECONDS = 300;
7
+
8
+ export class WebhookService {
9
+ constructor(private client: HttpClient) {}
10
+
11
+ async register(params: RegisterWebhookRequest): Promise<WebhookResponse & { secret: string }> {
12
+ return this.client.post<WebhookResponse & { secret: string }>('/v1/webhooks', params);
13
+ }
14
+
15
+ async list(): Promise<WebhookResponse[]> {
16
+ return this.client.get<WebhookResponse[]>('/v1/webhooks');
17
+ }
18
+
19
+ async delete(webhookId: string): Promise<void> {
20
+ await this.client.delete<void>(`/v1/webhooks/${webhookId}`);
21
+ }
22
+
23
+ /**
24
+ * Verify a webhook signature from an incoming request.
25
+ *
26
+ * ArisPay signs every webhook delivery with HMAC-SHA256 using the secret
27
+ * returned when you registered the webhook. The signature is sent in the
28
+ * `X-ArisPay-Signature` header and the unix timestamp in `X-ArisPay-Timestamp`.
29
+ *
30
+ * @param body - The raw request body string (do NOT parse it first)
31
+ * @param signature - Value of the `X-ArisPay-Signature` header
32
+ * @param timestamp - Value of the `X-ArisPay-Timestamp` header
33
+ * @param secret - Your webhook secret (`whsec_...`)
34
+ * @param toleranceSec - Maximum allowed age in seconds (default: 300 = 5 min)
35
+ * @returns `true` if the signature is valid and the timestamp is within tolerance
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * import { WebhookService } from 'arispay';
40
+ *
41
+ * app.post('/webhooks', (req, res) => {
42
+ * const isValid = WebhookService.verifyWebhookSignature(
43
+ * req.body, // raw body string
44
+ * req.headers['x-arispay-signature'],
45
+ * req.headers['x-arispay-timestamp'],
46
+ * process.env.WEBHOOK_SECRET!,
47
+ * );
48
+ * if (!isValid) return res.status(401).send('Invalid signature');
49
+ * // Process the webhook event...
50
+ * });
51
+ * ```
52
+ */
53
+ static verifyWebhookSignature(
54
+ body: string,
55
+ signature: string,
56
+ timestamp: string,
57
+ secret: string,
58
+ toleranceSec: number = WEBHOOK_TOLERANCE_SECONDS,
59
+ ): boolean {
60
+ // Validate inputs
61
+ if (!body || !signature || !timestamp || !secret) return false;
62
+
63
+ // Check timestamp freshness (replay protection)
64
+ const ts = parseInt(timestamp, 10);
65
+ if (isNaN(ts)) return false;
66
+ const age = Math.abs(Math.floor(Date.now() / 1000) - ts);
67
+ if (age > toleranceSec) return false;
68
+
69
+ // Compute expected signature
70
+ const signaturePayload = `${timestamp}.${body}`;
71
+ const expected = createHmac('sha256', secret).update(signaturePayload).digest('hex');
72
+
73
+ // Constant-time comparison to prevent timing attacks
74
+ try {
75
+ return timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expected, 'hex'));
76
+ } catch {
77
+ // Buffers different length = invalid
78
+ return false;
79
+ }
80
+ }
81
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src"]
8
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig([
4
+ {
5
+ entry: ['src/index.ts'],
6
+ format: ['esm'],
7
+ dts: true,
8
+ outDir: 'dist',
9
+ clean: true,
10
+ },
11
+ {
12
+ entry: ['src/cli.ts', 'src/cli-b.ts'],
13
+ format: ['esm'],
14
+ outDir: 'dist',
15
+ banner: { js: '#!/usr/bin/env node' },
16
+ noExternal: ['@arispay/shared'],
17
+ },
18
+ ]);