@volter/twin-polar 2.0.36 → 3.0.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.
Files changed (121) hide show
  1. package/README.md +24 -27
  2. package/dist/generated/pack-facts.json +69 -0
  3. package/dist/journeys/coverage.json +223 -0
  4. package/dist/journeys/customer-life.json +1222 -0
  5. package/dist/journeys/customer-life.outline.md +69 -0
  6. package/dist/journeys/customer-life.outline.verdict.json +41 -0
  7. package/dist/journeys/customer-life.verdict.json +71 -0
  8. package/dist/journeys/decisions.json +149 -0
  9. package/dist/journeys/demand.json +45 -0
  10. package/dist/journeys/score.json +249 -0
  11. package/dist/journeys/unreachable.json +13 -0
  12. package/dist/journeys/vendor-examples.json +9621 -0
  13. package/dist/journeys/vendor-examples.verdict.json +33 -0
  14. package/dist/spec/SOURCE.md +25 -0
  15. package/dist/spec/doc-examples.json +681 -0
  16. package/dist/spec/openapi.json.gz +0 -0
  17. package/dist/spec/sources.json +71 -0
  18. package/dist/src/cli.js +7 -16
  19. package/dist/src/fetch.d.ts +2 -0
  20. package/dist/src/fetch.js +24 -0
  21. package/dist/src/generated/surface.gen.json +1 -0
  22. package/dist/src/generated/ui.gen.json +1 -0
  23. package/dist/src/index.d.ts +5 -9
  24. package/dist/src/index.js +10 -55
  25. package/dist/src/manifest.d.ts +2 -0
  26. package/dist/src/manifest.js +123 -0
  27. package/dist/src/screens/checkout.d.ts +2 -0
  28. package/dist/src/screens/checkout.js +25 -0
  29. package/dist/src/screens/portal.d.ts +2 -0
  30. package/dist/src/screens/portal.js +18 -0
  31. package/dist/src/semantics/around.d.ts +2 -0
  32. package/dist/src/semantics/around.js +25 -0
  33. package/dist/src/semantics/checkouts.d.ts +14 -0
  34. package/dist/src/semantics/checkouts.js +131 -0
  35. package/dist/src/semantics/clock.d.ts +2 -0
  36. package/dist/src/semantics/clock.js +22 -0
  37. package/dist/src/semantics/customer-sessions.d.ts +2 -0
  38. package/dist/src/semantics/customer-sessions.js +12 -0
  39. package/dist/src/semantics/customers.d.ts +9 -0
  40. package/dist/src/semantics/customers.js +61 -0
  41. package/dist/src/semantics/doors.d.ts +11 -0
  42. package/dist/src/semantics/doors.js +47 -0
  43. package/dist/src/semantics/events.d.ts +12 -0
  44. package/dist/src/semantics/events.js +60 -0
  45. package/dist/src/semantics/index.d.ts +2 -0
  46. package/dist/src/semantics/index.js +27 -0
  47. package/dist/src/semantics/orders.d.ts +4 -0
  48. package/dist/src/semantics/orders.js +11 -0
  49. package/dist/src/semantics/products.d.ts +5 -0
  50. package/dist/src/semantics/products.js +43 -0
  51. package/dist/src/semantics/shared.d.ts +82 -0
  52. package/dist/src/semantics/shared.js +327 -0
  53. package/dist/src/semantics/states.d.ts +27 -0
  54. package/dist/src/semantics/states.js +43 -0
  55. package/dist/src/semantics/subscriptions.d.ts +4 -0
  56. package/dist/src/semantics/subscriptions.js +15 -0
  57. package/dist/src/semantics/tenant.d.ts +2 -0
  58. package/dist/src/semantics/tenant.js +2 -0
  59. package/dist/src/semantics/webhooks.d.ts +4 -0
  60. package/dist/src/semantics/webhooks.js +30 -0
  61. package/dist/src/server.d.ts +7 -0
  62. package/dist/src/server.js +7 -0
  63. package/generated/pack-facts.json +69 -0
  64. package/journeys/coverage.json +223 -0
  65. package/journeys/customer-life.json +1222 -0
  66. package/journeys/customer-life.outline.md +69 -0
  67. package/journeys/customer-life.outline.verdict.json +41 -0
  68. package/journeys/customer-life.verdict.json +71 -0
  69. package/journeys/decisions.json +149 -0
  70. package/journeys/demand.json +45 -0
  71. package/journeys/score.json +249 -0
  72. package/journeys/unreachable.json +13 -0
  73. package/journeys/vendor-examples.json +9621 -0
  74. package/journeys/vendor-examples.verdict.json +33 -0
  75. package/package.json +19 -31
  76. package/spec/SOURCE.md +25 -0
  77. package/spec/doc-examples.json +681 -0
  78. package/spec/openapi.json.gz +0 -0
  79. package/spec/sources.json +71 -0
  80. package/src/cli.ts +7 -14
  81. package/src/fetch.ts +25 -0
  82. package/src/generated/surface.gen.json +1 -0
  83. package/src/generated/ui.gen.json +1 -0
  84. package/src/index.ts +11 -85
  85. package/src/manifest.ts +135 -0
  86. package/src/screens/checkout.ts +33 -0
  87. package/src/screens/portal.ts +20 -0
  88. package/src/semantics/around.ts +25 -0
  89. package/src/semantics/checkouts.ts +133 -0
  90. package/src/semantics/clock.ts +24 -0
  91. package/src/semantics/customer-sessions.ts +13 -0
  92. package/src/semantics/customers.ts +68 -0
  93. package/src/semantics/doors.ts +51 -0
  94. package/src/semantics/events.ts +62 -0
  95. package/src/semantics/index.ts +31 -0
  96. package/src/semantics/orders.ts +15 -0
  97. package/src/semantics/products.ts +45 -0
  98. package/src/semantics/shared.ts +352 -0
  99. package/src/semantics/states.ts +50 -0
  100. package/src/semantics/subscriptions.ts +18 -0
  101. package/src/semantics/tenant.ts +5 -0
  102. package/src/semantics/webhooks.ts +29 -0
  103. package/src/server.ts +8 -0
  104. package/dist/src/polar-budget.d.ts +0 -83
  105. package/dist/src/polar-budget.js +0 -413
  106. package/dist/src/polar-capabilities.d.ts +0 -4
  107. package/dist/src/polar-capabilities.js +0 -389
  108. package/dist/src/polar-conformance.d.ts +0 -7
  109. package/dist/src/polar-conformance.js +0 -25
  110. package/dist/src/polar-connector.d.ts +0 -113
  111. package/dist/src/polar-connector.js +0 -146
  112. package/dist/src/polar-server.d.ts +0 -14
  113. package/dist/src/polar-server.js +0 -52
  114. package/dist/src/polar-twin.d.ts +0 -24
  115. package/dist/src/polar-twin.js +0 -941
  116. package/src/polar-budget.ts +0 -459
  117. package/src/polar-capabilities.ts +0 -448
  118. package/src/polar-conformance.ts +0 -33
  119. package/src/polar-connector.ts +0 -172
  120. package/src/polar-server.ts +0 -57
  121. package/src/polar-twin.ts +0 -938
