diff --git a/.changeset/pay-subscription-invoices.md b/.changeset/pay-subscription-invoices.md new file mode 100644 index 0000000..5e36b5a --- /dev/null +++ b/.changeset/pay-subscription-invoices.md @@ -0,0 +1,5 @@ +--- +"@agentaos/pay": minor +--- + +Add `subscriptions.invoices(id)` and expose `linkId` / cancel fields on `Subscription` so a logged-in SaaS can match a product, list billing history, and cancel. diff --git a/packages/pay/src/index.ts b/packages/pay/src/index.ts index 7be31db..4ccc161 100644 --- a/packages/pay/src/index.ts +++ b/packages/pay/src/index.ts @@ -14,6 +14,7 @@ export type { Invoice, ListInvoiceParams, Subscription, + SubscriptionInvoice, SubscriptionStatus, CancelSubscriptionParams, CancelSubscriptionResult, diff --git a/packages/pay/src/resources/resources.test.ts b/packages/pay/src/resources/resources.test.ts index 207999c..3e657b5 100644 --- a/packages/pay/src/resources/resources.test.ts +++ b/packages/pay/src/resources/resources.test.ts @@ -267,6 +267,19 @@ describe('subscriptions', () => { await client().subscriptions.cancel('sub_1', { atPeriodEnd: false }); expect(JSON.parse(calls[0]?.body ?? '{}')).toEqual({ atPeriodEnd: false }); }); + + it('invoices → GET /api/v1/gateway/subscriptions/:id/invoices', async () => { + const calls = stubRoutes([ + { id: 'inv_1', invoice_number: 'INV-2026-0001', issued_at: '2026-08-18T00:00:00Z' }, + ]); + const rows = await client().subscriptions.invoices('sub_1'); + expect(calls[0]?.method).toBe('GET'); + expect(pathOf(calls[0])).toBe('/api/v1/gateway/subscriptions/sub_1/invoices'); + expect(rows[0]).toMatchObject({ + invoiceNumber: 'INV-2026-0001', + issuedAt: '2026-08-18T00:00:00Z', + }); + }); }); describe('customers', () => { diff --git a/packages/pay/src/resources/subscriptions.ts b/packages/pay/src/resources/subscriptions.ts index f8e9a5d..1e1d316 100644 --- a/packages/pay/src/resources/subscriptions.ts +++ b/packages/pay/src/resources/subscriptions.ts @@ -4,6 +4,7 @@ import type { ListParams, PaginatedList, Subscription, + SubscriptionInvoice, } from '../types.js'; import { BaseResource } from './base.js'; @@ -34,4 +35,9 @@ export class SubscriptionsResource extends BaseResource { atPeriodEnd: params?.atPeriodEnd ?? true, }); } + + /** Per-cycle invoices for one subscription, newest first. */ + async invoices(id: string): Promise { + return this.get(`${BASE_PATH}/${id}/invoices`); + } } diff --git a/packages/pay/src/types.ts b/packages/pay/src/types.ts index 9c6a428..3173a10 100644 --- a/packages/pay/src/types.ts +++ b/packages/pay/src/types.ts @@ -96,6 +96,7 @@ export interface CreatePaymentLinkParams { } export interface PaymentLink { + /** Same value as `checkouts.create({ linkId })`. On the product page: Copy link ID. */ id: string; orgId: string; amount: number; @@ -130,7 +131,7 @@ export interface PaymentLink { // --------------------------------------------------------------------------- export interface CreateCheckoutParams { - /** Link ID to create checkout from. Optional — omit to create a standalone checkout. */ + /** `paymentLinks.id`. On the product page this is Copy link ID. Omit for a standalone checkout. */ linkId?: string; /** Amount in currency units (e.g. 10.00). Required if no linkId. */ amount?: number; @@ -292,6 +293,8 @@ export interface Subscription { customerName: string | null; /** The plan (subscription payment-link) name or description. */ planName: string | null; + /** Same UUID as `checkouts.create({ linkId })` / product page Copy link ID. */ + linkId: string; billingInterval: 'month' | 'year' | null; status: SubscriptionStatus; /** Per-cycle amount in integer minor units (e.g. 1999 = €19.99). */ @@ -300,6 +303,21 @@ export interface Subscription { /** ISO 8601 end of the current paid period; null before the first cycle books. */ currentPeriodEnd: string | null; stripeSubscriptionId: string | null; + cancelAtPeriodEnd: boolean; + canceledAt: string | null; + effectiveCancelDate: string | null; +} + +/** One cycle invoice from GET /gateway/subscriptions/:id/invoices. */ +export interface SubscriptionInvoice { + id: string; + invoiceNumber: string; + status: string; + issuedAt: string; + amount: number; + currency: string; + disputed: boolean; + chargedBack: boolean; } export interface CancelSubscriptionParams {