@pome-sh/cli 0.1.1 → 0.2.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/README.md +5 -6
- package/dist/build-info.json +3 -3
- package/dist/src/cli/eval.js +26 -8
- package/dist/src/cli/init-sdk.d.ts +0 -3
- package/dist/src/cli/init-sdk.js +117 -24
- package/dist/src/cli/main.js +23 -69
- package/dist/src/cli/register.d.ts +9 -0
- package/dist/src/cli/register.js +51 -5
- package/dist/src/cli/session.d.ts +7 -1
- package/dist/src/cli/session.js +87 -27
- package/dist/src/demo/runDemo.js +3 -2
- package/dist/src/hosted/client.d.ts +41 -5
- package/dist/src/hosted/client.js +333 -19
- package/dist/src/hosted/errors.d.ts +11 -1
- package/dist/src/hosted/errors.js +8 -1
- package/dist/src/hosted/evalResultView.d.ts +2 -0
- package/dist/src/hosted/evalResultView.js +22 -0
- package/dist/src/hosted/uploadAndFinalize.d.ts +18 -1
- package/dist/src/hosted/uploadAndFinalize.js +51 -3
- package/dist/src/recorder/inspect.js +20 -2
- package/dist/src/runner/mergeAdapterSignals.js +2 -2
- package/dist/src/runner/ports.js +3 -0
- package/dist/src/runner/runScenario.d.ts +79 -2
- package/dist/src/runner/runScenario.js +94 -36
- package/dist/src/runner/runScenarioHosted.d.ts +37 -0
- package/dist/src/runner/runScenarioHosted.js +257 -58
- package/dist/src/runner/runTrialGroup.d.ts +1 -3
- package/dist/src/runner/runTrialGroup.js +1 -3
- package/dist/src/scenario/parseScenario.d.ts +5 -0
- package/dist/src/scenario/parseScenario.js +135 -8
- package/dist/src/scenario/scenarioSchema.d.ts +200 -3
- package/dist/src/scenario/scenarioSchema.js +12 -1
- package/dist/src/twin/twinHarness.d.ts +10 -0
- package/dist/src/twin/twinHarness.js +6 -2
- package/dist/src/twin/twinStart.d.ts +19 -0
- package/dist/src/twin/twinStart.js +156 -0
- package/node_modules/@pome-sh/sdk/dist/fidelity-inventory.d.ts +121 -0
- package/node_modules/@pome-sh/sdk/dist/fidelity-inventory.js +204 -0
- package/node_modules/@pome-sh/sdk/dist/fidelity-inventory.js.map +1 -0
- package/node_modules/@pome-sh/sdk/dist/parity.d.ts +55 -0
- package/node_modules/@pome-sh/sdk/dist/parity.js +112 -0
- package/node_modules/@pome-sh/sdk/dist/parity.js.map +1 -0
- package/node_modules/@pome-sh/sdk/dist/server-helpers.d.ts +15 -0
- package/node_modules/@pome-sh/sdk/dist/server-helpers.js +96 -0
- package/node_modules/@pome-sh/sdk/dist/server-helpers.js.map +1 -1
- package/node_modules/@pome-sh/sdk/dist/server.d.ts +1 -1
- package/node_modules/@pome-sh/sdk/dist/server.js +9 -7
- package/node_modules/@pome-sh/sdk/dist/server.js.map +1 -1
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/errors.d.ts +39 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/errors.js +33 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/errors.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/evaluator-hooks.d.ts +13 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/evaluator-hooks.js +6 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/evaluator-hooks.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/finalize-shapes.d.ts +215 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/finalize-shapes.js +83 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/finalize-shapes.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/github-access-control.d.ts +175 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/github-access-control.js +182 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/github-access-control.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/identity.d.ts +90 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/identity.js +72 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/identity.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/index.d.ts +27 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/index.js +34 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/index.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/event-schema.d.ts +286 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/event-schema.js +33 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/event-schema.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/fixtures/data.d.ts +65 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/fixtures/data.js +455 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/fixtures/data.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/fixtures/index.d.ts +39 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/fixtures/index.js +47 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/fixtures/index.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/index.d.ts +25 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/index.js +27 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/index.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/legacy-shim.d.ts +100 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/legacy-shim.js +185 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/legacy-shim.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/map-span.d.ts +64 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/map-span.js +112 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/map-span.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/nano.d.ts +26 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/nano.js +61 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/nano.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/project.d.ts +42 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/project.js +96 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/project.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/semconv.d.ts +51 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/semconv.js +74 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/semconv.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/span-event.d.ts +134 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/span-event.js +152 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/otel/span-event.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/recorder-events.d.ts +418 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/recorder-events.js +234 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/recorder-events.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/redaction.d.ts +2 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/redaction.js +152 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/redaction.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/rest.d.ts +198 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/rest.js +225 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/rest.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/run.d.ts +201 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/run.js +166 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/run.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/seed-state.d.ts +446 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/seed-state.js +167 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/seed-state.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/sessions.d.ts +53 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/sessions.js +51 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/sessions.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/task-vocab.d.ts +66 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/task-vocab.js +84 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/task-vocab.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/task.d.ts +483 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/task.js +60 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/dist/task.js.map +1 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/package.json +54 -0
- package/node_modules/@pome-sh/sdk/node_modules/@pome-sh/shared-types/trace-contract.json +41 -0
- package/node_modules/@pome-sh/sdk/package.json +6 -2
- package/node_modules/@pome-sh/shared-types/dist/errors.d.ts +39 -0
- package/node_modules/@pome-sh/shared-types/dist/errors.js +33 -0
- package/node_modules/@pome-sh/shared-types/dist/errors.js.map +1 -0
- package/node_modules/@pome-sh/shared-types/dist/finalize-shapes.d.ts +252 -0
- package/node_modules/@pome-sh/shared-types/dist/finalize-shapes.js +122 -0
- package/node_modules/@pome-sh/shared-types/dist/finalize-shapes.js.map +1 -0
- package/node_modules/@pome-sh/shared-types/dist/identity.d.ts +90 -0
- package/node_modules/@pome-sh/shared-types/dist/identity.js +72 -0
- package/node_modules/@pome-sh/shared-types/dist/identity.js.map +1 -0
- package/node_modules/@pome-sh/shared-types/dist/index.d.ts +13 -1353
- package/node_modules/@pome-sh/shared-types/dist/index.js +14 -568
- package/node_modules/@pome-sh/shared-types/dist/index.js.map +1 -1
- package/node_modules/@pome-sh/shared-types/dist/otel/event-schema.d.ts +45 -0
- package/node_modules/@pome-sh/shared-types/dist/otel/legacy-shim.js +8 -0
- package/node_modules/@pome-sh/shared-types/dist/otel/legacy-shim.js.map +1 -1
- package/node_modules/@pome-sh/shared-types/dist/otel/semconv.js +7 -0
- package/node_modules/@pome-sh/shared-types/dist/otel/semconv.js.map +1 -1
- package/node_modules/@pome-sh/shared-types/dist/recorder-events.d.ts +62 -0
- package/node_modules/@pome-sh/shared-types/dist/recorder-events.js +36 -0
- package/node_modules/@pome-sh/shared-types/dist/recorder-events.js.map +1 -1
- package/node_modules/@pome-sh/shared-types/dist/rest.d.ts +245 -0
- package/node_modules/@pome-sh/shared-types/dist/rest.js +303 -0
- package/node_modules/@pome-sh/shared-types/dist/rest.js.map +1 -0
- package/node_modules/@pome-sh/shared-types/dist/run.d.ts +7 -0
- package/node_modules/@pome-sh/shared-types/dist/run.js +4 -0
- package/node_modules/@pome-sh/shared-types/dist/run.js.map +1 -1
- package/node_modules/@pome-sh/shared-types/dist/seed-envelope.d.ts +14 -0
- package/node_modules/@pome-sh/shared-types/dist/seed-envelope.js +46 -0
- package/node_modules/@pome-sh/shared-types/dist/seed-envelope.js.map +1 -0
- package/node_modules/@pome-sh/shared-types/dist/seed-state.d.ts +446 -0
- package/node_modules/@pome-sh/shared-types/dist/seed-state.js +167 -0
- package/node_modules/@pome-sh/shared-types/dist/seed-state.js.map +1 -0
- package/node_modules/@pome-sh/shared-types/dist/sessions.d.ts +53 -0
- package/node_modules/@pome-sh/shared-types/dist/sessions.js +51 -0
- package/node_modules/@pome-sh/shared-types/dist/sessions.js.map +1 -0
- package/node_modules/@pome-sh/shared-types/dist/task.d.ts +485 -0
- package/node_modules/@pome-sh/shared-types/dist/task.js +60 -0
- package/node_modules/@pome-sh/shared-types/dist/task.js.map +1 -0
- package/node_modules/@pome-sh/shared-types/package.json +2 -1
- package/node_modules/@pome-sh/shared-types/trace-contract.json +1 -1
- package/node_modules/@pome-sh/twin-github/FIDELITY.md +127 -67
- package/node_modules/@pome-sh/twin-github/README.md +3 -7
- package/node_modules/@pome-sh/twin-github/dist/examples/claude-github-agent.js +4 -20
- package/node_modules/@pome-sh/twin-github/dist/examples/claude-github-agent.js.map +1 -1
- package/node_modules/@pome-sh/twin-github/dist/src/access-control.d.ts +3 -3
- package/node_modules/@pome-sh/twin-github/dist/src/domain.d.ts +141 -3
- package/node_modules/@pome-sh/twin-github/dist/src/domain.js +0 -0
- package/node_modules/@pome-sh/twin-github/dist/src/domain.js.map +1 -1
- package/node_modules/@pome-sh/twin-github/dist/src/routes.js +2 -0
- package/node_modules/@pome-sh/twin-github/dist/src/routes.js.map +1 -1
- package/node_modules/@pome-sh/twin-github/dist/src/seed.d.ts +1 -1
- package/node_modules/@pome-sh/twin-github/dist/src/serializers.d.ts +2 -2
- package/node_modules/@pome-sh/twin-github/dist/src/server.js +7 -6
- package/node_modules/@pome-sh/twin-github/dist/src/server.js.map +1 -1
- package/node_modules/@pome-sh/twin-github/dist/src/tools.d.ts +168 -18
- package/node_modules/@pome-sh/twin-github/dist/src/tools.js +23 -0
- package/node_modules/@pome-sh/twin-github/dist/src/tools.js.map +1 -1
- package/node_modules/@pome-sh/twin-github/fidelity.inventory.json +773 -0
- package/node_modules/@pome-sh/twin-github/package.json +5 -4
- package/node_modules/@pome-sh/twin-slack/FIDELITY.md +121 -35
- package/node_modules/@pome-sh/twin-slack/README.md +2 -6
- package/node_modules/@pome-sh/twin-slack/dist/scripts/fidelity-parity.d.ts +1 -0
- package/node_modules/@pome-sh/twin-slack/dist/scripts/fidelity-parity.js +92 -0
- package/node_modules/@pome-sh/twin-slack/dist/scripts/fidelity-parity.js.map +1 -0
- package/node_modules/@pome-sh/twin-slack/dist/scripts/smoke.js +3 -3
- package/node_modules/@pome-sh/twin-slack/dist/scripts/smoke.js.map +1 -1
- package/node_modules/@pome-sh/twin-slack/dist/scripts/validate-mcp.js +1 -1
- package/node_modules/@pome-sh/twin-slack/dist/scripts/validate-mcp.js.map +1 -1
- package/node_modules/@pome-sh/twin-slack/dist/src/server.js +7 -6
- package/node_modules/@pome-sh/twin-slack/dist/src/server.js.map +1 -1
- package/node_modules/@pome-sh/twin-slack/dist/src/tools.d.ts +30 -4
- package/node_modules/@pome-sh/twin-slack/dist/src/tools.js +48 -1
- package/node_modules/@pome-sh/twin-slack/dist/src/tools.js.map +1 -1
- package/node_modules/@pome-sh/twin-slack/fidelity.inventory.json +377 -0
- package/node_modules/@pome-sh/twin-slack/package.json +5 -4
- package/node_modules/@pome-sh/twin-stripe/FIDELITY.md +151 -50
- package/node_modules/@pome-sh/twin-stripe/README.md +42 -45
- package/node_modules/@pome-sh/twin-stripe/dist/scripts/fidelity-parity.d.ts +1 -0
- package/node_modules/@pome-sh/twin-stripe/dist/scripts/fidelity-parity.js +186 -0
- package/node_modules/@pome-sh/twin-stripe/dist/scripts/fidelity-parity.js.map +1 -0
- package/node_modules/@pome-sh/twin-stripe/dist/scripts/smoke.js +8 -4
- package/node_modules/@pome-sh/twin-stripe/dist/scripts/smoke.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/db.js +26 -2
- package/node_modules/@pome-sh/twin-stripe/dist/src/db.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/billing.d.ts +101 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/billing.js +195 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/billing.js.map +1 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/charges.d.ts +15 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/charges.js +17 -5
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/charges.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/customers.d.ts +47 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/customers.js +103 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/customers.js.map +1 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/events.d.ts +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/events.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/index.d.ts +6 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/index.js +3 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/index.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/payment-intents.d.ts +61 -2
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/payment-intents.js +214 -52
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/payment-intents.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/payment-methods.d.ts +45 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/payment-methods.js +159 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/payment-methods.js.map +1 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/refunds.d.ts +6 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/refunds.js +16 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/refunds.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/schema.d.ts +1 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/schema.js +144 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/schema.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/stripe-domain.d.ts +420 -43
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/stripe-domain.js +442 -13
- package/node_modules/@pome-sh/twin-stripe/dist/src/domain/stripe-domain.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/errors.d.ts +19 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/errors.js +15 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/errors.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/ids.d.ts +2 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/ids.js +2 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/ids.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/_helpers.d.ts +8 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/_helpers.js +105 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/_helpers.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/billing.d.ts +4 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/billing.js +117 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/billing.js.map +1 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/customers.d.ts +4 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/customers.js +47 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/customers.js.map +1 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/index.js +6 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/index.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/payment-intents.js +28 -102
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/payment-intents.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/payment-methods.d.ts +4 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/payment-methods.js +33 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/routes/payment-methods.js.map +1 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/seed.d.ts +10 -8
- package/node_modules/@pome-sh/twin-stripe/dist/src/seed.js +3 -2
- package/node_modules/@pome-sh/twin-stripe/dist/src/seed.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/serializers.d.ts +181 -21
- package/node_modules/@pome-sh/twin-stripe/dist/src/serializers.js +257 -34
- package/node_modules/@pome-sh/twin-stripe/dist/src/serializers.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/server.js +7 -6
- package/node_modules/@pome-sh/twin-stripe/dist/src/server.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/tools.d.ts +107 -5
- package/node_modules/@pome-sh/twin-stripe/dist/src/tools.js +139 -6
- package/node_modules/@pome-sh/twin-stripe/dist/src/tools.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/types.d.ts +73 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/upstream-types.d.ts +8 -0
- package/node_modules/@pome-sh/twin-stripe/dist/src/x402.js +6 -1
- package/node_modules/@pome-sh/twin-stripe/dist/src/x402.js.map +1 -1
- package/node_modules/@pome-sh/twin-stripe/fidelity.inventory.json +425 -0
- package/node_modules/@pome-sh/twin-stripe/package.json +5 -4
- package/package.json +8 -8
- package/skills/pome-setup/SKILL.md +1 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// Customers domain — F-732 (M5 customer-management hot path).
|
|
4
|
+
//
|
|
5
|
+
// Stripe semantics carried here: metadata updates merge per-key (an empty
|
|
6
|
+
// or null value unsets the key), DELETE is a soft delete that keeps the row
|
|
7
|
+
// so retrieve can serve the `{deleted: true}` stub exactly like real
|
|
8
|
+
// Stripe, and deleting a customer detaches its payment methods in the same
|
|
9
|
+
// better-sqlite3 transaction.
|
|
10
|
+
import { TwinError } from "../errors.js";
|
|
11
|
+
import { newId } from "../ids.js";
|
|
12
|
+
import { nowUnix } from "../util.js";
|
|
13
|
+
import { ensureStripeTables } from "./schema.js";
|
|
14
|
+
import { listPaginated } from "./payment-intents.js";
|
|
15
|
+
export class CustomersDomain {
|
|
16
|
+
db;
|
|
17
|
+
constructor(db) {
|
|
18
|
+
this.db = db;
|
|
19
|
+
ensureStripeTables(db);
|
|
20
|
+
}
|
|
21
|
+
create(accountId, input) {
|
|
22
|
+
const id = newId("customer");
|
|
23
|
+
const metadata = {};
|
|
24
|
+
for (const [key, value] of Object.entries(input.metadata ?? {})) {
|
|
25
|
+
if (value !== null && value !== "")
|
|
26
|
+
metadata[key] = value;
|
|
27
|
+
}
|
|
28
|
+
this.db
|
|
29
|
+
.prepare(`INSERT INTO customers (
|
|
30
|
+
id, account_id, name, email, description, phone, metadata_json, deleted, created
|
|
31
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`)
|
|
32
|
+
.run(id, accountId, input.name ?? null, input.email ?? null, input.description ?? null, input.phone ?? null, JSON.stringify(metadata), nowUnix());
|
|
33
|
+
return this.requireById(accountId, id);
|
|
34
|
+
}
|
|
35
|
+
getById(accountId, id) {
|
|
36
|
+
return (this.db
|
|
37
|
+
.prepare("SELECT * FROM customers WHERE id = ? AND account_id = ?")
|
|
38
|
+
.get(id, accountId) ?? null);
|
|
39
|
+
}
|
|
40
|
+
/** Any row, deleted or live. Missing id → Stripe's 404 resource_missing. */
|
|
41
|
+
requireById(accountId, id) {
|
|
42
|
+
const row = this.getById(accountId, id);
|
|
43
|
+
if (!row)
|
|
44
|
+
throw noSuchCustomer(id);
|
|
45
|
+
return row;
|
|
46
|
+
}
|
|
47
|
+
/** Live row only. Deleted customers 404 for writes and sub-resources. */
|
|
48
|
+
requireLive(accountId, id) {
|
|
49
|
+
const row = this.requireById(accountId, id);
|
|
50
|
+
if (row.deleted)
|
|
51
|
+
throw noSuchCustomer(id);
|
|
52
|
+
return row;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Update fields; metadata merges per-key, an empty/null value unsets the
|
|
56
|
+
* key (real Stripe's metadata contract).
|
|
57
|
+
*/
|
|
58
|
+
update(accountId, id, input) {
|
|
59
|
+
const existing = this.requireLive(accountId, id);
|
|
60
|
+
const metadata = JSON.parse(existing.metadata_json);
|
|
61
|
+
for (const [key, value] of Object.entries(input.metadata ?? {})) {
|
|
62
|
+
if (value === null || value === "")
|
|
63
|
+
delete metadata[key];
|
|
64
|
+
else
|
|
65
|
+
metadata[key] = value;
|
|
66
|
+
}
|
|
67
|
+
this.db
|
|
68
|
+
.prepare(`UPDATE customers SET name = ?, email = ?, description = ?, phone = ?, metadata_json = ?
|
|
69
|
+
WHERE id = ? AND account_id = ?`)
|
|
70
|
+
.run(input.name !== undefined ? input.name : existing.name, input.email !== undefined ? input.email : existing.email, input.description !== undefined ? input.description : existing.description, input.phone !== undefined ? input.phone : existing.phone, JSON.stringify(metadata), id, accountId);
|
|
71
|
+
return this.requireById(accountId, id);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Soft-delete. Detaches the customer's payment methods in the same
|
|
75
|
+
* transaction (real Stripe invalidates them on customer deletion).
|
|
76
|
+
* Idempotent: deleting an already-deleted customer returns the row.
|
|
77
|
+
*/
|
|
78
|
+
delete(accountId, id) {
|
|
79
|
+
const tx = this.db.transaction(() => {
|
|
80
|
+
const existing = this.requireById(accountId, id);
|
|
81
|
+
if (existing.deleted)
|
|
82
|
+
return { row: existing, alreadyDeleted: true };
|
|
83
|
+
this.db
|
|
84
|
+
.prepare("UPDATE customers SET deleted = 1 WHERE id = ? AND account_id = ?")
|
|
85
|
+
.run(id, accountId);
|
|
86
|
+
this.db
|
|
87
|
+
.prepare("UPDATE payment_methods SET customer_id = NULL, detached = 1 WHERE customer_id = ? AND account_id = ?")
|
|
88
|
+
.run(id, accountId);
|
|
89
|
+
return { row: this.requireById(accountId, id), alreadyDeleted: false };
|
|
90
|
+
});
|
|
91
|
+
return tx();
|
|
92
|
+
}
|
|
93
|
+
list(accountId, input) {
|
|
94
|
+
const extra = [{ sql: "deleted = 0", args: [] }];
|
|
95
|
+
if (input.email)
|
|
96
|
+
extra.push({ sql: "email = ?", args: [input.email] });
|
|
97
|
+
return listPaginated(this.db, "customers", accountId, input, extra);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function noSuchCustomer(id) {
|
|
101
|
+
return new TwinError("invalid_request_error", "resource_missing", `No such customer: '${id}'.`, { param: "customer", statusCode: 404 });
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=customers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"customers.js","sourceRoot":"","sources":["../../../src/domain/customers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TwinStripeDatabase, EventRow } from "../types.js";
|
|
2
|
-
export type EventType = "payment_intent.created" | "payment_intent.requires_action" | "payment_intent.processing" | "payment_intent.succeeded" | "payment_intent.canceled" | "charge.succeeded" | "charge.refunded";
|
|
2
|
+
export type EventType = "payment_intent.created" | "payment_intent.requires_action" | "payment_intent.processing" | "payment_intent.succeeded" | "payment_intent.payment_failed" | "payment_intent.canceled" | "charge.succeeded" | "charge.failed" | "charge.refunded" | "refund.created" | "customer.created" | "customer.updated" | "customer.deleted" | "payment_method.attached" | "payment_method.detached";
|
|
3
3
|
export type CreateEventInput = {
|
|
4
4
|
type: EventType;
|
|
5
5
|
object: unknown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../../src/domain/events.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,mCAAmC;AACnC,EAAE;AACF,8DAA8D;AAC9D,2DAA2D;AAG3D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../../src/domain/events.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,mCAAmC;AACnC,EAAE;AACF,8DAA8D;AAC9D,2DAA2D;AAG3D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAoCrD,MAAM,OAAO,YAAY;IACF;IAArB,YAAqB,EAAsB;QAAtB,OAAE,GAAF,EAAE,CAAoB;QACzC,kBAAkB,CAAC,EAAE,CAAC,CAAC;IACzB,CAAC;IAED,MAAM,CAAC,SAAiB,EAAE,KAAuB;QAC/C,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1B,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,IAAI,CAAC,EAAE;aACJ,OAAO,CACN;;;0CAGkC,CACnC;aACA,GAAG,CACF,EAAE,EACF,SAAS,EACT,KAAK,CAAC,IAAI,EACV,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,EACxC,KAAK,CAAC,uBAAuB,IAAI,IAAI,EACrC,kBAAkB,EAClB,GAAG,CACJ,CAAC;QACJ,OAAO,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,OAAO,CAAC,SAAiB,EAAE,EAAU;QACnC,OAAO,CACJ,IAAI,CAAC,EAAE;aACL,OAAO,CAAC,sDAAsD,CAAC;aAC/D,GAAG,CAAC,EAAE,EAAE,SAAS,CAA0B,IAAI,IAAI,CACvD,CAAC;IACJ,CAAC;IAED,WAAW,CAAC,SAAiB,EAAE,EAAU;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,SAAS,CACjB,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EAAE,IAAI,EACzB,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,CACpC,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,IAAI,CAAC,SAAiB,EAAE,KAAsB;QAC5C,OAAO,aAAa,CAAW,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IACtE,CAAC;CACF"}
|
|
@@ -9,4 +9,10 @@ export { EventsDomain } from "./events.js";
|
|
|
9
9
|
export type { ListEventsInput } from "./events.js";
|
|
10
10
|
export { RefundsDomain } from "./refunds.js";
|
|
11
11
|
export type { CreateRefundInput, ListRefundsInput } from "./refunds.js";
|
|
12
|
+
export { CustomersDomain } from "./customers.js";
|
|
13
|
+
export type { CustomerFieldsInput, ListCustomersInput } from "./customers.js";
|
|
14
|
+
export { PaymentMethodsDomain } from "./payment-methods.js";
|
|
15
|
+
export type { CreatePaymentMethodInput, ListCustomerPaymentMethodsInput, } from "./payment-methods.js";
|
|
16
|
+
export { BillingDomain } from "./billing.js";
|
|
17
|
+
export type { CreatePriceInput, CreateSubscriptionInput, ListBillingInput, ProductFieldsInput, UpdateSubscriptionInput, } from "./billing.js";
|
|
12
18
|
export { ensureStripeTables, resetStripeTables } from "./schema.js";
|
|
@@ -9,5 +9,8 @@ export { ChargesDomain } from "./charges.js";
|
|
|
9
9
|
export { BalanceDomain } from "./balance.js";
|
|
10
10
|
export { EventsDomain } from "./events.js";
|
|
11
11
|
export { RefundsDomain } from "./refunds.js";
|
|
12
|
+
export { CustomersDomain } from "./customers.js";
|
|
13
|
+
export { PaymentMethodsDomain } from "./payment-methods.js";
|
|
14
|
+
export { BillingDomain } from "./billing.js";
|
|
12
15
|
export { ensureStripeTables, resetStripeTables } from "./schema.js";
|
|
13
16
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/domain/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,qEAAqE;AACrE,wEAAwE;AACxE,iEAAiE;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE/E,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/domain/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,qEAAqE;AACrE,wEAAwE;AACxE,iEAAiE;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE/E,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAK5D,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAQ7C,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -13,11 +13,20 @@ export type CreatePIInput = {
|
|
|
13
13
|
payment_method_options?: {
|
|
14
14
|
crypto?: PaymentMethodOptionsCrypto;
|
|
15
15
|
};
|
|
16
|
+
payment_method?: string;
|
|
17
|
+
customer?: string;
|
|
18
|
+
confirm?: boolean;
|
|
16
19
|
metadata?: Record<string, string>;
|
|
17
20
|
capture_method?: string;
|
|
18
21
|
confirmation_method?: string;
|
|
19
22
|
idempotency_key?: string | null;
|
|
20
23
|
};
|
|
24
|
+
export type UpdatePIInput = {
|
|
25
|
+
amount?: number;
|
|
26
|
+
metadata?: Record<string, string | null>;
|
|
27
|
+
payment_method?: string;
|
|
28
|
+
customer?: string;
|
|
29
|
+
};
|
|
21
30
|
export type ListPIsInput = {
|
|
22
31
|
limit?: number;
|
|
23
32
|
starting_after?: string;
|
|
@@ -31,13 +40,48 @@ declare const TERMINAL_STATES: ReadonlySet<PIStatus>;
|
|
|
31
40
|
export declare class PaymentIntentsDomain {
|
|
32
41
|
readonly db: TwinStripeDatabase;
|
|
33
42
|
constructor(db: TwinStripeDatabase);
|
|
34
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Insert a fresh PI. The caller (StripeDomain) has already resolved and
|
|
45
|
+
* cross-validated `payment_method` / `customer` for the card rail —
|
|
46
|
+
* `resolved` carries the validated ids so this module stays free of the
|
|
47
|
+
* sibling domains.
|
|
48
|
+
*/
|
|
49
|
+
create(accountId: string, input: CreatePIInput, resolved: {
|
|
50
|
+
paymentMethodId: string | null;
|
|
51
|
+
customerId: string | null;
|
|
52
|
+
}): PIRow;
|
|
35
53
|
/**
|
|
36
54
|
* Crypto-deposit confirm is idempotent. If the PI is already in
|
|
37
55
|
* `requires_action`, return it unchanged. If it's mid- or post-flight
|
|
38
56
|
* (`processing` / `succeeded`), return it. Only `canceled` is rejected.
|
|
39
57
|
*/
|
|
40
58
|
confirm(accountId: string, id: string): PIRow;
|
|
59
|
+
/**
|
|
60
|
+
* Card confirm leg 1: CAS the observed pre-confirm status
|
|
61
|
+
* (`requires_confirmation` or `requires_payment_method`) → `processing`,
|
|
62
|
+
* stamping the PM being attempted. Exactly one of N parallel confirms
|
|
63
|
+
* wins this CAS; losers see `payment_intent_unexpected_state`.
|
|
64
|
+
*/
|
|
65
|
+
confirmCardLeg1(accountId: string, id: string, expected: PIStatus, paymentMethodId: string): PIRow;
|
|
66
|
+
/** Card confirm leg 2, success: processing → succeeded with the charge. */
|
|
67
|
+
finalizeCardSuccess(accountId: string, id: string, latestChargeId: string): PIRow;
|
|
68
|
+
/**
|
|
69
|
+
* Card confirm leg 2, decline: processing → requires_payment_method with
|
|
70
|
+
* the failed charge and `last_payment_error` on the record. The declined
|
|
71
|
+
* PM is dropped from the PI, like real Stripe.
|
|
72
|
+
*/
|
|
73
|
+
finalizeCardDecline(accountId: string, id: string, failedChargeId: string, lastPaymentErrorJson: string): PIRow;
|
|
74
|
+
/**
|
|
75
|
+
* Update a non-terminal PI. Metadata merges per-key (empty/null unsets,
|
|
76
|
+
* Stripe's metadata contract). `payment_method` / `customer` are only
|
|
77
|
+
* valid on the card rail and have been resolved by the caller;
|
|
78
|
+
* attaching a PM moves requires_payment_method → requires_confirmation.
|
|
79
|
+
* The UPDATE is guarded on the observed status so a racing confirm wins.
|
|
80
|
+
*/
|
|
81
|
+
update(accountId: string, id: string, input: UpdatePIInput, resolved?: {
|
|
82
|
+
paymentMethodId?: string | null;
|
|
83
|
+
customerId?: string | null;
|
|
84
|
+
}): PIRow;
|
|
41
85
|
cancel(accountId: string, id: string): PIRow;
|
|
42
86
|
/**
|
|
43
87
|
* Drive a PI from requires_action → processing → succeeded.
|
|
@@ -57,12 +101,20 @@ export declare class PaymentIntentsDomain {
|
|
|
57
101
|
hasMore: boolean;
|
|
58
102
|
limit: number;
|
|
59
103
|
};
|
|
104
|
+
/**
|
|
105
|
+
* casStatus + the shared lost-the-race contract: re-read the fresh row
|
|
106
|
+
* so the loser's `payment_intent_unexpected_state` names the status that
|
|
107
|
+
* actually beat it. Every CAS leg on both rails goes through here.
|
|
108
|
+
*/
|
|
109
|
+
private casStatusOrThrow;
|
|
60
110
|
/**
|
|
61
111
|
* Compare-and-swap on `status`. Returns the new row, or `null` if no row
|
|
62
112
|
* matched the predicate. The predicate is `(id = ? AND account_id = ? AND status = ?)`.
|
|
63
113
|
*/
|
|
64
114
|
private casStatus;
|
|
65
115
|
}
|
|
116
|
+
/** Parsed payment_method_types for a PI row (["crypto"] fallback, pre-F-731 rows). */
|
|
117
|
+
export declare function piTypes(pi: PIRow): string[];
|
|
66
118
|
export declare function piUnexpectedState(currentStatus: string, message?: string): TwinError;
|
|
67
119
|
/**
|
|
68
120
|
* Deterministic 0x address (40 hex chars) derived from the PI ID. Stable
|
|
@@ -75,6 +127,10 @@ export declare function depositAddressFromId(id: string): string;
|
|
|
75
127
|
*
|
|
76
128
|
* `accountId` scopes results to the calling session's account so two
|
|
77
129
|
* sessions sharing a DB file cannot see each other's rows.
|
|
130
|
+
*
|
|
131
|
+
* `extraWheres` lets a caller inject table-specific predicates (e.g. the
|
|
132
|
+
* customers table's `deleted = 0`, payment_methods' `customer_id = ?`)
|
|
133
|
+
* without teaching this generic helper every column name.
|
|
78
134
|
*/
|
|
79
135
|
export declare function listPaginated<T extends {
|
|
80
136
|
id: string;
|
|
@@ -83,7 +139,10 @@ export declare function listPaginated<T extends {
|
|
|
83
139
|
type?: string;
|
|
84
140
|
payment_intent?: string;
|
|
85
141
|
customer?: string;
|
|
86
|
-
}
|
|
142
|
+
}, extraWheres?: Array<{
|
|
143
|
+
sql: string;
|
|
144
|
+
args: unknown[];
|
|
145
|
+
}>): {
|
|
87
146
|
rows: T[];
|
|
88
147
|
hasMore: boolean;
|
|
89
148
|
limit: number;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
// file-size: the PI state machine intentionally keeps both rails (x402 crypto + F-731 card) on one CAS core, plus the listPaginated helper every sibling domain imports.
|
|
1
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
3
|
//
|
|
3
4
|
// PaymentIntent state machine + CAS, owned by AGENT-B.
|
|
4
|
-
//
|
|
5
|
+
// Two rails: x402 crypto-deposit PIs (v1) and card PIs (F-731).
|
|
5
6
|
//
|
|
6
|
-
//
|
|
7
|
+
// Crypto rail (x402):
|
|
7
8
|
//
|
|
8
|
-
// create (
|
|
9
|
+
// create (payment_method_types: ["crypto"])
|
|
9
10
|
// │
|
|
10
11
|
// ▼
|
|
11
12
|
// requires_action ◀──── confirm (idempotent no-op for crypto)
|
|
@@ -19,11 +20,25 @@
|
|
|
19
20
|
// ▼ (simulate_crypto_deposit, leg 2 — synchronous)
|
|
20
21
|
// succeeded ← terminal
|
|
21
22
|
//
|
|
23
|
+
// Card rail (F-731):
|
|
24
|
+
//
|
|
25
|
+
// create (payment_method_types: ["card"])
|
|
26
|
+
// │ no payment_method │ payment_method
|
|
27
|
+
// ▼ ▼
|
|
28
|
+
// requires_payment_method ──update──▶ requires_confirmation
|
|
29
|
+
// ▲ │ confirm (leg 1 CAS → processing)
|
|
30
|
+
// │ decline (magic test PM; ▼
|
|
31
|
+
// │ failed charge + processing
|
|
32
|
+
// │ last_payment_error) │ leg 2 (synchronous outcome)
|
|
33
|
+
// └──────────────────────────────┤
|
|
34
|
+
// ▼
|
|
35
|
+
// succeeded ← terminal
|
|
36
|
+
//
|
|
22
37
|
// All status transitions go through `casStatus()`, a SQL UPDATE with a
|
|
23
38
|
// `WHERE status = ?` predicate. Zero rows updated ⇒ another writer beat us
|
|
24
|
-
// ⇒
|
|
39
|
+
// ⇒ 400 with `payment_intent_unexpected_state`. This is the
|
|
25
40
|
// "exactly one of N parallel confirms wins" guarantee that the
|
|
26
|
-
// concurrency
|
|
41
|
+
// concurrency tests depend on.
|
|
27
42
|
import { createHash } from "node:crypto";
|
|
28
43
|
import { TwinError } from "../errors.js";
|
|
29
44
|
import { newId, newClientSecret } from "../ids.js";
|
|
@@ -41,7 +56,13 @@ export class PaymentIntentsDomain {
|
|
|
41
56
|
ensureStripeTables(db);
|
|
42
57
|
}
|
|
43
58
|
// ---------- create ----------
|
|
44
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Insert a fresh PI. The caller (StripeDomain) has already resolved and
|
|
61
|
+
* cross-validated `payment_method` / `customer` for the card rail —
|
|
62
|
+
* `resolved` carries the validated ids so this module stays free of the
|
|
63
|
+
* sibling domains.
|
|
64
|
+
*/
|
|
65
|
+
create(accountId, input, resolved) {
|
|
45
66
|
if (!Number.isInteger(input.amount) || input.amount <= 0) {
|
|
46
67
|
throw new TwinError("invalid_request_error", "parameter_invalid_integer", "Amount must be a positive integer.", { param: "amount", statusCode: 400 });
|
|
47
68
|
}
|
|
@@ -53,49 +74,70 @@ export class PaymentIntentsDomain {
|
|
|
53
74
|
throw new TwinError("invalid_request_error", "currency_not_supported", `Currency '${currency}' is not supported by this twin (v1 supports: ${CRYPTO_CURRENCIES.join(", ")}).`, { param: "currency", statusCode: 400 });
|
|
54
75
|
}
|
|
55
76
|
const pmTypes = input.payment_method_types ?? [];
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const cryptoOpts = input.payment_method_options?.crypto;
|
|
60
|
-
if (!cryptoOpts || cryptoOpts.mode !== "deposit") {
|
|
61
|
-
throw new TwinError("invalid_request_error", "parameter_invalid_string", "payment_method_options.crypto.mode must be \"deposit\" for v1 of this twin.", { param: "payment_method_options[crypto][mode]", statusCode: 400 });
|
|
62
|
-
}
|
|
63
|
-
const networks = cryptoOpts.deposit_options?.networks ?? [];
|
|
64
|
-
const network = networks[0];
|
|
65
|
-
if (!network || !SUPPORTED_NETWORKS.includes(network)) {
|
|
66
|
-
throw new TwinError("invalid_request_error", "parameter_invalid_string", `payment_method_options.crypto.deposit_options.networks must include one of: ${SUPPORTED_NETWORKS.join(", ")}.`, { param: "payment_method_options[crypto][deposit_options][networks]", statusCode: 400 });
|
|
77
|
+
const pmType = pmTypes.length === 1 ? pmTypes[0] : undefined;
|
|
78
|
+
if (pmType !== "crypto" && pmType !== "card") {
|
|
79
|
+
throw new TwinError("invalid_request_error", "parameter_invalid_string", "payment_method_types must be exactly [\"crypto\"] or [\"card\"] for this twin.", { param: "payment_method_types", statusCode: 400 });
|
|
67
80
|
}
|
|
68
81
|
const id = newId("payment_intent");
|
|
69
82
|
const clientSecret = newClientSecret(id);
|
|
70
83
|
const now = nowUnix();
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
let status;
|
|
85
|
+
let nextActionJson = null;
|
|
86
|
+
let cryptoDepositJson = null;
|
|
87
|
+
if (pmType === "crypto") {
|
|
88
|
+
// Mirror image of the card rail's crypto-options rejection: the
|
|
89
|
+
// card-only params fail loudly instead of being silently dropped.
|
|
90
|
+
if (input.payment_method || input.customer || input.confirm) {
|
|
91
|
+
throw new TwinError("invalid_request_error", "parameter_invalid_string", "payment_method, customer, and confirm are only valid with payment_method_types [\"card\"].", { param: "payment_method", statusCode: 400 });
|
|
92
|
+
}
|
|
93
|
+
const cryptoOpts = input.payment_method_options?.crypto;
|
|
94
|
+
if (!cryptoOpts || cryptoOpts.mode !== "deposit") {
|
|
95
|
+
throw new TwinError("invalid_request_error", "parameter_invalid_string", "payment_method_options.crypto.mode must be \"deposit\" for crypto PaymentIntents.", { param: "payment_method_options[crypto][mode]", statusCode: 400 });
|
|
96
|
+
}
|
|
97
|
+
const networks = cryptoOpts.deposit_options?.networks ?? [];
|
|
98
|
+
const network = networks[0];
|
|
99
|
+
if (!network || !SUPPORTED_NETWORKS.includes(network)) {
|
|
100
|
+
throw new TwinError("invalid_request_error", "parameter_invalid_string", `payment_method_options.crypto.deposit_options.networks must include one of: ${SUPPORTED_NETWORKS.join(", ")}.`, { param: "payment_method_options[crypto][deposit_options][networks]", statusCode: 400 });
|
|
101
|
+
}
|
|
102
|
+
// Deterministic 0x deposit address derived from the PI ID. 40 hex chars.
|
|
103
|
+
const address = depositAddressFromId(id);
|
|
104
|
+
const cryptoDeposit = {
|
|
105
|
+
deposit_addresses: {
|
|
106
|
+
[network]: {
|
|
107
|
+
address,
|
|
108
|
+
supported_tokens: [
|
|
109
|
+
{
|
|
110
|
+
token_currency: "usdc",
|
|
111
|
+
token_contract_address: BASE_USDC_CONTRACT_ADDRESS,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
83
115
|
},
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
116
|
+
};
|
|
117
|
+
status = "requires_action";
|
|
118
|
+
nextActionJson = JSON.stringify({
|
|
119
|
+
type: "display_crypto_deposit_information",
|
|
120
|
+
crypto_display_details: cryptoDeposit,
|
|
121
|
+
});
|
|
122
|
+
cryptoDepositJson = JSON.stringify(cryptoDeposit);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// Card rail: the crypto deposit options are the x402 rail only.
|
|
126
|
+
if (input.payment_method_options?.crypto) {
|
|
127
|
+
throw new TwinError("invalid_request_error", "parameter_invalid_string", "payment_method_options.crypto is only valid with payment_method_types [\"crypto\"].", { param: "payment_method_options[crypto]", statusCode: 400 });
|
|
128
|
+
}
|
|
129
|
+
status = resolved.paymentMethodId ? "requires_confirmation" : "requires_payment_method";
|
|
130
|
+
}
|
|
90
131
|
this.db
|
|
91
132
|
.prepare(`INSERT INTO payment_intents (
|
|
92
133
|
id, account_id, amount, currency, status,
|
|
93
134
|
payment_method_types_json, next_action_json,
|
|
94
135
|
latest_charge_id, capture_method, confirmation_method,
|
|
95
136
|
idempotency_key, metadata_json, crypto_deposit_json,
|
|
96
|
-
client_secret, created, updated, canceled_at, captured_at
|
|
97
|
-
|
|
98
|
-
|
|
137
|
+
client_secret, created, updated, canceled_at, captured_at,
|
|
138
|
+
payment_method_id, customer_id, last_payment_error_json
|
|
139
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, ?, NULL)`)
|
|
140
|
+
.run(id, accountId, input.amount, currency, status, JSON.stringify(pmTypes), nextActionJson, input.capture_method ?? DEFAULT_CAPTURE_METHOD, input.confirmation_method ?? DEFAULT_CONFIRMATION_METHOD, input.idempotency_key ?? null, JSON.stringify(input.metadata ?? {}), cryptoDepositJson, clientSecret, now, now, resolved.paymentMethodId, resolved.customerId);
|
|
99
141
|
return this.requireById(accountId, id);
|
|
100
142
|
}
|
|
101
143
|
// ---------- confirm ----------
|
|
@@ -111,6 +153,98 @@ export class PaymentIntentsDomain {
|
|
|
111
153
|
}
|
|
112
154
|
return pi;
|
|
113
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Card confirm leg 1: CAS the observed pre-confirm status
|
|
158
|
+
* (`requires_confirmation` or `requires_payment_method`) → `processing`,
|
|
159
|
+
* stamping the PM being attempted. Exactly one of N parallel confirms
|
|
160
|
+
* wins this CAS; losers see `payment_intent_unexpected_state`.
|
|
161
|
+
*/
|
|
162
|
+
confirmCardLeg1(accountId, id, expected, paymentMethodId) {
|
|
163
|
+
return this.casStatusOrThrow(accountId, id, expected, "processing", {
|
|
164
|
+
payment_method_id: paymentMethodId,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/** Card confirm leg 2, success: processing → succeeded with the charge. */
|
|
168
|
+
finalizeCardSuccess(accountId, id, latestChargeId) {
|
|
169
|
+
return this.casStatusOrThrow(accountId, id, "processing", "succeeded", {
|
|
170
|
+
latest_charge_id: latestChargeId,
|
|
171
|
+
captured_at: nowUnix(),
|
|
172
|
+
last_payment_error_json: null,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Card confirm leg 2, decline: processing → requires_payment_method with
|
|
177
|
+
* the failed charge and `last_payment_error` on the record. The declined
|
|
178
|
+
* PM is dropped from the PI, like real Stripe.
|
|
179
|
+
*/
|
|
180
|
+
finalizeCardDecline(accountId, id, failedChargeId, lastPaymentErrorJson) {
|
|
181
|
+
return this.casStatusOrThrow(accountId, id, "processing", "requires_payment_method", {
|
|
182
|
+
latest_charge_id: failedChargeId,
|
|
183
|
+
payment_method_id: null,
|
|
184
|
+
last_payment_error_json: lastPaymentErrorJson,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
// ---------- update (POST /v1/payment_intents/:id, F-731) ----------
|
|
188
|
+
/**
|
|
189
|
+
* Update a non-terminal PI. Metadata merges per-key (empty/null unsets,
|
|
190
|
+
* Stripe's metadata contract). `payment_method` / `customer` are only
|
|
191
|
+
* valid on the card rail and have been resolved by the caller;
|
|
192
|
+
* attaching a PM moves requires_payment_method → requires_confirmation.
|
|
193
|
+
* The UPDATE is guarded on the observed status so a racing confirm wins.
|
|
194
|
+
*/
|
|
195
|
+
update(accountId, id, input, resolved = {}) {
|
|
196
|
+
const pi = this.requireById(accountId, id);
|
|
197
|
+
const updatable = new Set([
|
|
198
|
+
"requires_payment_method",
|
|
199
|
+
"requires_confirmation",
|
|
200
|
+
"requires_action",
|
|
201
|
+
]);
|
|
202
|
+
if (!updatable.has(pi.status)) {
|
|
203
|
+
throw piUnexpectedState(pi.status);
|
|
204
|
+
}
|
|
205
|
+
const isCard = piTypes(pi).includes("card");
|
|
206
|
+
// Crypto PIs accept metadata-only updates: the x402 challenge (amount,
|
|
207
|
+
// deposit address) is minted at create time and must stay consistent
|
|
208
|
+
// with the middleware's cached challenge.
|
|
209
|
+
if (!isCard &&
|
|
210
|
+
(input.amount !== undefined ||
|
|
211
|
+
input.payment_method !== undefined ||
|
|
212
|
+
input.customer !== undefined)) {
|
|
213
|
+
throw new TwinError("invalid_request_error", "parameter_invalid_string", "Only metadata can be updated on crypto PaymentIntents.", { param: "payment_method", statusCode: 400 });
|
|
214
|
+
}
|
|
215
|
+
if (input.amount !== undefined && (!Number.isInteger(input.amount) || input.amount <= 0)) {
|
|
216
|
+
throw new TwinError("invalid_request_error", "parameter_invalid_integer", "Amount must be a positive integer.", { param: "amount", statusCode: 400 });
|
|
217
|
+
}
|
|
218
|
+
const metadata = JSON.parse(pi.metadata_json);
|
|
219
|
+
for (const [key, value] of Object.entries(input.metadata ?? {})) {
|
|
220
|
+
if (value === null || value === "")
|
|
221
|
+
delete metadata[key];
|
|
222
|
+
else
|
|
223
|
+
metadata[key] = value;
|
|
224
|
+
}
|
|
225
|
+
const nextPaymentMethodId = input.payment_method !== undefined ? (resolved.paymentMethodId ?? null) : pi.payment_method_id;
|
|
226
|
+
const nextCustomerId = input.customer !== undefined ? (resolved.customerId ?? null) : pi.customer_id;
|
|
227
|
+
let nextStatus = pi.status;
|
|
228
|
+
if (isCard) {
|
|
229
|
+
if (nextPaymentMethodId && pi.status === "requires_payment_method") {
|
|
230
|
+
nextStatus = "requires_confirmation";
|
|
231
|
+
}
|
|
232
|
+
else if (!nextPaymentMethodId && pi.status === "requires_confirmation") {
|
|
233
|
+
nextStatus = "requires_payment_method";
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const updated = this.db
|
|
237
|
+
.prepare(`UPDATE payment_intents SET
|
|
238
|
+
amount = ?, metadata_json = ?, payment_method_id = ?, customer_id = ?,
|
|
239
|
+
status = ?, updated = ?
|
|
240
|
+
WHERE id = ? AND account_id = ? AND status = ? RETURNING *`)
|
|
241
|
+
.get(input.amount ?? pi.amount, JSON.stringify(metadata), nextPaymentMethodId, nextCustomerId, nextStatus, nowUnix(), id, accountId, pi.status);
|
|
242
|
+
if (!updated) {
|
|
243
|
+
const fresh = this.requireById(accountId, id);
|
|
244
|
+
throw piUnexpectedState(fresh.status);
|
|
245
|
+
}
|
|
246
|
+
return updated;
|
|
247
|
+
}
|
|
114
248
|
// ---------- cancel ----------
|
|
115
249
|
cancel(accountId, id) {
|
|
116
250
|
const pi = this.requireById(accountId, id);
|
|
@@ -144,25 +278,14 @@ export class PaymentIntentsDomain {
|
|
|
144
278
|
if (pi.status !== "requires_action") {
|
|
145
279
|
throw piUnexpectedState(pi.status);
|
|
146
280
|
}
|
|
147
|
-
|
|
148
|
-
if (!updated) {
|
|
149
|
-
// Lost the race. Read fresh state for the error message.
|
|
150
|
-
const fresh = this.requireById(accountId, id);
|
|
151
|
-
throw piUnexpectedState(fresh.status);
|
|
152
|
-
}
|
|
153
|
-
return updated;
|
|
281
|
+
return this.casStatusOrThrow(accountId, id, "requires_action", "processing");
|
|
154
282
|
}
|
|
155
283
|
/** Second CAS leg: processing → succeeded. */
|
|
156
284
|
simulateCryptoDepositLeg2(accountId, id, latestChargeId) {
|
|
157
|
-
|
|
285
|
+
return this.casStatusOrThrow(accountId, id, "processing", "succeeded", {
|
|
158
286
|
latest_charge_id: latestChargeId,
|
|
159
287
|
captured_at: nowUnix(),
|
|
160
288
|
});
|
|
161
|
-
if (!updated) {
|
|
162
|
-
const fresh = this.requireById(accountId, id);
|
|
163
|
-
throw piUnexpectedState(fresh.status);
|
|
164
|
-
}
|
|
165
|
-
return updated;
|
|
166
289
|
}
|
|
167
290
|
// ---------- read ----------
|
|
168
291
|
getById(accountId, id) {
|
|
@@ -181,6 +304,19 @@ export class PaymentIntentsDomain {
|
|
|
181
304
|
return listPaginated(this.db, "payment_intents", accountId, input);
|
|
182
305
|
}
|
|
183
306
|
// ---------- internals ----------
|
|
307
|
+
/**
|
|
308
|
+
* casStatus + the shared lost-the-race contract: re-read the fresh row
|
|
309
|
+
* so the loser's `payment_intent_unexpected_state` names the status that
|
|
310
|
+
* actually beat it. Every CAS leg on both rails goes through here.
|
|
311
|
+
*/
|
|
312
|
+
casStatusOrThrow(accountId, id, expected, next, extras = {}) {
|
|
313
|
+
const updated = this.casStatus(accountId, id, expected, next, extras);
|
|
314
|
+
if (!updated) {
|
|
315
|
+
const fresh = this.requireById(accountId, id);
|
|
316
|
+
throw piUnexpectedState(fresh.status);
|
|
317
|
+
}
|
|
318
|
+
return updated;
|
|
319
|
+
}
|
|
184
320
|
/**
|
|
185
321
|
* Compare-and-swap on `status`. Returns the new row, or `null` if no row
|
|
186
322
|
* matched the predicate. The predicate is `(id = ? AND account_id = ? AND status = ?)`.
|
|
@@ -200,6 +336,14 @@ export class PaymentIntentsDomain {
|
|
|
200
336
|
sets.push("captured_at = ?");
|
|
201
337
|
values.push(extras.captured_at ?? null);
|
|
202
338
|
}
|
|
339
|
+
if ("payment_method_id" in extras) {
|
|
340
|
+
sets.push("payment_method_id = ?");
|
|
341
|
+
values.push(extras.payment_method_id ?? null);
|
|
342
|
+
}
|
|
343
|
+
if ("last_payment_error_json" in extras) {
|
|
344
|
+
sets.push("last_payment_error_json = ?");
|
|
345
|
+
values.push(extras.last_payment_error_json ?? null);
|
|
346
|
+
}
|
|
203
347
|
const sql = `UPDATE payment_intents SET ${sets.join(", ")} WHERE id = ? AND account_id = ? AND status = ? RETURNING *`;
|
|
204
348
|
values.push(id, accountId, expected);
|
|
205
349
|
return (this.db.prepare(sql).get(...values) ??
|
|
@@ -207,6 +351,16 @@ export class PaymentIntentsDomain {
|
|
|
207
351
|
}
|
|
208
352
|
}
|
|
209
353
|
// ---------- helpers used by routes/serializers too ----------
|
|
354
|
+
/** Parsed payment_method_types for a PI row (["crypto"] fallback, pre-F-731 rows). */
|
|
355
|
+
export function piTypes(pi) {
|
|
356
|
+
try {
|
|
357
|
+
const parsed = JSON.parse(pi.payment_method_types_json);
|
|
358
|
+
return Array.isArray(parsed) ? parsed : ["crypto"];
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
return ["crypto"];
|
|
362
|
+
}
|
|
363
|
+
}
|
|
210
364
|
export function piUnexpectedState(currentStatus, message) {
|
|
211
365
|
return new TwinError("invalid_request_error", "payment_intent_unexpected_state", message ??
|
|
212
366
|
`The PaymentIntent has a status of '${currentStatus}'. The requested action could not be performed.`, { statusCode: 400 });
|
|
@@ -227,8 +381,12 @@ export function depositAddressFromId(id) {
|
|
|
227
381
|
*
|
|
228
382
|
* `accountId` scopes results to the calling session's account so two
|
|
229
383
|
* sessions sharing a DB file cannot see each other's rows.
|
|
384
|
+
*
|
|
385
|
+
* `extraWheres` lets a caller inject table-specific predicates (e.g. the
|
|
386
|
+
* customers table's `deleted = 0`, payment_methods' `customer_id = ?`)
|
|
387
|
+
* without teaching this generic helper every column name.
|
|
230
388
|
*/
|
|
231
|
-
export function listPaginated(db, table, accountId, input) {
|
|
389
|
+
export function listPaginated(db, table, accountId, input, extraWheres = []) {
|
|
232
390
|
const limitRaw = input.limit ?? 10;
|
|
233
391
|
const limit = Math.min(100, Math.max(1, Math.floor(limitRaw)));
|
|
234
392
|
const wheres = ["account_id = ?"];
|
|
@@ -271,6 +429,10 @@ export function listPaginated(db, table, accountId, input) {
|
|
|
271
429
|
wheres.push("payment_intent_id = ?");
|
|
272
430
|
args.push(input.payment_intent);
|
|
273
431
|
}
|
|
432
|
+
for (const extra of extraWheres) {
|
|
433
|
+
wheres.push(extra.sql);
|
|
434
|
+
args.push(...extra.args);
|
|
435
|
+
}
|
|
274
436
|
const where = `WHERE ${wheres.join(" AND ")}`;
|
|
275
437
|
const sql = `SELECT * FROM ${table} ${where} ORDER BY created DESC, id DESC LIMIT ?`;
|
|
276
438
|
// Fetch limit + 1 to compute `has_more`.
|