@@ -0,0 +1,1222 @@
1
+ {
2
+ "vendor": "polar",
3
+ "author": "Contributor 77",
4
+ "host": null,
5
+ "about": "Volter's year on Polar (journeys/customer-life.outline.md): the platform's token and Open Autonomy's webhook endpoint, the biller's meters and Team plan made at boot, an org upgrading on Polar's hosted checkout and its state read by its own id, usage reported once per event, the customer portal's sessions, Open Autonomy's patrons paying once and monthly with the paid orders told by webhook and read back on the thanks page, and a leaked token replaced.",
6
+ "steps": [
7
+ {
8
+ "name": "Monday 5 January: Volter sets its Polar organization up",
9
+ "wait": "6300m"
10
+ },
11
+ {
12
+ "name": "Lena makes the platform's Organization Access Token on the settings page",
13
+ "method": "POST",
14
+ "path": "/_twin/access-tokens",
15
+ "status": 201,
16
+ "body": {
17
+ "comment": "volter-platform"
18
+ },
19
+ "capture": {
20
+ "token": "token",
21
+ "token_id": "id"
22
+ }
23
+ },
24
+ {
25
+ "name": "The platform's biller boots and finds no meters",
26
+ "method": "GET",
27
+ "path": "https://api.polar.sh/v1/meters/?limit=100",
28
+ "status": 200,
29
+ "headers": {
30
+ "authorization": "Bearer {token}"
31
+ },
32
+ "expect": [
33
+ {
34
+ "path": "items",
35
+ "equals": []
36
+ }
37
+ ]
38
+ },
39
+ {
40
+ "name": "so it makes its world-hours meter, the sum of each world.hour event's units",
41
+ "method": "POST",
42
+ "path": "https://api.polar.sh/v1/meters/",
43
+ "status": 201,
44
+ "headers": {
45
+ "authorization": "Bearer {token}"
46
+ },
47
+ "body": {
48
+ "name": "world_hours",
49
+ "filter": {
50
+ "conjunction": "and",
51
+ "clauses": [
52
+ {
53
+ "property": "name",
54
+ "operator": "eq",
55
+ "value": "world.hour"
56
+ }
57
+ ]
58
+ },
59
+ "aggregation": {
60
+ "func": "sum",
61
+ "property": "units"
62
+ }
63
+ },
64
+ "capture": {
65
+ "meter_hours": "id"
66
+ },
67
+ "expect": [
68
+ {
69
+ "path": "name",
70
+ "equals": "world_hours"
71
+ },
72
+ {
73
+ "path": "aggregation.func",
74
+ "equals": "sum"
75
+ }
76
+ ]
77
+ },
78
+ {
79
+ "name": "and its worlds-provisioned meter, a count of world.provisioned events",
80
+ "method": "POST",
81
+ "path": "https://api.polar.sh/v1/meters/",
82
+ "status": 201,
83
+ "headers": {
84
+ "authorization": "Bearer {token}"
85
+ },
86
+ "body": {
87
+ "name": "worlds_provisioned",
88
+ "filter": {
89
+ "conjunction": "and",
90
+ "clauses": [
91
+ {
92
+ "property": "name",
93
+ "operator": "eq",
94
+ "value": "world.provisioned"
95
+ }
96
+ ]
97
+ },
98
+ "aggregation": {
99
+ "func": "count"
100
+ }
101
+ },
102
+ "capture": {
103
+ "meter_prov": "id"
104
+ }
105
+ },
106
+ {
107
+ "name": "It finds no products",
108
+ "method": "GET",
109
+ "path": "https://api.polar.sh/v1/products/?limit=100",
110
+ "status": 200,
111
+ "headers": {
112
+ "authorization": "Bearer {token}"
113
+ },
114
+ "expect": [
115
+ {
116
+ "path": "items",
117
+ "equals": []
118
+ }
119
+ ]
120
+ },
121
+ {
122
+ "name": "so it makes the Team plan: $49 a month, 2 cents a world-hour beyond the plan, a 14-day trial",
123
+ "method": "POST",
124
+ "path": "https://api.polar.sh/v1/products/",
125
+ "status": 201,
126
+ "headers": {
127
+ "authorization": "Bearer {token}"
128
+ },
129
+ "body": {
130
+ "name": "Volter World Team",
131
+ "recurring_interval": "month",
132
+ "description": "Worlds for every branch, pull request and teammate, deploying to the real vendors when you approve.",
133
+ "prices": [
134
+ {
135
+ "amount_type": "fixed",
136
+ "price_amount": 4900,
137
+ "price_currency": "usd"
138
+ },
139
+ {
140
+ "amount_type": "metered_unit",
141
+ "unit_amount": 2,
142
+ "meter_id": "{meter_hours}"
143
+ }
144
+ ],
145
+ "trial_interval": "day",
146
+ "trial_interval_count": 14
147
+ },
148
+ "capture": {
149
+ "team": "id"
150
+ },
151
+ "expect": [
152
+ {
153
+ "path": "is_recurring",
154
+ "equals": true
155
+ },
156
+ {
157
+ "path": "prices.0.price_amount",
158
+ "equals": 4900
159
+ },
160
+ {
161
+ "path": "prices.1.amount_type",
162
+ "equals": "metered_unit"
163
+ },
164
+ {
165
+ "path": "prices.1.meter_id",
166
+ "equals": "{meter_hours}"
167
+ },
168
+ {
169
+ "path": "prices.1.meter.unit",
170
+ "equals": "scalar"
171
+ },
172
+ {
173
+ "path": "prices.1.meter.custom_label",
174
+ "equals": null
175
+ },
176
+ {
177
+ "path": "prices.1.meter.custom_multiplier",
178
+ "equals": null
179
+ },
180
+ {
181
+ "path": "trial_interval_count",
182
+ "equals": 14
183
+ }
184
+ ]
185
+ },
186
+ {
187
+ "name": "At its next boot the biller finds both meters",
188
+ "method": "GET",
189
+ "path": "https://api.polar.sh/v1/meters/?limit=100",
190
+ "status": 200,
191
+ "headers": {
192
+ "authorization": "Bearer {token}"
193
+ },
194
+ "expect": [
195
+ {
196
+ "path": "pagination.total_count",
197
+ "equals": 2
198
+ }
199
+ ]
200
+ },
201
+ {
202
+ "name": "and the Team plan by its name",
203
+ "method": "GET",
204
+ "path": "https://api.polar.sh/v1/products/?limit=100",
205
+ "status": 200,
206
+ "headers": {
207
+ "authorization": "Bearer {token}"
208
+ },
209
+ "expect": [
210
+ {
211
+ "path": "items.0.id",
212
+ "equals": "{team}"
213
+ },
214
+ {
215
+ "path": "items.0.name",
216
+ "equals": "Volter World Team"
217
+ }
218
+ ]
219
+ },
220
+ {
221
+ "name": "Tuesday 6 January: Acme Robotics upgrades to Team",
222
+ "wait": "1500m"
223
+ },
224
+ {
225
+ "name": "The biller reads Acme's billing by its org id: Polar has no such customer, so Acme is on Free",
226
+ "method": "GET",
227
+ "path": "https://api.polar.sh/v1/customers/external/org_acme/state",
228
+ "status": 404,
229
+ "headers": {
230
+ "authorization": "Bearer {token}"
231
+ }
232
+ },
233
+ {
234
+ "name": "Ada, Acme's admin, chooses Team: the biller opens a checkout with Acme's org id as the customer's",
235
+ "method": "POST",
236
+ "path": "https://api.polar.sh/v1/checkouts/",
237
+ "status": 201,
238
+ "headers": {
239
+ "authorization": "Bearer {token}"
240
+ },
241
+ "body": {
242
+ "products": [
243
+ "{team}"
244
+ ],
245
+ "external_customer_id": "org_acme",
246
+ "success_url": "https://world.volter.ai/acme/billing?upgraded=1",
247
+ "customer_email": "ada@acme.dev",
248
+ "metadata": {
249
+ "org": "org_acme"
250
+ }
251
+ },
252
+ "capture": {
253
+ "co": "id",
254
+ "co_secret": "client_secret",
255
+ "co_url": "url"
256
+ },
257
+ "expect": [
258
+ {
259
+ "path": "status",
260
+ "equals": "open"
261
+ },
262
+ {
263
+ "path": "amount",
264
+ "equals": 4900
265
+ },
266
+ {
267
+ "path": "url",
268
+ "equals": "https://buy.polar.sh/{co_secret}"
269
+ },
270
+ {
271
+ "path": "external_customer_id",
272
+ "equals": "org_acme"
273
+ }
274
+ ]
275
+ },
276
+ {
277
+ "name": "Ada opens Polar's checkout page",
278
+ "method": "GET",
279
+ "path": "{co_url}",
280
+ "status": 200,
281
+ "expect": [
282
+ {
283
+ "path": "",
284
+ "contains": "Volter World Team"
285
+ },
286
+ {
287
+ "path": "",
288
+ "contains": "$49.00"
289
+ }
290
+ ],
291
+ "capture": {
292
+ "co_action": "text:action=\"([^\"]+)\""
293
+ }
294
+ },
295
+ {
296
+ "name": "Ada supplies payment details and starts the Team trial without an immediate charge; Polar returns her to billing",
297
+ "method": "POST",
298
+ "path": "{co_action}",
299
+ "status": 303,
300
+ "headers": {
301
+ "content-type": "application/x-www-form-urlencoded"
302
+ },
303
+ "body": "email=ada%40acme.dev",
304
+ "expect": [
305
+ {
306
+ "header": "location",
307
+ "equals": "https://world.volter.ai/acme/billing?upgraded=1"
308
+ }
309
+ ]
310
+ },
311
+ {
312
+ "name": "Back from the checkout, the biller reads Acme's state: the Team plan, trialing",
313
+ "method": "GET",
314
+ "path": "https://api.polar.sh/v1/customers/external/org_acme/state",
315
+ "status": 200,
316
+ "headers": {
317
+ "authorization": "Bearer {token}"
318
+ },
319
+ "capture": {
320
+ "acme": "id"
321
+ },
322
+ "expect": [
323
+ {
324
+ "path": "external_id",
325
+ "equals": "org_acme"
326
+ },
327
+ {
328
+ "path": "email",
329
+ "equals": "ada@acme.dev"
330
+ },
331
+ {
332
+ "path": "active_subscriptions.0.product_id",
333
+ "equals": "{team}"
334
+ },
335
+ {
336
+ "path": "active_subscriptions.0.status",
337
+ "equals": "trialing"
338
+ },
339
+ {
340
+ "path": "active_subscriptions.0.trial_end",
341
+ "exists": true
342
+ },
343
+ {
344
+ "path": "active_subscriptions.0.cancel_at_period_end",
345
+ "equals": false
346
+ }
347
+ ]
348
+ },
349
+ {
350
+ "name": "Wednesday 14 January: Acme works",
351
+ "wait": "11820m"
352
+ },
353
+ {
354
+ "name": "Acme creates a new World: the biller reports provisioning before its first completed hours",
355
+ "method": "POST",
356
+ "path": "https://api.polar.sh/v1/events/ingest",
357
+ "status": 200,
358
+ "headers": {
359
+ "authorization": "Bearer {token}"
360
+ },
361
+ "body": {
362
+ "events": [
363
+ {
364
+ "name": "world.provisioned",
365
+ "external_customer_id": "org_acme",
366
+ "external_id": "org_acme:worlds_provisioned:main",
367
+ "metadata": {}
368
+ }
369
+ ]
370
+ },
371
+ "expect": [
372
+ {
373
+ "path": "inserted",
374
+ "equals": 1
375
+ },
376
+ {
377
+ "path": "duplicates",
378
+ "equals": 0
379
+ }
380
+ ]
381
+ },
382
+ {
383
+ "name": "The newly created World runs for two hours",
384
+ "wait": "120m"
385
+ },
386
+ {
387
+ "name": "The hourly tick reports the World's two hours",
388
+ "method": "POST",
389
+ "path": "https://api.polar.sh/v1/events/ingest",
390
+ "status": 200,
391
+ "headers": {
392
+ "authorization": "Bearer {token}"
393
+ },
394
+ "body": {
395
+ "events": [
396
+ {
397
+ "name": "world.hour",
398
+ "external_customer_id": "org_acme",
399
+ "external_id": "org_acme:world_hours:2026-01:main:2026-01-14T15",
400
+ "metadata": {
401
+ "world": "main",
402
+ "units": 1
403
+ }
404
+ },
405
+ {
406
+ "name": "world.hour",
407
+ "external_customer_id": "org_acme",
408
+ "external_id": "org_acme:world_hours:2026-01:main:2026-01-14T16",
409
+ "metadata": {
410
+ "world": "main",
411
+ "units": 1
412
+ }
413
+ }
414
+ ]
415
+ },
416
+ "expect": [
417
+ {
418
+ "path": "inserted",
419
+ "equals": 2
420
+ },
421
+ {
422
+ "path": "duplicates",
423
+ "equals": 0
424
+ }
425
+ ]
426
+ },
427
+ {
428
+ "name": "A tick retried after a timeout sends the last hour again: a duplicate, not a third hour",
429
+ "method": "POST",
430
+ "path": "https://api.polar.sh/v1/events/ingest",
431
+ "status": 200,
432
+ "headers": {
433
+ "authorization": "Bearer {token}"
434
+ },
435
+ "body": {
436
+ "events": [
437
+ {
438
+ "name": "world.hour",
439
+ "external_customer_id": "org_acme",
440
+ "external_id": "org_acme:world_hours:2026-01:main:2026-01-14T16",
441
+ "metadata": {
442
+ "world": "main",
443
+ "units": 1
444
+ }
445
+ }
446
+ ]
447
+ },
448
+ "expect": [
449
+ {
450
+ "path": "inserted",
451
+ "equals": 0
452
+ },
453
+ {
454
+ "path": "duplicates",
455
+ "equals": 1
456
+ }
457
+ ]
458
+ },
459
+ {
460
+ "name": "The biller reads Acme's usage: two world-hours consumed",
461
+ "method": "GET",
462
+ "path": "https://api.polar.sh/v1/customers/external/org_acme/state",
463
+ "status": 200,
464
+ "headers": {
465
+ "authorization": "Bearer {token}"
466
+ },
467
+ "expect": [
468
+ {
469
+ "path": "active_meters",
470
+ "matches": "\"meter_id\":\"{meter_hours}\"[^{}]*\"consumed_units\":2[,}]|\"consumed_units\":2[,}][^{}]*\"meter_id\":\"{meter_hours}\""
471
+ }
472
+ ]
473
+ },
474
+ {
475
+ "name": "Monday 2 February: Manage billing",
476
+ "wait": "26910m"
477
+ },
478
+ {
479
+ "name": "Ada opens Manage billing: the biller makes a customer session for Acme by its org id",
480
+ "method": "POST",
481
+ "path": "https://api.polar.sh/v1/customer-sessions/",
482
+ "status": 201,
483
+ "headers": {
484
+ "authorization": "Bearer {token}"
485
+ },
486
+ "body": {
487
+ "external_customer_id": "org_acme",
488
+ "return_url": "https://world.volter.ai/acme/billing"
489
+ },
490
+ "expect": [
491
+ {
492
+ "path": "customer_id",
493
+ "equals": "{acme}"
494
+ },
495
+ {
496
+ "path": "customer_portal_url",
497
+ "exists": true
498
+ },
499
+ {
500
+ "path": "token",
501
+ "matches": "^polar_cst_"
502
+ }
503
+ ],
504
+ "capture": {
505
+ "portal_url": "customer_portal_url"
506
+ }
507
+ },
508
+ {
509
+ "name": "Ada follows the returned portal link and sees her Team subscription and purchase history",
510
+ "method": "GET",
511
+ "path": "{portal_url}",
512
+ "status": 200,
513
+ "expect": [
514
+ {
515
+ "path": "",
516
+ "contains": "Volter World Team"
517
+ },
518
+ {
519
+ "path": "",
520
+ "contains": "active"
521
+ },
522
+ {
523
+ "path": "",
524
+ "contains": "Purchase history"
525
+ }
526
+ ]
527
+ },
528
+ {
529
+ "name": "Tuesday 3 February: Globex asks for its billing portal",
530
+ "wait": "1440m"
531
+ },
532
+ {
533
+ "name": "Globex's admin opens Manage billing on Free, before Globex has a customer: refused",
534
+ "method": "POST",
535
+ "path": "https://api.polar.sh/v1/customer-sessions/",
536
+ "status": 422,
537
+ "headers": {
538
+ "authorization": "Bearer {token}"
539
+ },
540
+ "body": {
541
+ "external_customer_id": "org_globex",
542
+ "return_url": "https://world.volter.ai/globex/billing"
543
+ }
544
+ },
545
+ {
546
+ "name": "Lena creates Globex its customer after support reports the missing customer",
547
+ "method": "POST",
548
+ "path": "https://api.polar.sh/v1/customers/",
549
+ "status": 201,
550
+ "headers": {
551
+ "authorization": "Bearer {token}"
552
+ },
553
+ "body": {
554
+ "email": "ops@globex.io",
555
+ "external_id": "org_globex"
556
+ },
557
+ "capture": {
558
+ "globex": "id"
559
+ },
560
+ "expect": [
561
+ {
562
+ "path": "external_id",
563
+ "equals": "org_globex"
564
+ },
565
+ {
566
+ "path": "type",
567
+ "equals": "individual"
568
+ }
569
+ ]
570
+ },
571
+ {
572
+ "name": "Globex tries Manage billing again and the biller creates its session",
573
+ "method": "POST",
574
+ "path": "https://api.polar.sh/v1/customer-sessions/",
575
+ "status": 201,
576
+ "headers": {
577
+ "authorization": "Bearer {token}"
578
+ },
579
+ "body": {
580
+ "external_customer_id": "org_globex",
581
+ "return_url": "https://world.volter.ai/globex/billing"
582
+ },
583
+ "expect": [
584
+ {
585
+ "path": "customer_id",
586
+ "equals": "{globex}"
587
+ },
588
+ {
589
+ "path": "customer_portal_url",
590
+ "exists": true
591
+ }
592
+ ]
593
+ },
594
+ {
595
+ "name": "Tuesday 10 February: a patron gives to acme/rover on Open Autonomy",
596
+ "wait": "10080m"
597
+ },
598
+ {
599
+ "name": "and registers Open Autonomy's webhook endpoint for its orders",
600
+ "method": "POST",
601
+ "path": "https://api.polar.sh/v1/webhooks/endpoints",
602
+ "status": 201,
603
+ "headers": {
604
+ "authorization": "Bearer {token}"
605
+ },
606
+ "body": {
607
+ "url": "https://platform.open-autonomy.org/webhooks/polar",
608
+ "format": "raw",
609
+ "events": [
610
+ "order.created",
611
+ "order.paid"
612
+ ]
613
+ },
614
+ "capture": {
615
+ "whs": "secret",
616
+ "endpoint": "id"
617
+ },
618
+ "expect": [
619
+ {
620
+ "path": "secret",
621
+ "exists": true
622
+ },
623
+ {
624
+ "path": "url",
625
+ "equals": "https://platform.open-autonomy.org/webhooks/polar"
626
+ },
627
+ {
628
+ "path": "events",
629
+ "members": [
630
+ "order.created",
631
+ "order.paid"
632
+ ]
633
+ },
634
+ {
635
+ "path": "enabled",
636
+ "equals": true
637
+ }
638
+ ]
639
+ },
640
+ {
641
+ "name": "Open Autonomy makes the products behind acme/rover's first tier: monthly",
642
+ "method": "POST",
643
+ "path": "https://api.polar.sh/v1/products/",
644
+ "status": 201,
645
+ "headers": {
646
+ "authorization": "Bearer {token}"
647
+ },
648
+ "body": {
649
+ "name": "acme/rover \u00b7 $5 a month",
650
+ "description": "This gift is for whatever rover needs.",
651
+ "recurring_interval": "month",
652
+ "prices": [
653
+ {
654
+ "amount_type": "fixed",
655
+ "price_amount": 500,
656
+ "price_currency": "usd"
657
+ }
658
+ ],
659
+ "metadata": {
660
+ "account": "acme/rover",
661
+ "tier": "0",
662
+ "interval": "month"
663
+ }
664
+ },
665
+ "capture": {
666
+ "patron_month": "id"
667
+ },
668
+ "expect": [
669
+ {
670
+ "path": "is_recurring",
671
+ "equals": true
672
+ },
673
+ {
674
+ "path": "metadata.account",
675
+ "equals": "acme/rover"
676
+ }
677
+ ]
678
+ },
679
+ {
680
+ "name": "and once",
681
+ "method": "POST",
682
+ "path": "https://api.polar.sh/v1/products/",
683
+ "status": 201,
684
+ "headers": {
685
+ "authorization": "Bearer {token}"
686
+ },
687
+ "body": {
688
+ "name": "acme/rover \u00b7 $5 a month, once",
689
+ "description": "This gift is for whatever rover needs.",
690
+ "prices": [
691
+ {
692
+ "amount_type": "fixed",
693
+ "price_amount": 500,
694
+ "price_currency": "usd"
695
+ }
696
+ ],
697
+ "metadata": {
698
+ "account": "acme/rover",
699
+ "tier": "0",
700
+ "interval": "once"
701
+ }
702
+ },
703
+ "capture": {
704
+ "patron_once": "id"
705
+ },
706
+ "expect": [
707
+ {
708
+ "path": "is_recurring",
709
+ "equals": false
710
+ },
711
+ {
712
+ "path": "recurring_interval",
713
+ "equals": null
714
+ }
715
+ ]
716
+ },
717
+ {
718
+ "name": "Grace gives once: Open Autonomy opens her checkout, back to its thanks page with the checkout's id; she opens the returned hosted page and reads its payment form",
719
+ "method": "POST",
720
+ "path": "https://api.polar.sh/v1/checkouts/",
721
+ "status": 201,
722
+ "headers": {
723
+ "authorization": "Bearer {token}"
724
+ },
725
+ "body": {
726
+ "products": [
727
+ "{patron_once}"
728
+ ],
729
+ "success_url": "https://platform.open-autonomy.org/acme/rover/thanks?checkout_id={CHECKOUT_ID}",
730
+ "metadata": {
731
+ "account": "acme/rover",
732
+ "tier": "0",
733
+ "interval": "once",
734
+ "for": "{\"type\":\"project\"}",
735
+ "choice": "This gift is for whatever rover needs."
736
+ }
737
+ },
738
+ "capture": {
739
+ "pco": "id",
740
+ "pco_secret": "client_secret",
741
+ "pco_url": "url"
742
+ },
743
+ "expect": [
744
+ {
745
+ "path": "status",
746
+ "equals": "open"
747
+ },
748
+ {
749
+ "path": "amount",
750
+ "equals": 500
751
+ },
752
+ {
753
+ "path": "url",
754
+ "equals": "https://buy.polar.sh/{pco_secret}"
755
+ }
756
+ ]
757
+ },
758
+ {
759
+ "name": "Grace opens the returned hosted checkout and reads its payment form",
760
+ "method": "GET",
761
+ "path": "{pco_url}",
762
+ "status": 200,
763
+ "capture": {
764
+ "stale_action": "text:action=\"([^\"]+)\""
765
+ },
766
+ "expect": [
767
+ {
768
+ "path": "",
769
+ "contains": "$5.00"
770
+ }
771
+ ]
772
+ },
773
+ {
774
+ "name": "Grace leaves the checkout tab while she checks with the project maintainer",
775
+ "wait": "1500m"
776
+ },
777
+ {
778
+ "name": "She tries to pay from the stale tab and the checkout has expired",
779
+ "method": "POST",
780
+ "path": "{stale_action}",
781
+ "status": 410,
782
+ "headers": {
783
+ "content-type": "application/x-www-form-urlencoded"
784
+ },
785
+ "body": "email=grace%40example.org"
786
+ },
787
+ {
788
+ "name": "Grace returns from the expired checkout and Open Autonomy opens a fresh session",
789
+ "method": "POST",
790
+ "path": "https://api.polar.sh/v1/checkouts/",
791
+ "status": 201,
792
+ "headers": {
793
+ "authorization": "Bearer {token}"
794
+ },
795
+ "body": {
796
+ "products": [
797
+ "{patron_once}"
798
+ ],
799
+ "success_url": "https://platform.open-autonomy.org/acme/rover/thanks?checkout_id={CHECKOUT_ID}",
800
+ "metadata": {
801
+ "account": "acme/rover",
802
+ "tier": "0",
803
+ "interval": "once",
804
+ "for": "{\"type\":\"project\"}",
805
+ "choice": "This gift is for whatever rover needs."
806
+ }
807
+ },
808
+ "capture": {
809
+ "pco": "id",
810
+ "pco_secret": "client_secret",
811
+ "pco_url": "url"
812
+ },
813
+ "expect": [
814
+ {
815
+ "path": "status",
816
+ "equals": "open"
817
+ },
818
+ {
819
+ "path": "amount",
820
+ "equals": 500
821
+ },
822
+ {
823
+ "path": "url",
824
+ "equals": "https://buy.polar.sh/{pco_secret}"
825
+ }
826
+ ]
827
+ },
828
+ {
829
+ "name": "Grace opens Polar's checkout page",
830
+ "method": "GET",
831
+ "path": "{pco_url}",
832
+ "status": 200,
833
+ "expect": [
834
+ {
835
+ "path": "",
836
+ "contains": "$5.00"
837
+ }
838
+ ],
839
+ "capture": {
840
+ "grace_action": "text:action=\"([^\"]+)\""
841
+ }
842
+ },
843
+ {
844
+ "name": "and pays; Polar returns her to the thanks page, the checkout's id filled in",
845
+ "method": "POST",
846
+ "path": "{grace_action}",
847
+ "status": 303,
848
+ "headers": {
849
+ "content-type": "application/x-www-form-urlencoded"
850
+ },
851
+ "body": "email=grace%40example.org",
852
+ "expect": [
853
+ {
854
+ "header": "location",
855
+ "equals": "https://platform.open-autonomy.org/acme/rover/thanks?checkout_id={pco}"
856
+ }
857
+ ]
858
+ },
859
+ {
860
+ "name": "Polar tells Open Autonomy the order is paid, signed with the endpoint's secret",
861
+ "method": "GET",
862
+ "path": "/_twin/deliveries?to=https://platform.open-autonomy.org/webhooks/polar&type=order.paid",
863
+ "status": 200,
864
+ "capture": {
865
+ "order": "data.0.body.data.id",
866
+ "grace": "data.0.body.data.customer_id"
867
+ },
868
+ "expect": [
869
+ {
870
+ "path": "data.0.body.type",
871
+ "equals": "order.paid"
872
+ },
873
+ {
874
+ "path": "data.0.body.data.checkout_id",
875
+ "equals": "{pco}"
876
+ },
877
+ {
878
+ "path": "data.0.body.data.paid",
879
+ "equals": true
880
+ },
881
+ {
882
+ "path": "data.0.body.data.total_amount",
883
+ "equals": 500
884
+ },
885
+ {
886
+ "path": "data.0.body.data.billing_reason",
887
+ "equals": "purchase"
888
+ },
889
+ {
890
+ "path": "data.0.body.data.metadata.account",
891
+ "equals": "acme/rover"
892
+ },
893
+ {
894
+ "path": "data.0.headers.webhook-signature",
895
+ "matches": "^v1,"
896
+ }
897
+ ]
898
+ },
899
+ {
900
+ "name": "and, first, that it was made",
901
+ "method": "GET",
902
+ "path": "/_twin/deliveries?to=https://platform.open-autonomy.org/webhooks/polar&type=order.created",
903
+ "status": 200,
904
+ "expect": [
905
+ {
906
+ "path": "data.0.body.data.id",
907
+ "equals": "{order}"
908
+ }
909
+ ]
910
+ },
911
+ {
912
+ "name": "On the thanks page Open Autonomy reads Grace's checkout back: paid",
913
+ "method": "GET",
914
+ "path": "https://api.polar.sh/v1/checkouts/{pco}",
915
+ "status": 200,
916
+ "headers": {
917
+ "authorization": "Bearer {token}"
918
+ },
919
+ "expect": [
920
+ {
921
+ "path": "id",
922
+ "equals": "{pco}"
923
+ },
924
+ {
925
+ "path": "status",
926
+ "oneOf": [
927
+ "confirmed",
928
+ "succeeded"
929
+ ]
930
+ }
931
+ ]
932
+ },
933
+ {
934
+ "name": "and the checkout's orders",
935
+ "method": "GET",
936
+ "path": "https://api.polar.sh/v1/orders/?checkout_id={pco}",
937
+ "status": 200,
938
+ "headers": {
939
+ "authorization": "Bearer {token}"
940
+ },
941
+ "expect": [
942
+ {
943
+ "path": "items.0.id",
944
+ "equals": "{order}"
945
+ },
946
+ {
947
+ "path": "items.0.paid",
948
+ "equals": true
949
+ },
950
+ {
951
+ "path": "items.0.total_amount",
952
+ "equals": 500
953
+ },
954
+ {
955
+ "path": "pagination.total_count",
956
+ "equals": 1
957
+ }
958
+ ]
959
+ },
960
+ {
961
+ "name": "and the order's customer, for the patron's name on the wall",
962
+ "method": "GET",
963
+ "path": "https://api.polar.sh/v1/customers/{grace}",
964
+ "status": 200,
965
+ "headers": {
966
+ "authorization": "Bearer {token}"
967
+ },
968
+ "expect": [
969
+ {
970
+ "path": "email",
971
+ "equals": "grace@example.org"
972
+ }
973
+ ]
974
+ },
975
+ {
976
+ "name": "Thursday 12 February: a monthly patron",
977
+ "wait": "1380m"
978
+ },
979
+ {
980
+ "name": "Heidi gives $5 a month and opens the returned hosted payment form",
981
+ "method": "POST",
982
+ "path": "https://api.polar.sh/v1/checkouts/",
983
+ "status": 201,
984
+ "headers": {
985
+ "authorization": "Bearer {token}"
986
+ },
987
+ "body": {
988
+ "products": [
989
+ "{patron_month}"
990
+ ],
991
+ "success_url": "https://platform.open-autonomy.org/acme/rover/thanks?checkout_id={CHECKOUT_ID}",
992
+ "metadata": {
993
+ "account": "acme/rover",
994
+ "tier": "0",
995
+ "interval": "month"
996
+ }
997
+ },
998
+ "capture": {
999
+ "mco": "id",
1000
+ "mco_secret": "client_secret",
1001
+ "mco_url": "url"
1002
+ }
1003
+ },
1004
+ {
1005
+ "name": "Heidi opens the returned hosted checkout and reads its payment form",
1006
+ "method": "GET",
1007
+ "path": "{mco_url}",
1008
+ "status": 200,
1009
+ "capture": {
1010
+ "heidi_action": "text:action=\"([^\"]+)\""
1011
+ },
1012
+ "expect": [
1013
+ {
1014
+ "path": "",
1015
+ "contains": "$5.00"
1016
+ }
1017
+ ]
1018
+ },
1019
+ {
1020
+ "name": "and pays on the page",
1021
+ "method": "POST",
1022
+ "path": "{heidi_action}",
1023
+ "status": 303,
1024
+ "headers": {
1025
+ "content-type": "application/x-www-form-urlencoded"
1026
+ },
1027
+ "body": "email=heidi%40example.org",
1028
+ "expect": [
1029
+ {
1030
+ "header": "location",
1031
+ "equals": "https://platform.open-autonomy.org/acme/rover/thanks?checkout_id={mco}"
1032
+ }
1033
+ ]
1034
+ },
1035
+ {
1036
+ "name": "Polar tells Open Autonomy the order is paid: the first of a subscription",
1037
+ "method": "GET",
1038
+ "path": "/_twin/deliveries?to=https://platform.open-autonomy.org/webhooks/polar&type=order.paid",
1039
+ "status": 200,
1040
+ "expect": [
1041
+ {
1042
+ "path": "data.1.body.data.checkout_id",
1043
+ "equals": "{mco}"
1044
+ },
1045
+ {
1046
+ "path": "data.1.body.data.billing_reason",
1047
+ "equals": "subscription_create"
1048
+ },
1049
+ {
1050
+ "path": "data.1.body.data.subscription_id",
1051
+ "exists": true
1052
+ }
1053
+ ]
1054
+ },
1055
+ {
1056
+ "name": "Thursday 16 April: the platform's token leaks",
1057
+ "wait": "90720m"
1058
+ },
1059
+ {
1060
+ "name": "Lena revokes it on the settings page",
1061
+ "method": "DELETE",
1062
+ "path": "/_twin/access-tokens/{token_id}",
1063
+ "status": 204
1064
+ },
1065
+ {
1066
+ "name": "The old token is refused",
1067
+ "method": "GET",
1068
+ "path": "https://api.polar.sh/v1/meters/",
1069
+ "status": 401,
1070
+ "headers": {
1071
+ "authorization": "Bearer {token}"
1072
+ }
1073
+ },
1074
+ {
1075
+ "name": "She makes a new one",
1076
+ "method": "POST",
1077
+ "path": "/_twin/access-tokens",
1078
+ "status": 201,
1079
+ "body": {
1080
+ "comment": "volter-platform-2"
1081
+ },
1082
+ "capture": {
1083
+ "token": "token",
1084
+ "token_id": "id"
1085
+ }
1086
+ },
1087
+ {
1088
+ "name": "which works",
1089
+ "method": "GET",
1090
+ "path": "https://api.polar.sh/v1/meters/",
1091
+ "status": 200,
1092
+ "headers": {
1093
+ "authorization": "Bearer {token}"
1094
+ },
1095
+ "expect": [
1096
+ {
1097
+ "path": "pagination.total_count",
1098
+ "equals": 2
1099
+ }
1100
+ ]
1101
+ },
1102
+ {
1103
+ "name": "A vendor-backed World refreshes the organization: its webhook endpoints",
1104
+ "method": "GET",
1105
+ "path": "https://api.polar.sh/v1/webhooks/endpoints",
1106
+ "status": 200,
1107
+ "headers": {
1108
+ "authorization": "Bearer {token}"
1109
+ },
1110
+ "expect": [
1111
+ {
1112
+ "path": "items.0.id",
1113
+ "equals": "{endpoint}"
1114
+ },
1115
+ {
1116
+ "path": "items.0.secret",
1117
+ "equals": "{whs}"
1118
+ },
1119
+ {
1120
+ "path": "pagination.total_count",
1121
+ "equals": 1
1122
+ }
1123
+ ]
1124
+ },
1125
+ {
1126
+ "name": "its checkouts, newest first: Heidi's, paid",
1127
+ "method": "GET",
1128
+ "path": "https://api.polar.sh/v1/checkouts/",
1129
+ "status": 200,
1130
+ "headers": {
1131
+ "authorization": "Bearer {token}"
1132
+ },
1133
+ "expect": [
1134
+ {
1135
+ "path": "items.0.id",
1136
+ "equals": "{mco}"
1137
+ },
1138
+ {
1139
+ "path": "items.0.status",
1140
+ "equals": "succeeded"
1141
+ },
1142
+ {
1143
+ "path": "pagination.total_count",
1144
+ "equals": 4
1145
+ }
1146
+ ]
1147
+ },
1148
+ {
1149
+ "name": "its subscriptions, the latest started first: Heidi's, then Acme's Team",
1150
+ "method": "GET",
1151
+ "path": "https://api.polar.sh/v1/subscriptions/",
1152
+ "status": 200,
1153
+ "headers": {
1154
+ "authorization": "Bearer {token}"
1155
+ },
1156
+ "expect": [
1157
+ {
1158
+ "path": "items.0.product_id",
1159
+ "equals": "{patron_month}"
1160
+ },
1161
+ {
1162
+ "path": "items.0.status",
1163
+ "equals": "active"
1164
+ },
1165
+ {
1166
+ "path": "items.1.product_id",
1167
+ "equals": "{team}"
1168
+ },
1169
+ {
1170
+ "path": "pagination.total_count",
1171
+ "equals": 2
1172
+ }
1173
+ ]
1174
+ },
1175
+ {
1176
+ "name": "its customers, newest first: Heidi, Grace, Globex and Acme",
1177
+ "method": "GET",
1178
+ "path": "https://api.polar.sh/v1/customers/",
1179
+ "status": 200,
1180
+ "headers": {
1181
+ "authorization": "Bearer {token}"
1182
+ },
1183
+ "expect": [
1184
+ {
1185
+ "path": "items.0.email",
1186
+ "equals": "heidi@example.org"
1187
+ },
1188
+ {
1189
+ "path": "items.3.id",
1190
+ "equals": "{acme}"
1191
+ },
1192
+ {
1193
+ "path": "pagination.total_count",
1194
+ "equals": 4
1195
+ }
1196
+ ]
1197
+ },
1198
+ {
1199
+ "name": "and its usage events: Acme's World and its two hours, the retried hour not a third",
1200
+ "method": "GET",
1201
+ "path": "https://api.polar.sh/v1/events/",
1202
+ "status": 200,
1203
+ "headers": {
1204
+ "authorization": "Bearer {token}"
1205
+ },
1206
+ "expect": [
1207
+ {
1208
+ "path": "pagination.total_count",
1209
+ "equals": 3
1210
+ },
1211
+ {
1212
+ "path": "items.0.customer.id",
1213
+ "equals": "{acme}"
1214
+ },
1215
+ {
1216
+ "path": "items.0.source",
1217
+ "equals": "user"
1218
+ }
1219
+ ]
1220
+ }
1221
+ ]
1222
+ }