cope-sdk 0.0.1-reserved.0 → 0.1.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,157 @@
1
+ # Changelog
2
+
3
+ All notable changes to `cope-sdk` are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ `cope-sdk` is the new-architecture copy of `@copecart/sdk` 0.3.0 (`svcs/cart-sdk`, whose changelog holds the history
9
+ before the copy). `@copecart/sdk` is unchanged and keeps serving the pages that already load it.
10
+
11
+ ## [0.1.1] — 2026-10-05
12
+
13
+ 0.2.0 is reserved for the release that reads only the consistent integrator fields (plan 72 S12). A `^0.1.0` range
14
+ takes 0.1.1 on its next install, so everything a 0.1.0 caller can notice is listed under **Behaviour changes**.
15
+
16
+ ### Behaviour changes
17
+
18
+ Measured against the published 0.1.0 (`cope-sdk@0.1.0` on npm, published from `4b0bd1abad`), not against
19
+ `@copecart/sdk`. Retries, `baseUrl` validation and every request path are unchanged from 0.1.0: 0.1.0 already
20
+ retried only reads and `createPrefilledCheckout`, and already refused a `baseUrl` that is not a bare origin (a query,
21
+ a fragment, a path, or a doubled trailing slash, whose path is `//`).
22
+
23
+ - **`CartLine.id` is typed `number`.** The API always sent a number, so nothing changes at runtime, but code that
24
+ stores a line id in a `string`-typed variable stops compiling. `updateLine` and `removeLine` still accept a string.
25
+ - **An asctime `Retry-After` (`Sun Nov 6 08:49:37 1994`) is read as GMT**, where 0.1.0 read it in the host's time
26
+ zone, so `CopeApiError.retryAfter` changes by the host's UTC offset. A value that is neither delay-seconds nor an
27
+ HTTP-date (an ISO 8601 string, say) is now `null`, where 0.1.0 passed it to `Date.parse`.
28
+ - **The insecure-page error reads `COPE SDK requires HTTPS...`** (was `CopeCart SDK requires HTTPS...`). Code that
29
+ matches the message text needs the new wording.
30
+ - **`createPrefilledCheckout` returns `fieldErrors`** when the API attaches any, and sends
31
+ `collect_buyer_identity_at_checkout` when `collectBuyerIdentityAtCheckout` is set. Both are absent otherwise.
32
+ - **The MCP endpoint (`cope-sdk/mcp`):** `update_line` and `remove_line` take the number `get_cart` returns (0.1.0
33
+ refused it); `get_cart` returns the lines with `totalsPending` instead of failing when the cart has no buyer
34
+ location; `start_checkout` returns `buyerNext`; the handshake reads its version from `package.json` (it was hard-coded).
35
+
36
+ ### Changed
37
+
38
+ - **The class is `Cope`**, after the brand: `import { Cope } from 'cope-sdk'` and `new Cope({ publishableKey })`. Its
39
+ options type is `CopeOptions`.
40
+ - **The CDN build defines the global `Cope`**, the class itself, so a script tag reads `new Cope({...})`. The global
41
+ `CopeCart` namespace is unchanged and still carries every export.
42
+ - The insecure-page error reads `COPE SDK requires HTTPS...` (was `CopeCart SDK requires HTTPS...`).
43
+ - `CheckoutPayload.consents` is optional: creating a checkout does not require them. The hosted checkout page requires
44
+ `buyer_tos` when the buyer starts paying.
45
+
46
+ ### Added
47
+
48
+ - `UpdateBuyerIdentityPayload` takes `phone` and `locale`, which the API already accepted. A malformed phone is dropped,
49
+ not refused, and the cart carries `field_errors`.
50
+ - `createPrefilledCheckout` takes `collectBuyerIdentityAtCheckout` (hosted checkout only) and returns `fieldErrors`
51
+ when the API attaches any.
52
+ - The MCP `start_checkout` result carries `buyerNext`: the buyer accepts the terms, and for a digital product waives
53
+ the right of withdrawal, on the checkout page; an agent cannot do either. `ApiErrorCode` gains
54
+ `missing_withdrawal_waiver`, and `CheckoutPayload.consents` and the README say a digital product needs it.
55
+ - `ApiErrorCode` declares every code the endpoints this SDK calls can answer — 21 were missing, among them
56
+ `invalid_sdk_key`, `price_changed`, `mixed_seller_cart` and `idempotency_conflict` — and the README table lists them.
57
+
58
+ ### Fixed
59
+
60
+ - **A cart line's `id` is a number.** The API renders it as an integer and `CartLine.id` was typed `string`, so the
61
+ MCP tools refused the line id they had just returned (`expected string, received number`): `update_line` and
62
+ `remove_line` could not act on any line. `CartLine.id` is `number`; `updateLine`/`removeLine` take a number (a
63
+ string of its digits is still accepted), and the MCP tools return it as a number and take a number or a string of
64
+ its digits.
65
+ - **The MCP `get_cart` failed on a cart with no buyer location** (`missing_tax_location`), so an agent could not show
66
+ the cart without first asking for the buyer's country and postal code. It now returns the lines with
67
+ `totalsPending`.
68
+ - **An asctime `Retry-After` was read in the host's time zone.** `CopeApiError.retryAfter` now reads it as GMT, as
69
+ RFC 9110 defines it, and ignores a value that is neither delay-seconds nor an HTTP-date.
70
+ - The MCP endpoint's handshake reports the package's version (it said `0.1.0`), and tells an agent to set the buyer's
71
+ country and postal code on `missing_tax_location`.
72
+
73
+ ### Removed
74
+
75
+ - `missing_code` from `ApiErrorCode`: no endpoint answers it (an empty promo code is `not_found`).
76
+
77
+ ### Deprecated
78
+
79
+ - **`CopeCart` and `CopeCartOptions`**: aliases of `Cope` and `CopeOptions`, kept so code written against 0.1.0 —
80
+ `new CopeCart({...})`, and `new CopeCart.CopeCart({...})` on a script tag — runs unchanged. `CopeCart` is the same
81
+ class, so `instanceof` agrees across both names. `CopeCartExpiredError` is not deprecated: it names an expired
82
+ cart.
83
+
84
+ ## [0.1.0] — 2026-09-28
85
+
86
+ ### Added
87
+
88
+ - **`cope-sdk/mcp`**: an MCP endpoint a store mounts on its own server so a buyer's AI agent can look up products,
89
+ build a cart and get a checkout URL (`createCopeMcpHandler`, `generateCartRefKey`; `createCopeMcpNodeHandler` from
90
+ `cope-sdk/mcp/node`). Server-only; needs `@modelcontextprotocol/server` and `zod` as peers, plus
91
+ `@modelcontextprotocol/node` for the Node entry. The browser build does not include it.
92
+ - **Ids in request paths are URL-encoded**, so an id such as `../promo_code` cannot aim a request carrying the cart
93
+ secret at another route.
94
+ - **`persistence: 'none'`** keeps the cart only in memory, for a server that serves many buyers from one process; the
95
+ default stays `'localStorage'`; a server needs one client per buyer. **`cart: { id, secret }`** acts on a cart whose
96
+ credentials the caller holds, and never touches stored carts.
97
+ - **`checkout(cartId, payload, { idempotencyKey })`**: a repeated call with the same key and payload returns the
98
+ checkout already created instead of `cart_not_active` (409 `idempotency_conflict` while the first call is still
99
+ in flight).
100
+ - **`CopeApiError.retryAfter`**: the server's `Retry-After`, in seconds, or null.
101
+
102
+ ### Changed
103
+
104
+ - **Published as `cope-sdk`**, not `@copecart/sdk`.
105
+ - **MIT-licensed** (`@copecart/sdk` was `UNLICENSED`), so merchants may use, modify and redistribute it in their
106
+ own sites.
107
+ - **Default `baseUrl` is `https://app.cope.com`.** Requests go to `https://app.cope.com/api/cart/v1/...`, the checkout
108
+ block cope-edge serves; the old default `https://app.cope.com/gateway/cart_api` pointed at the old estate's gateway,
109
+ which the new estate does not have. The hosted checkout origin (the `checkoutBaseUrl` default) stays `app.cope.com`.
110
+ - **`baseUrl` must be an origin.** A value with a path, such as `@copecart/sdk`'s documented
111
+ `https://app.cope.com/gateway/cart_api`, throws `CopeError('Invalid baseUrl...')` instead of 404ing on every call.
112
+ - **The cart is stored under `cope_sdk_cart`**, not `cope_cart`, so `cope-sdk` never reads or clears a cart
113
+ `@copecart/sdk` stored on the same page.
114
+
115
+ ### Fixed
116
+
117
+ - **`cope-sdk/mcp` failed every `create_cart` and `get_cart` against the real cart API** (staging, 2026-09-26). cope-core
118
+ answers `POST /carts` without `lines` or `buyer_identity`, and reprice and the promo-code routes without
119
+ `buyer_identity`; the MCP projection read both unconditionally and answered `internal_error`. `createCart` now
120
+ returns `lines: []` and `buyer_identity: null` (a new cart has neither), `Cart.buyer_identity` is optional (absent
121
+ where the response does not report it), and the MCP result leaves `buyer` out rather than claiming none.
122
+ - **`Cart.promo_code` never existed.** cope-core sends `applied_promo_code`, an object with the code and the discount;
123
+ the type now says so, and the MCP result's `promoCode` reads it.
124
+ - **`get_product` quoted plan prices 100 times too low.** The cart API's plan amounts are display strings in major
125
+ units despite their `_cents` names (`"999.00"`); the MCP projection read them as minor units. It converts them now,
126
+ so `firstPaymentMinorUnits` matches what the cart charges.
127
+ - **A refused promo code was never recognised.** The cart API answers 422 with the validator's reason (`not_found`,
128
+ `expired`, `exhausted`, ...), never `invalid_promo_code`; `ApiErrorCode` lists the real reasons, and
129
+ `apply_promo_code` answers `promo_code_not_accepted` with the API's code as `reason`.
130
+ - The MCP tests' cart API fake had the same four shapes wrong, which is why they passed; it now follows
131
+ `CartBlueprint` and `PaymentPlanBlueprint`.
132
+
133
+ - **Promo codes 404ed.** `applyPromoCode` and `removePromoCode` sent `POST`/`DELETE /carts/:id/promo-codes`; the cart
134
+ API has only ever drawn `/carts/:id/promo_code` (singular, underscore). They now send `promo_code`. The old unit tests
135
+ asserted the wrong path, so they agreed with the bug; `tests/edge-route-agreement.test.ts` now checks every request
136
+ the SDK sends against cope-edge's own route table.
137
+ - **The docs promised an unbounded prefilled replay.** A replay returns the stored handoff only within the cart API's
138
+ 24-hour idempotency TTL; after it the same `externalReference` creates a new cart and checkout. README and the
139
+ `externalReference` doc comment now say so (plan 30 W2-16).
140
+ - **`cart_id_mismatch` was documented as 422.** The cart API answers it with 403.
141
+ - **Two external references could share an Idempotency-Key.** The prefilled key replaced characters outside
142
+ `[A-Za-z0-9._:-]` with `-` and cut at 120, so `INV/2026/1` and `INV-2026-1` collided and the second checkout got
143
+ 422 `idempotency_body_mismatch` for 24 hours. A safe reference of up to 120 characters keeps its readable key
144
+ (`prefilled:<reference>:cart`); any other is SHA-256'd whole (`prefilled#<hex>:cart`).
145
+ - **Errors cope-edge answers itself surfaced as `unknown`.** Edge's checkout-block refusals (413 `payload_too_large`,
146
+ 429 `rate_limited`, 502 `upstream_unavailable`, 504 `gateway_timeout`) use `{error, request_id}`, not core's
147
+ `{errors: [...]}`. `CopeApiError.code` now carries edge's code with a readable `message`, and `requestId` falls back to the
148
+ body's `request_id` (plan 30 W2-47). An `error` that is prose rather than a code becomes `code: 'unknown'`.
149
+ - **A cart write could be applied twice.** `createCart`, `addLine`, `reprice` and `applyPromoCode` were resent after a
150
+ 5xx, a timeout or a network error, but the cart API deduplicates none of them — so a write edge timed out on (504
151
+ `gateway_timeout`) but core committed was applied again. Only GETs and `createPrefilledCheckout`, which the server
152
+ does deduplicate, are resent after an ambiguous failure; a 429 is still retried everywhere it was, because it is
153
+ refused before processing.
154
+ - **`baseUrl` validation let a query, fragment or credentials through**, and a malformed URL threw a bare `TypeError`.
155
+ It must now equal its own origin, and anything else throws `CopeError`.
156
+ - **Typecheck failed on a fresh install.** The iframe timer ids were typed `ReturnType<typeof window.setInterval>`,
157
+ which resolves to Node's `Timeout` once `@types/node` is present; they are typed as the DOM's `number`.