@paybond/kit 0.12.0 → 0.12.2

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.
Files changed (107) hide show
  1. package/dist/agent/evidence.d.ts +10 -0
  2. package/dist/agent/evidence.js +16 -0
  3. package/dist/agent/index.d.ts +1 -1
  4. package/dist/agent/index.js +1 -1
  5. package/dist/agent/interceptor.js +4 -0
  6. package/dist/agent/receipt-client.d.ts +16 -1
  7. package/dist/agent/run.d.ts +3 -0
  8. package/dist/agent/run.js +1 -1
  9. package/dist/agent-mandate.d.ts +103 -0
  10. package/dist/agent-mandate.js +423 -0
  11. package/dist/agent-receipt-external-attestations.d.ts +27 -0
  12. package/dist/agent-receipt-external-attestations.js +61 -0
  13. package/dist/agent-receipt-pdf-export.d.ts +43 -0
  14. package/dist/agent-receipt-pdf-export.js +109 -0
  15. package/dist/agent-receipt-prevalidate.d.ts +7 -0
  16. package/dist/agent-receipt-prevalidate.js +63 -0
  17. package/dist/agent-receipt.d.ts +21 -1
  18. package/dist/agent-receipt.js +63 -4
  19. package/dist/cli/command-spec.js +50 -1
  20. package/dist/cli/commands/dev.js +10 -3
  21. package/dist/cli/commands/setup.js +19 -1
  22. package/dist/cli/commands/shopify.d.ts +53 -0
  23. package/dist/cli/commands/shopify.js +808 -0
  24. package/dist/cli/help.d.ts +1 -1
  25. package/dist/cli/help.js +13 -3
  26. package/dist/cli/router.js +15 -1
  27. package/dist/commerce-binding.d.ts +59 -0
  28. package/dist/commerce-binding.js +129 -0
  29. package/dist/doctor-completion.d.ts +16 -0
  30. package/dist/doctor-completion.js +157 -1
  31. package/dist/index.d.ts +27 -2
  32. package/dist/index.js +43 -7
  33. package/dist/mcp-server.d.ts +1 -0
  34. package/dist/mcp-server.js +38 -11
  35. package/dist/policy/presets.d.ts +1 -1
  36. package/dist/policy/presets.js +1 -0
  37. package/dist/project-init.d.ts +1 -1
  38. package/dist/project-init.js +1 -0
  39. package/dist/protocol-receipt.d.ts +129 -0
  40. package/dist/protocol-receipt.js +620 -0
  41. package/dist/shopify/checkout.d.ts +29 -0
  42. package/dist/shopify/checkout.js +79 -0
  43. package/dist/shopify/evidence.d.ts +13 -0
  44. package/dist/shopify/evidence.js +85 -0
  45. package/dist/shopify/index.d.ts +8 -0
  46. package/dist/shopify/index.js +6 -0
  47. package/dist/shopify/instrument.d.ts +61 -0
  48. package/dist/shopify/instrument.js +52 -0
  49. package/dist/shopify/order.d.ts +8 -0
  50. package/dist/shopify/order.js +110 -0
  51. package/dist/shopify/types.d.ts +82 -0
  52. package/dist/shopify/types.js +4 -0
  53. package/dist/solutions/catalog.d.ts +1 -1
  54. package/dist/solutions/catalog.js +1 -1
  55. package/dist/stripe-commerce/evidence.d.ts +13 -0
  56. package/dist/stripe-commerce/evidence.js +164 -0
  57. package/dist/stripe-commerce/index.d.ts +3 -0
  58. package/dist/stripe-commerce/index.js +2 -0
  59. package/dist/stripe-commerce/metadata.d.ts +11 -0
  60. package/dist/stripe-commerce/metadata.js +34 -0
  61. package/dist/stripe-commerce/types.d.ts +38 -0
  62. package/dist/stripe-commerce/types.js +1 -0
  63. package/dist/template-init.d.ts +1 -1
  64. package/dist/template-init.js +6 -1
  65. package/package.json +1 -1
  66. package/policy/presets/stripe-commerce.yaml +19 -0
  67. package/solutions/stripe-commerce.json +19 -0
  68. package/templates/manifest.json +82 -10
  69. package/templates/openai-shopping-agent/package-lock.json +10 -10
  70. package/templates/openai-shopping-agent/src/paybond.config.ts +1 -1
  71. package/templates/paybond-aws-operator/package-lock.json +7 -7
  72. package/templates/paybond-aws-operator/src/paybond.config.ts +1 -1
  73. package/templates/paybond-claude-agents-demo/package-lock.json +7 -7
  74. package/templates/paybond-claude-agents-demo/src/paybond.config.ts +1 -1
  75. package/templates/paybond-mastra-travel-agent/package-lock.json +30 -30
  76. package/templates/paybond-mastra-travel-agent/src/paybond.config.ts +1 -1
  77. package/templates/paybond-mcp-coding-agent/package-lock.json +7 -7
  78. package/templates/paybond-mcp-coding-agent/src/paybond.config.ts +1 -1
  79. package/templates/paybond-openai-agents-demo/package-lock.json +10 -10
  80. package/templates/paybond-openai-agents-demo/src/paybond.config.ts +1 -1
  81. package/templates/paybond-procurement-agent/package-lock.json +7 -7
  82. package/templates/paybond-procurement-agent/src/paybond.config.ts +1 -1
  83. package/templates/paybond-shopify-shopping-agent/.env.example +3 -0
  84. package/templates/paybond-shopify-shopping-agent/.github/workflows/smoke.yml +20 -0
  85. package/templates/paybond-shopify-shopping-agent/LICENSE +201 -0
  86. package/templates/paybond-shopify-shopping-agent/README.md +29 -0
  87. package/templates/paybond-shopify-shopping-agent/package-lock.json +223 -0
  88. package/templates/paybond-shopify-shopping-agent/package.json +20 -0
  89. package/templates/paybond-shopify-shopping-agent/paybond.policy.yaml +22 -0
  90. package/templates/paybond-shopify-shopping-agent/src/index.ts +54 -0
  91. package/templates/paybond-shopify-shopping-agent/src/paybond.config.ts +51 -0
  92. package/templates/paybond-shopify-shopping-agent/tsconfig.json +13 -0
  93. package/templates/paybond-stripe-agent-demo/.env.example +9 -0
  94. package/templates/paybond-stripe-agent-demo/.github/workflows/smoke.yml +20 -0
  95. package/templates/paybond-stripe-agent-demo/LICENSE +201 -0
  96. package/templates/paybond-stripe-agent-demo/README.md +50 -0
  97. package/templates/paybond-stripe-agent-demo/package-lock.json +223 -0
  98. package/templates/paybond-stripe-agent-demo/package.json +20 -0
  99. package/templates/paybond-stripe-agent-demo/paybond.policy.yaml +22 -0
  100. package/templates/paybond-stripe-agent-demo/src/charge-customer.ts +237 -0
  101. package/templates/paybond-stripe-agent-demo/src/index.ts +78 -0
  102. package/templates/paybond-stripe-agent-demo/src/paybond.config.ts +51 -0
  103. package/templates/paybond-stripe-agent-demo/tsconfig.json +13 -0
  104. package/templates/paybond-travel-agent/package-lock.json +13 -13
  105. package/templates/paybond-travel-agent/src/paybond.config.ts +1 -1
  106. package/templates/paybond-vercel-shopping-agent/package-lock.json +7 -7
  107. package/templates/paybond-vercel-shopping-agent/src/paybond.config.ts +1 -1
