cope-sdk 0.1.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/dist/mcp/node.cjs CHANGED
@@ -200,7 +200,7 @@ var CHECKOUT_MOUNT_MAX_ATTEMPTS = 20;
200
200
  var CHECKOUT_READY_TIMEOUT_MS = 8e3;
201
201
  var SUPPORTED_ONE_CLICK_CHARGE_TERMS_VERSIONS = /* @__PURE__ */ new Set(["oto-v1"]);
202
202
  function seg(id) {
203
- return encodeURIComponent(id);
203
+ return encodeURIComponent(String(id));
204
204
  }
205
205
  function isRecord2(value) {
206
206
  return typeof value === "object" && value !== null;
@@ -232,10 +232,28 @@ function parseErrorBody(body) {
232
232
  function isApiErrorEntry(value) {
233
233
  return isRecord2(value) && typeof value.code === "string" && typeof value.message === "string";
234
234
  }
235
+ var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
236
+ var ASCTIME = /^[A-Z][a-z]{2} ([A-Z][a-z]{2}) ([ \d]\d) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/;
237
+ function asctimeToUtc([, monthName, day, hh, mm, ss, year]) {
238
+ const fields = [Number(year), MONTHS.indexOf(monthName ?? ""), Number((day ?? "").trim()), Number(hh), Number(mm), Number(ss)];
239
+ const at = Date.UTC(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5]);
240
+ const back = new Date(at);
241
+ const roundTrip = [
242
+ back.getUTCFullYear(),
243
+ back.getUTCMonth(),
244
+ back.getUTCDate(),
245
+ back.getUTCHours(),
246
+ back.getUTCMinutes(),
247
+ back.getUTCSeconds()
248
+ ];
249
+ return roundTrip.every((value, i) => value === fields[i]) ? at : Number.NaN;
250
+ }
235
251
  function parseRetryAfter(value) {
236
- if (value === null || value.trim() === "") return null;
237
- if (/^\d+$/.test(value.trim())) return Number(value.trim());
238
- const at = Date.parse(value);
252
+ if (value === null) return null;
253
+ const trimmed = value.trim();
254
+ if (/^\d+$/.test(trimmed)) return Number(trimmed);
255
+ const asctime = ASCTIME.exec(trimmed);
256
+ const at = asctime ? asctimeToUtc(asctime) : / GMT$/.test(trimmed) ? Date.parse(trimmed) : Number.NaN;
239
257
  return Number.isNaN(at) ? null : Math.max(0, Math.ceil((at - Date.now()) / 1e3));
240
258
  }
241
259
  function bodyRequestId(body) {
@@ -406,7 +424,7 @@ function parseCheckoutEmbedEvent(data) {
406
424
  }
407
425
  return null;
408
426
  }
409
- var CopeCart = class {
427
+ var Cope = class {
410
428
  publishableKey;
411
429
  baseUrl;
412
430
  checkoutBaseUrl;
@@ -424,7 +442,7 @@ var CopeCart = class {
424
442
  throw new CopeError('Invalid publishableKey. Must start with "cope_pk_".');
425
443
  }
426
444
  if (typeof window !== "undefined" && window.location?.protocol === "http:" && window.location.hostname !== "localhost") {
427
- throw new CopeError("CopeCart SDK requires HTTPS. Refusing to operate on an insecure page.");
445
+ throw new CopeError("COPE SDK requires HTTPS. Refusing to operate on an insecure page.");
428
446
  }
429
447
  this.publishableKey = options.publishableKey;
430
448
  const baseOrigin = originOnly(options.baseUrl ?? DEFAULT_BASE_URL);
@@ -462,9 +480,11 @@ var CopeCart = class {
462
480
  async addLine(cartId, payload) {
463
481
  return normalizeCart(await this.post(`/carts/${seg(cartId)}/lines`, payload));
464
482
  }
483
+ /** `lineId` is the line's `id`, an integer; a string of its digits is accepted too. */
465
484
  async updateLine(cartId, lineId2, payload) {
466
485
  return normalizeCart(await this.patch(`/carts/${seg(cartId)}/lines/${seg(lineId2)}`, payload));
467
486
  }
487
+ /** `lineId` is the line's `id`, an integer; a string of its digits is accepted too. */
468
488
  async removeLine(cartId, lineId2) {
469
489
  return normalizeCart(await this.del(`/carts/${seg(cartId)}/lines/${seg(lineId2)}`));
470
490
  }
@@ -496,6 +516,7 @@ var CopeCart = class {
496
516
  ...input.attribution ? { attribution: input.attribution } : {},
497
517
  ...input.intendedPaymentMethod != null ? { intended_payment_method: input.intendedPaymentMethod } : {},
498
518
  ...input.metadata ? { metadata: input.metadata } : {},
519
+ ...input.collectBuyerIdentityAtCheckout != null ? { collect_buyer_identity_at_checkout: input.collectBuyerIdentityAtCheckout } : {},
499
520
  lines: lines.map((line) => ({
500
521
  product_id: line.productId,
501
522
  ...line.planId !== void 0 ? { plan_id: line.planId } : {},
@@ -519,7 +540,8 @@ var CopeCart = class {
519
540
  status: checkout.status,
520
541
  expiresAt: checkout.expiresAt,
521
542
  totals: checkout.totals,
522
- ...input.intendedPaymentMethod != null ? { intendedPaymentMethod: input.intendedPaymentMethod } : {}
543
+ ...input.intendedPaymentMethod != null ? { intendedPaymentMethod: input.intendedPaymentMethod } : {},
544
+ ...raw.field_errors?.length ? { fieldErrors: raw.field_errors } : {}
523
545
  };
524
546
  }
525
547
  async cancelCheckout(checkoutId) {
@@ -1002,15 +1024,20 @@ function projectLine(line) {
1002
1024
  unitPriceMinorUnits: line.price_snapshot?.unit_price_minor_units ?? null
1003
1025
  };
1004
1026
  }
1005
- function projectCheckout(checkout) {
1027
+ function projectCheckout(checkout, lines) {
1006
1028
  return {
1007
1029
  checkoutUrl: checkout.checkoutUrl,
1008
1030
  status: checkout.status,
1009
1031
  expiresAt: checkout.expiresAt,
1010
1032
  totalMinorUnits: checkout.totals.total_minor_units,
1011
- currency: checkout.totals.currency
1033
+ currency: checkout.totals.currency,
1034
+ buyerNext: buyerNextFor(lines)
1012
1035
  };
1013
1036
  }
1037
+ function buyerNextFor(lines) {
1038
+ const waiver = lines.some((line) => line.requires_shipping === false) ? ", waive their right of withdrawal (a product in this cart does not ship)" : lines.some((line) => line.requires_shipping === void 0) ? ", waive their right of withdrawal if a product in the cart does not ship" : "";
1039
+ return `Send the buyer to checkoutUrl. There they accept the terms${waiver}, and pay. You cannot do any of these for them.`;
1040
+ }
1014
1041
  var GUIDANCE = {
1015
1042
  invalid_cart_ref: "The cartRef is not valid here. Start again with create_cart.",
1016
1043
  cart_expired: "This cart is finished: ordered (the buyer has paid), expired or abandoned. For a new purchase, start again with create_cart.",
@@ -1024,6 +1051,7 @@ var GUIDANCE = {
1024
1051
  offer_sourced_no_promo: "This cart comes from a negotiated offer; promo codes do not apply.",
1025
1052
  checkout_expired: "The checkout expired. Call start_checkout again.",
1026
1053
  tax_address_invalid: "The buyer address is incomplete or invalid for tax. Ask the buyer and call set_buyer_identity.",
1054
+ missing_tax_location: "Totals need the buyer's country and postal code. Ask the buyer and call set_buyer_identity, then get_cart.",
1027
1055
  tax_service_unavailable: "Tax could not be calculated right now. Retry shortly.",
1028
1056
  business_checkout_hold: "This store cannot take orders right now. Tell the buyer to try later.",
1029
1057
  rate_limited: "Too many requests. Wait retryAfter seconds, then retry.",
@@ -1074,7 +1102,7 @@ var CONFLICT_WAITS_MS = [500, 1e3, 2e3];
1074
1102
  var cartRef = z__namespace.string().min(1).max(2048).describe("The cartRef returned by create_cart. Pass it back unchanged.");
1075
1103
  var ID = /^[A-Za-z0-9_-]{1,100}$/;
1076
1104
  var productId = z__namespace.string().regex(ID).describe("The product id, e.g. from the store page");
1077
- var lineId = z__namespace.string().regex(ID).describe("A lineId from the cart");
1105
+ var lineId = z__namespace.union([z__namespace.number().int().positive(), z__namespace.string().regex(/^\d{1,19}$/)]).describe("A lineId from the cart, as get_cart returned it");
1078
1106
  var address = z__namespace.object({
1079
1107
  line1: z__namespace.string(),
1080
1108
  line2: z__namespace.string().optional(),
@@ -1091,10 +1119,10 @@ function registerCheckoutTools(server, ctx) {
1091
1119
  return refusal(error) ?? fail(error);
1092
1120
  }
1093
1121
  };
1094
- const newClient = () => new CopeCart({ ...ctx.clientOptions, persistence: "none" });
1122
+ const newClient = () => new Cope({ ...ctx.clientOptions, persistence: "none" });
1095
1123
  const openCart = async (ref) => {
1096
1124
  const creds = await ctx.codec.decode(ref);
1097
- return { creds, cope: new CopeCart({ ...ctx.clientOptions, cart: creds }) };
1125
+ return { creds, cope: new Cope({ ...ctx.clientOptions, cart: creds }) };
1098
1126
  };
1099
1127
  server.registerTool("get_product", {
1100
1128
  description: "Look up a product the store sells: name, plans and prices. Prices are in minor units (cents).",
@@ -1121,8 +1149,14 @@ function registerCheckoutTools(server, ctx) {
1121
1149
  try {
1122
1150
  return { cart: projectCart(await cope.reprice(creds.id)) };
1123
1151
  } catch (error) {
1124
- if (!hasCode(error, "cart_not_repriceable")) throw error;
1125
- return { cart: projectCart(await cope.getCart(creds.id)) };
1152
+ if (hasCode(error, "cart_not_repriceable")) return { cart: projectCart(await cope.getCart(creds.id)) };
1153
+ if (hasCode(error, "missing_tax_location")) {
1154
+ return {
1155
+ cart: projectCart(await cope.getCart(creds.id)),
1156
+ totalsPending: "Totals need the buyer's country and postal code. Call set_buyer_identity when the buyer gives them."
1157
+ };
1158
+ }
1159
+ throw error;
1126
1160
  }
1127
1161
  }));
1128
1162
  server.registerTool("add_line", {
@@ -1235,17 +1269,19 @@ function registerCheckoutTools(server, ctx) {
1235
1269
  return { cart: projectCart(cart) };
1236
1270
  }));
1237
1271
  server.registerTool("start_checkout", {
1238
- description: "Create the checkout and return the URL where the buyer reviews, accepts the terms and pays. Calling it again returns the same checkout while it is open.",
1272
+ description: "Create the checkout and return the URL where the buyer reviews, gives the consents the cart needs and pays; buyerNext in the result says which. Calling it again returns the same checkout while it is open.",
1239
1273
  inputSchema: z__namespace.object({ cartRef }),
1240
1274
  annotations: { idempotentHint: true }
1241
1275
  }, ({ cartRef: ref }) => run(async () => {
1242
1276
  const { creds, cope } = await openCart(ref);
1243
1277
  const payload = { success_url: ctx.checkout.successUrl, cancel_url: ctx.checkout.cancelUrl, consents: [] };
1244
- return projectCheckout(await startCheckout(cope, creds, payload));
1278
+ const { checkout, cart } = await startCheckout(cope, creds, payload);
1279
+ return projectCheckout(checkout, cart.lines);
1245
1280
  }));
1246
1281
  }
1247
1282
  async function startCheckout(cope, creds, payload) {
1248
- const inCheckout = (await cope.getCart(creds.id)).state === "checkout_in_progress";
1283
+ const cart = await cope.getCart(creds.id);
1284
+ const inCheckout = cart.state === "checkout_in_progress";
1249
1285
  let open = null;
1250
1286
  for (let n = 0; n < MAX_CHECKOUT_KEYS; n++) {
1251
1287
  let result;
@@ -1261,9 +1297,9 @@ async function startCheckout(cope, creds, payload) {
1261
1297
  if (Date.parse(result.expiresAt) > Date.now()) open = result;
1262
1298
  continue;
1263
1299
  }
1264
- if ((await cope.getCart(creds.id)).state !== "active") return result;
1300
+ if ((await cope.getCart(creds.id)).state !== "active") return { checkout: result, cart };
1265
1301
  }
1266
- if (open) return open;
1302
+ if (open) return { checkout: open, cart };
1267
1303
  if (inCheckout) {
1268
1304
  throw new CopeApiError(409, [{ code: "checkout_not_recoverable", message: "The open checkout expired or cannot be recovered" }]);
1269
1305
  }
@@ -1285,8 +1321,11 @@ function hasCode(error, code) {
1285
1321
  return error instanceof CopeApiError && error.code === code;
1286
1322
  }
1287
1323
 
1324
+ // package.json
1325
+ var version = "0.1.1";
1326
+
1288
1327
  // src/mcp/handler.ts
1289
- var SERVER_INFO = { name: "cope-checkout", version: "0.1.0" };
1328
+ var SERVER_INFO = { name: "cope-checkout", version };
1290
1329
  function createCopeMcpHandler(options) {
1291
1330
  if (options.allowedHosts.length === 0) throw new Error("allowedHosts must name at least one host");
1292
1331
  parseCartRefKeys(options.cartRefKeys);