diff --git a/packages/cli/src/__tests__/cli.test.ts b/packages/cli/src/__tests__/cli.test.ts index 0648d02..c469dd5 100644 --- a/packages/cli/src/__tests__/cli.test.ts +++ b/packages/cli/src/__tests__/cli.test.ts @@ -355,6 +355,61 @@ describe('production mode', () => { }); }); + it('parses a single --metadata flag with comma-separated pairs', async () => { + setNextResponse(200, BASE_REQUEST); + + const result = await runProdCli( + 'spend-request', + 'create', + '--payment-method-id', + 'pd_prod_test', + '--merchant-name', + 'Test Merchant', + '--merchant-url', + 'https://example.com', + '--context', + VALID_CONTEXT, + '--amount', + '5000', + '--metadata', + 'order_id:ord_123,team:growth', + '--no-request-approval', + '--json', + ); + + expect(result.exitCode).toBe(0); + const sentBody = JSON.parse(lastRequest.body); + expect(sentBody.metadata).toEqual({ + order_id: 'ord_123', + team: 'growth', + }); + }); + + it('does not include metadata in POST body when --metadata is omitted', async () => { + setNextResponse(200, BASE_REQUEST); + + const result = await runProdCli( + 'spend-request', + 'create', + '--payment-method-id', + 'pd_prod_test', + '--merchant-name', + 'Test Merchant', + '--merchant-url', + 'https://example.com', + '--context', + VALID_CONTEXT, + '--amount', + '5000', + '--no-request-approval', + '--json', + ); + + expect(result.exitCode).toBe(0); + const sentBody = JSON.parse(lastRequest.body); + expect(sentBody.metadata).toBeUndefined(); + }); + it('sends test flag in POST body when --test is used', async () => { setNextResponse(200, BASE_REQUEST); @@ -808,6 +863,27 @@ describe('production mode', () => { expect(card.number).toBe('4000009990001984'); }); + it('returns metadata in JSON output when present', async () => { + setNextResponse(200, { + ...BASE_REQUEST, + metadata: { order_id: 'ord_123', team: 'growth' }, + }); + + const result = await runProdCli( + 'spend-request', + 'retrieve', + 'lsrq_prod_001', + '--json', + ); + + expect(result.exitCode).toBe(0); + const output = parseJson(result.stdout) as Record[]; + expect(output[0].metadata).toEqual({ + order_id: 'ord_123', + team: 'growth', + }); + }); + it('returns card with billing_address and valid_until when present', async () => { setNextResponse(200, { ...BASE_REQUEST, diff --git a/packages/sdk/src/resources/__tests__/spend-request.test.ts b/packages/sdk/src/resources/__tests__/spend-request.test.ts index 7f76c15..2c9bbd6 100644 --- a/packages/sdk/src/resources/__tests__/spend-request.test.ts +++ b/packages/sdk/src/resources/__tests__/spend-request.test.ts @@ -134,6 +134,16 @@ describe('SpendRequestResource', () => { }); }); + it('does not include metadata in POST body when not set', async () => { + mockFetchResponse(200, spendRequestResponse); + + await repo.createSpendRequest(validParams); + + const [, opts] = mockFetch.mock.calls[0]; + const sentBody = JSON.parse(opts.body); + expect(sentBody.metadata).toBeUndefined(); + }); + it('serializes test flag in POST body when true', async () => { const paramsWithTest: CreateSpendRequestParams = { ...validParams, @@ -397,6 +407,20 @@ describe('SpendRequestResource', () => { }); }); + it('returns metadata when present in the response', async () => { + mockFetchResponse(200, { + ...spendRequestResponse, + metadata: { order_id: 'ord_123', team: 'growth' }, + }); + + const result = await repo.getSpendRequest('si_123'); + + expect(result?.metadata).toEqual({ + order_id: 'ord_123', + team: 'growth', + }); + }); + it('normalizes legacy string shared_payment_token to object form', async () => { mockFetchResponse(200, { ...spendRequestResponse, diff --git a/packages/sdk/src/types/index.ts b/packages/sdk/src/types/index.ts index aa60c9b..f2935d5 100644 --- a/packages/sdk/src/types/index.ts +++ b/packages/sdk/src/types/index.ts @@ -138,6 +138,7 @@ export interface SpendRequest { payment_status_details?: PaymentStatusDetails | null; link_transaction_id?: string; activity_url?: string; + metadata?: Record; created_at: string; updated_at: string; } diff --git a/plugins/link/.claude-plugin/plugin.json b/plugins/link/.claude-plugin/plugin.json index 2a96971..4a900ab 100644 --- a/plugins/link/.claude-plugin/plugin.json +++ b/plugins/link/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "link", - "version": "0.10.1", + "version": "0.11.0", "description": "Authenticate with Link, create spend requests, and retrieve one-time-use card or shared payment token credentials for user-approved purchases.", "author": { "name": "Stripe" diff --git a/plugins/link/.codex-plugin/plugin.json b/plugins/link/.codex-plugin/plugin.json index 00c8768..55b6d87 100644 --- a/plugins/link/.codex-plugin/plugin.json +++ b/plugins/link/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "link", - "version": "0.10.1", + "version": "0.11.0", "description": "Secure, one-time-use payment credentials from Link", "author": { "name": "Stripe", diff --git a/plugins/link/.cursor-plugin/plugin.json b/plugins/link/.cursor-plugin/plugin.json index 5f3264a..50819fc 100644 --- a/plugins/link/.cursor-plugin/plugin.json +++ b/plugins/link/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "link", "displayName": "Stripe Link", - "version": "0.10.1", + "version": "0.11.0", "description": "Get secure, one-time-use payment credentials from a Link wallet so agents can complete purchases on your behalf.", "author": { "name": "Stripe" diff --git a/skills/create-payment-credential/SKILL.md b/skills/create-payment-credential/SKILL.md index ce6cca3..9a5cb6b 100644 --- a/skills/create-payment-credential/SKILL.md +++ b/skills/create-payment-credential/SKILL.md @@ -1,5 +1,5 @@ --- -version: 0.10.1 +version: 0.11.0 name: create-payment-credential description: | Gets secure, one-time-use payment credentials (cards, tokens) from a Link wallet so agents can complete purchases on behalf of users. Use when the user says "get me a card", "buy something", "pay for X", "make a purchase", "I need to pay", "complete checkout", or asks to transact on any merchant site. Use when the user asks to connect or log in to or sign up for their Link account.