cope-sdk 0.0.1-reserved.0 → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,84 @@
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
+ ## [Unreleased] — 0.1.0
12
+
13
+ ### Added
14
+
15
+ - **`cope-sdk/mcp`**: an MCP endpoint a store mounts on its own server so a buyer's AI agent can look up products,
16
+ build a cart and get a checkout URL (`createCopeMcpHandler`, `generateCartRefKey`; `createCopeMcpNodeHandler` from
17
+ `cope-sdk/mcp/node`). Server-only; needs `@modelcontextprotocol/server` and `zod` as peers, plus
18
+ `@modelcontextprotocol/node` for the Node entry. The browser build does not include it.
19
+ - **Ids in request paths are URL-encoded**, so an id such as `../promo_code` cannot aim a request carrying the cart
20
+ secret at another route.
21
+ - **`persistence: 'none'`** keeps the cart only in memory, for a server that serves many buyers from one process; the
22
+ default stays `'localStorage'`; a server needs one client per buyer. **`cart: { id, secret }`** acts on a cart whose
23
+ credentials the caller holds, and never touches stored carts.
24
+ - **`checkout(cartId, payload, { idempotencyKey })`**: a repeated call with the same key and payload returns the
25
+ checkout already created instead of `cart_not_active` (409 `idempotency_conflict` while the first call is still
26
+ in flight).
27
+ - **`CopeApiError.retryAfter`**: the server's `Retry-After`, in seconds, or null.
28
+
29
+ ### Changed
30
+
31
+ - **Published as `cope-sdk`**, not `@copecart/sdk`.
32
+ - **MIT-licensed** (`@copecart/sdk` was `UNLICENSED`), so merchants may use, modify and redistribute it in their
33
+ own sites.
34
+ - **Default `baseUrl` is `https://app.cope.com`.** Requests go to `https://app.cope.com/api/cart/v1/...`, the checkout
35
+ block cope-edge serves; the old default `https://app.cope.com/gateway/cart_api` pointed at the old estate's gateway,
36
+ which the new estate does not have. The hosted checkout origin (the `checkoutBaseUrl` default) stays `app.cope.com`.
37
+ - **`baseUrl` must be an origin.** A value with a path, such as `@copecart/sdk`'s documented
38
+ `https://app.cope.com/gateway/cart_api`, throws `CopeError('Invalid baseUrl...')` instead of 404ing on every call.
39
+ - **The cart is stored under `cope_sdk_cart`**, not `cope_cart`, so `cope-sdk` never reads or clears a cart
40
+ `@copecart/sdk` stored on the same page.
41
+
42
+ ### Fixed
43
+
44
+ - **`cope-sdk/mcp` failed every `create_cart` and `get_cart` against the real cart API** (staging, 2026-09-26). cope-core
45
+ answers `POST /carts` without `lines` or `buyer_identity`, and reprice and the promo-code routes without
46
+ `buyer_identity`; the MCP projection read both unconditionally and answered `internal_error`. `createCart` now
47
+ returns `lines: []` and `buyer_identity: null` (a new cart has neither), `Cart.buyer_identity` is optional (absent
48
+ where the response does not report it), and the MCP result leaves `buyer` out rather than claiming none.
49
+ - **`Cart.promo_code` never existed.** cope-core sends `applied_promo_code`, an object with the code and the discount;
50
+ the type now says so, and the MCP result's `promoCode` reads it.
51
+ - **`get_product` quoted plan prices 100 times too low.** The cart API's plan amounts are display strings in major
52
+ units despite their `_cents` names (`"999.00"`); the MCP projection read them as minor units. It converts them now,
53
+ so `firstPaymentMinorUnits` matches what the cart charges.
54
+ - **A refused promo code was never recognised.** The cart API answers 422 with the validator's reason (`not_found`,
55
+ `expired`, `exhausted`, ...), never `invalid_promo_code`; `ApiErrorCode` lists the real reasons, and
56
+ `apply_promo_code` answers `promo_code_not_accepted` with the API's code as `reason`.
57
+ - The MCP tests' cart API fake had the same four shapes wrong, which is why they passed; it now follows
58
+ `CartBlueprint` and `PaymentPlanBlueprint`.
59
+
60
+ - **Promo codes 404ed.** `applyPromoCode` and `removePromoCode` sent `POST`/`DELETE /carts/:id/promo-codes`; the cart
61
+ API has only ever drawn `/carts/:id/promo_code` (singular, underscore). They now send `promo_code`. The old unit tests
62
+ asserted the wrong path, so they agreed with the bug; `tests/edge-route-agreement.test.ts` now checks every request
63
+ the SDK sends against cope-edge's own route table.
64
+ - **The docs promised an unbounded prefilled replay.** A replay returns the stored handoff only within the cart API's
65
+ 24-hour idempotency TTL; after it the same `externalReference` creates a new cart and checkout. README and the
66
+ `externalReference` doc comment now say so (plan 30 W2-16).
67
+ - **`cart_id_mismatch` was documented as 422.** The cart API answers it with 403.
68
+ - **Two external references could share an Idempotency-Key.** The prefilled key replaced characters outside
69
+ `[A-Za-z0-9._:-]` with `-` and cut at 120, so `INV/2026/1` and `INV-2026-1` collided and the second checkout got
70
+ 422 `idempotency_body_mismatch` for 24 hours. A safe reference of up to 120 characters keeps its readable key
71
+ (`prefilled:<reference>:cart`); any other is SHA-256'd whole (`prefilled#<hex>:cart`).
72
+ - **Errors cope-edge answers itself surfaced as `unknown`.** Edge's checkout-block refusals (413 `payload_too_large`,
73
+ 429 `rate_limited`, 502 `upstream_unavailable`, 504 `gateway_timeout`) use `{error, request_id}`, not core's
74
+ `{errors: [...]}`. `CopeApiError.code` now carries edge's code with a readable `message`, and `requestId` falls back to the
75
+ body's `request_id` (plan 30 W2-47). An `error` that is prose rather than a code becomes `code: 'unknown'`.
76
+ - **A cart write could be applied twice.** `createCart`, `addLine`, `reprice` and `applyPromoCode` were resent after a
77
+ 5xx, a timeout or a network error, but the cart API deduplicates none of them — so a write edge timed out on (504
78
+ `gateway_timeout`) but core committed was applied again. Only GETs and `createPrefilledCheckout`, which the server
79
+ does deduplicate, are resent after an ambiguous failure; a 429 is still retried everywhere it was, because it is
80
+ refused before processing.
81
+ - **`baseUrl` validation let a query, fragment or credentials through**, and a malformed URL threw a bare `TypeError`.
82
+ It must now equal its own origin, and anything else throws `CopeError`.
83
+ - **Typecheck failed on a fresh install.** The iframe timer ids were typed `ReturnType<typeof window.setInterval>`,
84
+ which resolves to Node's `Timeout` once `@types/node` is present; they are typed as the DOM's `number`.