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/CHANGELOG.md +74 -1
- package/README.md +94 -41
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -10
- package/dist/index.d.ts +55 -10
- package/dist/index.global.js +1 -2
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/node.cjs +59 -20
- package/dist/mcp/node.cjs.map +1 -1
- package/dist/mcp/node.js +59 -20
- package/dist/mcp/node.js.map +1 -1
- package/dist/mcp.cjs +59 -20
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.js +59 -20
- package/dist/mcp.js.map +1 -1
- package/package.json +2 -1
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
|
|
237
|
-
|
|
238
|
-
|
|
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
|
|
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("
|
|
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(
|
|
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
|
|
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
|
|
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 (
|
|
1125
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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);
|