@payments-ai/mcp 2.4.1 → 2.5.0

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.
Files changed (2) hide show
  1. package/dist/main.http.js +215 -59
  2. package/package.json +1 -1
package/dist/main.http.js CHANGED
@@ -247,17 +247,23 @@ var SUPPORTED_OAUTH_SCOPES = [
247
247
  "docs:read",
248
248
  "checkout:read",
249
249
  "checkout:write",
250
- "transaction:read"
250
+ "transaction:read",
251
+ "customer:read",
252
+ "invoice:read"
251
253
  ];
252
254
  var OAUTH_CONSENT_READ_SCOPES = [
253
255
  "merchant:read",
254
256
  "product:read",
255
257
  "docs:read",
256
258
  "checkout:read",
257
- "transaction:read"
259
+ "transaction:read",
260
+ "customer:read",
261
+ "invoice:read"
258
262
  ];
259
263
  var EXPLICIT_GRANT_OAUTH_SCOPES = [
260
- "transaction:read"
264
+ "transaction:read",
265
+ "customer:read",
266
+ "invoice:read"
261
267
  ];
262
268
  var EXPLICIT_GRANT_SCOPE_SET = new Set(EXPLICIT_GRANT_OAUTH_SCOPES);
263
269
  function isExplicitGrantScope(scope) {
@@ -272,6 +278,7 @@ function buildOAuthAuthorizationServerMetadata(issuerUrl, options2) {
272
278
  issuer: issuerUrl,
273
279
  authorization_endpoint: `${issuerUrl}/api/v1/oauth/authorize`,
274
280
  token_endpoint: `${issuerUrl}/api/v1/oauth/token`,
281
+ revocation_endpoint: `${issuerUrl}/api/v1/oauth/revoke`,
275
282
  registration_endpoint: `${issuerUrl}/api/v1/oauth/register`,
276
283
  scopes_supported: SUPPORTED_OAUTH_SCOPES,
277
284
  response_types_supported: OAUTH_RESPONSE_TYPES_SUPPORTED,
@@ -287,6 +294,9 @@ var MCP_OAUTH_AUTHORIZATION_WINDOW_MINUTES = 30;
287
294
  var MCP_OAUTH_AUTHORIZATION_WINDOW_MS = MCP_OAUTH_AUTHORIZATION_WINDOW_MINUTES * 60 * 1e3;
288
295
 
289
296
  // ../../packages/mcp-contracts/dist/order-limits.js
297
+ var import_currency2 = __toESM(require_currency(), 1);
298
+
299
+ // ../../packages/mcp-contracts/dist/currency-precision.js
290
300
  var import_currency = __toESM(require_currency(), 1);
291
301
 
292
302
  // ../../packages/mcp-contracts/dist/plan-amount-limits.js
@@ -301,7 +311,8 @@ var MIN_PAID_PLAN_AMOUNT_BY_CURRENCY = {
301
311
  dkk: 2.5,
302
312
  nok: 3,
303
313
  cad: 1,
304
- aud: 1
314
+ aud: 1,
315
+ jpy: 50
305
316
  };
306
317
  var SUPPORTED_CURRENCIES = Object.freeze(Object.keys(MIN_PAID_PLAN_AMOUNT_BY_CURRENCY));
307
318
  function isSupportedCurrency(currency5) {
@@ -320,15 +331,49 @@ function formatMinPaidPlanAmountMessage(currency5, minAmount) {
320
331
  const code = currency5.trim().toUpperCase() || "USD";
321
332
  return `Amount must be at least ${minAmount} ${code} for paid plans`;
322
333
  }
334
+ function formatAmountPrecisionMessage(currency5, fractionDigits) {
335
+ const code = currency5.trim().toUpperCase();
336
+ if (fractionDigits === 0) {
337
+ return `Amount must be a whole number of ${code}, which has no minor unit. Recovery: round the amount to whole ${code}, e.g. 500 rather than 500.5.`;
338
+ }
339
+ return `Amount must have at most ${fractionDigits} decimal places in ${code}. Recovery: round the amount to ${fractionDigits} decimal places.`;
340
+ }
323
341
  var LOWEST_MIN_PAID_PLAN_AMOUNT = Math.min(...Object.values(MIN_PAID_PLAN_AMOUNT_BY_CURRENCY));
324
342
  var PAID_PLAN_NOT_FREE_BY_ZERO_AMOUNT = "A paid plan cannot be made free by setting its amount to 0; create a free-access plan instead.";
325
343
  function formatAmountBelowLowestFloorMessage() {
326
344
  const higherFloors = Object.entries(MIN_PAID_PLAN_AMOUNT_BY_CURRENCY).filter(([, minAmount]) => minAmount > LOWEST_MIN_PAID_PLAN_AMOUNT).map(([currency5, minAmount]) => `${minAmount} ${currency5.toUpperCase()}`).join(", ");
327
345
  return `Amount must be at least ${LOWEST_MIN_PAID_PLAN_AMOUNT} for paid plans, and more in some currencies (${higherFloors}). Recovery: send an amount of at least the floor of the plan's currency, or 0 for a free-access plan. ${PAID_PLAN_NOT_FREE_BY_ZERO_AMOUNT}`;
328
346
  }
329
- var MAX_PLAN_AMOUNT = 1e5;
330
- function formatMaxPlanAmountMessage() {
331
- return `Amount must be at most ${MAX_PLAN_AMOUNT}. Recovery: lower the plan amount, or contact support if you need a higher per-plan limit.`;
347
+ var DEFAULT_MAX_PLAN_AMOUNT = 1e5;
348
+ var MAX_PLAN_AMOUNT_BY_CURRENCY = {
349
+ usd: DEFAULT_MAX_PLAN_AMOUNT,
350
+ eur: DEFAULT_MAX_PLAN_AMOUNT,
351
+ gbp: DEFAULT_MAX_PLAN_AMOUNT,
352
+ pln: DEFAULT_MAX_PLAN_AMOUNT,
353
+ chf: DEFAULT_MAX_PLAN_AMOUNT,
354
+ sek: DEFAULT_MAX_PLAN_AMOUNT,
355
+ dkk: DEFAULT_MAX_PLAN_AMOUNT,
356
+ nok: DEFAULT_MAX_PLAN_AMOUNT,
357
+ cad: DEFAULT_MAX_PLAN_AMOUNT,
358
+ aud: DEFAULT_MAX_PLAN_AMOUNT,
359
+ // Tech-lead decision (about the USD 100000 ceiling in yen), not a measured Whop limit.
360
+ jpy: 15e6
361
+ };
362
+ function getMaxPlanAmount(currency5) {
363
+ const key = currency5.trim().toLowerCase();
364
+ return MAX_PLAN_AMOUNT_BY_CURRENCY[key] ?? DEFAULT_MAX_PLAN_AMOUNT;
365
+ }
366
+ var HIGHEST_MAX_PLAN_AMOUNT = Math.max(...Object.values(MAX_PLAN_AMOUNT_BY_CURRENCY));
367
+ var MAX_AMOUNT_RECOVERY = "Recovery: lower the plan amount, or contact support if you need a higher per-plan limit.";
368
+ function formatMaxPlanAmountMessage(currency5) {
369
+ const code = currency5.trim().toUpperCase();
370
+ return `Amount must be at most ${getMaxPlanAmount(currency5)} ${code}. ${MAX_AMOUNT_RECOVERY}`;
371
+ }
372
+ function formatHigherMaxAmounts() {
373
+ return Object.entries(MAX_PLAN_AMOUNT_BY_CURRENCY).filter(([, maxAmount]) => maxAmount > DEFAULT_MAX_PLAN_AMOUNT).map(([currency5, maxAmount]) => `${maxAmount} ${currency5.toUpperCase()}`).join(", ");
374
+ }
375
+ function formatAmountAboveCeilingMessage() {
376
+ return `Amount must be at most ${DEFAULT_MAX_PLAN_AMOUNT}, or the higher ceiling of its currency (${formatHigherMaxAmounts()}). ${MAX_AMOUNT_RECOVERY}`;
332
377
  }
333
378
  var MAX_PERIOD_LENGTH_BY_BILLING_PERIOD = {
334
379
  week: 52,
@@ -343,8 +388,52 @@ function formatPeriodLengthRecovery(maxLength) {
343
388
  }
344
389
  var MAX_FREE_TRIAL_DAYS = 730;
345
390
 
391
+ // ../../packages/mcp-contracts/dist/currency-precision.js
392
+ function currencyFractionDigits(currencyCode3) {
393
+ const code = currencyCode3.trim().toUpperCase();
394
+ let digits;
395
+ try {
396
+ digits = new Intl.NumberFormat("en-US", {
397
+ style: "currency",
398
+ currency: code
399
+ }).resolvedOptions().maximumFractionDigits;
400
+ } catch {
401
+ throw new Error(`Unsupported currency "${currencyCode3}"`);
402
+ }
403
+ if (typeof digits !== "number") {
404
+ throw new Error(`Unsupported currency "${currencyCode3}"`);
405
+ }
406
+ return digits;
407
+ }
408
+ var MINOR_UNIT_RELATIVE_TOLERANCE = Number.EPSILON * 16;
409
+ function amountExceedsCurrencyPrecision(amount, currencyCode3) {
410
+ const minorUnits = amount * 10 ** currencyFractionDigits(currencyCode3);
411
+ const drift = Math.abs(minorUnits - Math.round(minorUnits));
412
+ return drift > Math.abs(minorUnits) * MINOR_UNIT_RELATIVE_TOLERANCE;
413
+ }
414
+ function describeAmountPrecisionViolation(amount, currencyCode3) {
415
+ const fractionDigits = resolveFractionDigits(currencyCode3);
416
+ if (fractionDigits === void 0 || !amountExceedsCurrencyPrecision(amount, currencyCode3)) {
417
+ return void 0;
418
+ }
419
+ return formatAmountPrecisionMessage(currencyCode3, fractionDigits);
420
+ }
421
+ function describeRequestAmountPrecisionViolation(amount, currencyCode3) {
422
+ return describeAmountPrecisionViolation((0, import_currency.default)(amount).value, currencyCode3);
423
+ }
424
+ function resolveFractionDigits(currencyCode3) {
425
+ try {
426
+ return currencyFractionDigits(currencyCode3);
427
+ } catch {
428
+ return void 0;
429
+ }
430
+ }
431
+
346
432
  // ../../packages/mcp-contracts/dist/order-limits.js
347
- var MAX_ORDER_AMOUNT = MAX_PLAN_AMOUNT;
433
+ function getMaxOrderAmount(currency5) {
434
+ return getMaxPlanAmount(currency5);
435
+ }
436
+ var HIGHEST_MAX_ORDER_AMOUNT = HIGHEST_MAX_PLAN_AMOUNT;
348
437
  var MAX_ORDER_ITEM_QUANTITY = 1e4;
349
438
  var MAX_ORDER_PERIOD_LENGTH_BY_BILLING_PERIOD = {
350
439
  day: 365,
@@ -358,7 +447,11 @@ function getMaxOrderPeriodLength(billingPeriod) {
358
447
  var ORDER_PERIOD_CAPS = Object.entries(MAX_ORDER_PERIOD_LENGTH_BY_BILLING_PERIOD).map(([period, max]) => `${max} ${period}`).join(" / ");
359
448
  var ORDER_PERIOD_LENGTH_TOO_LONG_MESSAGE = `periodLength must be at most ${MAX_ORDER_PERIOD_LENGTH}, and at most one year of the chosen billingPeriod (${ORDER_PERIOD_CAPS}). Recovery: lower periodLength to the cap for your billingPeriod; nothing can bill less often than once a year.`;
360
449
  var HIGHER_LIMIT_RECOVERY = "or contact support if you need a higher per-order limit.";
361
- var ORDER_UNIT_PRICE_TOO_HIGH_MESSAGE = `unitPrice must be at most ${MAX_ORDER_AMOUNT}. Recovery: confirm the price with the user (amounts are in major units, so 19.99 means $19.99), ${HIGHER_LIMIT_RECOVERY}`;
450
+ var UNIT_PRICE_RECOVERY = `Recovery: confirm the price with the user (amounts are in major units, so 19.99 means $19.99), ${HIGHER_LIMIT_RECOVERY}`;
451
+ var ORDER_UNIT_PRICE_TOO_HIGH_MESSAGE = `unitPrice must be at most ${DEFAULT_MAX_PLAN_AMOUNT}, or the higher ceiling of the order currency (${formatHigherMaxAmounts()}). ${UNIT_PRICE_RECOVERY}`;
452
+ function formatOrderUnitPriceTooHighMessage(currency5) {
453
+ return `unitPrice must be at most ${formatOrderCeiling(currency5)}. ${UNIT_PRICE_RECOVERY}`;
454
+ }
362
455
  var ORDER_QUANTITY_TOO_HIGH_MESSAGE = `quantity must be at most ${MAX_ORDER_ITEM_QUANTITY}. Recovery: lower the quantity, or contact support if you need more units on one order line.`;
363
456
  var ORDER_TRIAL_PERIOD_DAYS_TOO_LONG_MESSAGE = `trialPeriodDays must be at most ${MAX_FREE_TRIAL_DAYS} days. Recovery: lower the trial length.`;
364
457
  var RECURRING_ORDER_ITEM_CADENCE_MESSAGE = 'billingPeriod and periodLength are required when type is "recurring". Recovery: send both, e.g. billingPeriod "month" with periodLength 1 to bill monthly, or billingPeriod "day" with periodLength 30 to bill every 30 days.';
@@ -368,48 +461,86 @@ var INTERVAL_DAYS_TOO_SHORT_MESSAGE = `intervalDays must be at least ${MIN_WHOP_
368
461
  function isBelowMinWhopBillingPeriod(billingPeriod, periodLength) {
369
462
  return billingPeriod === "day" && periodLength < MIN_WHOP_BILLING_PERIOD_DAYS;
370
463
  }
371
- function formatOrderLineTotalTooHighMessage(lineTotal2) {
372
- return `unitPrice x quantity is ${formatOrderAmount(lineTotal2)}; one order line can charge at most ${MAX_ORDER_AMOUNT}. Recovery: lower the quantity or the unitPrice, ${HIGHER_LIMIT_RECOVERY}`;
464
+ function formatOrderLineTotalTooHighMessage(lineTotal2, currency5) {
465
+ return `unitPrice x quantity is ${formatOrderAmount(lineTotal2, currency5)}; one order line can charge at most ${formatOrderCeiling(currency5)}. Recovery: lower the quantity or the unitPrice, ${HIGHER_LIMIT_RECOVERY}`;
373
466
  }
374
- function formatOrderTotalTooHighMessage(total, moment) {
375
- return `This order charges ${formatOrderAmount(total)} ${moment}; one order can charge at most ${MAX_ORDER_AMOUNT}. Recovery: remove items or lower quantities, ${HIGHER_LIMIT_RECOVERY}`;
467
+ function formatOrderTotalTooHighMessage(total, moment, currency5) {
468
+ return `This order charges ${formatOrderAmount(total, currency5)} ${moment}; one order can charge at most ${formatOrderCeiling(currency5)}. Recovery: remove items or lower quantities, ${HIGHER_LIMIT_RECOVERY}`;
376
469
  }
377
470
  function formatMaxOrderPeriodLengthMessage(billingPeriod, maxLength) {
378
471
  return `periodLength must be at most ${maxLength} when billingPeriod is "${billingPeriod}" (an order renews at least once a year). ${formatPeriodLengthRecovery(maxLength)}`;
379
472
  }
380
- function formatOrderAmount(amount) {
381
- return (0, import_currency.default)(amount).toString();
473
+ function formatOrderCeiling(currencyCode3) {
474
+ return `${getMaxOrderAmount(currencyCode3)} ${currencyCode3.trim().toUpperCase()}`;
475
+ }
476
+ function formatOrderAmount(amount, currencyCode3) {
477
+ return (0, import_currency2.default)(amount, { precision: fractionDigitsOrTwo(currencyCode3) }).toString();
478
+ }
479
+ function fractionDigitsOrTwo(currencyCode3) {
480
+ try {
481
+ return currencyFractionDigits(currencyCode3);
482
+ } catch {
483
+ return 2;
484
+ }
382
485
  }
383
486
 
384
487
  // ../../packages/mcp-contracts/dist/order-item-limits.js
385
- var import_currency2 = __toESM(require_currency(), 1);
488
+ var import_currency3 = __toESM(require_currency(), 1);
386
489
  var TRIAL_PERIOD_DAYS_NOT_RECURRING_MESSAGE = 'trialPeriodDays only applies to recurring custom items; a one_time line charges immediately. Recovery: remove trialPeriodDays, or use type "recurring" if the buyer should get a trial before the first charge.';
387
490
  function checkCustomOrderItemLimits(item, ctx) {
388
491
  checkRecurringCadence(item, ctx);
389
492
  checkTrialPeriodDaysIsRecurring(item, ctx);
390
493
  checkPeriodLengthCap(item, ctx);
391
494
  checkDailyCadence(item, ctx);
392
- checkLineTotal(item, ctx);
393
495
  }
394
- function checkTrialPeriodDaysIsRecurring(item, ctx) {
395
- if (item.type === "recurring" || item.trialPeriodDays === void 0)
496
+ function checkCustomOrderItemAmounts(body, ctx) {
497
+ body.items.forEach((item, index) => {
498
+ if (!isPricedItem(item))
499
+ return;
500
+ const issue2 = describeLineAmountIssue(item, body.currency);
501
+ if (issue2 === void 0)
502
+ return;
503
+ ctx.addIssue({ code: "custom", message: issue2.message, path: ["items", index, issue2.field] });
504
+ });
505
+ }
506
+ function checkCustomOrderItemsTotal(body, ctx) {
507
+ const items = body.items.filter(isPricedItem);
508
+ if (items.some((item) => hasLineIssue(item, body.currency)))
509
+ return;
510
+ const total = items.reduce((sum, item) => sum.add(lineTotal(item)), (0, import_currency3.default)(0));
511
+ if (total.value <= getMaxOrderAmount(body.currency))
396
512
  return;
397
513
  ctx.addIssue({
398
514
  code: "custom",
399
- message: TRIAL_PERIOD_DAYS_NOT_RECURRING_MESSAGE,
400
- path: ["trialPeriodDays"]
515
+ message: formatOrderTotalTooHighMessage(total.value, "today", body.currency),
516
+ path: ["items"]
401
517
  });
402
518
  }
403
- function checkCustomOrderItemsTotal(items, ctx) {
404
- if (items.some(exceedsLineCeiling))
405
- return;
406
- const total = items.reduce((sum, item) => sum.add(lineTotal(item)), (0, import_currency2.default)(0));
407
- if (total.value <= MAX_ORDER_AMOUNT)
519
+ function hasLineIssue(item, currencyCode3) {
520
+ return exceedsFieldCeiling(item) || describeLineAmountIssue(item, currencyCode3) !== void 0;
521
+ }
522
+ function describeLineAmountIssue(item, currencyCode3) {
523
+ if (exceedsFieldCeiling(item))
524
+ return void 0;
525
+ const maxAmount = getMaxOrderAmount(currencyCode3);
526
+ if (item.unitPrice > maxAmount) {
527
+ return { field: "unitPrice", message: formatOrderUnitPriceTooHighMessage(currencyCode3) };
528
+ }
529
+ const precision = describeRequestAmountPrecisionViolation(item.unitPrice, currencyCode3);
530
+ if (precision !== void 0)
531
+ return { field: "unitPrice", message: precision };
532
+ const total = lineTotal(item).value;
533
+ if (total <= maxAmount)
534
+ return void 0;
535
+ return { field: "quantity", message: formatOrderLineTotalTooHighMessage(total, currencyCode3) };
536
+ }
537
+ function checkTrialPeriodDaysIsRecurring(item, ctx) {
538
+ if (item.type === "recurring" || item.trialPeriodDays === void 0)
408
539
  return;
409
540
  ctx.addIssue({
410
541
  code: "custom",
411
- message: formatOrderTotalTooHighMessage(total.value, "today"),
412
- path: ["items"]
542
+ message: TRIAL_PERIOD_DAYS_NOT_RECURRING_MESSAGE,
543
+ path: ["trialPeriodDays"]
413
544
  });
414
545
  }
415
546
  function checkRecurringCadence(item, ctx) {
@@ -447,26 +578,14 @@ function checkDailyCadence(item, ctx) {
447
578
  path: ["periodLength"]
448
579
  });
449
580
  }
450
- function checkLineTotal(item, ctx) {
451
- if (exceedsFieldCeiling(item))
452
- return;
453
- const total = lineTotal(item);
454
- if (total.value <= MAX_ORDER_AMOUNT)
455
- return;
456
- ctx.addIssue({
457
- code: "custom",
458
- message: formatOrderLineTotalTooHighMessage(total.value),
459
- path: ["quantity"]
460
- });
461
- }
462
581
  function lineTotal(item) {
463
- return (0, import_currency2.default)(item.unitPrice).multiply(item.quantity);
582
+ return (0, import_currency3.default)(item.unitPrice).multiply(item.quantity);
464
583
  }
465
584
  function exceedsFieldCeiling(item) {
466
- return item.unitPrice > MAX_ORDER_AMOUNT || item.quantity > MAX_ORDER_ITEM_QUANTITY;
585
+ return item.unitPrice > HIGHEST_MAX_ORDER_AMOUNT || item.quantity > MAX_ORDER_ITEM_QUANTITY;
467
586
  }
468
- function exceedsLineCeiling(item) {
469
- return exceedsFieldCeiling(item) || lineTotal(item).value > MAX_ORDER_AMOUNT;
587
+ function isPricedItem(item) {
588
+ return typeof item === "object" && item !== null && "unitPrice" in item && typeof item.unitPrice === "number" && "quantity" in item && typeof item.quantity === "number";
470
589
  }
471
590
 
472
591
  // ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/external.js
@@ -20170,7 +20289,7 @@ var createOrderPlanItemSchema = external_exports.object({
20170
20289
  }).strict();
20171
20290
  var createOrderCustomItemSchema = external_exports.object({
20172
20291
  label: external_exports.string().trim().min(1).max(255),
20173
- unitPrice: external_exports.number().positive().max(MAX_ORDER_AMOUNT, ORDER_UNIT_PRICE_TOO_HIGH_MESSAGE).describe("Price of one unit in major units of the order currency (decimal dollars, not cents): 10 means $10.00, 1999 means $1,999.00."),
20292
+ unitPrice: external_exports.number().positive().max(HIGHEST_MAX_ORDER_AMOUNT, ORDER_UNIT_PRICE_TOO_HIGH_MESSAGE).describe("Price of one unit in major units of the order currency (decimal dollars, not cents): 10 means $10.00, 1999 means $1,999.00. A currency with no minor unit takes whole numbers: 500 means \xA5500."),
20174
20293
  quantity: orderQuantitySchema,
20175
20294
  type: external_exports.enum(ORDER_ITEM_TYPES),
20176
20295
  billingPeriod: external_exports.enum(ORDER_BILLING_PERIODS).optional(),
@@ -20186,7 +20305,10 @@ var createOrderInputSchema = external_exports.object({
20186
20305
  currency: supportedCurrencyCodeSchema,
20187
20306
  items: external_exports.array(createOrderItemSchema).min(1).max(MAX_ORDER_ITEMS),
20188
20307
  idempotencyKey: external_exports.string().trim().min(1).max(MAX_IDEMPOTENCY_KEY_LENGTH).optional()
20189
- }).strict().superRefine((input2, ctx) => checkCustomOrderItemsTotal(input2.items.filter((item) => "unitPrice" in item), ctx));
20308
+ }).strict().superRefine((input2, ctx) => {
20309
+ checkCustomOrderItemAmounts(input2, ctx);
20310
+ checkCustomOrderItemsTotal(input2, ctx);
20311
+ });
20190
20312
  var createOrderItemResponseSchema = external_exports.object({
20191
20313
  label: external_exports.string(),
20192
20314
  unitPrice: external_exports.number(),
@@ -20241,13 +20363,13 @@ function formatPlanNameTooLong(length) {
20241
20363
  function formatPlanDescriptionTooLong(length) {
20242
20364
  return `Plan description is ${length} characters; max ${PLAN_DESCRIPTION_MAX}. Recovery: shorten the description to <=${PLAN_DESCRIPTION_MAX} characters \u2014 it is a one-line caption, so keep the pricing detail that distinguishes this plan \u2014 and move the full copy into this plan's richDescription (up to ${PLAN_RICH_DESCRIPTION_MAX} characters), which the hosted checkout page renders beneath it.`;
20243
20365
  }
20244
- var PLAN_AMOUNT_DESCRIPTION = "Price in major units of `currency` (decimal dollars, not cents): 19.99 means $19.99 and 1999 means $1,999.00. Stored as 0 on a free-access plan.";
20366
+ var PLAN_AMOUNT_DESCRIPTION = "Price in major units of `currency` (decimal dollars, not cents): 19.99 means $19.99 and 1999 means $1,999.00. A currency with no minor unit takes whole numbers: 500 means \xA5500. Stored as 0 on a free-access plan.";
20245
20367
  var planShape = {
20246
20368
  name: external_exports.string().trim().min(1).max(PLAN_NAME_MAX, tooLongError(formatPlanNameTooLong)),
20247
20369
  description: external_exports.string().trim().max(PLAN_DESCRIPTION_MAX, tooLongError(formatPlanDescriptionTooLong)).optional(),
20248
20370
  richDescription: external_exports.string().trim().max(PLAN_RICH_DESCRIPTION_MAX).optional(),
20249
20371
  currency: supportedCurrencyCodeSchema,
20250
- amount: external_exports.number().nonnegative().max(MAX_PLAN_AMOUNT, `Amount must be at most ${MAX_PLAN_AMOUNT}. Recovery: lower the plan amount, or contact support if you need a higher per-plan limit.`).describe(PLAN_AMOUNT_DESCRIPTION),
20372
+ amount: external_exports.number().nonnegative().max(HIGHEST_MAX_PLAN_AMOUNT, formatAmountAboveCeilingMessage()).describe(PLAN_AMOUNT_DESCRIPTION),
20251
20373
  type: external_exports.enum(PLAN_TYPES2),
20252
20374
  billingPeriod: external_exports.enum(BILLING_PERIODS2),
20253
20375
  periodLength: external_exports.number().int().positive(),
@@ -20265,6 +20387,8 @@ var createMcpPlanSchema = external_exports.strictObject(planShape, { error: unre
20265
20387
  checkPlanPeriodLength(plan, ctx);
20266
20388
  checkFreeTrialIsRecurring(plan, ctx);
20267
20389
  checkPaidPlanMinimum(plan, ctx);
20390
+ checkPlanCeiling(plan, ctx);
20391
+ checkPaidPlanPrecision(plan, ctx);
20268
20392
  });
20269
20393
  function checkPlanPeriodLength(plan, ctx) {
20270
20394
  const maxPeriodLength = MAX_PERIOD_LENGTH_BY_BILLING_PERIOD[plan.billingPeriod];
@@ -20297,6 +20421,24 @@ function checkPaidPlanMinimum(plan, ctx) {
20297
20421
  path: ["amount"]
20298
20422
  });
20299
20423
  }
20424
+ function checkPlanCeiling(plan, ctx) {
20425
+ if (plan.amount > HIGHEST_MAX_PLAN_AMOUNT || plan.amount <= getMaxPlanAmount(plan.currency)) {
20426
+ return;
20427
+ }
20428
+ ctx.addIssue({
20429
+ code: "custom",
20430
+ message: formatMaxPlanAmountMessage(plan.currency),
20431
+ path: ["amount"]
20432
+ });
20433
+ }
20434
+ function checkPaidPlanPrecision(plan, ctx) {
20435
+ if (plan.type === "free-access")
20436
+ return;
20437
+ const message = describeRequestAmountPrecisionViolation(plan.amount, plan.currency);
20438
+ if (message === void 0)
20439
+ return;
20440
+ ctx.addIssue({ code: "custom", message, path: ["amount"] });
20441
+ }
20300
20442
  var productBodyBaseSchema = external_exports.object({
20301
20443
  name: external_exports.string().trim().min(1).max(PRODUCT_NAME_MAX, tooLongError(formatProductNameTooLong)).refine((name) => !containsMarkupTag(name), PRODUCT_NAME_MARKUP_MESSAGE),
20302
20444
  description: external_exports.string().trim().min(1).max(PRODUCT_DESCRIPTION_MAX, tooLongError(formatProductDescriptionTooLong)).optional(),
@@ -20385,7 +20527,7 @@ var updatePlanFieldsSchema = external_exports.object({
20385
20527
  description: external_exports.string().trim().max(PLAN_DESCRIPTION_MAX, tooLongError3(formatPlanDescriptionTooLong2)).optional(),
20386
20528
  billingPeriod: external_exports.enum(BILLING_PERIODS3).optional(),
20387
20529
  periodLength: external_exports.number().int().positive().optional(),
20388
- amount: external_exports.number().nonnegative().max(MAX_PLAN_AMOUNT, formatMaxPlanAmountMessage()).refine((amount) => amount === 0 || amount >= LOWEST_MIN_PAID_PLAN_AMOUNT, {
20530
+ amount: external_exports.number().nonnegative().max(HIGHEST_MAX_PLAN_AMOUNT, formatAmountAboveCeilingMessage()).refine((amount) => amount === 0 || amount >= LOWEST_MIN_PAID_PLAN_AMOUNT, {
20389
20531
  error: formatAmountBelowLowestFloorMessage()
20390
20532
  }).optional(),
20391
20533
  freeTrial: external_exports.number().int().min(0).max(MAX_FREE_TRIAL_DAYS, `freeTrial must be at most ${MAX_FREE_TRIAL_DAYS} days. Recovery: lower the trial length.`).optional()
@@ -20435,6 +20577,9 @@ var updateMcpPlanBackendBodySchema = external_exports.object({
20435
20577
 
20436
20578
  // ../../packages/mcp-contracts/dist/resource-url.js
20437
20579
  function canonicalResourceUrl(value) {
20580
+ if (typeof value !== "string" || value.length === 0) {
20581
+ return null;
20582
+ }
20438
20583
  if (value.includes("#")) {
20439
20584
  return null;
20440
20585
  }
@@ -25282,13 +25427,17 @@ var SANDBOX_TEST_CARD_NOTE = "To pay a sandbox checkout link, use test card 4242
25282
25427
 
25283
25428
  // src/tools/tool-descriptions.ts
25284
25429
  var CREATE_MERCHANT_CONTACT_GUIDANCE = "Requires only name and email. The email must be the user's own real address, asked from the user or taken from the conversation: example or placeholder addresses (such as you@example.com) and addresses that cannot receive mail are rejected. Phone is optional. If you pass one it must be the user's own mobile number, taken from the conversation or asked from the user; never invent a placeholder or a fictional number (such as a 555 number), because after go_live the identity-verification link is sent to it by SMS and a number that cannot be dialled is rejected. Use E.164 with country code (format only: +14155552671). If the user does not share a phone, omit it and the link goes by email.";
25285
- var CREATE_PRODUCT_PLAN_BOUNDS = `A plan \`amount\` is at most ${MAX_PLAN_AMOUNT}, \`periodLength\` at most one year of the chosen billingPeriod (${formatPeriodCaps(MAX_PERIOD_LENGTH_BY_BILLING_PERIOD)}), and \`freeTrial\` at most ${MAX_FREE_TRIAL_DAYS} days and only on recurring plans. A plan accepts only its documented keys; an unknown key (such as freeTrialDays) is rejected, not ignored.`;
25430
+ var CREATE_PRODUCT_PLAN_BOUNDS = `A plan \`amount\` is at most ${formatAmountCeilings()} in the plan currency, \`periodLength\` at most one year of the chosen billingPeriod (${formatPeriodCaps(MAX_PERIOD_LENGTH_BY_BILLING_PERIOD)}), and \`freeTrial\` at most ${MAX_FREE_TRIAL_DAYS} days and only on recurring plans. A plan accepts only its documented keys; an unknown key (such as freeTrialDays) is rejected, not ignored.`;
25431
+ function formatAmountCeilings() {
25432
+ const higher = Object.entries(MAX_PLAN_AMOUNT_BY_CURRENCY).filter(([, max]) => max > DEFAULT_MAX_PLAN_AMOUNT).map(([currency5, max]) => `${max} ${currency5.toUpperCase()}`);
25433
+ return higher.length > 0 ? `${DEFAULT_MAX_PLAN_AMOUNT} (${higher.join(", ")})` : `${DEFAULT_MAX_PLAN_AMOUNT}`;
25434
+ }
25286
25435
  function formatPeriodCaps(caps) {
25287
25436
  return Object.entries(caps).map(([period, max]) => `${max} ${period}`).join(" / ");
25288
25437
  }
25289
- var PRICE_UNITS = `a decimal dollar amount in the plan currency (major units, e.g. 9.99 for $9.99 \u2014 NOT cents, so 1999 means $1,999.00; currency is one of ${SUPPORTED_CURRENCIES.join(", ")})`;
25438
+ var PRICE_UNITS = `a decimal dollar amount in the plan currency (major units, e.g. 9.99 for $9.99 \u2014 NOT cents, so 1999 means $1,999.00; a currency with no minor unit takes whole numbers, so 500 means \xA5500 and 500.5 is rejected; currency is one of ${SUPPORTED_CURRENCIES.join(", ")})`;
25290
25439
  var CREATE_PRODUCT_STORED_PLANS = "`plans` repeats what was stored for each plan, in `planIds` order: amount, currency, `price` formatted as the checkout shows it, and for recurring plans billingPeriod, periodLength and freeTrial \u2014 check `price` against what the user asked for. `warnings`, when present, flags an amount that looks like cents; relay it and, if the user meant the smaller price, fix it with update_plan before sharing the link.";
25291
- var CREATE_ORDER_BOUNDS = `currency is one of ${SUPPORTED_CURRENCIES.join(", ")}. unitPrice is at most ${MAX_ORDER_AMOUNT}, and so is each line (unitPrice x quantity) and the order total; quantity is at most ${MAX_ORDER_ITEM_QUANTITY}; periodLength is at most one year of the billingPeriod (${formatPeriodCaps(MAX_ORDER_PERIOD_LENGTH_BY_BILLING_PERIOD)}). When billingPeriod is "day", periodLength must be at least ${MIN_WHOP_BILLING_PERIOD_DAYS} (the payment provider rejects true daily subscriptions). trialPeriodDays is at most ${MAX_FREE_TRIAL_DAYS}, only on recurring custom lines, and must match the trial length of every other recurring line in the order (catalogue plan or custom).`;
25440
+ var CREATE_ORDER_BOUNDS = `currency is one of ${SUPPORTED_CURRENCIES.join(", ")}. unitPrice is at most ${formatAmountCeilings()} in the order currency, and so is each line (unitPrice x quantity) and the order total; quantity is at most ${MAX_ORDER_ITEM_QUANTITY}; periodLength is at most one year of the billingPeriod (${formatPeriodCaps(MAX_ORDER_PERIOD_LENGTH_BY_BILLING_PERIOD)}). When billingPeriod is "day", periodLength must be at least ${MIN_WHOP_BILLING_PERIOD_DAYS} (the payment provider rejects true daily subscriptions). trialPeriodDays is at most ${MAX_FREE_TRIAL_DAYS}, only on recurring custom lines, and must match the trial length of every other recurring line in the order (catalogue plan or custom).`;
25292
25441
  var CREATE_PRODUCT_STATUS_NOTE = "Optional `status` ('draft' | 'active' | 'archived') defaults to 'active' \u2014 the product is created live and purchasable immediately via its checkout link, regardless of status. Pass `status: 'draft'` only when the end user explicitly asks for a draft or temporary/not-yet-ready product.";
25293
25442
  var CREATE_PRODUCT_COPY_CAPS = `Product \`name\` (<=${PRODUCT_NAME_MAX} chars), product \`description\` (<=${PRODUCT_DESCRIPTION_MAX} chars) and plan \`description\` (<=${PLAN_DESCRIPTION_MAX} chars) are all short one-line copy \u2014 if yours is longer, shorten it before calling and put the full version in that plan's \`richDescription\` (<=${PLAN_RICH_DESCRIPTION_MAX} chars), which the hosted checkout page renders as plain text beneath the short description (newlines preserved, markup not interpreted). \`richDescription\` is per plan, so product-level copy that overflows must still be edited down.`;
25294
25443
  var TRANSACTION_STATUS_RULE = 'Status: only "succeeded" means paid. "pending" is not yet confirmed by the payment provider, so never report a pending transaction as paid. "partially_refunded" means some but not all of the amount was refunded; "refunded", "failed" and "disputed" mean what they say.';
@@ -25650,18 +25799,17 @@ function buildPlanCheckoutUrl(params) {
25650
25799
  }
25651
25800
 
25652
25801
  // src/tools/product/stored-plan-summary.ts
25653
- var import_currency4 = __toESM(require_currency(), 1);
25802
+ var import_currency5 = __toESM(require_currency(), 1);
25654
25803
 
25655
25804
  // src/shared/format-price.ts
25656
- var import_currency3 = __toESM(require_currency(), 1);
25805
+ var import_currency4 = __toESM(require_currency(), 1);
25657
25806
  function formatPrice(amount, currencyCode3) {
25658
- const price = (0, import_currency3.default)(amount);
25807
+ const price = (0, import_currency4.default)(amount);
25659
25808
  const code = currencyCode3.toUpperCase();
25660
25809
  try {
25661
25810
  return new Intl.NumberFormat("en-US", {
25662
25811
  style: "currency",
25663
- currency: code,
25664
- minimumFractionDigits: 2
25812
+ currency: code
25665
25813
  }).format(price.value);
25666
25814
  } catch {
25667
25815
  return `${price.toString()} ${code}`;
@@ -25702,12 +25850,19 @@ function buildAmountWarnings(plans) {
25702
25850
  );
25703
25851
  }
25704
25852
  function looksLikeCents(plan) {
25705
- return plan.type !== "free-access" && Number.isInteger(plan.amount) && plan.amount >= CENTS_SUSPECT_FLOOR_MULTIPLE * getMinPaidPlanAmount(plan.currency);
25853
+ return plan.type !== "free-access" && hasMinorUnit(plan.currency) && Number.isInteger(plan.amount) && plan.amount >= CENTS_SUSPECT_FLOOR_MULTIPLE * getMinPaidPlanAmount(plan.currency);
25706
25854
  }
25707
25855
  function formatCentsWarning(plan, index) {
25708
- const asCents = (0, import_currency4.default)(plan.amount).divide(100).value;
25856
+ const asCents = (0, import_currency5.default)(plan.amount).divide(100).value;
25709
25857
  return `plans.${index} ("${plan.name}"): amount ${plan.amount} is stored as ${formatPrice(plan.amount, plan.currency)}, because amounts are in major units, not cents. If the user meant ${formatPrice(asCents, plan.currency)}, call update_plan with amount ${asCents} before sharing the checkout link; otherwise no action is needed.`;
25710
25858
  }
25859
+ function hasMinorUnit(currencyCode3) {
25860
+ try {
25861
+ return currencyFractionDigits(currencyCode3) > 0;
25862
+ } catch {
25863
+ return true;
25864
+ }
25865
+ }
25711
25866
 
25712
25867
  // src/tools/product/create-product.tool.ts
25713
25868
  var backendCreatedProductShapeSchema = external_exports.object({
@@ -27810,6 +27965,7 @@ var DEFAULT_ALLOWED_MCP_ORIGIN_HOSTNAMES = [
27810
27965
  "grok.com",
27811
27966
  "www.grok.com",
27812
27967
  "x.ai",
27968
+ "antigravity.google",
27813
27969
  "gemini.google.com",
27814
27970
  "aistudio.google.com",
27815
27971
  "cursor.com",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payments-ai/mcp",
3
- "version": "2.4.1",
3
+ "version": "2.5.0",
4
4
  "private": false,
5
5
  "description": "MCP for Payments AI",
6
6
  "repository": {