@homie-rocks/studio 0.33.0 → 0.34.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/shop/SELLING.md CHANGED
@@ -1,97 +1,19 @@
1
- # Selling things in your games: what you are taking on
1
+ # Your shop, your choices
2
2
 
3
- Plain words for a studio's owner, from the Homie studio kit. **This is not legal or tax advice.** If you will
4
- sell for real, ask an accountant where you must register for tax, and a lawyer anything this page leaves you
5
- unsure about.
3
+ You sell with your own Stripe account. Money goes directly to you; Homie takes no cut.
4
+ The studio is the seller and is responsible for the law where it sells and for Stripe's terms.
6
5
 
7
- ## The short version
6
+ Your `shop.json` chooses items, wording, prices, currency, tax integration, sale dates, entitlements,
7
+ referral terms, optional spending caps and optional refund windows. Nothing requires an account or age
8
+ question by default. Guests can buy repeatedly and keep their purchases when signing in later.
8
9
 
9
- You sell with **your own Stripe account**. Money goes straight from your players to you. Homie never sees it,
10
- never holds it, and takes no cut. That also means **you are the shop**: you set the prices, you answer refund
11
- requests, you deal with card disputes, and the tax on your sales is yours to handle.
10
+ You can choose `policy.preset: "protective"` or `"adults-only"`, or set individual options.
11
+ Without a preset the shop is open. SHOP.md describes each setting and the game cart API.
12
12
 
13
- ## Who does what
13
+ A cart is one payment with several lines and quantities. Your office can refund a whole order or a line.
14
+ A player can refund items within a window you set; a tip stays given unless you refund it.
15
+ Stripe handles payments, receipts, payment limits, held refunds and disputes. Stripe Tax is optional;
16
+ Managed Payments is another selectable integration. Your office links Stripe's own pages.
14
17
 
15
- | | You (the studio) | Stripe | Homie |
16
- |---|---|---|---|
17
- | Prices, what you sell | yes | | |
18
- | Taking the card, the receipt email | | yes | |
19
- | Refunds | yes: one tap in your office | carries them out | |
20
- | Card disputes (chargebacks) | yes: you answer them in Stripe and carry the loss if you lose | runs the process | |
21
- | Sales tax and VAT | yes, with Stripe Tax's help (or Managed Payments, below) | calculates and collects | |
22
- | Your players' data | yes: you are responsible for it | keeps the card and email | stores nothing |
23
- | Who may buy | your editable shop policy; protective defaults | provider terms | supplies presets |
24
-
25
- ## Refunds
26
-
27
- - A player can refund an **unused** item themselves within the optional refund window you set from their
28
- account page. Set `policy.refundUsedItems` to include used items. Without a window, buyers ask you for a refund.
29
- Fewer people turn to card disputes when a refund is easy.
30
- - Anything else, you refund with **one tap** on your office's shop page. Your AI can only *ask* you to refund; it
31
- can never do it by itself.
32
- - Stripe does not give back its processing fee on a refund. That is the cost of a happy player.
33
-
34
- ## Disputes (chargebacks)
35
-
36
- - A buyer's bank can reverse a payment. Stripe charges a dispute fee (about US$15) whether you win or lose, and
37
- you lose the sale's money if the bank decides for the buyer. On a US$5 item one dispute costs more than the
38
- sale, so consider your costs when setting prices. Homie imposes no price or earnings ceiling.
39
- - **A dispute never deletes or locks the player's account.** While it is open nothing changes. If you lose it,
40
- that one item is taken back, as a refund would.
41
-
42
- ## Tax
43
-
44
- You choose who the seller is when you connect your Stripe (`homie-studio shop connect` shows both):
45
-
46
- - **You are the seller** (`"till": "stripe"`): Stripe Tax is on. It works out and collects tax **only where you
47
- have told Stripe you are registered**, and it does not file your returns. Some places expect a foreign seller
48
- of digital goods to register from the first sale (the EU and the UK do). Cheapest; the most work.
49
- - **Stripe is the seller** (`"till": "stripe-managed"`, Stripe Managed Payments): **3.5% more** a sale. Stripe's
50
- Link, LLC becomes the seller of record and registers, collects, files and pays sales tax and VAT in 80+
51
- countries, runs fraud checks and answers disputes for you. You still carry the money of a lost dispute, and
52
- Stripe may refund a buyer within 60 days. You turn it on in Stripe first (after Stripe's eligibility review).
53
-
54
- ## Kids
55
-
56
- Your shop starts with the `protective` preset in shop.json. It requires an account and a neutral age question,
57
- blocks purchases under 13, uses a parent's checkout for teens, closes shops on kids servers and kids studios,
58
- hides advantages on beginner and kids servers, refuses paid randomness, countdowns and virtual-currency items,
59
- and sends TV shoppers to a phone. Existing shops without a policy keep these protections.
60
-
61
- These are **your settings**: read and edit `policy` in shop.json. `adults-only` also blocks teens;
62
- `custom` enables all policies. You can override individual rules without changing the others. SHOP.md lists
63
- every setting. No spending cap or refund window applies unless you set one; existing written values stay yours.
64
-
65
- As the seller, you are responsible for the law and your payment provider's terms wherever you sell, including
66
- rules protecting children. Choosing a preset does not establish compliance. This is information, not legal advice.
67
-
68
- ## Referrals
69
-
70
- If another studio (or homie.rocks, or a creator) sends you a new player who buys something, you owe them the
71
- referral share you published in `shop.json` (10% if you kept the suggestion; 0 if you turn it off). Your office
72
- shows what you owe and to whom, and signs a monthly statement for each. **They invoice you, and you pay them
73
- yourself.** Nothing moves through Homie.
74
-
75
- ## What you do, and what your AI does
76
-
77
- Your AI sets the shop up with Stripe's own tools for AI (Stripe's agent plugin: its MCP server and skills). It
78
- makes your products in Stripe, checks your tax settings, makes the webhook, and answers "how are sales?" by reading
79
- your Stripe. What stays yours, in Stripe's own pages:
80
-
81
- - making the Stripe account, and later activating it (your business details, bank account and identity checks);
82
- - one sign-in page that lets your AI use Stripe's tools: give it your **sandbox** (test) first;
83
- - one restricted key you make in Stripe and paste into a page on your own computer (never into a chat);
84
- - approving anything Stripe asks you to confirm (a refund your AI asked Stripe for, for example), and every refund
85
- in your office.
86
-
87
- If your AI's app cannot use Stripe's sign-in page, Stripe offers an "Agent" key for it instead. From 31 October 2026
88
- Stripe's tools for AI accept only those (or the sign-in page). The shop's own key is a different, ordinary
89
- restricted key; never make that one an Agent key, or Stripe will hold every refund for a second approval.
90
-
91
- ## Before the first real sale
92
-
93
- 1. Make (or pick) your Stripe account, verify it, and add a bank account.
94
- 2. Test everything in **test mode** first: buy your own item with a Stripe test card, refund it from the office.
95
- 3. Fill in your public business details in Stripe (name, support email, the refund page `/shop/refunds/`).
96
- 4. Talk to an accountant about where you must register for tax, or choose Managed Payments.
97
- 5. Then, and only then, connect a live key (`homie-studio shop connect --live`).
18
+ `homie-studio shop connect` provides a local page for the key and webhook signing secret; both are part of payment setup, credentials stay in Worker secrets.
19
+ The scaffold has no policy, cap or refund window. Test mode is available before choosing live mode.
package/shop/SHOP.md CHANGED
@@ -1,292 +1,165 @@
1
- # The shop: selling in a studio's games with the studio's own Stripe
1
+ # Your shop, your choices
2
2
 
