Skip to content

Commit 459b517

Browse files
feat(stripe): standardize PaymentIntent analytics metadata (#860)
* feat(stripe): add analytics to crypto payments Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> * style(stripe): group payment intent metadata options Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> * feat(stripe): identify payment analytics SDK Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> * refactor: keep SDK versioning out of CLI Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> * refactor: limit payment success analytics context Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> * chore(stripe): remove unused success credential Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> * refactor(stripe): centralize payment analytics metadata Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> * fix(stripe): limit analytics metadata values Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> * chore: update package file count budget Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> --------- Co-authored-by: codex <noreply@openai.com>
1 parent a802ce9 commit 459b517

17 files changed

Lines changed: 177 additions & 30 deletions

.changeset/green-taxis-report.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'mppx': patch
3+
---
4+
5+
Added challenge context to payment-success hooks, standardized Stripe PaymentIntent analytics
6+
metadata, and added a shared SDK version identifier.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"scripts": {
3-
"build": "pnpm build:html && zile && pnpm build:cli && node scripts/set-workspace-bin.js",
3+
"build": "pnpm sync:version && pnpm build:html && zile && pnpm build:cli && node scripts/set-workspace-bin.js",
44
"build:cli": "node --import tsx scripts/build:cli.ts",
55
"build:html": "node --import tsx scripts/build:html.ts",
66
"changeset:publish:main": "zile publish:prepare && node --import tsx scripts/build:cli.ts && node --import tsx scripts/prepare:publish.ts && changeset publish --no-git-tag --tag main && zile publish:post",
77
"changeset:publish": "zile publish:prepare && node --import tsx scripts/build:cli.ts && node --import tsx scripts/prepare:publish.ts && changeset publish && zile publish:post",
8-
"changeset:version": "changeset version && vp fmt .",
8+
"changeset:version": "changeset version && pnpm sync:version && vp fmt .",
99
"check": "vp lint --fix && vp fmt --write .",
1010
"check:ci": "vp lint && vp fmt --check .",
1111
"check:package": "node --import tsx scripts/check:package.ts",
@@ -17,6 +17,7 @@
1717
"dev": "zile dev && node scripts/set-workspace-bin.js",
1818
"dev:example": "node scripts/dev:example.ts",
1919
"mppx": "node --import tsx src/bin.ts",
20+
"sync:version": "node --import tsx scripts/sync-version.ts",
2021
"test": "vp test",
2122
"test:html": "playwright test --config test/html/playwright.config.ts"
2223
},

scripts/check:package.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import path from 'node:path'
66
const root = path.resolve(import.meta.dirname, '..')
77
const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'mppx-package-'))
88
const limits = {
9-
fileCount: 480,
9+
fileCount: 500,
1010
packedBytes: 1_200_000,
1111
unpackedBytes: 4_750_000,
1212
}

scripts/sync-version.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import fs from 'node:fs'
2+
import path from 'node:path'
3+
4+
const root = path.resolve(import.meta.dirname, '..')
5+
const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')) as {
6+
version: string
7+
}
8+
if (!/^[0-9A-Za-z.+-]+$/.test(packageJson.version))
9+
throw new Error(`Invalid package version: ${packageJson.version}`)
10+
11+
const content = `// Generated by scripts/sync-version.ts.
12+
13+
/** Current mppx package version. */
14+
export const version = '${packageJson.version}'
15+
16+
/** Canonical SDK identifier for this mppx release. */
17+
export const sdkIdentifier = \`mppx/\${version}\`
18+
`
19+
20+
fs.writeFileSync(path.join(root, 'src/internal/version.ts'), content)

src/Method.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,12 @@ export type CanOfferFn<method extends Method> = (parameters: {
240240
* like recording the payment in an external system.
241241
*/
242242
export type OnPaymentSuccessFn<method extends Method> = (parameters: {
243+
/** Challenge associated with the successful payment. */
244+
challenge?:
245+
| DeepReadonly<
246+
Challenge.Challenge<z.output<method['schema']['request']>, method['intent'], method['name']>
247+
>
248+
| undefined
243249
input?: globalThis.Request
244250
receipt: DeepReadonly<Receipt.Receipt>
245251
/** Canonical payment request included in the challenge. */

src/internal/version.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import fs from 'node:fs'
2+
3+
import { describe, expect, test } from 'vp/test'
4+
5+
import { sdkIdentifier, version } from './version.js'
6+
7+
const packageJson = JSON.parse(
8+
fs.readFileSync(new URL('../../package.json', import.meta.url), 'utf8'),
9+
) as { version: string }
10+
11+
describe('version', () => {
12+
test('matches the package version', () => {
13+
expect(version).toBe(packageJson.version)
14+
expect(sdkIdentifier).toBe(`mppx/${packageJson.version}`)
15+
})
16+
})

src/internal/version.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Generated by scripts/sync-version.ts.
2+
3+
/** Current mppx package version. */
4+
export const version = '0.9.2'
5+
6+
/** Canonical SDK identifier for this mppx release. */
7+
export const sdkIdentifier = `mppx/${version}`

src/server/Mppx.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,8 +1597,8 @@ describe('server events', () => {
15971597
test('per-method onPaymentSuccess hook fires on successful payment', async () => {
15981598
const calls: string[] = []
15991599
const serverMethod = Method.toServer(eventCharge, {
1600-
onPaymentSuccess: async ({ receipt }) => {
1601-
calls.push(receipt.reference)
1600+
onPaymentSuccess: async ({ challenge, receipt }) => {
1601+
calls.push(`${receipt.reference}:${challenge?.id}`)
16021602
},
16031603
async verify() {
16041604
return receipt('tx-hook')
@@ -1616,7 +1616,8 @@ describe('server events', () => {
16161616
{ request: options() },
16171617
)
16181618

1619-
expect(calls).toEqual(['tx-hook'])
1619+
const parsedChallenge = Challenge.fromResponse(challenge.challenge)
1620+
expect(calls).toEqual([`tx-hook:${parsedChallenge.id}`])
16201621
})
16211622

16221623
test('onPaymentSuccess does not fire for a different method name', async () => {

src/server/Mppx.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ export function create<
536536
serverEvents.on('payment.success', (async (ctx: PaymentSuccessContext) => {
537537
if (ctx.method.name === mi.name && ctx.method.intent === mi.intent) {
538538
await mi.onPaymentSuccess({
539+
challenge: ctx.challenge,
539540
input: ctx.input,
540541
receipt: ctx.receipt,
541542
request: ctx.request,

src/stripe/server/Charge.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Mppx, stripe } from 'mppx/server'
33
import { afterEach, describe, expect, test, vi } from 'vp/test'
44
import * as Http from '~test/Http.js'
55

6+
import { sdkIdentifier } from '../../internal/version.js'
67
import type { StripeClient } from '../internal/types.js'
78
import type { charge as StripeCharge } from './Charge.js'
89

@@ -160,6 +161,12 @@ describe('stripe.charge with client', () => {
160161
amount: 100,
161162
confirm: true,
162163
currency: 'usd',
164+
metadata: {
165+
machine_payment: 'true',
166+
mpp_challenge_id: challenge.id,
167+
mpp_intent: 'charge',
168+
mpp_sdk: sdkIdentifier,
169+
},
163170
shared_payment_granted_token: 'spt_test_token',
164171
})
165172
expect(params.payment_method).toBeUndefined()

0 commit comments

Comments
 (0)