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.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
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
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("
|
|
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(
|
|
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
|
|
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
|
|
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 (
|
|
1127
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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);
|