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.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
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
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("
|
|
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(
|
|
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
|
|
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
|
|
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 (
|
|
1103
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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);
|