@relipay/node 0.1.0-beta.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.
@@ -0,0 +1,677 @@
1
+ /**
2
+ * @relipay/node — server SDK for ReliPay.
3
+ *
4
+ * One client instance per Application. Construct with the Application's
5
+ * secret key (`rp_live_…` or `rp_test_…`) and the URL of your ReliPay
6
+ * deployment. Never ship the secret key to the browser — for browser code
7
+ * use `@relipay/react` with the Application's public key instead.
8
+ *
9
+ * @example Smoke-test your credentials
10
+ * ```ts
11
+ * import { ReliPay } from "@relipay/node";
12
+ *
13
+ * const relipay = new ReliPay({
14
+ * apiUrl: process.env.RELIPAY_URL!,
15
+ * secretKey: process.env.RELIPAY_SECRET!,
16
+ * });
17
+ *
18
+ * const me = await relipay.applications.me();
19
+ * console.log(`Connected to "${me.name}" (${me.slug})`);
20
+ * ```
21
+ */
22
+ import type { ApplicationDto, AuthResultDto, ChangePasswordRequest, CheckoutResultDto, ConsumeCreditsRequest, CreateCheckoutRequest, CreditBalanceDto, CreditLedgerEntryDto, EndUserDto, ForgotPasswordRequest, ForgotPasswordResultDto, MfaVerifyRequest, PlanDto, ProvidersListDto, RelipayError as RelipayErrorShape, ResetPasswordRequest, SignInOutcomeDto, SignInRequest, SignUpRequest, SubscriptionDto, ValidateCouponRequest, ValidateCouponResultDto } from '@relipay/shared-types';
23
+ export type { ApplicationDto, EndUserDto, ApiKeyDto, AuthResultDto, MfaChallengeResultDto, MfaVerifyRequest, SignInOutcomeDto, SignInRequest, SignUpRequest, RefreshRequest, ForgotPasswordRequest, ForgotPasswordResultDto, ResetPasswordRequest, ChangePasswordRequest, PlanDto, SubscriptionDto, CreateCheckoutRequest, CheckoutResultDto, CouponDto, ValidateCouponRequest, ValidateCouponResultDto, CouponDiscountTypeValue, PlanIntervalType, PlanKindType, LicenseKindType, CreditReasonType, CreditBalanceDto, CreditLedgerEntryDto, ConsumeCreditsRequest, SubscriptionStatusType, RelipayError as RelipayErrorShape, AuthConfig, BillingConfig, BillingProvider, } from '@relipay/shared-types';
24
+ /** Configuration for a ReliPay client instance. */
25
+ export interface ReliPayConfig {
26
+ /** Base URL of the ReliPay API. e.g. `https://relipay.example.com` */
27
+ apiUrl: string;
28
+ /** Secret key for one Application — `rp_live_…` or `rp_test_…`. Never ship to the browser. */
29
+ secretKey: string;
30
+ /** Optional fetch override (test stubs, custom keep-alive agents, etc.). */
31
+ fetch?: typeof fetch;
32
+ }
33
+ /**
34
+ * An error thrown by the ReliPay SDK. Always carries a stable `code` and
35
+ * (when the server provided one) a concrete `fix` field. **Read `error.fix`
36
+ * first** when debugging — it is by far the most actionable piece of data.
37
+ */
38
+ export declare class RelipayError extends Error implements RelipayErrorShape {
39
+ readonly code: string;
40
+ readonly fix: string | undefined;
41
+ readonly docs: string | undefined;
42
+ readonly statusCode: number | undefined;
43
+ /** Server-assigned request id — share with support to look up the matching log entry. */
44
+ readonly requestId: string | undefined;
45
+ constructor(error: RelipayErrorShape & {
46
+ statusCode?: number;
47
+ requestId?: string;
48
+ });
49
+ }
50
+ /**
51
+ * Top-level ReliPay client. Auth and billing live as namespaces
52
+ * (`relipay.applications`, `relipay.auth`, `relipay.billing`) so an agent
53
+ * reading `relipay.` in an editor sees a discoverable surface.
54
+ */
55
+ export declare class ReliPay {
56
+ private readonly apiUrl;
57
+ private readonly secretKey;
58
+ private readonly fetchImpl;
59
+ /** Operations on the calling Application itself. */
60
+ readonly applications: ApplicationsClient;
61
+ /** Auth operations — sign-in, sign-up, sessions, passkeys, magic-link. */
62
+ readonly auth: AuthClient;
63
+ /** Billing operations — plans, checkout, subscriptions, coupons. */
64
+ readonly billing: BillingClient;
65
+ /** End-user organizations — create, invite, members, role changes. */
66
+ readonly organizations: OrganizationsClient;
67
+ /** License key verification + activation. */
68
+ readonly licenses: LicensesClient;
69
+ /** Usage metering — record events, aggregate windows. */
70
+ readonly usage: UsageClient;
71
+ /** Prepaid credits — balance reads, idempotent drawdown, ledger. */
72
+ readonly credits: CreditsClient;
73
+ constructor(config: ReliPayConfig);
74
+ /** @internal */
75
+ request<T>(method: string, path: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<T>;
76
+ }
77
+ declare class ApplicationsClient {
78
+ private readonly client;
79
+ constructor(client: ReliPay);
80
+ /**
81
+ * Verify credentials and fetch the calling Application. Use this as your
82
+ * SDK smoke test — if it returns, your secret key is good and you're
83
+ * pointed at the right ReliPay deployment.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * const me = await relipay.applications.me();
88
+ * console.log(`Connected to "${me.name}" (${me.slug})`);
89
+ * ```
90
+ *
91
+ * @throws {RelipayError} with `code: "API_KEY_INVALID"` if the key is wrong/revoked/expired.
92
+ */
93
+ me(): Promise<ApplicationDto>;
94
+ }
95
+ declare class AuthClient {
96
+ private readonly client;
97
+ constructor(client: ReliPay);
98
+ /**
99
+ * Create a new end-user in the calling Application via email + password.
100
+ * Returns the user and a JWT to use for subsequent per-user calls
101
+ * (e.g. `getCurrentUser(token)`).
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * const { endUser, token } = await relipay.auth.signUp({
106
+ * email: 'alice@example.com',
107
+ * password: 'correct-horse-battery-staple',
108
+ * });
109
+ * // store token in your session, return it to the browser, etc.
110
+ * ```
111
+ *
112
+ * @throws {RelipayError} `EMAIL_ALREADY_EXISTS` (409) if the email is taken in this Application.
113
+ * @throws {RelipayError} `PASSWORD_TOO_SHORT` (400) if shorter than the Application's `passwordMinLength`.
114
+ * @throws {RelipayError} `AUTH_METHOD_DISABLED` (400) if the Application doesn't have `"password"` enabled.
115
+ */
116
+ signUp(input: SignUpRequest): Promise<AuthResultDto>;
117
+ /**
118
+ * Authenticate an existing end-user with email + password.
119
+ *
120
+ * Returns a discriminated union over `mfaRequired`:
121
+ * - `mfaRequired === false` → full `AuthResultDto` with access+refresh.
122
+ * - `mfaRequired === true` → `mfaChallengeToken` (5-minute lifetime).
123
+ * Prompt the user for their TOTP / backup code and call
124
+ * `mfaVerify({ mfaChallengeToken, code })` to receive a real session.
125
+ *
126
+ * **Branch on `result.mfaRequired` before reading `accessToken`** — the
127
+ * MFA-required branch has no session tokens.
128
+ *
129
+ * @throws {RelipayError} `INVALID_CREDENTIALS` (401) — single code on purpose.
130
+ * Don't try to distinguish wrong-email from wrong-password from the SDK side either.
131
+ */
132
+ signIn(input: SignInRequest): Promise<SignInOutcomeDto>;
133
+ /**
134
+ * Exchange an MFA challenge token + TOTP/backup code for a real session.
135
+ * Use after `signIn` (or OAuth callback) returns `mfaRequired: true`.
136
+ *
137
+ * @throws {RelipayError} `MFA_CHALLENGE_INVALID` (401) if the token is
138
+ * forged, expired, or signed with a different secret.
139
+ * @throws {RelipayError} `MFA_CHALLENGE_WRONG_APPLICATION` (401) if the
140
+ * token was issued under a different Application.
141
+ * @throws {RelipayError} `MFA_CODE_INVALID` (401) if the code doesn't
142
+ * verify against the user's TOTP secret or remaining backup codes.
143
+ */
144
+ mfaVerify(input: MfaVerifyRequest): Promise<AuthResultDto>;
145
+ /**
146
+ * Request a magic-link sign-in email. Enumeration-safe: same response
147
+ * shape whether the email exists or not. When the Application has
148
+ * email transport configured, the link is sent and `magicLinkToken`
149
+ * is null; otherwise the raw token is returned for you to forward.
150
+ */
151
+ requestMagicLink(input: {
152
+ email: string;
153
+ signInUrl?: string;
154
+ }): Promise<{
155
+ delivered: boolean;
156
+ emailSent: boolean;
157
+ magicLinkToken: string | null;
158
+ }>;
159
+ /**
160
+ * Consume a magic-link token. Returns `SignInOutcome` — branch on
161
+ * `mfaRequired` before reading `accessToken`. For MFA-enrolled users
162
+ * the response carries `mfaChallengeToken` and you must complete via
163
+ * `mfaVerify(...)`.
164
+ */
165
+ verifyMagicLink(input: {
166
+ token: string;
167
+ }): Promise<SignInOutcomeDto>;
168
+ /**
169
+ * Begin a passkey authentication ceremony. Returns the WebAuthn options
170
+ * to forward to the browser (`navigator.credentials.get(...)`) along
171
+ * with `expectedChallenge` — bind the challenge to your session and
172
+ * pass both back via `verifyPasskeyAuthentication(...)`.
173
+ */
174
+ startPasskeyAuthentication(input?: {
175
+ email?: string;
176
+ }): Promise<{
177
+ options: unknown;
178
+ expectedChallenge: string;
179
+ }>;
180
+ /**
181
+ * Complete a passkey authentication. Returns the same `SignInOutcome`
182
+ * shape as `signIn` — but passkeys are themselves a strong factor, so
183
+ * `mfaRequired` will always be `false` in practice.
184
+ */
185
+ verifyPasskeyAuthentication(input: {
186
+ response: unknown;
187
+ expectedChallenge: string;
188
+ }): Promise<SignInOutcomeDto>;
189
+ /**
190
+ * Begin a passkey registration ceremony for an authenticated user.
191
+ * Forward `options` to `navigator.credentials.create(...)`; store
192
+ * `expectedChallenge` in session; POST both back via
193
+ * `verifyPasskeyRegistration(...)`.
194
+ */
195
+ startPasskeyRegistration(accessToken: string): Promise<{
196
+ options: unknown;
197
+ expectedChallenge: string;
198
+ }>;
199
+ verifyPasskeyRegistration(accessToken: string, input: {
200
+ response: unknown;
201
+ expectedChallenge: string;
202
+ deviceName?: string;
203
+ }): Promise<{
204
+ credentialId: string;
205
+ deviceName: string | null;
206
+ }>;
207
+ /** List the user's registered passkeys. */
208
+ listPasskeys(accessToken: string): Promise<Array<{
209
+ id: string;
210
+ credentialId: string;
211
+ deviceName: string | null;
212
+ lastUsedAt: string | null;
213
+ createdAt: string;
214
+ }>>;
215
+ /** Remove a passkey. Returns `{deleted: false}` if the row doesn't belong to this user. */
216
+ deletePasskey(accessToken: string, credentialRowId: string): Promise<{
217
+ deleted: boolean;
218
+ }>;
219
+ createOrganization(accessToken: string, input: {
220
+ name: string;
221
+ slug: string;
222
+ metadata?: Record<string, unknown>;
223
+ }): Promise<{
224
+ organization: {
225
+ id: string;
226
+ name: string;
227
+ slug: string;
228
+ metadata: unknown;
229
+ createdAt: string;
230
+ updatedAt: string;
231
+ };
232
+ membership: {
233
+ id: string;
234
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
235
+ };
236
+ }>;
237
+ listMyOrganizations(accessToken: string): Promise<Array<{
238
+ id: string;
239
+ name: string;
240
+ slug: string;
241
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
242
+ createdAt: string;
243
+ }>>;
244
+ inviteToOrganization(accessToken: string, organizationId: string, input: {
245
+ email: string;
246
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
247
+ }): Promise<{
248
+ invitation: {
249
+ id: string;
250
+ email: string;
251
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
252
+ expiresAt: string;
253
+ };
254
+ token: string;
255
+ }>;
256
+ acceptOrganizationInvitation(accessToken: string, input: {
257
+ token: string;
258
+ }): Promise<{
259
+ membership: {
260
+ id: string;
261
+ organizationId: string;
262
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
263
+ };
264
+ }>;
265
+ /**
266
+ * Resolve the end-user behind a presented access token.
267
+ *
268
+ * @throws {RelipayError} `USER_TOKEN_INVALID` (401) if expired/forged/wrong-secret.
269
+ * @throws {RelipayError} `USER_TOKEN_WRONG_APPLICATION` (401) if the token was issued
270
+ * by a different Application than the calling secret key represents.
271
+ */
272
+ getCurrentUser(accessToken: string): Promise<EndUserDto>;
273
+ /**
274
+ * Exchange a refresh token for a fresh {access, refresh} pair. The presented
275
+ * refresh is revoked atomically — call this **once** and store the new
276
+ * `refreshToken` from the response immediately.
277
+ *
278
+ * @throws {RelipayError} `REFRESH_TOKEN_REUSED` (401) if you replay an already-used token.
279
+ * This is a strong signal the original was leaked; treat as compromise.
280
+ * @throws {RelipayError} `REFRESH_TOKEN_EXPIRED` (401) after the 30-day refresh window.
281
+ */
282
+ refresh(refreshToken: string): Promise<AuthResultDto>;
283
+ /**
284
+ * Revoke a refresh token. Idempotent — no-op for unknown tokens. The
285
+ * access token paired with this refresh remains valid until its short
286
+ * (15 min) expiry; for true "log out everywhere" semantics, also clear
287
+ * the access token from your client.
288
+ */
289
+ signOut(refreshToken: string): Promise<{
290
+ signedOut: true;
291
+ }>;
292
+ /**
293
+ * Request a password-reset token for an email. Always succeeds — never
294
+ * tells you whether the email exists. **You must email the returned
295
+ * `resetToken` to the user**: ReliPay does not send email.
296
+ *
297
+ * @example
298
+ * ```ts
299
+ * const { resetToken } = await relipay.auth.requestPasswordReset({ email });
300
+ * if (resetToken) await sendgrid.send({ to: email, subject: 'Reset', text: `link: ${url(resetToken)}` });
301
+ * ```
302
+ */
303
+ requestPasswordReset(input: ForgotPasswordRequest): Promise<ForgotPasswordResultDto>;
304
+ /**
305
+ * Consume a reset token + set a new password. Single-use. On success,
306
+ * every refresh token for the user is revoked.
307
+ *
308
+ * @throws {RelipayError} `PASSWORD_RESET_TOKEN_INVALID` / `_USED` / `_EXPIRED` / `_WRONG_APPLICATION`
309
+ * @throws {RelipayError} `PASSWORD_TOO_SHORT` if below the Application's `passwordMinLength`
310
+ */
311
+ resetPassword(input: ResetPasswordRequest): Promise<{
312
+ ok: true;
313
+ }>;
314
+ /**
315
+ * Authenticated password change. Pass the user's *current* access token.
316
+ * On success, every refresh token for the user is revoked — other devices
317
+ * are signed out.
318
+ */
319
+ changePassword(accessToken: string, input: ChangePasswordRequest): Promise<{
320
+ ok: true;
321
+ }>;
322
+ /**
323
+ * Revoke every refresh token for the calling user. "Sign out of all
324
+ * devices." The caller's access token remains valid until 15-min expiry
325
+ * — clear it client-side for full logout.
326
+ */
327
+ signOutEverywhere(accessToken: string): Promise<{
328
+ revokedCount: number;
329
+ }>;
330
+ /**
331
+ * Send (or re-send) an email-verification link to the current user.
332
+ * If email transport is configured on the Application, ReliPay sends
333
+ * the email and `verificationToken` is null. Otherwise the raw token
334
+ * is returned for the caller to forward via their own provider.
335
+ *
336
+ * Pass `verifyUrl` containing `{token}` to template the link target
337
+ * (e.g. `https://app.example.com/verify?t={token}`).
338
+ */
339
+ sendVerificationEmail(accessToken: string, input?: {
340
+ verifyUrl?: string;
341
+ }): Promise<{
342
+ emailSent: boolean;
343
+ verificationToken: string | null;
344
+ }>;
345
+ /**
346
+ * Consume an email-verification token. Single-use, 24-hour lifetime.
347
+ * Marks `emailVerified: true` on the user record. Cross-Application
348
+ * tokens are refused with `EMAIL_VERIFICATION_TOKEN_WRONG_APPLICATION`.
349
+ */
350
+ verifyEmail(input: {
351
+ token: string;
352
+ }): Promise<{
353
+ verified: true;
354
+ endUser: EndUserDto;
355
+ }>;
356
+ }
357
+ interface OrganizationDto {
358
+ id: string;
359
+ name: string;
360
+ slug: string;
361
+ metadata: unknown;
362
+ createdAt: string;
363
+ updatedAt: string;
364
+ }
365
+ interface OrganizationWithRoleDto extends OrganizationDto {
366
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
367
+ }
368
+ interface OrganizationMemberDto {
369
+ id: string;
370
+ organizationId: string;
371
+ endUserId: string;
372
+ email: string;
373
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
374
+ createdAt: string;
375
+ }
376
+ interface OrganizationInvitationDto {
377
+ id: string;
378
+ organizationId: string;
379
+ email: string;
380
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
381
+ expiresAt: string;
382
+ createdAt: string;
383
+ }
384
+ declare class OrganizationsClient {
385
+ private readonly client;
386
+ constructor(client: ReliPay);
387
+ /** Create an organization; the calling user becomes the OWNER. */
388
+ create(accessToken: string, input: {
389
+ name: string;
390
+ slug: string;
391
+ metadata?: Record<string, unknown>;
392
+ }): Promise<{
393
+ organization: OrganizationDto;
394
+ membership: {
395
+ id: string;
396
+ role: 'OWNER';
397
+ };
398
+ }>;
399
+ /** List organizations the calling user belongs to, with their role. */
400
+ listMine(accessToken: string): Promise<OrganizationWithRoleDto[]>;
401
+ /** Fetch one organization the caller belongs to. */
402
+ get(accessToken: string, organizationId: string): Promise<OrganizationWithRoleDto>;
403
+ /** Update org name / metadata. OWNER + ADMIN only. */
404
+ update(accessToken: string, organizationId: string, input: {
405
+ name?: string;
406
+ metadata?: Record<string, unknown>;
407
+ }): Promise<OrganizationDto>;
408
+ /** List members of an organization the caller belongs to. */
409
+ listMembers(accessToken: string, organizationId: string): Promise<OrganizationMemberDto[]>;
410
+ /**
411
+ * Invite a user. Returns the raw token ONCE — surface via your own
412
+ * email/share channel. OWNER + ADMIN only.
413
+ */
414
+ invite(accessToken: string, organizationId: string, input: {
415
+ email: string;
416
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
417
+ }): Promise<{
418
+ invitation: OrganizationInvitationDto;
419
+ token: string;
420
+ }>;
421
+ /** Revoke a pending invitation. OWNER + ADMIN only. Idempotent. */
422
+ revokeInvitation(accessToken: string, organizationId: string, invitationId: string): Promise<{
423
+ revoked: boolean;
424
+ }>;
425
+ /**
426
+ * Change a member's role. OWNER manages anyone; ADMIN manages MEMBER
427
+ * only. Last-OWNER guard refuses demoting the only OWNER.
428
+ */
429
+ setMemberRole(accessToken: string, organizationId: string, targetEndUserId: string, input: {
430
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
431
+ }): Promise<{
432
+ id: string;
433
+ organizationId: string;
434
+ endUserId: string;
435
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
436
+ }>;
437
+ /** Remove a member (or self). Refuses removing the last OWNER. */
438
+ removeMember(accessToken: string, organizationId: string, targetEndUserId: string): Promise<{
439
+ removed: true;
440
+ }>;
441
+ /** Self-leave. Refuses leaving as the last OWNER. */
442
+ leave(accessToken: string, organizationId: string): Promise<{
443
+ left: true;
444
+ }>;
445
+ /**
446
+ * Accept an organization invitation by raw token. Refuses cross-
447
+ * Application invitations. Idempotent if the caller is already a member.
448
+ */
449
+ acceptInvitation(accessToken: string, input: {
450
+ token: string;
451
+ }): Promise<{
452
+ membership: {
453
+ id: string;
454
+ organizationId: string;
455
+ role: 'OWNER' | 'ADMIN' | 'MEMBER';
456
+ };
457
+ }>;
458
+ }
459
+ interface LicenseShape {
460
+ id: string;
461
+ applicationId: string;
462
+ endUserId: string;
463
+ planId: string | null;
464
+ kind: 'PERPETUAL' | 'TIMED' | 'SEATS';
465
+ status: 'ACTIVE' | 'EXPIRED' | 'REVOKED';
466
+ keyPrefix: string;
467
+ expiresAt: string | null;
468
+ seatsAllowed: number | null;
469
+ metadata: unknown;
470
+ createdAt: string;
471
+ updatedAt: string;
472
+ revokedAt: string | null;
473
+ }
474
+ interface LicenseVerifyOk {
475
+ ok: true;
476
+ license: LicenseShape;
477
+ }
478
+ interface LicenseVerifyFail {
479
+ ok: false;
480
+ reason: 'unknown' | 'wrong_application' | 'revoked' | 'expired' | 'seats_exhausted';
481
+ license?: LicenseShape;
482
+ }
483
+ type LicenseVerifyResult = LicenseVerifyOk | LicenseVerifyFail;
484
+ declare class LicensesClient {
485
+ private readonly client;
486
+ constructor(client: ReliPay);
487
+ /**
488
+ * Verify a license key + record an activation for this machine. Call
489
+ * once at app startup; you'll get a deterministic body (`ok=false` for
490
+ * invalid licenses — never an HTTP error — so your software can loop
491
+ * on the result without try/catch noise).
492
+ *
493
+ * `machineFingerprint` should be a stable identifier you derive client-
494
+ * side (hostname + OS + mac address, hashed). The same fingerprint
495
+ * across re-verifications does NOT consume a new seat.
496
+ *
497
+ * @example
498
+ * ```ts
499
+ * const result = await relipay.licenses.verify({
500
+ * key,
501
+ * machineFingerprint,
502
+ * label: 'Adam\'s MacBook',
503
+ * });
504
+ * if (!result.ok) showLicenseError(result.reason);
505
+ * ```
506
+ */
507
+ verify(input: {
508
+ key: string;
509
+ machineFingerprint: string;
510
+ label?: string;
511
+ }): Promise<LicenseVerifyResult>;
512
+ }
513
+ interface UsageRecordDto {
514
+ id: string;
515
+ applicationId: string;
516
+ meterSlug: string;
517
+ quantity: number;
518
+ endUserId: string | null;
519
+ occurredAt: string;
520
+ }
521
+ interface UsageAggregateDto {
522
+ meterSlug: string;
523
+ total: number;
524
+ from: string | null;
525
+ to: string | null;
526
+ }
527
+ declare class UsageClient {
528
+ private readonly client;
529
+ constructor(client: ReliPay);
530
+ /**
531
+ * Record a usage event against a named meter. `quantity` can be
532
+ * negative to credit back (e.g. refunds). `occurredAt` defaults to
533
+ * server time; pass an ISO string when ingesting historical events.
534
+ */
535
+ record(input: {
536
+ meterSlug: string;
537
+ quantity: number;
538
+ endUserId?: string;
539
+ occurredAt?: string;
540
+ metadata?: Record<string, unknown>;
541
+ }): Promise<UsageRecordDto>;
542
+ /**
543
+ * Sum recorded quantity for a meter, optionally bounded by a time
544
+ * window and/or scoped to one end-user. Use to drive in-app "you've
545
+ * used X of your Y quota this month" displays.
546
+ */
547
+ aggregate(input: {
548
+ meterSlug: string;
549
+ from?: string;
550
+ to?: string;
551
+ endUserId?: string;
552
+ }): Promise<UsageAggregateDto>;
553
+ }
554
+ /**
555
+ * Prepaid credits — the "lead pack" / pay-as-you-go drawdown model. The
556
+ * customer's backend grants credits (by selling a CREDIT-kind plan, which
557
+ * grants automatically on payment) and draws them down per unit consumed.
558
+ *
559
+ * All calls are server-to-server (secret key) and scoped to an end-user id.
560
+ */
561
+ declare class CreditsClient {
562
+ private readonly client;
563
+ constructor(client: ReliPay);
564
+ /** Current spendable balance for an end-user (0 if they've never held credits). */
565
+ getBalance(endUserId: string): Promise<CreditBalanceDto>;
566
+ /**
567
+ * Deduct credits from an end-user. Throws `RelipayError` with
568
+ * `code: "CREDITS_INSUFFICIENT"` (HTTP 402) when the balance is too low.
569
+ *
570
+ * Pass `idempotencyKey` (e.g. the lead id) so a retried call never
571
+ * double-charges — a repeat with the same key returns the original result
572
+ * with `applied: false`.
573
+ */
574
+ consume(input: ConsumeCreditsRequest & {
575
+ endUserId: string;
576
+ }): Promise<{
577
+ balance: number;
578
+ entryId: string;
579
+ applied: boolean;
580
+ }>;
581
+ /** Recent ledger entries for an end-user, newest first. */
582
+ listLedger(endUserId: string, limit?: number): Promise<CreditLedgerEntryDto[]>;
583
+ }
584
+ /**
585
+ * Verify the HMAC signature on an inbound webhook from ReliPay. Returns
586
+ * `true` only when (a) the timestamp is fresh (within `toleranceSeconds`,
587
+ * default 300) AND (b) the signature matches a constant-time compare.
588
+ *
589
+ * Use against the `X-Relipay-Signature` header and the raw request body
590
+ * BYTES (not the parsed JSON — any reserialization breaks the HMAC).
591
+ *
592
+ * @example
593
+ * ```ts
594
+ * import { verifyWebhookSignature } from '@relipay/node';
595
+ *
596
+ * app.post('/webhooks/relipay', { config: { rawBody: true } }, (req) => {
597
+ * const ok = verifyWebhookSignature({
598
+ * header: req.headers['x-relipay-signature'] as string,
599
+ * payload: req.rawBody!,
600
+ * secret: process.env.RELIPAY_WEBHOOK_SECRET!,
601
+ * });
602
+ * if (!ok) return reply.status(401).send({ error: 'bad signature' });
603
+ * // safe to act on req.body
604
+ * });
605
+ * ```
606
+ */
607
+ export declare function verifyWebhookSignature(args: {
608
+ header: string | null | undefined;
609
+ payload: string | Buffer;
610
+ secret: string;
611
+ toleranceSeconds?: number;
612
+ now?: () => number;
613
+ }): boolean;
614
+ declare class BillingClient {
615
+ private readonly client;
616
+ constructor(client: ReliPay);
617
+ /**
618
+ * List the calling Application's active plans. Public — pricing pages
619
+ * typically render straight from this. Application API key only; no
620
+ * user JWT needed.
621
+ *
622
+ * `amount` is in the smallest currency unit (cents/paise/sen) — never
623
+ * a float. Format on display: `${amount / 100} ${currency}`.
624
+ */
625
+ getPlans(): Promise<PlanDto[]>;
626
+ /**
627
+ * Fetch the current end-user's active subscription, or `null` if they
628
+ * have none. Returns the most recent ACTIVE / PENDING / PAST_DUE row.
629
+ *
630
+ * Pass the user's access token (the SDK puts it in `X-Relipay-User-Token`).
631
+ */
632
+ getSubscription(accessToken: string): Promise<SubscriptionDto | null>;
633
+ /**
634
+ * Start a hosted-checkout session. Returns the URL to redirect the user
635
+ * to and the local PENDING Subscription row. Subscription activation
636
+ * happens via the provider's webhook — not synchronously here.
637
+ *
638
+ * Pass `couponCode` to apply a discount. The whole checkout fails if the
639
+ * coupon doesn't validate (typed `RelipayError` with the precise reason).
640
+ *
641
+ * @example
642
+ * ```ts
643
+ * const { url, discountAmount } = await relipay.billing.createCheckout(userAccessToken, {
644
+ * planSlug: 'pro_monthly',
645
+ * successUrl: 'https://yourapp.com/billing?status=ok',
646
+ * cancelUrl: 'https://yourapp.com/billing?status=cancel',
647
+ * couponCode: 'LAUNCH50', // optional
648
+ * });
649
+ * res.redirect(url);
650
+ * ```
651
+ */
652
+ createCheckout(accessToken: string, input: CreateCheckoutRequest & {
653
+ couponCode?: string;
654
+ }): Promise<CheckoutResultDto>;
655
+ /**
656
+ * Validate a coupon for the current user against a plan, *without*
657
+ * applying it. Render "$50 off" on a pricing page before submit.
658
+ *
659
+ * @throws {RelipayError} with one of `COUPON_NOT_FOUND` / `COUPON_INACTIVE`
660
+ * / `COUPON_NOT_YET_STARTED` / `COUPON_EXPIRED` / `COUPON_NOT_APPLICABLE`
661
+ * / `COUPON_CURRENCY_MISMATCH` / `COUPON_REDEMPTION_LIMIT_REACHED` /
662
+ * `COUPON_USER_LIMIT_REACHED`. Surface the message + fix to the user.
663
+ */
664
+ validateCoupon(accessToken: string, input: ValidateCouponRequest): Promise<ValidateCouponResultDto>;
665
+ /**
666
+ * List the billing providers configured + enabled for this Application,
667
+ * in the order the geo router would prefer them. Forward the end-user's
668
+ * `country` (ISO 3166-1 alpha-2) when you have it — the panel/SDK will
669
+ * surface India-specific providers (Razorpay) for IN-country users, etc.
670
+ *
671
+ * Returns the resolved country (echoed back from the server's view of
672
+ * `CF-IPCountry` etc.) plus the ordered provider list. Use this to render
673
+ * a "Pay with..." picker on your pricing page.
674
+ */
675
+ getProviders(country?: string): Promise<ProvidersListDto>;
676
+ }
677
+ //# sourceMappingURL=index.d.ts.map