3
- `@homie-rocks/studio` 0.24.0, set up with Stripe's own agent tools since 0.24.3. A studio sells items for its games
4
- through **its own Stripe account**. The studio
5
- is the seller; homie.rocks never holds, routes or settles its money, is not a Stripe Connect platform, and takes
6
- no cut. There is no shared currency between studios. `SELLING.md` beside this file is the owner's plain-words
7
- note on what selling means for them (refunds, disputes, tax, kids; not legal advice).
3
+ You sell through your own Stripe account on your own Cloudflare account. Homie takes no cut.
4
+ The studio is the seller and is responsible for the law where it sells and for Stripe's terms.
8
5
 
9
- ## shop.json
10
-
11
- At the studio's root, reviewed in git. Nothing secret is ever in it.
6
+ A minimal `shop.json`:
12
7
 
13
8
  ```json
14
9
  {
15
- "till": "stripe",
16
- "currency": "usd",
17
- "policy": { "preset": "protective" },
18
10
  "items": [
19
- { "id": "supporter", "kind": "supporter", "name": "Supporter", "price": 500, "days": 365,
20
- "gives": ["badge:supporter", "credits-name"], "badge": "Supporter" },
21
- { "id": "ember-skin", "kind": "cosmetic", "game": "orbit", "name": "Ember hull", "price": 300, "gives": ["skin:ember"] },
22
- { "id": "season-1", "kind": "pass", "name": "Season 1", "price": 800, "ends": "2027-01-31", "gives": ["track:season-1"] },
23
- { "id": "tip", "kind": "tip", "name": "Buy the studio a coffee", "price": "choose", "min": 200, "max": 5000 }
24
- ],
25
- "referrals": { "rate": 0.10, "windowDays": 30, "capPerPlayer": 1000, "holdDays": 30, "minimumInvoice": 2500 }
11
+ { "id": "badge", "name": "Studio badge", "price": 500, "gives": ["badge:studio"] },
12
+ { "id": "gift", "kind": "tip", "name": "A gift", "price": "choose" }
13
+ ]
26
14
  }
27
15
  ```
28
16
 