@@ -0,0 +1,223 @@
1
+ {
2
+ "name": "paybond-stripe-agent-demo",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {
6
+ "": {
7
+ "name": "paybond-stripe-agent-demo",
8
+ "dependencies": {
9
+ "@paybond/kit": "^0.12.1"
10
+ },
11
+ "devDependencies": {
12
+ "@types/node": "^22.10.1",
13
+ "typescript": "^5.7.2"
14
+ },
15
+ "engines": {
16
+ "node": ">=22"
17
+ }
18
+ },
19
+ "node_modules/@noble/ed25519": {
20
+ "version": "2.3.0",
21
+ "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.3.0.tgz",
22
+ "integrity": "sha512-M7dvXL2B92/M7dw9+gzuydL8qn/jiqNHaoR3Q+cb1q1GHV7uwE17WCyFMG+Y+TZb5izcaXk5TdJRrDUxHXL78A==",
23
+ "license": "MIT",
24
+ "funding": {
25
+ "url": "https://paulmillr.com/funding/"
26
+ }
27
+ },
28
+ "node_modules/@noble/hashes": {
29
+ "version": "1.8.0",
30
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
31
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
32
+ "license": "MIT",
33
+ "engines": {
34
+ "node": "^14.21.3 || >=16"
35
+ },
36
+ "funding": {
37
+ "url": "https://paulmillr.com/funding/"
38
+ }
39
+ },
40
+ "node_modules/@noble/secp256k1": {
41
+ "version": "2.3.0",
42
+ "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.3.0.tgz",
43
+ "integrity": "sha512-0TQed2gcBbIrh7Ccyw+y/uZQvbJwm7Ao4scBUxqpBCcsOlZG0O4KGfjtNAy/li4W8n1xt3dxrwJ0beZ2h2G6Kw==",
44
+ "license": "MIT",
45
+ "funding": {
46
+ "url": "https://paulmillr.com/funding/"
47
+ }
48
+ },
49
+ "node_modules/@paybond/kit": {
50
+ "version": "0.12.1",
51
+ "resolved": "https://registry.npmjs.org/@paybond/kit/-/kit-0.12.1.tgz",
52
+ "integrity": "sha512-dwLMq8MkuLiuWQrXy1AtFR++tom14hkpKYGe4koiPyGKu2LQyOHtresZQ6IDoC7HAUSyDWEilg0maAPslKnm+Q==",
53
+ "license": "Apache-2.0",
54
+ "dependencies": {
55
+ "@noble/ed25519": "^2.2.1",
56
+ "@noble/hashes": "^1.7.1",
57
+ "@noble/secp256k1": "^2.2.3",
58
+ "ajv": "^8.17.1",
59
+ "blake3": "^2.1.7",
60
+ "uuid": "^14.0.0",
61
+ "zod": "^3.25.32 || ^4.2.0"
62
+ },
63
+ "bin": {
64
+ "paybond": "dist/cli.js",
65
+ "paybond-init": "dist/init.js",
66
+ "paybond-kit-login": "dist/login.js",
67
+ "paybond-mcp-server": "dist/mcp-server.js"
68
+ },
69
+ "engines": {
70
+ "node": ">=22"
71
+ },
72
+ "peerDependencies": {
73
+ "@anthropic-ai/claude-agent-sdk": ">=0.2.100",
74
+ "@langchain/core": ">=0.3.0",
75
+ "@langchain/langgraph": ">=0.2.50",
76
+ "@mastra/core": ">=1.0.0",
77
+ "@openai/agents": ">=0.12.0",
78
+ "agents": ">=0.10.0",
79
+ "ai": ">=4.0.0",
80
+ "zod": "^3.25.32 || ^4.2.0"
81
+ },
82
+ "peerDependenciesMeta": {
83
+ "@anthropic-ai/claude-agent-sdk": {
84
+ "optional": true
85
+ },
86
+ "@langchain/core": {
87
+ "optional": true
88
+ },
89
+ "@langchain/langgraph": {
90
+ "optional": true
91
+ },
92
+ "@mastra/core": {
93
+ "optional": true
94
+ },
95
+ "@openai/agents": {
96
+ "optional": true
97
+ },
98
+ "agents": {
99
+ "optional": true
100
+ },
101
+ "ai": {
102
+ "optional": true
103
+ },
104
+ "zod": {
105
+ "optional": true
106
+ }
107
+ }
108
+ },
109
+ "node_modules/@types/node": {
110
+ "version": "22.20.0",
111
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz",
112
+ "integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==",
113
+ "dev": true,
114
+ "license": "MIT",
115
+ "dependencies": {
116
+ "undici-types": "~6.21.0"
117
+ }
118
+ },
119
+ "node_modules/ajv": {
120
+ "version": "8.20.0",
121
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
122
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
123
+ "license": "MIT",
124
+ "dependencies": {
125
+ "fast-deep-equal": "^3.1.3",
126
+ "fast-uri": "^3.0.1",
127
+ "json-schema-traverse": "^1.0.0",
128
+ "require-from-string": "^2.0.2"
129
+ },
130
+ "funding": {
131
+ "type": "github",
132
+ "url": "https://github.com/sponsors/epoberezkin"
133
+ }
134
+ },
135
+ "node_modules/blake3": {
136
+ "version": "2.1.7",
137
+ "resolved": "https://registry.npmjs.org/blake3/-/blake3-2.1.7.tgz",
138
+ "integrity": "sha512-5d+TdKJvju96IyEaGJ0eO6CHbckWi+NBrCezGYM/WsnI3R03aLL2TWfsuZSh1rs0fTv/L3ps/r0vykjYurcIwA==",
139
+ "hasInstallScript": true,
140
+ "license": "MIT"
141
+ },
142
+ "node_modules/fast-deep-equal": {
143
+ "version": "3.1.3",
144
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
145
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
146
+ "license": "MIT"
147
+ },
148
+ "node_modules/fast-uri": {
149
+ "version": "3.1.3",
150
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
151
+ "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
152
+ "funding": [
153
+ {
154
+ "type": "github",
155
+ "url": "https://github.com/sponsors/fastify"
156
+ },
157
+ {
158
+ "type": "opencollective",
159
+ "url": "https://opencollective.com/fastify"
160
+ }
161
+ ],
162
+ "license": "BSD-3-Clause"
163
+ },
164
+ "node_modules/json-schema-traverse": {
165
+ "version": "1.0.0",
166
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
167
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
168
+ "license": "MIT"
169
+ },
170
+ "node_modules/require-from-string": {
171
+ "version": "2.0.2",
172
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
173
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
174
+ "license": "MIT",
175
+ "engines": {
176
+ "node": ">=0.10.0"
177
+ }
178
+ },
179
+ "node_modules/typescript": {
180
+ "version": "5.9.3",
181
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
182
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
183
+ "dev": true,
184
+ "license": "Apache-2.0",
185
+ "bin": {
186
+ "tsc": "bin/tsc",
187
+ "tsserver": "bin/tsserver"
188
+ },
189
+ "engines": {
190
+ "node": ">=14.17"
191
+ }
192
+ },
193
+ "node_modules/undici-types": {
194
+ "version": "6.21.0",
195
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
196
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
197
+ "dev": true,
198
+ "license": "MIT"
199
+ },
200
+ "node_modules/uuid": {
201
+ "version": "14.0.1",
202
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz",
203
+ "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==",
204
+ "funding": [
205
+ "https://github.com/sponsors/broofa",
206
+ "https://github.com/sponsors/ctavan"
207
+ ],
208
+ "license": "MIT",
209
+ "bin": {
210
+ "uuid": "dist-node/bin/uuid"
211
+ }
212
+ },
213
+ "node_modules/zod": {
214
+ "version": "4.4.3",
215
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
216
+ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
217
+ "license": "MIT",
218
+ "funding": {
219
+ "url": "https://github.com/sponsors/colinhacks"
220
+ }
221
+ }
222
+ }
223
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "paybond-stripe-agent-demo",
3
+ "private": false,
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsc -p tsconfig.json",
7
+ "start": "node dist/index.js",
8
+ "smoke": "paybond agent sandbox smoke --policy-file paybond.policy.yaml --operation payments.charge_customer --requested-spend-cents 2500 --result-body '{\"payment_intent_id\":\"pi_smoke\",\"charge_id\":\"ch_smoke\",\"cost_cents\":2500,\"status\":\"succeeded\"}' --format json"
9
+ },
10
+ "dependencies": {
11
+ "@paybond/kit": "^0.12.1"
12
+ },
13
+ "devDependencies": {
14
+ "@types/node": "^22.10.1",
15
+ "typescript": "^5.7.2"
16
+ },
17
+ "engines": {
18
+ "node": ">=22"
19
+ }
20
+ }
@@ -0,0 +1,22 @@
1
+ # Reference implementation — edit freely. Regenerate with:
2
+ # paybond policy init --preset stripe-commerce --out paybond.policy.yaml
3
+
4
+ version: 1
5
+ name: stripe-commerce-agent-v1
6
+ default_deny: true
7
+
8
+ tools:
9
+ payments.charge_customer:
10
+ side_effecting: true
11
+ max_spend_cents: 50000
12
+ evidence_preset: stripe_charge
13
+
14
+ payments.list_invoices:
15
+ side_effecting: false
16
+
17
+ intent:
18
+ allowed_tools:
19
+ - payments.charge_customer
20
+ budget:
21
+ currency: usd
22
+ max_spend_usd: 500
@@ -0,0 +1,237 @@
1
+ import {
2
+ buildPaybondStripeMetadata,
3
+ mapStripeToolResultToEvidence,
4
+ type PaybondEnvironment,
5
+ type PaybondStripeSettlementRail,
6
+ type StripeCommerceEvidencePreset,
7
+ type StripeToolResultInput,
8
+ } from "@paybond/kit";
9
+
10
+ /**
11
+ * Resolved Paybond session environment used to gate the optional Stripe test-mode charge.
12
+ * `"unknown"` when the gateway did not report one — treated as non-sandbox (fail closed).
13
+ */
14
+ export type PaybondSessionEnvironment = PaybondEnvironment | "unknown";
15
+
16
+ /**
17
+ * Paybond session binding for Stripe metadata — never sourced from client input.
18
+ *
19
+ * Tenant isolation + sandbox-only invariant: `tenantId`/`intentId` come from the authenticated
20
+ * Paybond session (populated after sandbox bind), and `environment` is the server-derived session
21
+ * environment. The optional Stripe test-mode charge is a per-tenant capability that is only valid
22
+ * when `environment === "sandbox"` — a live session must never forward a template-held secret to
23
+ * Stripe.
24
+ */
25
+ export type PaybondSessionBinding = {
26
+ tenantId: string;
27
+ intentId: string;
28
+ environment: PaybondSessionEnvironment;
29
+ };
30
+
31
+ export type ChargeCustomerArgs = {
32
+ amountCents: number;
33
+ customerId?: string;
34
+ rail?: PaybondStripeSettlementRail;
35
+ };
36
+
37
+ export type ChargeCustomerResult = StripeToolResultInput & {
38
+ payment_intent_id: string;
39
+ charge_id: string;
40
+ cost_cents: number;
41
+ status: string;
42
+ metadata: Record<string, string>;
43
+ mode: "mock" | "stripe_test";
44
+ };
45
+
46
+ function assertPositiveIntegerAmount(amountCents: number): void {
47
+ if (!Number.isInteger(amountCents) || amountCents <= 0) {
48
+ throw new Error("amountCents must be a positive integer");
49
+ }
50
+ }
51
+
52
+ function mockSuffix(intentId: string): string {
53
+ return intentId.replace(/-/g, "").slice(0, 12) || "demo";
54
+ }
55
+
56
+ /**
57
+ * Offline mock charge — no Stripe secret required.
58
+ */
59
+ export function mockChargeCustomer(
60
+ metadata: Record<string, string>,
61
+ args: ChargeCustomerArgs,
62
+ intentId: string,
63
+ ): ChargeCustomerResult {
64
+ assertPositiveIntegerAmount(args.amountCents);
65
+ const suffix = mockSuffix(intentId);
66
+ return {
67
+ payment_intent_id: `pi_mock_${suffix}`,
68
+ charge_id: `ch_mock_${suffix}`,
69
+ cost_cents: args.amountCents,
70
+ status: "succeeded",
71
+ metadata,
72
+ mode: "mock",
73
+ };
74
+ }
75
+
76
+ function stripeMetadataParams(metadata: Record<string, string>): URLSearchParams {
77
+ const params = new URLSearchParams();
78
+ for (const [key, value] of Object.entries(metadata)) {
79
+ params.set(`metadata[${key}]`, value);
80
+ }
81
+ return params;
82
+ }
83
+
84
+ /**
85
+ * Optional Stripe test-mode PaymentIntent create + confirm via REST (requires `STRIPE_SECRET_KEY`).
86
+ */
87
+ export async function stripeTestChargeCustomer(
88
+ secretKey: string,
89
+ metadata: Record<string, string>,
90
+ args: ChargeCustomerArgs,
91
+ ): Promise<ChargeCustomerResult> {
92
+ assertPositiveIntegerAmount(args.amountCents);
93
+
94
+ const body = stripeMetadataParams(metadata);
95
+ body.set("amount", String(args.amountCents));
96
+ body.set("currency", "usd");
97
+ body.set("confirm", "true");
98
+ body.set("payment_method", "pm_card_visa");
99
+ if (args.customerId?.trim()) {
100
+ body.set("customer", args.customerId.trim());
101
+ }
102
+
103
+ const response = await fetch("https://api.stripe.com/v1/payment_intents", {
104
+ method: "POST",
105
+ headers: {
106
+ Authorization: `Bearer ${secretKey}`,
107
+ "Content-Type": "application/x-www-form-urlencoded",
108
+ },
109
+ body,
110
+ });
111
+
112
+ const payload = (await response.json()) as Record<string, unknown>;
113
+ if (!response.ok) {
114
+ const message =
115
+ typeof payload.error === "object" &&
116
+ payload.error !== null &&
117
+ "message" in payload.error &&
118
+ typeof payload.error.message === "string"
119
+ ? payload.error.message
120
+ : `Stripe API error (${response.status})`;
121
+ throw new Error(message);
122
+ }
123
+
124
+ const paymentIntentId =
125
+ typeof payload.id === "string" && payload.id.startsWith("pi_") ? payload.id : undefined;
126
+ if (!paymentIntentId) {
127
+ throw new Error("Stripe response missing payment_intent id");
128
+ }
129
+
130
+ let chargeId: string | undefined;
131
+ const latestCharge = payload.latest_charge;
132
+ if (typeof latestCharge === "string" && latestCharge.startsWith("ch_")) {
133
+ chargeId = latestCharge;
134
+ } else if (
135
+ typeof latestCharge === "object" &&
136
+ latestCharge !== null &&
137
+ "id" in latestCharge &&
138
+ typeof latestCharge.id === "string"
139
+ ) {
140
+ chargeId = latestCharge.id;
141
+ }
142
+ if (!chargeId) {
143
+ throw new Error("Stripe response missing charge id");
144
+ }
145
+
146
+ const status = typeof payload.status === "string" ? payload.status : "succeeded";
147
+ return {
148
+ payment_intent_id: paymentIntentId,
149
+ charge_id: chargeId,
150
+ cost_cents: args.amountCents,
151
+ status,
152
+ metadata,
153
+ mode: "stripe_test",
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Resolve the sandbox-only Stripe test key, enforcing the per-tenant + sandbox-only invariant.
159
+ *
160
+ * Returns the validated `sk_test_...` secret only when a secret is present AND the bound Paybond
161
+ * session environment is `sandbox`. Returns `undefined` when no secret is configured (the default
162
+ * offline mock path). Fails closed with an explanatory message when:
163
+ * - a secret is set but the session is not sandbox (live or unknown environment), or
164
+ * - the secret is not a Stripe TEST key (e.g. a live `sk_live_...` key).
165
+ *
166
+ * This guarantees a live tenant can never forward a template-held secret to Stripe.
167
+ *
168
+ * @param environment Server-derived Paybond session environment (preferred over client hints).
169
+ * @returns The validated Stripe test secret, or `undefined` to use the offline mock.
170
+ */
171
+ export function resolveSandboxStripeTestKey(
172
+ environment: PaybondSessionEnvironment,
173
+ ): string | undefined {
174
+ const stripeSecretKey = process.env.STRIPE_SECRET_KEY?.trim();
175
+ if (!stripeSecretKey) {
176
+ // Default offline path: no secret required, so `paybond dev loop --offline` and the sandbox
177
+ // smoke run with zero secrets.
178
+ return undefined;
179
+ }
180
+
181
+ if (environment !== "sandbox") {
182
+ throw new Error(
183
+ `STRIPE_SECRET_KEY is set but the Paybond session environment is "${environment}". ` +
184
+ "The Stripe test-mode charge is a per-tenant, sandbox-only capability; unset " +
185
+ "STRIPE_SECRET_KEY for live (or unconfirmed) sessions — the mock path needs no secret.",
186
+ );
187
+ }
188
+
189
+ if (!stripeSecretKey.startsWith("sk_test_")) {
190
+ throw new Error(
191
+ "STRIPE_SECRET_KEY must be a Stripe TEST key (sk_test_...). " +
192
+ "Live keys (sk_live_...) are refused: this sandbox-only demo never charges live Stripe.",
193
+ );
194
+ }
195
+
196
+ return stripeSecretKey;
197
+ }
198
+
199
+ /**
200
+ * Instrumented `payments.charge_customer` handler: binds Harbor metadata, then charges (mock or Stripe test).
201
+ *
202
+ * `tenantId` and `intentId` must come from Paybond session context after sandbox bind — never from
203
+ * unauthenticated client input. The Stripe test-mode charge is per-tenant + sandbox-only: it is used
204
+ * only when a `sk_test_...` secret is configured AND `binding.environment === "sandbox"`; otherwise
205
+ * this falls back to the offline mock (and fails closed if a secret is misconfigured for live).
206
+ */
207
+ export async function chargeCustomer(
208
+ binding: PaybondSessionBinding,
209
+ args: ChargeCustomerArgs,
210
+ ): Promise<ChargeCustomerResult> {
211
+ if (!binding.tenantId.trim() || !binding.intentId.trim()) {
212
+ throw new Error("Paybond session binding is required before charging");
213
+ }
214
+
215
+ const metadata = buildPaybondStripeMetadata({
216
+ tenantId: binding.tenantId,
217
+ intentId: binding.intentId,
218
+ rail: args.rail ?? "stripe_connect",
219
+ });
220
+
221
+ const sandboxStripeTestKey = resolveSandboxStripeTestKey(binding.environment);
222
+ if (sandboxStripeTestKey) {
223
+ return stripeTestChargeCustomer(sandboxStripeTestKey, metadata, args);
224
+ }
225
+
226
+ return mockChargeCustomer(metadata, args, binding.intentId);
227
+ }
228
+
229
+ /**
230
+ * Maps a charge tool result into completion-catalog evidence for the configured preset.
231
+ */
232
+ export function mapChargeEvidence(
233
+ toolResult: Record<string, unknown>,
234
+ preset: StripeCommerceEvidencePreset = "stripe_charge",
235
+ ): ReturnType<typeof mapStripeToolResultToEvidence> {
236
+ return mapStripeToolResultToEvidence(toolResult, { preset });
237
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Stripe commerce agent (generic TypeScript) — instrumented mock `payments.charge_customer`.
3
+ * Uses `buildPaybondStripeMetadata` + `mapStripeToolResultToEvidence` from @paybond/kit.
4
+ * No live LLM required for the sandbox smoke path.
5
+ */
6
+ import { createPaybondClient } from "./paybond.config.js";
7
+ import {
8
+ chargeCustomer,
9
+ mapChargeEvidence,
10
+ type PaybondSessionBinding,
11
+ } from "./charge-customer.js";
12
+
13
+ const PRIMARY_OPERATION = "payments.charge_customer";
14
+ const REQUESTED_SPEND_CENTS = 2500;
15
+
16
+ async function main(): Promise<void> {
17
+ const paybond = await createPaybondClient();
18
+ try {
19
+ // tenant_id and paybond_intent_id are populated after sandbox bind — never from client input.
20
+ // environment is server-derived (gateway principal); the Stripe test key is sandbox-only.
21
+ const bindingRef: PaybondSessionBinding = {
22
+ tenantId: "",
23
+ intentId: "",
24
+ environment: paybond.environment ?? "unknown",
25
+ };
26
+
27
+ const agent = await paybond.agent({
28
+ policy: "./paybond.policy.yaml",
29
+ framework: "generic",
30
+ tools: {
31
+ [PRIMARY_OPERATION]: async (args: { amountCents: number; customerId?: string }) =>
32
+ chargeCustomer(bindingRef, args),
33
+ },
34
+ sandbox: true,
35
+ });
36
+
37
+ bindingRef.tenantId = agent.run.tenantId;
38
+ bindingRef.intentId = agent.run.intentId;
39
+
40
+ const tool = agent.tools.find((entry) => entry.name === PRIMARY_OPERATION);
41
+ if (!tool) {
42
+ throw new Error(`missing tool ${PRIMARY_OPERATION}`);
43
+ }
44
+
45
+ const result = await tool.execute({
46
+ toolName: PRIMARY_OPERATION,
47
+ toolCallId: "demo-1",
48
+ arguments: { amountCents: REQUESTED_SPEND_CENTS },
49
+ });
50
+
51
+ const toolResult =
52
+ typeof result.toolResult === "object" && result.toolResult !== null
53
+ ? (result.toolResult as Record<string, unknown>)
54
+ : {};
55
+
56
+ const mappedEvidence = mapChargeEvidence(toolResult, "stripe_charge");
57
+
58
+ console.log(
59
+ JSON.stringify(
60
+ {
61
+ runId: agent.run.runId,
62
+ intentId: agent.run.intentId,
63
+ tenantId: agent.run.tenantId,
64
+ authorization: result.authorization,
65
+ evidence: result.evidence,
66
+ mappedEvidence,
67
+ toolResult: result.toolResult,
68
+ },
69
+ null,
70
+ 2,
71
+ ),
72
+ );
73
+ } finally {
74
+ await paybond.aclose();
75
+ }
76
+ }
77
+
78
+ void main();
@@ -0,0 +1,51 @@
1
+ import { Paybond } from "@paybond/kit";
2
+
3
+ declare const process: {
4
+ env: Record<string, string | undefined>;
5
+ };
6
+
7
+ function readEnvValue(body: string, key: string): string | undefined {
8
+ const pattern = new RegExp("^\\s*(?:export\\s+)?" + key + "\\s*=\\s*(.*)$", "m");
9
+ const match = body.match(pattern);
10
+ if (!match) return undefined;
11
+ let value = (match[1] ?? "").trim();
12
+ if (value.startsWith('"') && value.endsWith('"')) {
13
+ try {
14
+ value = JSON.parse(value);
15
+ } catch {
16
+ value = value.slice(1, -1);
17
+ }
18
+ } else if (value.startsWith("'") && value.endsWith("'")) {
19
+ value = value.slice(1, -1);
20
+ }
21
+ return value.trim() || undefined;
22
+ }
23
+
24
+ export async function loadPaybondEnvFile(envFile = ".env.local"): Promise<void> {
25
+ if (process.env.PAYBOND_API_KEY?.trim()) return;
26
+ let body: string;
27
+ try {
28
+ const { readFile } = await import("node:fs/promises");
29
+ body = await readFile(envFile, "utf8");
30
+ } catch (err) {
31
+ if ((err as { code?: string }).code === "ENOENT") return;
32
+ throw err;
33
+ }
34
+ const apiKey = readEnvValue(body, "PAYBOND_API_KEY");
35
+ if (apiKey) {
36
+ process.env.PAYBOND_API_KEY = apiKey;
37
+ }
38
+ }
39
+
40
+ export async function createPaybondClient(): Promise<Paybond> {
41
+ await loadPaybondEnvFile(".env.local");
42
+ const apiKey = process.env.PAYBOND_API_KEY?.trim();
43
+ if (!apiKey) {
44
+ throw new Error("PAYBOND_API_KEY is required; run paybond login");
45
+ }
46
+ return Paybond.open({
47
+ apiKey,
48
+ gatewayBaseUrl: process.env.PAYBOND_GATEWAY_URL ?? process.env.PAYBOND_GATEWAY_BASE_URL,
49
+ expectedEnvironment: "sandbox",
50
+ });
51
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "declaration": true
11
+ },
12
+ "include": ["src/**/*.ts"]
13
+ }