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