29
- | Field | Meaning |
30
- |---|---|
31
- | `till` | `stripe`: the studio is the seller, Stripe Tax on (`automatic_tax`). `stripe-managed`: Stripe Managed Payments, Stripe's Link, LLC is the seller of record and files the tax (3.5% more; turn it on in Stripe first). `off`. |
32
- | `currency` | one Stripe presentment currency, including zero-decimal currencies such as `jpy`. |
33
- | `refundDays` | Optional days for self-service refunds of unused items. No default or ceiling; omitted or null means ask the studio. |
34
- | `capPerPlayerMonth` | Optional amount one player may spend per calendar month, in Stripe currency units. No default or ceiling; omit or null for no cap. |
35
- | `items[].kind` | Any nonempty label chosen by the studio. `tip` uses a buyer-chosen amount, with an optional studio `max`. Other kinds use a fixed price; a label does not add recurring billing or fulfillment. |
36
- | `items[].price` | whole Stripe currency units. No Homie maximum. Stripe minimums and currency rules apply. |
37
- | `items[].gives` | the entitlement keys the game reads: `skin:ember`, `badge:supporter`. A `badge:` key shows beside the player's name in rooms and on their account page. |
38
- | `items[].days` / `starts` / `ends` | Optional duration or sale dates, without a Homie duration ceiling. Fractional days are allowed; end times are rounded to whole milliseconds and must fit safe arithmetic and the JavaScript timestamp range. |
39
- | `items[].game` | only in that game's shop (left out: every game's). |
40
- | `items[].advantage` | `true` when it changes how the game plays. The studio policy decides whether it is offered or counted on beginner and kids servers. |
41
- | `items[].taxCode` | a Stripe product tax code. Managed Payments needs one: by default `txcd_10201000` (video games, downloaded, permanent) or `txcd_10201001` (limited time). |
42
- | `referrals` | what this studio pays a referrer for a new player's purchase (left out: nothing). |
43
- | `catalog` | where the items are Products in Stripe: `["test"]`, `["test", "live"]`. `homie-studio shop catalog` writes it once Stripe's catalog matches; the Worker's checkouts then name each item's Product (0.24.3). |
44
-
45
- ## The studio decides
46
-
47
- There is no Homie ceiling on item prices, tips, catalog size, entitlement count, text lengths, durations,
48
- refund days, monthly spending or referral terms. The existing amount fields keep their meaning: a cap or
49
- refund window already written in shop.json stays in effect until the studio edits or removes it.
50
- No migration or rewrite is needed. `shop init` writes an explicit protective policy and no spending cap,
51
- refund window or referral agreement. Settings are public, readable and editable in shop.json.
52
-
53
- The studio is responsible for applicable law and its payment provider's terms wherever it sells. These settings
54
- are choices, not a statement that a sale is legal or permitted by Stripe. This is information, not legal advice.
55
-
56
- ### Policy presets
57
-
58
- `policy.preset` selects `protective`, `adults-only` or `custom`. An absent policy uses `protective`, preserving
59
- the existing protections. Override any row directly in `policy`; `shop init` writes all rows so they are easy
60
- to read and edit. Explicit overrides take precedence when you change presets; remove an override to inherit
61
- the preset value. Invalid values fail validation rather than silently relaxing a rule.
62
-
63
- | Setting | protective (new shop default) | adults-only | custom |
17
+ The default is an open shop: guests can buy, repeatedly, from any page, without an age question.
18
+ Guest purchases belong to their browser session; signing in keeps them. Clearing the guest cookie loses
19
+ access, so adding a passkey is useful. Checkout checks browser cookies and explains how to enable them if blocked. Item kinds and wording are yours; nothing scans them.
20
+ A kind is a label, not a recurring-billing, wallet, random-reward or fulfillment implementation.
21
+
22
+ ## Settings you can choose
23
+
24
+ - `till`: `stripe` (default), `stripe-managed`, or `off`; `currency`: `usd` by default.
25
+ `automaticTax: true` enables Stripe Tax. Managed Payments uses Stripe's eligibility and tax settings.
26
+ - Items: unique `id`, `name`, `price`, optional `kind` (default `item`), `blurb`, `gives`, `badge`,
27
+ `game` (catalog placement), `advantage`, `taxCode`, `starts`, `ends`, and `days`.
28
+ Any item can use `price: "choose"`; a `tip` uses it when price is omitted, or can have a fixed price.
29
+ Variable amounts have optional `min` (zero if omitted) and `max` (unbounded if omitted).
30
+ Dates and durations apply only when written.
31
+ - `capPerPlayerMonth`: optional spending cap. A cart reserves its whole pre-tax total atomically.
32
+ - `checkoutMinutes`: optional session lifetime, 30 to 1440 minutes (values below 31 use 31 to leave a transport margin); Stripe defaults to 1440 (24 hours).
33
+ Returning from checkout or starting another checkout asks Stripe to expire your open session.
34
+ Its reservation releases only when Stripe confirms expiry or another terminal outcome.
35
+ - `refundDays`: optional self-service refund window for items, including used items by default.
36
+ A tip is never refunded by the player. The studio can refund any paid order or line from its office; disputed charges wait for Stripe to resolve the dispute.
37
+ - `referrals`: optional `rate`, `windowDays`, `capPerPlayer`, `holdDays`, `minimumInvoice`,
38
+ `billingEmail` and `accept`. `rate` defaults to zero until written;
39
+ omitted limits do not apply. `referralNewPlayersOnly: true` limits attribution to new players.
40
+ - `catalog`: modes (`test`, `live`) populated by `homie-studio shop catalog` for named Stripe Products.
41
+ Prices always come from your file, even when a Stripe Product exists.
42
+ - Flood protection: `purchaseAttemptsPerMinute` (6 per buyer),
43
+ `purchaseAttemptsPerAddressPerMinute` (600), `guestBuyersPerAddressPerHour` (600).
44
+ These are configurable positive integers, per Worker instance. `PLAYER_LIMIT_DAILY` also applies.
45
+ Referral delivery uses `REFERRAL_STATEMENTS_PER_MINUTE`, `REFERRAL_STATEMENTS_GLOBAL_PER_MINUTE`
46
+ (both 30), and `REFERRAL_STATEMENT_BYTES` (65536). `requestBytes` overrides the catalog-sized shop request
47
+ allowance; `SHOP_WEBHOOK_BYTES` (524288) controls the webhook byte allowance.
48
+
49
+ No toolkit ceiling applies to prices, quantities, tips, durations, refund windows, caps or referral terms.
50
+ Money is integer Stripe currency units, with checked safe arithmetic. Stripe's currency rules and
51
+ [Checkout's maximum of 100 lines](https://docs.stripe.com/api/checkout/sessions/create#line_items) apply.
52
+ Free and paid lines can share a cart. An entirely free cart grants locally without a Stripe call. Product text limits and currency-unit requirements come from Stripe;
53
+ [Stripe decides settlement minimums and payment-method limits](https://docs.stripe.com/currencies).
54
+
55
+ ## Optional policy bundles
56
+
57
+ An omitted `policy` or omitted `preset` uses the open settings (`custom`).
58
+ Write `"policy": { "preset": "protective" }` to choose the earlier account and age behavior.
59
+ Existing files with no preset now use the open default. Explicit settings remain in effect.
60
+ Each field can be overridden independently.
61
+
62
+ | Policy | Open / custom | protective | adults-only |
64
63
  |---|---|---|---|
65
- | `withdrawalAcknowledgement` | true | true | true |
66
- | `childAge`, `adultAge` | 13, 18 | 13, 18 | 13, 18 |
67
- | `requireAccount` | true | true | false |
68
- | `ageQuestion` | true | true | false |
69
- | `children` (under 13) | deny | deny | allow |
70
- | `teens` (13–17) | parent | deny | allow |
71
- | `kidsStudio`, `kidsServer` | false | false | true |
72
- | `kidsAdvantages`, `beginnerAdvantages` | false | false | true |
73
- | `paidRandomRewards`, `countdownOffers`, `virtualCurrency` | false | false | true |
74
- | `supporterAdvantages`, `repeatPurchases`, `refundUsedItems`, `televisionCheckout` | false | false | true |
75
-
76
- `children` and `teens` each accept `deny`, `parent`, or `allow`. The neutral age question stores only a band,
77
- never the birth year. `childAge` and `adultAge` set the band thresholds (13 and 18); the neutral picker spans 120 years. A parent's link lasts a week as a security expiry. Account-free purchases use a guest
78
- session in this browser; clearing its cookie loses access, so buyers can add a passkey to keep their purchases.
79
- The protective preset hides the shop on kids servers and kids studios, blocks purchases under 13, requires
80
- parent checkout for teens, hides game advantages on beginner and kids servers, refuses paid randomness and
81
- countdowns, and sends TV shoppers to a phone. Nothing relaxes those protections without a studio setting.
82
-
83
- For example, `{ "policy": { "preset": "protective", "repeatPurchases": true } }` changes only repeat purchases.
84
- The custom preset deliberately enables all policies; choose it only as a considered studio decision.
85
- `kind` is descriptive, not a whitelist: services and subscription labels still use the existing one-time checkout.
86
- Virtual-currency keys and random-reward metadata do not implement a wallet or random-reward fulfillment engine.
87
-
88
- ### Currency and abuse protection
64
+ | `requireAccount`, `ageQuestion` | false | true | true |
65
+ | `children` | allow | deny | deny |
66
+ | `teens` | allow | parent | deny |
67
+ | `kidsStudio`, `kidsServer` | true | false | false |
68
+ | `kidsAdvantages`, `beginnerAdvantages` | true | false | false |
69
+ | `repeatPurchases`, `refundUsedItems`, `televisionCheckout` | true | false | false |
70
+ | `withdrawalAcknowledgement` | false | true | true |
71
+ | `childAge`, `adultAge` | 13, 18 (inactive without age policy) | 13, 18 | 13, 18 |
89
72
 
90
- Amounts use Stripe's API units: `500` USD is $5; `500` JPY is ¥500. ISK and UGX retain Stripe's two-decimal API
91
- representation but require multiples of 100. BHD, JOD, KWD, OMR and TND use scale 1000. Stripe's current currency documentation does not state a divisible-by-ten charge rule; the toolkit does not refuse those amounts and shows Stripe's error if Stripe refuses a checkout. The check warns about the documented settlement-currency minimum where
92
- known; Stripe decides the actual minimum after conversion and any payment-method maximum at checkout.
93
- Errors name Stripe as the source. See [Stripe currencies](https://docs.stripe.com/currencies).
94
- Zero-price Checkout orders are supported and can be revoked without a Stripe refund. Every amount setting accepts only nonnegative whole minor units (numbers or decimal numeric strings); a fractional amount stops the build, names the setting and says what to write. No amount is rounded. All amounts must fit JavaScript safe integer arithmetic. Homie does not impose its own price ceiling or fee floor.
73
+ Age-band choices are `allow`, `deny`, or `parent`. The question retains a band, not a birth year.
74
+ Parent links expire after a week for security. The TV option can show a phone code instead of checkout.
75
+ Content scans do not exist in any preset.
95
76
 
96
- `purchaseAttemptsPerMinute` defaults to 6 per account and `purchaseAttemptsPerAddressPerMinute` to 600 per
97
- address. Both accept positive whole numbers (including decimal numeric strings). The larger address rate
98
- allows households, schools and venues to share a connection; rotating addresses still hits the account rate.
99
- `guestBuyersPerAddressPerHour` defaults to 600 new guest buyers per address per hour, allowing a venue or school to arrive together. It must be a positive whole number; 0 cannot disable it accidentally. The existing `PLAYER_LIMIT_DAILY` also applies.
100
- Checkout, parent links and parent payments have separate counts, per Worker instance. Referral statements use
101
- `REFERRAL_STATEMENTS_PER_MINUTE` (30 per address), `REFERRAL_STATEMENTS_GLOBAL_PER_MINUTE` (30 globally), and
102
- `REFERRAL_STATEMENT_BYTES` (65536). Written values must be positive whole decimal numbers; invalid values produce a named settings error. Security
103
- checks for origins, sessions, signed webhooks and request sizes remain. They protect the studio's money.
104
-
105
- ## In a game
77
+ ## Game API and carts
106
78
 
107
79
  ```ts
108
80
  import { createShop } from '@homie-rocks/studio/shop';
109
81
  const shop = createShop();
110
- await shop.ready; // { open, kids, screen }
111
- if (shop.has('skin:ember')) useEmber(); // synchronous: fine in a render loop
112
- shop.on('change', (owns) => redraw()); // a purchase landed, or a refund took one back
113
- buyButton.onclick = () => shop.open('ember-skin'); // the store sheet (a code to scan on a TV)
114
- shop.used('skin:ember'); // marks it used; the studio refund policy applies
82
+ await shop.ready;
83
+ shop.has('badge:studio');
84
+ shop.on('change', () => redraw());
85
+ shop.open(); // show the shop
86
+ shop.add('badge', 2); // quantity
87
+ shop.add('gift', 1, 500); // chosen tip amount in currency units
88
+ await shop.checkout(); // one Stripe Checkout Session
89
+ await shop.buy('badge'); // direct purchase stays one call
90
+ shop.cart(); shop.clear();
91
+ shop.used('badge:studio'); // used-item policy applies only if chosen
115
92
  ```
116
93
 
117
- The protective preset recommends opening the shop only from a button the player pressed, never from the play, start or wake
118
- button, never on a timer; show prices in real money; never draw a countdown; never sell randomness; do nothing
119
- differently for a supporter on a beginner server.
120
-
121
- The play shell around the game answers these calls (the game never sees a card, an email, an order or a
122
- session). The room button's sheet has a **Shop** button too. A purchase opens Stripe's page in a new tab, the game
123
- keeps running, and the shell looks again when the tab comes back: `change` fires once it is paid.
124
-
125
- **Badges in rooms.** A player whose account owns a `badge:` key carries it on their seat: `net.players()` gives
126
- `peer.badge` ("Supporter"), set by the studio's Worker from what the account owns when the player connects (a
127
- hello can never claim one). Kids-server badges follow `policy.kidsServer`; AI seats have no purchases.
128
-
129
- ## The money path
130
-
131
- ```
132
- phone (signed in) ─ POST /api/shop/buy ─► studio Worker ─ Checkout Session (the studio's restricted key) ─► Stripe
133
- ◄──────────────── Stripe's hosted page (new tab): the player pays ─────────────────────────────────┘
134
- Stripe ─ signed webhook ─► POST /api/shop/hook: order paid → entitlements → referral line (held)
135
- homie.rocks: not involved at any step.
136
- ```
137
-
138
- - The order row atomically reserves against the studio monthly cap before Stripe is called. Reservations are
139
- released on confirmed expiry, failure, missing session or refund, or an explicit owner release, never merely because a webhook is late; the Checkout Session carries the order, the player and the
140
- item in its metadata; the hook checks the session matches the order (player, price) before it grants anything.
141
- - A sessionless reservation stops counting after its 31-minute creation window plus a one-minute margin.
142
- A session with no final result, including a bank payment still processing, keeps counting even past expiry.
143
- Shop GETs never call Stripe. With a cap configured, only a purchase refused by the atomic cap check waits
144
- for reconciliation: up to three rows in parallel, each with a two-second timeout. Successful purchases continue
145
- reconciliation in `waitUntil` (the shop has no scheduled handler). Rows are atomically claimed before reading
146
- [Stripe's session and expanded payment](https://docs.stripe.com/api/checkout/sessions/retrieve).
147
- Successful reads set a per-row interval of one minute (five minutes for processing bank payments),
148
- growing with age to one hour. Failed reads retry after a short backoff of about one minute and log the order and reason. With more than three lost rows, a refusal may need another purchase attempt.
149
- Paid counts in the month of [the Stripe charge's creation time](https://docs.stripe.com/api/charges/object#charge_object-created);
150
- missing payment time, pending or unreachable keeps counting. Confirmed expiry or failure releases it.
151
- Stripe's [404 resource_missing](https://docs.stripe.com/error-codes#resource-missing) means this key's account
152
- has no such session, not that no payment can follow: the row stops reserving the cap and is shown as `missing` in the office.
153
- The office lists unresolved orders first, with Next orders for more. Release reservation records the confirming
154
- owner's account (or a fingerprint of the owner's office sign-in session) and time in the office-only note, and asks Stripe to
155
- [expire an open Checkout Session](https://docs.stripe.com/api/checkout/sessions/expire). It does not refund a payment.
156
- A verified payment still grants the item and counts toward spending, including after a missing mark or release,
157
- and can be refunded from the office. Duplicate events change nothing. Players see their previous unfinished
158
- order state, never the internal mark or note.
159
- An owner release or missing mark can free space for another purchase before a late payment counts again;
160
- new reservations still obey the configured cap. Test orders never count toward a live
161
- cap. Free orders remain available if an owner lowers a cap below prior spending. Partial refunds keep the whole
162
- price counted; full refunds and lost disputes release it. No spending SUM runs when the cap is absent.
163
- Deploy applies `0010_shop_reservations.sql` and `0011_shop_statements.sql`; readiness names these migrations
164
- if the required schema is absent.
165
- - The webhook's `Stripe-Signature` is checked (HMAC-SHA256 of `<t>.<payload>` with the endpoint's `whsec_`
166
- secret, within five minutes); each event id is handled once; a test event never touches a live shop.
167
- - Paid: the entitlements are granted. Refunded: that one order's entitlements are revoked. A dispute: nothing
168
- changes until it is decided; lost: that one item goes, as a refund would; won: it stays. **The account is never
169
- locked or deleted over a dispute.**
170
- - Receipts come from Stripe by email (the buyer types it on Stripe's page, never on the studio's). The account
171
- page lists the player's orders and badges; `/api/player/export` includes them; deleting an account asks first
172
- when it owns things, and keeps the order rows without the player.
173
- - Each Checkout Session says beside the pay button that the item is delivered at once (the EU and UK withdrawal
174
- acknowledgement) and the studio's configured refund terms. `policy.withdrawalAcknowledgement` is true in every preset, restoring the existing wording, and is editable. Stripe caps product names at 5000 characters, descriptions at 40000 and submit wording at 1200; the build reports these provider constraints without truncation.
175
-
176
- ## Setting it up (the AI does the commands; the owner only uses Stripe's pages and one page here)
177
-
178
- The AI works with **Stripe's own agent tools** (0.24.3): Stripe's agent plugin (`npm install -g @stripe/cli@latest &&
179
- stripe agent setup`: Stripe's MCP server at `https://mcp.stripe.com` and Stripe's skills, for Claude Code and Codex) or
180
- Stripe's connector in the Claude app. The owner signs it in once on Stripe's consent page and gives it **a sandbox
181
- first**. An agent that cannot do OAuth uses an **Agent key** (a restricted key Stripe tags "Agent") from its
182
- environment; from **2026-10-31** Stripe's MCP refuses full secret keys and restricted keys without that tag. The shop's
183
- own key, in the Worker, is a plain restricted key and is not affected.
184
-
185
- What only the owner does: make the Stripe account and (for live) activate it: business details, bank, identity, and,
186
- for Managed Payments, its terms and Stripe's eligibility review; approve Stripe's sign-in page; make **one restricted
187
- key** in Stripe's Dashboard (Stripe has no API that makes API keys) and paste it on the connect page; approve any
188
- write Stripe sends to them for confirmation (a refund through Stripe's MCP, for one).
189
-
190
- 1. `npx --no-install homie-studio shop init --supporter` (shop.json and SELLING.md), then `shop check`, commit,
191
- `npm run deploy` (it applies migration `0008_studio_shop.sql`; the shop stays closed).
192
- 2. **The catalog**, through Stripe's MCP: `homie-studio shop catalog` names the one read to make
193
- (`stripe_api_read GET /v1/products`, expanding each default price); `homie-studio shop catalog --have <the saved
194
- answer>` lists exactly the `stripe_api_write` calls still needed: each item a Product with the id
195
- `homie_<studio slug>_<item id>` (the same in a sandbox and live), its name, blurb, a tax code (video games,
196
- downloaded: `txcd_10201000`, or `txcd_10201001` for an item that lasts days or ends on a date; eligible for
197
- Managed Payments) and metadata naming the studio and the item, with a default Price of shop.json's amount (a tip:
198
- the Product only). A changed price is a new Price made the default; an item gone from shop.json is archived, never
199
- deleted; a product the studio did not make is never named. When Stripe matches, it says so and writes
200
- `"catalog": ["test"]` into shop.json (commit, deploy). shop.json's checked price is always what is charged; the
201
- checkout names the Product so Stripe's Dashboard, reports and MCP see sales by product, and a Product missing in
202
- this mode falls back to the item described inline.
203
- 3. **Tax**, read through Stripe's MCP: `GET /v1/tax/settings` (with the studio as the seller, Stripe Tax needs
204
- `status: active`, the business address, or checkouts fail) and `GET /v1/tax/registrations` (none: Stripe Tax
205
- collects nothing anywhere until the owner registers somewhere). Reading only; the owner and their accountant decide.
206
- 4. `npx --no-install homie-studio shop connect`: a page on the owner's computer (127.0.0.1, one use, ten minutes). The
207
- owner makes one restricted key with **Checkout Sessions: Write, Charges: Write (refunds), PaymentIntents: Read,
208
- Disputes: Read, Webhook Endpoints: Write**, not an Agent key, and pastes it. With it the page reads once (a typo is
209
- caught here), **makes the webhook** at `https://<site>/api/shop/hook` for `checkout.session.completed`,
210
- `checkout.session.async_payment_succeeded`, `checkout.session.async_payment_failed`, `checkout.session.expired`,
211
- `charge.refunded`, `refund.created`, `refund.updated`, `charge.dispute.created`, `charge.dispute.closed` (API version
212
- `2025-03-31.basil`, metadata `homie: shop-v1`), and puts the key and the webhook's signing secret, which Stripe hands
213
- back once, straight into the Worker secrets `STRIPE_KEY` and `STRIPE_WEBHOOK_SECRET` on Wrangler's standard input.
214
- An older endpoint the kit made for the same address is turned off, never deleted. The page offers the seller
215
- choice (the studio, or Stripe Managed Payments, with what each costs and does) and, in test mode with Managed
216
- Payments, tries one test checkout with it (expired at once) and says whether Stripe took it. Afterwards the owner
217
- may set the key's Webhook Endpoints back to None (recommended; the shop never needs it again). A key without
218
- Webhook Endpoints: the page takes a webhook signing secret the owner made and revealed in Stripe instead. Test keys
219
- only, unless `--live`.
220
- 5. `homie-studio shop` says whether anything is still missing. Buy your own item with a Stripe test card, and
221
- refund it from `/_studio/office/shop`. Through Stripe's MCP, `GET /v1/webhook_endpoints` shows the endpoint the
222
- page made (its `we_…` id): if it is not there, the MCP is signed in to another account or sandbox than the key.
223
- 6. **Live**, only when the owner says so: they activate the account, give Stripe's MCP access to the live account,
224
- `shop catalog --mode live` makes the live catalog the same way, and `shop connect --live` takes a live key.
225
-
226
- Never ask for a key in a chat, never put one in a file, an argument, a log or a commit. **Never make a webhook endpoint
227
- or event destination through Stripe's MCP**: its answer carries the signing secret into the conversation (in Claude
228
- Code the Homie mod refuses the call). Never use a Stripe CLI login or a key found on the computer.
229
-
230
- ## The office
231
-
232
- `/_studio/office/shop` (the owner's browser) and `/_studio/api/shop` (an office key): open or what is missing,
233
- the last 30 days, every order with its Stripe page, Refund (one tap), disputes, payouts, balance and tax as links
234
- to the studio's own Stripe Dashboard, a CSV for the accountant, and the referral books. From an office key a
235
- refund (`POST /_studio/api/shop/refund`, `homie-studio shop refund <order>`) is always an ASK the owner confirms
236
- with one tap; so is marking a referrer paid.
237
-
238
- A refund made anywhere else (in Stripe's Dashboard, or through Stripe's MCP after the owner approved Stripe's own
239
- confirmation link) reaches the shop as `refund.created` / `charge.refunded`, and the item leaves the player's account
240
- the same way. If the Worker's key is an Agent key, Stripe holds the Worker's refunds for a person's approval
241
- (`approval_required`): the office answers 202 "held", nothing changes until someone approves it in Stripe (Settings,
242
- Approvals), and the webhook finishes it. Reconnect with a plain restricted key to get the one tap back.
243
-
244
- "How are sales?": `homie-studio shop` and `shop orders` (the studio's own books, no names), and through Stripe's MCP,
245
- read-only, `stripe_analytics` or `stripe_api_read` on the balance, payouts and checkout sessions.
246
-
247
- ## Referral pages
248
-
249
- `/api/shop?cursor=...` serves 100 items per page and returns `nextCursor`. The shop page and in-game sheet offer the next page.
250
- The office's `/_studio/api/shop?cursor=...` pages by referrer, with exact SQL totals and sale counts shown once per referrer and currency.
251
- `/_studio/api/shop/lines?via=...&currency=...&cursor=...` pages the sales inside that book without repeating its totals.
252
- Statement pages use `/_studio/api/shop/statements?period=YYYY-MM&cursor=...`; `shop statements --cursor ...` reads the next page.
253
- One signed statement per referrer and currency has its totals on the first page only; continuation pages carry lines. A new first page replaces all previously received pages for that period and currency.
254
- Read-only statement views read the current lines without making stored editions.
255
- `shop statements --send` and the office's Send statements button save their progress and immutable lines in D1.
256
- Keep the command or tab open to continue; close midway and start again to resume safely, without doubles or skipped
257
- pages. After completion, starting again sends a fresh edition safely. Failed pages retry three times with exponential
258
- backoff (at least a minute for rate refusals). Persistent failures name the referrer, stop its remaining pages and
259
- continue the rest; starting a fresh run retries those referrers. The CLI reuses one office key, renewing its expiry
260
- near ten minutes. Mark paid applies only to the selected currency and cannot change the edition being sent.
261
- Received signed pages are available at `/_studio/api/shop/received?seller=...&period=...&cursor=...`. No row is hidden by a row cap.
262
-
263
-
264
-
265
- ## Referrals
266
-
267
- The studio sets `rate` (a nonnegative share, including values over 100%), `windowDays`, `capPerPlayer`,
268
- `holdDays`, `minimumInvoice` and accepted invoice methods. No ceilings or forced relation between hold and
269
- refund days. Omitted window and per-player cap impose neither; omitted hold means no hold and omitted minimum
270
- invoice imposes no minimum. `rate` keeps the existing 10% suggestion when referrals are explicitly enabled.
271
- An absent `referrals` pays nothing. Published invoice terms are settled by the studio, outside Homie.
272
-
273
-
274
- - A link with `?via=<host>` (another studio, homie.rocks, anywhere) opened by a person whose browser has never
275
- been to this studio leaves a signed cookie with the host and the time (only on a studio that pays referrals;
276
- never a prefetch, a crawler, this site, or a server's page or room).
277
- - A paid order by an account made after that arrival, within `windowDays`, writes a referral line in THIS
278
- studio's D1, if the referrer's own `/.well-known/homie-studio.json` says it takes statements. Held for
279
- `holdDays`; a refund or a lost dispute inside the hold voids it, after it a clawback nets off the next statement.
280
- - `homie-studio shop statements [--send]` signs each referrer's statement with the studio's Ed25519 key (the
281
- public half is in the manifest's `referrals.key`) and POSTs it to the referrer's `/api/referrals/statement`,
282
- which checks the signature against the seller's own manifest. No player, name or email is in a statement.
283
- - Paying stays outside Homie: the referrer invoices the seller from its own Stripe; the seller pays and marks it
284
- paid in the office.
285
-
286
- ## Testing without money
287
-
288
- The kit's tests run the Worker against `stripe-mock` (Stripe's open-source API mock, which checks every parameter
289
- against Stripe's own API description) and sign webhooks with a test secret. stripe-mock also takes every write
290
- `shop catalog` lists, a checkout that names its Product, and the webhook the connect page makes. Stripe's MCP is
291
- never called by the tests: `shop catalog --have` reads an answer in the shapes Stripe's API documents. `STRIPE_API_BASE` (a loopback address
292
- only) points a Worker at such a mock; it is never honoured for anything but 127.0.0.1 or localhost.
94
+ The studio chooses where to place or open its shop. The shell verifies game messages and the Worker verifies
95
+ sessions, ownership, origins and payments. `GET /api/player/owns` includes quantity in each detail row.
96
+ The shop page has quantities, Add to cart, Remove and Checkout cart. The game API opens checkout in another tab, keeping the game and room running. If another tab cannot open, checkout uses the current tab and returns to the game or TV page after payment or cancellation. The cart stays in memory and is lost when its game page closes.
97
+
98
+ `POST /api/shop/buy` accepts `{ "lines": [{ "item": "badge", "quantity": 2 }] }` or
99
+ `{ "item": "badge" }`. Tip lines also take `amount`. Client-supplied fixed prices are ignored.
100
+ One order stores every line and its item snapshot. `/api/shop/mine` lists the lines;
101
+ `POST /api/shop/refund` takes `order` and optionally `line`. The office offers both.
102
+
103
+ ## Connecting and operating
104
+
105
+ `homie-studio shop init --supporter` writes an example item and `SELLING.md`, without a policy, cap or refund
106
+ window. `shop check` validates settings; the build uses the same validation.
107
+ `shop connect` opens a local page for the studio's Stripe key and webhook secret; `--live` selects live mode.
108
+ Both restricted (`rk_`) and full secret (`sk_`) keys are accepted. A restricted key is useful: Checkout Sessions Write, Charges Write, PaymentIntents Read, Disputes Read;
109
+ Webhook Endpoints Write lets the connect page create the webhook. The key and webhook signing secret are both
110
+ part of working payment setup; without either, readiness names the missing step and opens no checkout.
111
+ [Stripe fulfillment](https://docs.stripe.com/checkout/fulfillment) uses webhooks as well as the redirect.
112
+ Credentials stay in Worker secrets. A preview normally has no database and cannot sell. A preview that you
113
+ configure with a database, key and signing secret sells into that database.
114
+ Stripe's agent tools can populate Products with `shop catalog` and read sales; secret-returning endpoint
115
+ creation belongs on the connect page. Stripe approval links are handled by the owner.
116
+
117
+ `shop`, `shop orders`, and `/_studio/office/shop` show readiness and sales. Office refunds are owner actions;
118
+ `shop refund <order>` asks the owner to confirm. Stripe controls pending or held refunds.
119
+ The office links Stripe's disputes and provides CSV exports. A dispute does not lock a player's account;
120
+ a lost dispute revokes the order's entitlements. Referral statements record what the studio owes;
121
+ the studio pays referrers directly and marks payment in its office.
122
+
123
+ ## Payment guarantees and schema
124
+
125
+ The schema step `0012_shop_lines.sql` follows the reservation and statement steps. Deploy applies it through
126
+ the existing migration mechanism. It adds tables and columns without changing any existing primary key or
127
+ column meaning. Released code keeps working after the step, including through rollback. The new Worker
128
+ without the step still reads owned items and refunds from the office; new sales wait for the named step.
129
+
130
+ A verified payment atomically grants all line snapshots and records the referral share on the order total.
131
+ Duplicate events, reconciliation and late payments grant exactly once, including after a missing mark,
132
+ owner release, expiry or failure. Refunds revoke only their own lines, retain other purchases of the same key,
133
+ and adjust the referral share with exact integer allocation. Stripe tax is read per line when applicable.
134
+ Snapshots preserve delivery after catalog edits; the schema step represents older orders as one line.
135
+ An unpaid older order has no historical item snapshot and needs its catalog definition to complete delivery.
136
+ A missing definition gives a named retryable response and office note; the office can refund the verified
137
+ payment. Older paid orders missing a grant are repaired once. An unknown older item is refunded by the office,
138
+ since its original kind cannot be recovered safely for player self-service.
139
+ A whole-order refund uses one Stripe refund for the remaining charge. Dashboard partial refunds reduce cap
140
+ usage and referral shares by their pre-tax portion, without assigning money to an unnamed line.
141
+ Refunds are rebuilt from [Stripe’s refunds list](https://docs.stripe.com/api/refunds/list). Only
142
+ [succeeded refunds](https://docs.stripe.com/api/refunds/object#refund_object-status) revoke items;
143
+ pending refunds keep them and failed refunds restore them. A named line loses only its own items;
144
+ an untagged Dashboard refund revokes items only when the whole order is refunded.
145
+ Unneeded signed events are acknowledged without recording. A refund or dispute naming an unrecorded payment
146
+ of this database recovers its Checkout Session first, regardless of event age.
147
+
148
+ Sessions reserve the optional cap until Stripe confirms an outcome. With the default lifetime, a sessionless
149
+ reservation ages out after 24 hours plus a one-minute margin; your `checkoutMinutes` changes that window.
150
+ [Stripe session lifetime](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-expires_at)
151
+ and [expiring open sessions](https://docs.stripe.com/api/checkout/sessions/expire) define these provider bounds.
152
+ Replacing an open checkout expires only reservations at least a minute old; a named cancellation can expire immediately.
153
+ Unresolved orders become eligible for reconciliation after one minute, even with no cap. A buyer's next shop
154
+ or owned-items request schedules up to three reads off the response path, with atomic claims and backoff up
155
+ to an hour; an optional Worker cron invokes the same reconciliation across buyers. No cron is installed for you. To run it every five minutes, add `"triggers": { "crons": ["*/5 * * * *"] }` to the studio’s root `wrangler.jsonc`; deploy preserves its triggers.
156
+ Every recorded payment checks Stripe’s refunds and dispute state, including payments recovered by an early refund or dispute event. The thanks page can verify a payment directly with the studio's key. Webhook events require Stripe's signature.
157
+ Test and live books stay separate. Owner release attempts to expire an open session, records who released it
158
+ and when, and never prevents a later verified payment grant.
159
+
160
+ Test mode uses Stripe's test cards. Test a cart, its grants and individual refunds before choosing live mode.
161
+
162
+ Game and TV checkout use the same tab if a new tab is unavailable, then return to the page the buyer left.
163
+ During rollback, the released Worker grants only a cart’s first item until the new Worker repairs it; it
164
+ allows whole-cart player refunds even when a tip is included, and counts partially refunded carts at
165
+ the full amount toward a cap. The additive schema preserves its original rows and entitlement keys.