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.js CHANGED
@@ -178,7 +178,7 @@ var CHECKOUT_MOUNT_MAX_ATTEMPTS = 20;
178
178
  var CHECKOUT_READY_TIMEOUT_MS = 8e3;
179
179
  var SUPPORTED_ONE_CLICK_CHARGE_TERMS_VERSIONS = /* @__PURE__ */ new Set(["oto-v1"]);
180
180
  function seg(id) {
181
- return encodeURIComponent(id);
181
+ return encodeURIComponent(String(id));
182
182
  }
183
183
  function isRecord2(value) {
184
184
  return typeof value === "object" && value !== null;
@@ -210,10 +210,28 @@ function parseErrorBody(body) {
210
210
  function isApiErrorEntry(value) {
211
211
  return isRecord2(value) && typeof value.code === "string" && typeof value.message === "string";
212
212
  }
213
+ var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
214
+ var ASCTIME = /^[A-Z][a-z]{2} ([A-Z][a-z]{2}) ([ \d]\d) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/;
215
+ function asctimeToUtc([, monthName, day, hh, mm, ss, year]) {
216
+ const fields = [Number(year), MONTHS.indexOf(monthName ?? ""), Number((day ?? "").trim()), Number(hh), Number(mm), Number(ss)];
217
+ const at = Date.UTC(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5]);
218
+ const back = new Date(at);
219
+ const roundTrip = [
220
+ back.getUTCFullYear(),
221
+ back.getUTCMonth(),
222
+ back.getUTCDate(),
223
+ back.getUTCHours(),
224
+ back.getUTCMinutes(),
225
+ back.getUTCSeconds()
226
+ ];
227
+ return roundTrip.every((value, i) => value === fields[i]) ? at : Number.NaN;
228
+ }
213
229
  function parseRetryAfter(value) {
214
- if (value === null || value.trim() === "") return null;
215
- if (/^\d+$/.test(value.trim())) return Number(value.trim());
216
- const at = Date.parse(value);
230
+ if (value === null) return null;
231
+ const trimmed = value.trim();
232
+ if (/^\d+$/.test(trimmed)) return Number(trimmed);
233
+ const asctime = ASCTIME.exec(trimmed);
234
+ const at = asctime ? asctimeToUtc(asctime) : / GMT$/.test(trimmed) ? Date.parse(trimmed) : Number.NaN;
217
235
  return Number.isNaN(at) ? null : Math.max(0, Math.ceil((at - Date.now()) / 1e3));
218
236
  }
219
237
  function bodyRequestId(body) {
@@ -384,7 +402,7 @@ function parseCheckoutEmbedEvent(data) {
384
402
  }
385
403
  return null;
386
404
  }
387
- var CopeCart = class {
405
+ var Cope = class {
388
406
  publishableKey;
389
407
  baseUrl;
390
408
  checkoutBaseUrl;
@@ -402,7 +420,7 @@ var CopeCart = class {
402
420
  throw new CopeError('Invalid publishableKey. Must start with "cope_pk_".');
403
421
  }
404
422
  if (typeof window !== "undefined" && window.location?.protocol === "http:" && window.location.hostname !== "localhost") {
405
- throw new CopeError("CopeCart SDK requires HTTPS. Refusing to operate on an insecure page.");
423
+ throw new CopeError("COPE SDK requires HTTPS. Refusing to operate on an insecure page.");
406
424
  }
407
425
  this.publishableKey = options.publishableKey;
408
426
  const baseOrigin = originOnly(options.baseUrl ?? DEFAULT_BASE_URL);
@@ -440,9 +458,11 @@ var CopeCart = class {
440
458
  async addLine(cartId, payload) {
441
459
  return normalizeCart(await this.post(`/carts/${seg(cartId)}/lines`, payload));
442
460
  }
461
+ /** `lineId` is the line's `id`, an integer; a string of its digits is accepted too. */
443
462
  async updateLine(cartId, lineId2, payload) {
444
463
  return normalizeCart(await this.patch(`/carts/${seg(cartId)}/lines/${seg(lineId2)}`, payload));
445
464
  }
465
+ /** `lineId` is the line's `id`, an integer; a string of its digits is accepted too. */
446
466
  async removeLine(cartId, lineId2) {
447
467
  return normalizeCart(await this.del(`/carts/${seg(cartId)}/lines/${seg(lineId2)}`));
448
468
  }
@@ -474,6 +494,7 @@ var CopeCart = class {
474
494
  ...input.attribution ? { attribution: input.attribution } : {},
475
495
  ...input.intendedPaymentMethod != null ? { intended_payment_method: input.intendedPaymentMethod } : {},
476
496
  ...input.metadata ? { metadata: input.metadata } : {},
497
+ ...input.collectBuyerIdentityAtCheckout != null ? { collect_buyer_identity_at_checkout: input.collectBuyerIdentityAtCheckout } : {},
477
498
  lines: lines.map((line) => ({
478
499
  product_id: line.productId,
479
500
  ...line.planId !== void 0 ? { plan_id: line.planId } : {},
@@ -497,7 +518,8 @@ var CopeCart = class {
497
518
  status: checkout.status,
498
519
  expiresAt: checkout.expiresAt,
499
520
  totals: checkout.totals,
500
- ...input.intendedPaymentMethod != null ? { intendedPaymentMethod: input.intendedPaymentMethod } : {}
521
+ ...input.intendedPaymentMethod != null ? { intendedPaymentMethod: input.intendedPaymentMethod } : {},
522
+ ...raw.field_errors?.length ? { fieldErrors: raw.field_errors } : {}
501
523
  };
502
524
  }
503
525
  async cancelCheckout(checkoutId) {
@@ -980,15 +1002,20 @@ function projectLine(line) {
980
1002
  unitPriceMinorUnits: line.price_snapshot?.unit_price_minor_units ?? null
981
1003
  };
982
1004
  }
983
- function projectCheckout(checkout) {
1005
+ function projectCheckout(checkout, lines) {
984
1006
  return {
985
1007
  checkoutUrl: checkout.checkoutUrl,
986
1008
  status: checkout.status,
987
1009
  expiresAt: checkout.expiresAt,
988
1010
  totalMinorUnits: checkout.totals.total_minor_units,
989
- currency: checkout.totals.currency
1011
+ currency: checkout.totals.currency,
1012
+ buyerNext: buyerNextFor(lines)
990
1013
  };
991
1014
  }
1015
+ function buyerNextFor(lines) {
1016
+ 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" : "";
1017
+ return `Send the buyer to checkoutUrl. There they accept the terms${waiver}, and pay. You cannot do any of these for them.`;
1018
+ }
992
1019
  var GUIDANCE = {
993
1020
  invalid_cart_ref: "The cartRef is not valid here. Start again with create_cart.",
994
1021
  cart_expired: "This cart is finished: ordered (the buyer has paid), expired or abandoned. For a new purchase, start again with create_cart.",
@@ -1002,6 +1029,7 @@ var GUIDANCE = {
1002
1029
  offer_sourced_no_promo: "This cart comes from a negotiated offer; promo codes do not apply.",
1003
1030
  checkout_expired: "The checkout expired. Call start_checkout again.",
1004
1031
  tax_address_invalid: "The buyer address is incomplete or invalid for tax. Ask the buyer and call set_buyer_identity.",
1032
+ missing_tax_location: "Totals need the buyer's country and postal code. Ask the buyer and call set_buyer_identity, then get_cart.",
1005
1033
  tax_service_unavailable: "Tax could not be calculated right now. Retry shortly.",
1006
1034
  business_checkout_hold: "This store cannot take orders right now. Tell the buyer to try later.",
1007
1035
  rate_limited: "Too many requests. Wait retryAfter seconds, then retry.",
@@ -1052,7 +1080,7 @@ var CONFLICT_WAITS_MS = [500, 1e3, 2e3];
1052
1080
  var cartRef = z.string().min(1).max(2048).describe("The cartRef returned by create_cart. Pass it back unchanged.");
1053
1081
  var ID = /^[A-Za-z0-9_-]{1,100}$/;
1054
1082
  var productId = z.string().regex(ID).describe("The product id, e.g. from the store page");
1055
- var lineId = z.string().regex(ID).describe("A lineId from the cart");
1083
+ var lineId = z.union([z.number().int().positive(), z.string().regex(/^\d{1,19}$/)]).describe("A lineId from the cart, as get_cart returned it");
1056
1084
  var address = z.object({
1057
1085
  line1: z.string(),
1058
1086
  line2: z.string().optional(),
@@ -1069,10 +1097,10 @@ function registerCheckoutTools(server, ctx) {
1069
1097
  return refusal(error) ?? fail(error);
1070
1098
  }
1071
1099
  };
1072
- const newClient = () => new CopeCart({ ...ctx.clientOptions, persistence: "none" });
1100
+ const newClient = () => new Cope({ ...ctx.clientOptions, persistence: "none" });
1073
1101
  const openCart = async (ref) => {
1074
1102
  const creds = await ctx.codec.decode(ref);
1075
- return { creds, cope: new CopeCart({ ...ctx.clientOptions, cart: creds }) };
1103
+ return { creds, cope: new Cope({ ...ctx.clientOptions, cart: creds }) };
1076
1104
  };
1077
1105
  server.registerTool("get_product", {
1078
1106
  description: "Look up a product the store sells: name, plans and prices. Prices are in minor units (cents).",
@@ -1099,8 +1127,14 @@ function registerCheckoutTools(server, ctx) {
1099
1127
  try {
1100
1128
  return { cart: projectCart(await cope.reprice(creds.id)) };
1101
1129
  } catch (error) {
1102
- if (!hasCode(error, "cart_not_repriceable")) throw error;
1103
- return { cart: projectCart(await cope.getCart(creds.id)) };
1130
+ if (hasCode(error, "cart_not_repriceable")) return { cart: projectCart(await cope.getCart(creds.id)) };
1131
+ if (hasCode(error, "missing_tax_location")) {
1132
+ return {
1133
+ cart: projectCart(await cope.getCart(creds.id)),
1134
+ totalsPending: "Totals need the buyer's country and postal code. Call set_buyer_identity when the buyer gives them."
1135
+ };
1136
+ }
1137
+ throw error;
1104
1138
  }
1105
1139
  }));
1106
1140
  server.registerTool("add_line", {
@@ -1213,17 +1247,19 @@ function registerCheckoutTools(server, ctx) {
1213
1247
  return { cart: projectCart(cart) };
1214
1248
  }));
1215
1249
  server.registerTool("start_checkout", {
1216
- 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.",
1250
+ 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.",
1217
1251
  inputSchema: z.object({ cartRef }),
1218
1252
  annotations: { idempotentHint: true }
1219
1253
  }, ({ cartRef: ref }) => run(async () => {
1220
1254
  const { creds, cope } = await openCart(ref);
1221
1255
  const payload = { success_url: ctx.checkout.successUrl, cancel_url: ctx.checkout.cancelUrl, consents: [] };
1222
- return projectCheckout(await startCheckout(cope, creds, payload));
1256
+ const { checkout, cart } = await startCheckout(cope, creds, payload);
1257
+ return projectCheckout(checkout, cart.lines);
1223
1258
  }));
1224
1259
  }
1225
1260
  async function startCheckout(cope, creds, payload) {
1226
- const inCheckout = (await cope.getCart(creds.id)).state === "checkout_in_progress";
1261
+ const cart = await cope.getCart(creds.id);
1262
+ const inCheckout = cart.state === "checkout_in_progress";
1227
1263
  let open = null;
1228
1264
  for (let n = 0; n < MAX_CHECKOUT_KEYS; n++) {
1229
1265
  let result;
@@ -1239,9 +1275,9 @@ async function startCheckout(cope, creds, payload) {
1239
1275
  if (Date.parse(result.expiresAt) > Date.now()) open = result;
1240
1276
  continue;
1241
1277
  }
1242
- if ((await cope.getCart(creds.id)).state !== "active") return result;
1278
+ if ((await cope.getCart(creds.id)).state !== "active") return { checkout: result, cart };
1243
1279
  }
1244
- if (open) return open;
1280
+ if (open) return { checkout: open, cart };
1245
1281
  if (inCheckout) {
1246
1282
  throw new CopeApiError(409, [{ code: "checkout_not_recoverable", message: "The open checkout expired or cannot be recovered" }]);
1247
1283
  }
@@ -1263,8 +1299,11 @@ function hasCode(error, code) {
1263
1299
  return error instanceof CopeApiError && error.code === code;
1264
1300
  }
1265
1301
 
1302
+ // package.json
1303
+ var version = "0.1.1";
1304
+
1266
1305
  // src/mcp/handler.ts
1267
- var SERVER_INFO = { name: "cope-checkout", version: "0.1.0" };
1306
+ var SERVER_INFO = { name: "cope-checkout", version };
1268
1307
  function createCopeMcpHandler(options) {
1269
1308
  if (options.allowedHosts.length === 0) throw new Error("allowedHosts must name at least one host");
1270
1309
  parseCartRefKeys(options.cartRefKeys);