@solvapay/server 2.5.0 → 2.6.0-preview-09731417a0a7b4fd3afb4783fdf20b5ad293f63d
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/edge.d.ts +21 -1
- package/dist/edge.js +68 -24
- package/dist/fetch/index.cjs +63 -21
- package/dist/fetch/index.d.cts +4 -0
- package/dist/fetch/index.d.ts +4 -0
- package/dist/fetch/index.js +64 -22
- package/dist/index.cjs +67 -23
- package/dist/index.d.cts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +68 -24
- package/package.json +3 -3
package/dist/edge.d.ts
CHANGED
|
@@ -831,6 +831,10 @@ interface components {
|
|
|
831
831
|
currency: string;
|
|
832
832
|
/** @description How many metered items the credit balance still covers (`balance / creditsPerUnit`) */
|
|
833
833
|
remainingUnits?: number;
|
|
834
|
+
/** @description Credits per minor unit of `currency` (typically 100) */
|
|
835
|
+
creditsPerMinorUnit?: number;
|
|
836
|
+
/** @description USD → display-currency rate used with creditsPerMinorUnit */
|
|
837
|
+
displayExchangeRate?: number;
|
|
834
838
|
};
|
|
835
839
|
LimitPlanItemDto: {
|
|
836
840
|
/** @description Derived billing cycle */
|
|
@@ -4291,6 +4295,10 @@ type PaywallGateRecoveryFields = {
|
|
|
4291
4295
|
shortfallCredits?: number;
|
|
4292
4296
|
/** How many calls the current allowance or wallet still covers. */
|
|
4293
4297
|
remainingCalls?: number;
|
|
4298
|
+
/** Credits per minor currency unit, from the limits balance peg. */
|
|
4299
|
+
creditsPerMinorUnit?: number;
|
|
4300
|
+
/** USD → display-currency rate used with `creditsPerMinorUnit`. */
|
|
4301
|
+
displayExchangeRate?: number;
|
|
4294
4302
|
/** Active purchase reference when the customer already holds a purchase. */
|
|
4295
4303
|
purchaseRef?: string;
|
|
4296
4304
|
/** Purchase/plan status when the backend sent one. */
|
|
@@ -5354,6 +5362,12 @@ interface SolvaPay {
|
|
|
5354
5362
|
*/
|
|
5355
5363
|
paywall: {
|
|
5356
5364
|
decide<TArgs extends PaywallArgs>(args: TArgs, metadata?: PaywallMetadata, getCustomerRef?: (args: TArgs) => string): Promise<PaywallDecision<TArgs>>;
|
|
5365
|
+
/**
|
|
5366
|
+
* Drop cached `checkLimits` snapshots for this customer (optionally
|
|
5367
|
+
* scoped to one product). Call after `activate_plan`, a completed
|
|
5368
|
+
* checkout, or a top-up so the next payable call refetches.
|
|
5369
|
+
*/
|
|
5370
|
+
invalidateLimits(customerRef: string, product?: string): void;
|
|
5357
5371
|
};
|
|
5358
5372
|
/**
|
|
5359
5373
|
* Direct access to the API client for advanced operations.
|
|
@@ -6144,7 +6158,13 @@ declare function processTopupPaymentIntentCore(request: Request, body: {
|
|
|
6144
6158
|
declare function createCheckoutSessionCore(request: Request, body: {
|
|
6145
6159
|
productRef: string;
|
|
6146
6160
|
planRef?: string;
|
|
6147
|
-
|
|
6161
|
+
/**
|
|
6162
|
+
* URL to send the customer back to after payment. Pass `null` to omit
|
|
6163
|
+
* the field entirely — skips `options.returnUrl` and the request-origin
|
|
6164
|
+
* fallback. Use this when the caller has no browsable page to return to
|
|
6165
|
+
* (MCP servers whose public URL is an API endpoint, not a website).
|
|
6166
|
+
*/
|
|
6167
|
+
returnUrl?: string | null;
|
|
6148
6168
|
purpose?: 'credit_topup';
|
|
6149
6169
|
}, options?: {
|
|
6150
6170
|
solvaPay?: SolvaPay;
|
package/dist/edge.js
CHANGED
|
@@ -683,9 +683,8 @@ async function verifyProductConfiguration(options) {
|
|
|
683
683
|
}
|
|
684
684
|
|
|
685
685
|
// src/paywall-state.ts
|
|
686
|
-
import { minorUnitsPerMajor } from "@solvapay/core";
|
|
686
|
+
import { creditsToDisplayMinorUnits, minorUnitsPerMajor } from "@solvapay/core";
|
|
687
687
|
var CHECKOUT_SESSION_TTL_MINUTES = 15;
|
|
688
|
-
var DOCS_HINT = "See docs://solvapay/overview.md.";
|
|
689
688
|
function creditSignals(limits) {
|
|
690
689
|
if (!limits) return { isCreditBased: false };
|
|
691
690
|
const creditBalance = limits.balance?.creditBalance ?? limits.creditBalance;
|
|
@@ -769,6 +768,24 @@ function formatMinor(amountMinor, currency) {
|
|
|
769
768
|
function formatCredits(value) {
|
|
770
769
|
return value.toLocaleString("en-US");
|
|
771
770
|
}
|
|
771
|
+
function formatCreditsWithMoney(credits, gate) {
|
|
772
|
+
const amount = formatCredits(credits);
|
|
773
|
+
const peg = gate.creditsPerMinorUnit;
|
|
774
|
+
const currency = gate.currency;
|
|
775
|
+
const rate = gate.displayExchangeRate;
|
|
776
|
+
if (peg && peg > 0 && currency && rate != null && rate > 0) {
|
|
777
|
+
const minor = creditsToDisplayMinorUnits({
|
|
778
|
+
credits,
|
|
779
|
+
creditsPerMinorUnit: peg,
|
|
780
|
+
displayExchangeRate: rate,
|
|
781
|
+
displayCurrency: currency
|
|
782
|
+
});
|
|
783
|
+
if (minor != null) {
|
|
784
|
+
return `${amount} credits (~${formatMinor(minor, currency)})`;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return `${amount} credits`;
|
|
788
|
+
}
|
|
772
789
|
function meterLabel(gate) {
|
|
773
790
|
if (!gate.meterName) return "units";
|
|
774
791
|
return gate.meterName.replace(/_/g, " ");
|
|
@@ -812,49 +829,51 @@ function buildGateMessage(state, gate) {
|
|
|
812
829
|
const nextLine = price ? ` The next call is ${price}.` : "";
|
|
813
830
|
const planName = gate.planName ?? "This plan";
|
|
814
831
|
const antiTrap = gate.creditBalance !== void 0 && gate.creditBalance > 0 && (gate.creditsPerCall === void 0 || gate.creditsPerCall === 0) ? ` Adding credits will not help, because ${planName} does not spend them.` : "";
|
|
815
|
-
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : recoverClause(url, "continue", "checkout");
|
|
816
|
-
return `${usedLine}${nextLine}${antiTrap}${switchLine}
|
|
832
|
+
const switchLine = ladder ? ` Or switch plan: ${ladder}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.` : recoverClause(url, "continue", "checkout");
|
|
833
|
+
return `${usedLine}${nextLine}${antiTrap}${switchLine}`;
|
|
817
834
|
}
|
|
818
835
|
case "activation_required":
|
|
819
|
-
return `Your plan needs activation.${recoverClause(url, "activate", "checkout")} Or call \`activate_plan\` with a \`planRef
|
|
836
|
+
return `Your plan needs activation.${recoverClause(url, "activate", "checkout")} Or call \`activate_plan\` with a \`planRef\`.`;
|
|
820
837
|
case "topup_required": {
|
|
821
838
|
const balance = gate.creditBalance;
|
|
822
839
|
const cost = gate.creditsPerCall;
|
|
823
840
|
const shortfall = gate.shortfallCredits;
|
|
824
841
|
let lead;
|
|
825
842
|
if (balance !== void 0 && cost !== void 0 && shortfall !== void 0) {
|
|
826
|
-
lead = `Out of credits for this call. Balance ${
|
|
843
|
+
lead = `Out of credits for this call. Balance ${formatCreditsWithMoney(balance, gate)}; this call costs ${formatCreditsWithMoney(cost, gate)} \u2014 ${formatCredits(shortfall)} short.`;
|
|
827
844
|
} else {
|
|
828
845
|
lead = "Included usage is exhausted.";
|
|
829
846
|
}
|
|
830
847
|
const topup = recoverClause(url, "add credits", "topup", "Add credits");
|
|
831
|
-
const auto = autoRechargeDisabled(gate) ?
|
|
832
|
-
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : ""
|
|
833
|
-
return `${lead}${topup}${auto}${switchLine}
|
|
848
|
+
const auto = autoRechargeDisabled(gate) ? ` Auto-recharge is off \u2014 ${callViewer("account")} to turn it on and avoid this next time.` : "";
|
|
849
|
+
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : hasActivePlan(gate) ? "" : ` Or ${callViewer("checkout")} to pick a plan.`;
|
|
850
|
+
return `${lead}${topup}${auto}${switchLine}`;
|
|
834
851
|
}
|
|
835
852
|
case "upgrade_required": {
|
|
836
853
|
if (hasActivePlan(gate)) {
|
|
837
854
|
const planName = gate.planName ?? "This plan";
|
|
838
855
|
const lead = `${planName} is active but its included usage is exhausted, and the automatic switch to the next plan did not complete.`;
|
|
839
856
|
if (ladder) {
|
|
840
|
-
return `${lead} Switch here: ${ladder}. ${
|
|
857
|
+
return `${lead} Switch here: ${ladder}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.`;
|
|
841
858
|
}
|
|
842
|
-
return `${lead}${recoverClause(url, "switch plan", "checkout")}
|
|
859
|
+
return `${lead}${recoverClause(url, "switch plan", "checkout")}`;
|
|
843
860
|
}
|
|
844
861
|
if (ladder) {
|
|
845
|
-
return `You don't have an active plan for this tool. Pick a plan to use this tool: ${ladder} (links expire in ${CHECKOUT_SESSION_TTL_MINUTES} minutes), or ${callViewer("checkout")}. ${
|
|
862
|
+
return `You don't have an active plan for this tool. Pick a plan to use this tool: ${ladder} (links expire in ${CHECKOUT_SESSION_TTL_MINUTES} minutes), or ${callViewer("checkout")}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.`;
|
|
846
863
|
}
|
|
847
|
-
return `You don't have an active plan for this tool.${recoverClause(url, "pick a plan", "checkout")}
|
|
864
|
+
return `You don't have an active plan for this tool.${recoverClause(url, "pick a plan", "checkout")}`;
|
|
848
865
|
}
|
|
849
866
|
case "reactivation_required":
|
|
850
|
-
return `Your previous plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it, or ${callViewer("checkout")} to pick a new plan
|
|
867
|
+
return `Your previous plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it, or ${callViewer("checkout")} to pick a new plan.`;
|
|
851
868
|
}
|
|
852
869
|
}
|
|
853
870
|
function buildNudgeMessage(state, limits) {
|
|
854
871
|
const url = limits?.checkoutUrl && limits.checkoutUrl.length > 0 ? limits.checkoutUrl : null;
|
|
855
872
|
const visitClause = url ? `, or ${namedCheckoutMarkdown(url)}` : "";
|
|
856
|
-
const
|
|
857
|
-
|
|
873
|
+
const signals = creditSignals(limits);
|
|
874
|
+
const remaining = signals.remainingCalls;
|
|
875
|
+
const kind = state.kind === "topup_required" && limits && !signals.isCreditBased ? "limit_reached" : state.kind;
|
|
876
|
+
switch (kind) {
|
|
858
877
|
case "topup_required":
|
|
859
878
|
if (remaining === 0) {
|
|
860
879
|
return `Heads up \u2014 0 calls left \u2014 the next call needs a top-up. ${callViewer("topup").replace(/^c/, "C")} to add more${visitClause}.`;
|
|
@@ -939,6 +958,8 @@ function recoveryFields(limits, state) {
|
|
|
939
958
|
...signals.creditsPerCall !== void 0 ? { creditsPerCall: signals.creditsPerCall } : {},
|
|
940
959
|
...signals.shortfallCredits !== void 0 ? { shortfallCredits: signals.shortfallCredits } : {},
|
|
941
960
|
...signals.remainingCalls !== void 0 ? { remainingCalls: signals.remainingCalls } : {},
|
|
961
|
+
...limits.balance?.creditsPerMinorUnit !== void 0 ? { creditsPerMinorUnit: limits.balance.creditsPerMinorUnit } : {},
|
|
962
|
+
...limits.balance?.displayExchangeRate !== void 0 ? { displayExchangeRate: limits.balance.displayExchangeRate } : {},
|
|
942
963
|
...limits.purchaseRef !== void 0 ? { purchaseRef: limits.purchaseRef } : {},
|
|
943
964
|
...limits.autoRecharge !== void 0 ? { autoRecharge: limits.autoRecharge } : {},
|
|
944
965
|
...links ? { links } : {}
|
|
@@ -1195,6 +1216,23 @@ var SolvaPayPaywall = class {
|
|
|
1195
1216
|
console.log(...args);
|
|
1196
1217
|
}
|
|
1197
1218
|
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Drop cached limits for `customerRef`. When `product` is omitted every
|
|
1221
|
+
* meter/product entry for that customer is removed. Matches both the
|
|
1222
|
+
* backend `cus_…` ref and an unmapped external ref used as a cache key.
|
|
1223
|
+
*/
|
|
1224
|
+
invalidateLimits(customerRef, product) {
|
|
1225
|
+
const aliases = /* @__PURE__ */ new Set([customerRef]);
|
|
1226
|
+
const mapped = this.customerRefMapping.get(customerRef);
|
|
1227
|
+
if (mapped) aliases.add(mapped);
|
|
1228
|
+
if (!customerRef.startsWith("cus_")) aliases.add(`cus_${customerRef}`);
|
|
1229
|
+
for (const key of [...this.limitsCache.keys()]) {
|
|
1230
|
+
const [ref, prod] = key.split(":");
|
|
1231
|
+
if (!ref || !aliases.has(ref)) continue;
|
|
1232
|
+
if (product && prod !== product) continue;
|
|
1233
|
+
this.limitsCache.delete(key);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1198
1236
|
resolveProduct(metadata) {
|
|
1199
1237
|
return requireProductRef(metadata.product);
|
|
1200
1238
|
}
|
|
@@ -2214,6 +2252,9 @@ function createSolvaPay(config) {
|
|
|
2214
2252
|
paywall: {
|
|
2215
2253
|
decide(args, metadata, getCustomerRef) {
|
|
2216
2254
|
return paywall.decide(args, metadata, getCustomerRef);
|
|
2255
|
+
},
|
|
2256
|
+
invalidateLimits(customerRef, product) {
|
|
2257
|
+
paywall.invalidateLimits(customerRef, product);
|
|
2217
2258
|
}
|
|
2218
2259
|
},
|
|
2219
2260
|
// Common API methods exposed directly for convenience
|
|
@@ -2282,7 +2323,7 @@ function createSolvaPay(config) {
|
|
|
2282
2323
|
customerRef: params.customerRef,
|
|
2283
2324
|
productRef: params.productRef,
|
|
2284
2325
|
planRef: params.planRef,
|
|
2285
|
-
returnUrl: params.returnUrl,
|
|
2326
|
+
...params.returnUrl ? { returnUrl: params.returnUrl } : {},
|
|
2286
2327
|
...params.purpose ? { purpose: params.purpose } : {}
|
|
2287
2328
|
});
|
|
2288
2329
|
},
|
|
@@ -2981,12 +3022,15 @@ async function createCheckoutSessionCore(request, body, options = {}) {
|
|
|
2981
3022
|
return customerResult;
|
|
2982
3023
|
}
|
|
2983
3024
|
const customerRef = customerResult;
|
|
2984
|
-
let returnUrl
|
|
2985
|
-
if (
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
3025
|
+
let returnUrl;
|
|
3026
|
+
if (body.returnUrl !== null) {
|
|
3027
|
+
returnUrl = body.returnUrl || options.returnUrl;
|
|
3028
|
+
if (!returnUrl) {
|
|
3029
|
+
try {
|
|
3030
|
+
const url = new URL(request.url);
|
|
3031
|
+
returnUrl = url.origin;
|
|
3032
|
+
} catch {
|
|
3033
|
+
}
|
|
2990
3034
|
}
|
|
2991
3035
|
}
|
|
2992
3036
|
const solvaPay = options.solvaPay || createSolvaPay();
|
|
@@ -2994,7 +3038,7 @@ async function createCheckoutSessionCore(request, body, options = {}) {
|
|
|
2994
3038
|
productRef: body.productRef,
|
|
2995
3039
|
customerRef,
|
|
2996
3040
|
planRef: body.planRef || void 0,
|
|
2997
|
-
returnUrl,
|
|
3041
|
+
...returnUrl ? { returnUrl } : {},
|
|
2998
3042
|
...body.purpose ? { purpose: body.purpose } : {}
|
|
2999
3043
|
});
|
|
3000
3044
|
return {
|
package/dist/fetch/index.cjs
CHANGED
|
@@ -841,7 +841,6 @@ function createSolvaPayClient(opts) {
|
|
|
841
841
|
// src/paywall-state.ts
|
|
842
842
|
var import_core3 = require("@solvapay/core");
|
|
843
843
|
var CHECKOUT_SESSION_TTL_MINUTES = 15;
|
|
844
|
-
var DOCS_HINT = "See docs://solvapay/overview.md.";
|
|
845
844
|
function creditSignals(limits) {
|
|
846
845
|
if (!limits) return { isCreditBased: false };
|
|
847
846
|
const creditBalance = limits.balance?.creditBalance ?? limits.creditBalance;
|
|
@@ -925,6 +924,24 @@ function formatMinor(amountMinor, currency) {
|
|
|
925
924
|
function formatCredits(value) {
|
|
926
925
|
return value.toLocaleString("en-US");
|
|
927
926
|
}
|
|
927
|
+
function formatCreditsWithMoney(credits, gate) {
|
|
928
|
+
const amount = formatCredits(credits);
|
|
929
|
+
const peg = gate.creditsPerMinorUnit;
|
|
930
|
+
const currency = gate.currency;
|
|
931
|
+
const rate = gate.displayExchangeRate;
|
|
932
|
+
if (peg && peg > 0 && currency && rate != null && rate > 0) {
|
|
933
|
+
const minor = (0, import_core3.creditsToDisplayMinorUnits)({
|
|
934
|
+
credits,
|
|
935
|
+
creditsPerMinorUnit: peg,
|
|
936
|
+
displayExchangeRate: rate,
|
|
937
|
+
displayCurrency: currency
|
|
938
|
+
});
|
|
939
|
+
if (minor != null) {
|
|
940
|
+
return `${amount} credits (~${formatMinor(minor, currency)})`;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return `${amount} credits`;
|
|
944
|
+
}
|
|
928
945
|
function meterLabel(gate) {
|
|
929
946
|
if (!gate.meterName) return "units";
|
|
930
947
|
return gate.meterName.replace(/_/g, " ");
|
|
@@ -968,42 +985,42 @@ function buildGateMessage(state, gate) {
|
|
|
968
985
|
const nextLine = price ? ` The next call is ${price}.` : "";
|
|
969
986
|
const planName = gate.planName ?? "This plan";
|
|
970
987
|
const antiTrap = gate.creditBalance !== void 0 && gate.creditBalance > 0 && (gate.creditsPerCall === void 0 || gate.creditsPerCall === 0) ? ` Adding credits will not help, because ${planName} does not spend them.` : "";
|
|
971
|
-
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : recoverClause(url, "continue", "checkout");
|
|
972
|
-
return `${usedLine}${nextLine}${antiTrap}${switchLine}
|
|
988
|
+
const switchLine = ladder ? ` Or switch plan: ${ladder}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.` : recoverClause(url, "continue", "checkout");
|
|
989
|
+
return `${usedLine}${nextLine}${antiTrap}${switchLine}`;
|
|
973
990
|
}
|
|
974
991
|
case "activation_required":
|
|
975
|
-
return `Your plan needs activation.${recoverClause(url, "activate", "checkout")} Or call \`activate_plan\` with a \`planRef
|
|
992
|
+
return `Your plan needs activation.${recoverClause(url, "activate", "checkout")} Or call \`activate_plan\` with a \`planRef\`.`;
|
|
976
993
|
case "topup_required": {
|
|
977
994
|
const balance = gate.creditBalance;
|
|
978
995
|
const cost = gate.creditsPerCall;
|
|
979
996
|
const shortfall = gate.shortfallCredits;
|
|
980
997
|
let lead;
|
|
981
998
|
if (balance !== void 0 && cost !== void 0 && shortfall !== void 0) {
|
|
982
|
-
lead = `Out of credits for this call. Balance ${
|
|
999
|
+
lead = `Out of credits for this call. Balance ${formatCreditsWithMoney(balance, gate)}; this call costs ${formatCreditsWithMoney(cost, gate)} \u2014 ${formatCredits(shortfall)} short.`;
|
|
983
1000
|
} else {
|
|
984
1001
|
lead = "Included usage is exhausted.";
|
|
985
1002
|
}
|
|
986
1003
|
const topup = recoverClause(url, "add credits", "topup", "Add credits");
|
|
987
|
-
const auto = autoRechargeDisabled(gate) ?
|
|
988
|
-
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : ""
|
|
989
|
-
return `${lead}${topup}${auto}${switchLine}
|
|
1004
|
+
const auto = autoRechargeDisabled(gate) ? ` Auto-recharge is off \u2014 ${callViewer("account")} to turn it on and avoid this next time.` : "";
|
|
1005
|
+
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : hasActivePlan(gate) ? "" : ` Or ${callViewer("checkout")} to pick a plan.`;
|
|
1006
|
+
return `${lead}${topup}${auto}${switchLine}`;
|
|
990
1007
|
}
|
|
991
1008
|
case "upgrade_required": {
|
|
992
1009
|
if (hasActivePlan(gate)) {
|
|
993
1010
|
const planName = gate.planName ?? "This plan";
|
|
994
1011
|
const lead = `${planName} is active but its included usage is exhausted, and the automatic switch to the next plan did not complete.`;
|
|
995
1012
|
if (ladder) {
|
|
996
|
-
return `${lead} Switch here: ${ladder}. ${
|
|
1013
|
+
return `${lead} Switch here: ${ladder}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.`;
|
|
997
1014
|
}
|
|
998
|
-
return `${lead}${recoverClause(url, "switch plan", "checkout")}
|
|
1015
|
+
return `${lead}${recoverClause(url, "switch plan", "checkout")}`;
|
|
999
1016
|
}
|
|
1000
1017
|
if (ladder) {
|
|
1001
|
-
return `You don't have an active plan for this tool. Pick a plan to use this tool: ${ladder} (links expire in ${CHECKOUT_SESSION_TTL_MINUTES} minutes), or ${callViewer("checkout")}. ${
|
|
1018
|
+
return `You don't have an active plan for this tool. Pick a plan to use this tool: ${ladder} (links expire in ${CHECKOUT_SESSION_TTL_MINUTES} minutes), or ${callViewer("checkout")}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.`;
|
|
1002
1019
|
}
|
|
1003
|
-
return `You don't have an active plan for this tool.${recoverClause(url, "pick a plan", "checkout")}
|
|
1020
|
+
return `You don't have an active plan for this tool.${recoverClause(url, "pick a plan", "checkout")}`;
|
|
1004
1021
|
}
|
|
1005
1022
|
case "reactivation_required":
|
|
1006
|
-
return `Your previous plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it, or ${callViewer("checkout")} to pick a new plan
|
|
1023
|
+
return `Your previous plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it, or ${callViewer("checkout")} to pick a new plan.`;
|
|
1007
1024
|
}
|
|
1008
1025
|
}
|
|
1009
1026
|
|
|
@@ -1073,6 +1090,8 @@ function recoveryFields(limits, state) {
|
|
|
1073
1090
|
...signals.creditsPerCall !== void 0 ? { creditsPerCall: signals.creditsPerCall } : {},
|
|
1074
1091
|
...signals.shortfallCredits !== void 0 ? { shortfallCredits: signals.shortfallCredits } : {},
|
|
1075
1092
|
...signals.remainingCalls !== void 0 ? { remainingCalls: signals.remainingCalls } : {},
|
|
1093
|
+
...limits.balance?.creditsPerMinorUnit !== void 0 ? { creditsPerMinorUnit: limits.balance.creditsPerMinorUnit } : {},
|
|
1094
|
+
...limits.balance?.displayExchangeRate !== void 0 ? { displayExchangeRate: limits.balance.displayExchangeRate } : {},
|
|
1076
1095
|
...limits.purchaseRef !== void 0 ? { purchaseRef: limits.purchaseRef } : {},
|
|
1077
1096
|
...limits.autoRecharge !== void 0 ? { autoRecharge: limits.autoRecharge } : {},
|
|
1078
1097
|
...links ? { links } : {}
|
|
@@ -1329,6 +1348,23 @@ var SolvaPayPaywall = class {
|
|
|
1329
1348
|
console.log(...args);
|
|
1330
1349
|
}
|
|
1331
1350
|
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Drop cached limits for `customerRef`. When `product` is omitted every
|
|
1353
|
+
* meter/product entry for that customer is removed. Matches both the
|
|
1354
|
+
* backend `cus_…` ref and an unmapped external ref used as a cache key.
|
|
1355
|
+
*/
|
|
1356
|
+
invalidateLimits(customerRef, product) {
|
|
1357
|
+
const aliases = /* @__PURE__ */ new Set([customerRef]);
|
|
1358
|
+
const mapped = this.customerRefMapping.get(customerRef);
|
|
1359
|
+
if (mapped) aliases.add(mapped);
|
|
1360
|
+
if (!customerRef.startsWith("cus_")) aliases.add(`cus_${customerRef}`);
|
|
1361
|
+
for (const key of [...this.limitsCache.keys()]) {
|
|
1362
|
+
const [ref, prod] = key.split(":");
|
|
1363
|
+
if (!ref || !aliases.has(ref)) continue;
|
|
1364
|
+
if (product && prod !== product) continue;
|
|
1365
|
+
this.limitsCache.delete(key);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1332
1368
|
resolveProduct(metadata) {
|
|
1333
1369
|
return requireProductRef(metadata.product);
|
|
1334
1370
|
}
|
|
@@ -2348,6 +2384,9 @@ function createSolvaPay(config) {
|
|
|
2348
2384
|
paywall: {
|
|
2349
2385
|
decide(args, metadata, getCustomerRef) {
|
|
2350
2386
|
return paywall.decide(args, metadata, getCustomerRef);
|
|
2387
|
+
},
|
|
2388
|
+
invalidateLimits(customerRef, product) {
|
|
2389
|
+
paywall.invalidateLimits(customerRef, product);
|
|
2351
2390
|
}
|
|
2352
2391
|
},
|
|
2353
2392
|
// Common API methods exposed directly for convenience
|
|
@@ -2416,7 +2455,7 @@ function createSolvaPay(config) {
|
|
|
2416
2455
|
customerRef: params.customerRef,
|
|
2417
2456
|
productRef: params.productRef,
|
|
2418
2457
|
planRef: params.planRef,
|
|
2419
|
-
returnUrl: params.returnUrl,
|
|
2458
|
+
...params.returnUrl ? { returnUrl: params.returnUrl } : {},
|
|
2420
2459
|
...params.purpose ? { purpose: params.purpose } : {}
|
|
2421
2460
|
});
|
|
2422
2461
|
},
|
|
@@ -2762,12 +2801,15 @@ async function createCheckoutSessionCore(request, body, options = {}) {
|
|
|
2762
2801
|
return customerResult;
|
|
2763
2802
|
}
|
|
2764
2803
|
const customerRef = customerResult;
|
|
2765
|
-
let returnUrl
|
|
2766
|
-
if (
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2804
|
+
let returnUrl;
|
|
2805
|
+
if (body.returnUrl !== null) {
|
|
2806
|
+
returnUrl = body.returnUrl || options.returnUrl;
|
|
2807
|
+
if (!returnUrl) {
|
|
2808
|
+
try {
|
|
2809
|
+
const url = new URL(request.url);
|
|
2810
|
+
returnUrl = url.origin;
|
|
2811
|
+
} catch {
|
|
2812
|
+
}
|
|
2771
2813
|
}
|
|
2772
2814
|
}
|
|
2773
2815
|
const solvaPay = options.solvaPay || createSolvaPay();
|
|
@@ -2775,7 +2817,7 @@ async function createCheckoutSessionCore(request, body, options = {}) {
|
|
|
2775
2817
|
productRef: body.productRef,
|
|
2776
2818
|
customerRef,
|
|
2777
2819
|
planRef: body.planRef || void 0,
|
|
2778
|
-
returnUrl,
|
|
2820
|
+
...returnUrl ? { returnUrl } : {},
|
|
2779
2821
|
...body.purpose ? { purpose: body.purpose } : {}
|
|
2780
2822
|
});
|
|
2781
2823
|
return {
|
package/dist/fetch/index.d.cts
CHANGED
|
@@ -828,6 +828,10 @@ interface components {
|
|
|
828
828
|
currency: string;
|
|
829
829
|
/** @description How many metered items the credit balance still covers (`balance / creditsPerUnit`) */
|
|
830
830
|
remainingUnits?: number;
|
|
831
|
+
/** @description Credits per minor unit of `currency` (typically 100) */
|
|
832
|
+
creditsPerMinorUnit?: number;
|
|
833
|
+
/** @description USD → display-currency rate used with creditsPerMinorUnit */
|
|
834
|
+
displayExchangeRate?: number;
|
|
831
835
|
};
|
|
832
836
|
LimitPlanItemDto: {
|
|
833
837
|
/** @description Derived billing cycle */
|
package/dist/fetch/index.d.ts
CHANGED
|
@@ -828,6 +828,10 @@ interface components {
|
|
|
828
828
|
currency: string;
|
|
829
829
|
/** @description How many metered items the credit balance still covers (`balance / creditsPerUnit`) */
|
|
830
830
|
remainingUnits?: number;
|
|
831
|
+
/** @description Credits per minor unit of `currency` (typically 100) */
|
|
832
|
+
creditsPerMinorUnit?: number;
|
|
833
|
+
/** @description USD → display-currency rate used with creditsPerMinorUnit */
|
|
834
|
+
displayExchangeRate?: number;
|
|
831
835
|
};
|
|
832
836
|
LimitPlanItemDto: {
|
|
833
837
|
/** @description Derived billing cycle */
|
package/dist/fetch/index.js
CHANGED
|
@@ -785,9 +785,8 @@ function createSolvaPayClient(opts) {
|
|
|
785
785
|
}
|
|
786
786
|
|
|
787
787
|
// src/paywall-state.ts
|
|
788
|
-
import { minorUnitsPerMajor } from "@solvapay/core";
|
|
788
|
+
import { creditsToDisplayMinorUnits, minorUnitsPerMajor } from "@solvapay/core";
|
|
789
789
|
var CHECKOUT_SESSION_TTL_MINUTES = 15;
|
|
790
|
-
var DOCS_HINT = "See docs://solvapay/overview.md.";
|
|
791
790
|
function creditSignals(limits) {
|
|
792
791
|
if (!limits) return { isCreditBased: false };
|
|
793
792
|
const creditBalance = limits.balance?.creditBalance ?? limits.creditBalance;
|
|
@@ -871,6 +870,24 @@ function formatMinor(amountMinor, currency) {
|
|
|
871
870
|
function formatCredits(value) {
|
|
872
871
|
return value.toLocaleString("en-US");
|
|
873
872
|
}
|
|
873
|
+
function formatCreditsWithMoney(credits, gate) {
|
|
874
|
+
const amount = formatCredits(credits);
|
|
875
|
+
const peg = gate.creditsPerMinorUnit;
|
|
876
|
+
const currency = gate.currency;
|
|
877
|
+
const rate = gate.displayExchangeRate;
|
|
878
|
+
if (peg && peg > 0 && currency && rate != null && rate > 0) {
|
|
879
|
+
const minor = creditsToDisplayMinorUnits({
|
|
880
|
+
credits,
|
|
881
|
+
creditsPerMinorUnit: peg,
|
|
882
|
+
displayExchangeRate: rate,
|
|
883
|
+
displayCurrency: currency
|
|
884
|
+
});
|
|
885
|
+
if (minor != null) {
|
|
886
|
+
return `${amount} credits (~${formatMinor(minor, currency)})`;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return `${amount} credits`;
|
|
890
|
+
}
|
|
874
891
|
function meterLabel(gate) {
|
|
875
892
|
if (!gate.meterName) return "units";
|
|
876
893
|
return gate.meterName.replace(/_/g, " ");
|
|
@@ -914,42 +931,42 @@ function buildGateMessage(state, gate) {
|
|
|
914
931
|
const nextLine = price ? ` The next call is ${price}.` : "";
|
|
915
932
|
const planName = gate.planName ?? "This plan";
|
|
916
933
|
const antiTrap = gate.creditBalance !== void 0 && gate.creditBalance > 0 && (gate.creditsPerCall === void 0 || gate.creditsPerCall === 0) ? ` Adding credits will not help, because ${planName} does not spend them.` : "";
|
|
917
|
-
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : recoverClause(url, "continue", "checkout");
|
|
918
|
-
return `${usedLine}${nextLine}${antiTrap}${switchLine}
|
|
934
|
+
const switchLine = ladder ? ` Or switch plan: ${ladder}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.` : recoverClause(url, "continue", "checkout");
|
|
935
|
+
return `${usedLine}${nextLine}${antiTrap}${switchLine}`;
|
|
919
936
|
}
|
|
920
937
|
case "activation_required":
|
|
921
|
-
return `Your plan needs activation.${recoverClause(url, "activate", "checkout")} Or call \`activate_plan\` with a \`planRef
|
|
938
|
+
return `Your plan needs activation.${recoverClause(url, "activate", "checkout")} Or call \`activate_plan\` with a \`planRef\`.`;
|
|
922
939
|
case "topup_required": {
|
|
923
940
|
const balance = gate.creditBalance;
|
|
924
941
|
const cost = gate.creditsPerCall;
|
|
925
942
|
const shortfall = gate.shortfallCredits;
|
|
926
943
|
let lead;
|
|
927
944
|
if (balance !== void 0 && cost !== void 0 && shortfall !== void 0) {
|
|
928
|
-
lead = `Out of credits for this call. Balance ${
|
|
945
|
+
lead = `Out of credits for this call. Balance ${formatCreditsWithMoney(balance, gate)}; this call costs ${formatCreditsWithMoney(cost, gate)} \u2014 ${formatCredits(shortfall)} short.`;
|
|
929
946
|
} else {
|
|
930
947
|
lead = "Included usage is exhausted.";
|
|
931
948
|
}
|
|
932
949
|
const topup = recoverClause(url, "add credits", "topup", "Add credits");
|
|
933
|
-
const auto = autoRechargeDisabled(gate) ?
|
|
934
|
-
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : ""
|
|
935
|
-
return `${lead}${topup}${auto}${switchLine}
|
|
950
|
+
const auto = autoRechargeDisabled(gate) ? ` Auto-recharge is off \u2014 ${callViewer("account")} to turn it on and avoid this next time.` : "";
|
|
951
|
+
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : hasActivePlan(gate) ? "" : ` Or ${callViewer("checkout")} to pick a plan.`;
|
|
952
|
+
return `${lead}${topup}${auto}${switchLine}`;
|
|
936
953
|
}
|
|
937
954
|
case "upgrade_required": {
|
|
938
955
|
if (hasActivePlan(gate)) {
|
|
939
956
|
const planName = gate.planName ?? "This plan";
|
|
940
957
|
const lead = `${planName} is active but its included usage is exhausted, and the automatic switch to the next plan did not complete.`;
|
|
941
958
|
if (ladder) {
|
|
942
|
-
return `${lead} Switch here: ${ladder}. ${
|
|
959
|
+
return `${lead} Switch here: ${ladder}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.`;
|
|
943
960
|
}
|
|
944
|
-
return `${lead}${recoverClause(url, "switch plan", "checkout")}
|
|
961
|
+
return `${lead}${recoverClause(url, "switch plan", "checkout")}`;
|
|
945
962
|
}
|
|
946
963
|
if (ladder) {
|
|
947
|
-
return `You don't have an active plan for this tool. Pick a plan to use this tool: ${ladder} (links expire in ${CHECKOUT_SESSION_TTL_MINUTES} minutes), or ${callViewer("checkout")}. ${
|
|
964
|
+
return `You don't have an active plan for this tool. Pick a plan to use this tool: ${ladder} (links expire in ${CHECKOUT_SESSION_TTL_MINUTES} minutes), or ${callViewer("checkout")}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.`;
|
|
948
965
|
}
|
|
949
|
-
return `You don't have an active plan for this tool.${recoverClause(url, "pick a plan", "checkout")}
|
|
966
|
+
return `You don't have an active plan for this tool.${recoverClause(url, "pick a plan", "checkout")}`;
|
|
950
967
|
}
|
|
951
968
|
case "reactivation_required":
|
|
952
|
-
return `Your previous plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it, or ${callViewer("checkout")} to pick a new plan
|
|
969
|
+
return `Your previous plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it, or ${callViewer("checkout")} to pick a new plan.`;
|
|
953
970
|
}
|
|
954
971
|
}
|
|
955
972
|
|
|
@@ -1019,6 +1036,8 @@ function recoveryFields(limits, state) {
|
|
|
1019
1036
|
...signals.creditsPerCall !== void 0 ? { creditsPerCall: signals.creditsPerCall } : {},
|
|
1020
1037
|
...signals.shortfallCredits !== void 0 ? { shortfallCredits: signals.shortfallCredits } : {},
|
|
1021
1038
|
...signals.remainingCalls !== void 0 ? { remainingCalls: signals.remainingCalls } : {},
|
|
1039
|
+
...limits.balance?.creditsPerMinorUnit !== void 0 ? { creditsPerMinorUnit: limits.balance.creditsPerMinorUnit } : {},
|
|
1040
|
+
...limits.balance?.displayExchangeRate !== void 0 ? { displayExchangeRate: limits.balance.displayExchangeRate } : {},
|
|
1022
1041
|
...limits.purchaseRef !== void 0 ? { purchaseRef: limits.purchaseRef } : {},
|
|
1023
1042
|
...limits.autoRecharge !== void 0 ? { autoRecharge: limits.autoRecharge } : {},
|
|
1024
1043
|
...links ? { links } : {}
|
|
@@ -1275,6 +1294,23 @@ var SolvaPayPaywall = class {
|
|
|
1275
1294
|
console.log(...args);
|
|
1276
1295
|
}
|
|
1277
1296
|
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Drop cached limits for `customerRef`. When `product` is omitted every
|
|
1299
|
+
* meter/product entry for that customer is removed. Matches both the
|
|
1300
|
+
* backend `cus_…` ref and an unmapped external ref used as a cache key.
|
|
1301
|
+
*/
|
|
1302
|
+
invalidateLimits(customerRef, product) {
|
|
1303
|
+
const aliases = /* @__PURE__ */ new Set([customerRef]);
|
|
1304
|
+
const mapped = this.customerRefMapping.get(customerRef);
|
|
1305
|
+
if (mapped) aliases.add(mapped);
|
|
1306
|
+
if (!customerRef.startsWith("cus_")) aliases.add(`cus_${customerRef}`);
|
|
1307
|
+
for (const key of [...this.limitsCache.keys()]) {
|
|
1308
|
+
const [ref, prod] = key.split(":");
|
|
1309
|
+
if (!ref || !aliases.has(ref)) continue;
|
|
1310
|
+
if (product && prod !== product) continue;
|
|
1311
|
+
this.limitsCache.delete(key);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1278
1314
|
resolveProduct(metadata) {
|
|
1279
1315
|
return requireProductRef(metadata.product);
|
|
1280
1316
|
}
|
|
@@ -2294,6 +2330,9 @@ function createSolvaPay(config) {
|
|
|
2294
2330
|
paywall: {
|
|
2295
2331
|
decide(args, metadata, getCustomerRef) {
|
|
2296
2332
|
return paywall.decide(args, metadata, getCustomerRef);
|
|
2333
|
+
},
|
|
2334
|
+
invalidateLimits(customerRef, product) {
|
|
2335
|
+
paywall.invalidateLimits(customerRef, product);
|
|
2297
2336
|
}
|
|
2298
2337
|
},
|
|
2299
2338
|
// Common API methods exposed directly for convenience
|
|
@@ -2362,7 +2401,7 @@ function createSolvaPay(config) {
|
|
|
2362
2401
|
customerRef: params.customerRef,
|
|
2363
2402
|
productRef: params.productRef,
|
|
2364
2403
|
planRef: params.planRef,
|
|
2365
|
-
returnUrl: params.returnUrl,
|
|
2404
|
+
...params.returnUrl ? { returnUrl: params.returnUrl } : {},
|
|
2366
2405
|
...params.purpose ? { purpose: params.purpose } : {}
|
|
2367
2406
|
});
|
|
2368
2407
|
},
|
|
@@ -2710,12 +2749,15 @@ async function createCheckoutSessionCore(request, body, options = {}) {
|
|
|
2710
2749
|
return customerResult;
|
|
2711
2750
|
}
|
|
2712
2751
|
const customerRef = customerResult;
|
|
2713
|
-
let returnUrl
|
|
2714
|
-
if (
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2752
|
+
let returnUrl;
|
|
2753
|
+
if (body.returnUrl !== null) {
|
|
2754
|
+
returnUrl = body.returnUrl || options.returnUrl;
|
|
2755
|
+
if (!returnUrl) {
|
|
2756
|
+
try {
|
|
2757
|
+
const url = new URL(request.url);
|
|
2758
|
+
returnUrl = url.origin;
|
|
2759
|
+
} catch {
|
|
2760
|
+
}
|
|
2719
2761
|
}
|
|
2720
2762
|
}
|
|
2721
2763
|
const solvaPay = options.solvaPay || createSolvaPay();
|
|
@@ -2723,7 +2765,7 @@ async function createCheckoutSessionCore(request, body, options = {}) {
|
|
|
2723
2765
|
productRef: body.productRef,
|
|
2724
2766
|
customerRef,
|
|
2725
2767
|
planRef: body.planRef || void 0,
|
|
2726
|
-
returnUrl,
|
|
2768
|
+
...returnUrl ? { returnUrl } : {},
|
|
2727
2769
|
...body.purpose ? { purpose: body.purpose } : {}
|
|
2728
2770
|
});
|
|
2729
2771
|
return {
|
package/dist/index.cjs
CHANGED
|
@@ -722,7 +722,6 @@ function createSolvaPayClient(opts) {
|
|
|
722
722
|
// src/paywall-state.ts
|
|
723
723
|
var import_core2 = require("@solvapay/core");
|
|
724
724
|
var CHECKOUT_SESSION_TTL_MINUTES = 15;
|
|
725
|
-
var DOCS_HINT = "See docs://solvapay/overview.md.";
|
|
726
725
|
function creditSignals(limits) {
|
|
727
726
|
if (!limits) return { isCreditBased: false };
|
|
728
727
|
const creditBalance = limits.balance?.creditBalance ?? limits.creditBalance;
|
|
@@ -806,6 +805,24 @@ function formatMinor(amountMinor, currency) {
|
|
|
806
805
|
function formatCredits(value) {
|
|
807
806
|
return value.toLocaleString("en-US");
|
|
808
807
|
}
|
|
808
|
+
function formatCreditsWithMoney(credits, gate) {
|
|
809
|
+
const amount = formatCredits(credits);
|
|
810
|
+
const peg = gate.creditsPerMinorUnit;
|
|
811
|
+
const currency = gate.currency;
|
|
812
|
+
const rate = gate.displayExchangeRate;
|
|
813
|
+
if (peg && peg > 0 && currency && rate != null && rate > 0) {
|
|
814
|
+
const minor = (0, import_core2.creditsToDisplayMinorUnits)({
|
|
815
|
+
credits,
|
|
816
|
+
creditsPerMinorUnit: peg,
|
|
817
|
+
displayExchangeRate: rate,
|
|
818
|
+
displayCurrency: currency
|
|
819
|
+
});
|
|
820
|
+
if (minor != null) {
|
|
821
|
+
return `${amount} credits (~${formatMinor(minor, currency)})`;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return `${amount} credits`;
|
|
825
|
+
}
|
|
809
826
|
function meterLabel(gate) {
|
|
810
827
|
if (!gate.meterName) return "units";
|
|
811
828
|
return gate.meterName.replace(/_/g, " ");
|
|
@@ -849,49 +866,51 @@ function buildGateMessage(state, gate) {
|
|
|
849
866
|
const nextLine = price ? ` The next call is ${price}.` : "";
|
|
850
867
|
const planName = gate.planName ?? "This plan";
|
|
851
868
|
const antiTrap = gate.creditBalance !== void 0 && gate.creditBalance > 0 && (gate.creditsPerCall === void 0 || gate.creditsPerCall === 0) ? ` Adding credits will not help, because ${planName} does not spend them.` : "";
|
|
852
|
-
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : recoverClause(url, "continue", "checkout");
|
|
853
|
-
return `${usedLine}${nextLine}${antiTrap}${switchLine}
|
|
869
|
+
const switchLine = ladder ? ` Or switch plan: ${ladder}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.` : recoverClause(url, "continue", "checkout");
|
|
870
|
+
return `${usedLine}${nextLine}${antiTrap}${switchLine}`;
|
|
854
871
|
}
|
|
855
872
|
case "activation_required":
|
|
856
|
-
return `Your plan needs activation.${recoverClause(url, "activate", "checkout")} Or call \`activate_plan\` with a \`planRef
|
|
873
|
+
return `Your plan needs activation.${recoverClause(url, "activate", "checkout")} Or call \`activate_plan\` with a \`planRef\`.`;
|
|
857
874
|
case "topup_required": {
|
|
858
875
|
const balance = gate.creditBalance;
|
|
859
876
|
const cost = gate.creditsPerCall;
|
|
860
877
|
const shortfall = gate.shortfallCredits;
|
|
861
878
|
let lead;
|
|
862
879
|
if (balance !== void 0 && cost !== void 0 && shortfall !== void 0) {
|
|
863
|
-
lead = `Out of credits for this call. Balance ${
|
|
880
|
+
lead = `Out of credits for this call. Balance ${formatCreditsWithMoney(balance, gate)}; this call costs ${formatCreditsWithMoney(cost, gate)} \u2014 ${formatCredits(shortfall)} short.`;
|
|
864
881
|
} else {
|
|
865
882
|
lead = "Included usage is exhausted.";
|
|
866
883
|
}
|
|
867
884
|
const topup = recoverClause(url, "add credits", "topup", "Add credits");
|
|
868
|
-
const auto = autoRechargeDisabled(gate) ?
|
|
869
|
-
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : ""
|
|
870
|
-
return `${lead}${topup}${auto}${switchLine}
|
|
885
|
+
const auto = autoRechargeDisabled(gate) ? ` Auto-recharge is off \u2014 ${callViewer("account")} to turn it on and avoid this next time.` : "";
|
|
886
|
+
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : hasActivePlan(gate) ? "" : ` Or ${callViewer("checkout")} to pick a plan.`;
|
|
887
|
+
return `${lead}${topup}${auto}${switchLine}`;
|
|
871
888
|
}
|
|
872
889
|
case "upgrade_required": {
|
|
873
890
|
if (hasActivePlan(gate)) {
|
|
874
891
|
const planName = gate.planName ?? "This plan";
|
|
875
892
|
const lead = `${planName} is active but its included usage is exhausted, and the automatic switch to the next plan did not complete.`;
|
|
876
893
|
if (ladder) {
|
|
877
|
-
return `${lead} Switch here: ${ladder}. ${
|
|
894
|
+
return `${lead} Switch here: ${ladder}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.`;
|
|
878
895
|
}
|
|
879
|
-
return `${lead}${recoverClause(url, "switch plan", "checkout")}
|
|
896
|
+
return `${lead}${recoverClause(url, "switch plan", "checkout")}`;
|
|
880
897
|
}
|
|
881
898
|
if (ladder) {
|
|
882
|
-
return `You don't have an active plan for this tool. Pick a plan to use this tool: ${ladder} (links expire in ${CHECKOUT_SESSION_TTL_MINUTES} minutes), or ${callViewer("checkout")}. ${
|
|
899
|
+
return `You don't have an active plan for this tool. Pick a plan to use this tool: ${ladder} (links expire in ${CHECKOUT_SESSION_TTL_MINUTES} minutes), or ${callViewer("checkout")}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.`;
|
|
883
900
|
}
|
|
884
|
-
return `You don't have an active plan for this tool.${recoverClause(url, "pick a plan", "checkout")}
|
|
901
|
+
return `You don't have an active plan for this tool.${recoverClause(url, "pick a plan", "checkout")}`;
|
|
885
902
|
}
|
|
886
903
|
case "reactivation_required":
|
|
887
|
-
return `Your previous plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it, or ${callViewer("checkout")} to pick a new plan
|
|
904
|
+
return `Your previous plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it, or ${callViewer("checkout")} to pick a new plan.`;
|
|
888
905
|
}
|
|
889
906
|
}
|
|
890
907
|
function buildNudgeMessage(state, limits) {
|
|
891
908
|
const url = limits?.checkoutUrl && limits.checkoutUrl.length > 0 ? limits.checkoutUrl : null;
|
|
892
909
|
const visitClause = url ? `, or ${namedCheckoutMarkdown(url)}` : "";
|
|
893
|
-
const
|
|
894
|
-
|
|
910
|
+
const signals = creditSignals(limits);
|
|
911
|
+
const remaining = signals.remainingCalls;
|
|
912
|
+
const kind = state.kind === "topup_required" && limits && !signals.isCreditBased ? "limit_reached" : state.kind;
|
|
913
|
+
switch (kind) {
|
|
895
914
|
case "topup_required":
|
|
896
915
|
if (remaining === 0) {
|
|
897
916
|
return `Heads up \u2014 0 calls left \u2014 the next call needs a top-up. ${callViewer("topup").replace(/^c/, "C")} to add more${visitClause}.`;
|
|
@@ -976,6 +995,8 @@ function recoveryFields(limits, state) {
|
|
|
976
995
|
...signals.creditsPerCall !== void 0 ? { creditsPerCall: signals.creditsPerCall } : {},
|
|
977
996
|
...signals.shortfallCredits !== void 0 ? { shortfallCredits: signals.shortfallCredits } : {},
|
|
978
997
|
...signals.remainingCalls !== void 0 ? { remainingCalls: signals.remainingCalls } : {},
|
|
998
|
+
...limits.balance?.creditsPerMinorUnit !== void 0 ? { creditsPerMinorUnit: limits.balance.creditsPerMinorUnit } : {},
|
|
999
|
+
...limits.balance?.displayExchangeRate !== void 0 ? { displayExchangeRate: limits.balance.displayExchangeRate } : {},
|
|
979
1000
|
...limits.purchaseRef !== void 0 ? { purchaseRef: limits.purchaseRef } : {},
|
|
980
1001
|
...limits.autoRecharge !== void 0 ? { autoRecharge: limits.autoRecharge } : {},
|
|
981
1002
|
...links ? { links } : {}
|
|
@@ -1232,6 +1253,23 @@ var SolvaPayPaywall = class {
|
|
|
1232
1253
|
console.log(...args);
|
|
1233
1254
|
}
|
|
1234
1255
|
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Drop cached limits for `customerRef`. When `product` is omitted every
|
|
1258
|
+
* meter/product entry for that customer is removed. Matches both the
|
|
1259
|
+
* backend `cus_…` ref and an unmapped external ref used as a cache key.
|
|
1260
|
+
*/
|
|
1261
|
+
invalidateLimits(customerRef, product) {
|
|
1262
|
+
const aliases = /* @__PURE__ */ new Set([customerRef]);
|
|
1263
|
+
const mapped = this.customerRefMapping.get(customerRef);
|
|
1264
|
+
if (mapped) aliases.add(mapped);
|
|
1265
|
+
if (!customerRef.startsWith("cus_")) aliases.add(`cus_${customerRef}`);
|
|
1266
|
+
for (const key of [...this.limitsCache.keys()]) {
|
|
1267
|
+
const [ref, prod] = key.split(":");
|
|
1268
|
+
if (!ref || !aliases.has(ref)) continue;
|
|
1269
|
+
if (product && prod !== product) continue;
|
|
1270
|
+
this.limitsCache.delete(key);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1235
1273
|
resolveProduct(metadata) {
|
|
1236
1274
|
return requireProductRef(metadata.product);
|
|
1237
1275
|
}
|
|
@@ -2252,6 +2290,9 @@ function createSolvaPay(config) {
|
|
|
2252
2290
|
paywall: {
|
|
2253
2291
|
decide(args, metadata, getCustomerRef) {
|
|
2254
2292
|
return paywall.decide(args, metadata, getCustomerRef);
|
|
2293
|
+
},
|
|
2294
|
+
invalidateLimits(customerRef, product) {
|
|
2295
|
+
paywall.invalidateLimits(customerRef, product);
|
|
2255
2296
|
}
|
|
2256
2297
|
},
|
|
2257
2298
|
// Common API methods exposed directly for convenience
|
|
@@ -2320,7 +2361,7 @@ function createSolvaPay(config) {
|
|
|
2320
2361
|
customerRef: params.customerRef,
|
|
2321
2362
|
productRef: params.productRef,
|
|
2322
2363
|
planRef: params.planRef,
|
|
2323
|
-
returnUrl: params.returnUrl,
|
|
2364
|
+
...params.returnUrl ? { returnUrl: params.returnUrl } : {},
|
|
2324
2365
|
...params.purpose ? { purpose: params.purpose } : {}
|
|
2325
2366
|
});
|
|
2326
2367
|
},
|
|
@@ -3066,12 +3107,15 @@ async function createCheckoutSessionCore(request, body, options = {}) {
|
|
|
3066
3107
|
return customerResult;
|
|
3067
3108
|
}
|
|
3068
3109
|
const customerRef = customerResult;
|
|
3069
|
-
let returnUrl
|
|
3070
|
-
if (
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3110
|
+
let returnUrl;
|
|
3111
|
+
if (body.returnUrl !== null) {
|
|
3112
|
+
returnUrl = body.returnUrl || options.returnUrl;
|
|
3113
|
+
if (!returnUrl) {
|
|
3114
|
+
try {
|
|
3115
|
+
const url = new URL(request.url);
|
|
3116
|
+
returnUrl = url.origin;
|
|
3117
|
+
} catch {
|
|
3118
|
+
}
|
|
3075
3119
|
}
|
|
3076
3120
|
}
|
|
3077
3121
|
const solvaPay = options.solvaPay || createSolvaPay();
|
|
@@ -3079,7 +3123,7 @@ async function createCheckoutSessionCore(request, body, options = {}) {
|
|
|
3079
3123
|
productRef: body.productRef,
|
|
3080
3124
|
customerRef,
|
|
3081
3125
|
planRef: body.planRef || void 0,
|
|
3082
|
-
returnUrl,
|
|
3126
|
+
...returnUrl ? { returnUrl } : {},
|
|
3083
3127
|
...body.purpose ? { purpose: body.purpose } : {}
|
|
3084
3128
|
});
|
|
3085
3129
|
return {
|
package/dist/index.d.cts
CHANGED
|
@@ -831,6 +831,10 @@ interface components {
|
|
|
831
831
|
currency: string;
|
|
832
832
|
/** @description How many metered items the credit balance still covers (`balance / creditsPerUnit`) */
|
|
833
833
|
remainingUnits?: number;
|
|
834
|
+
/** @description Credits per minor unit of `currency` (typically 100) */
|
|
835
|
+
creditsPerMinorUnit?: number;
|
|
836
|
+
/** @description USD → display-currency rate used with creditsPerMinorUnit */
|
|
837
|
+
displayExchangeRate?: number;
|
|
834
838
|
};
|
|
835
839
|
LimitPlanItemDto: {
|
|
836
840
|
/** @description Derived billing cycle */
|
|
@@ -4309,6 +4313,10 @@ type PaywallGateRecoveryFields = {
|
|
|
4309
4313
|
shortfallCredits?: number;
|
|
4310
4314
|
/** How many calls the current allowance or wallet still covers. */
|
|
4311
4315
|
remainingCalls?: number;
|
|
4316
|
+
/** Credits per minor currency unit, from the limits balance peg. */
|
|
4317
|
+
creditsPerMinorUnit?: number;
|
|
4318
|
+
/** USD → display-currency rate used with `creditsPerMinorUnit`. */
|
|
4319
|
+
displayExchangeRate?: number;
|
|
4312
4320
|
/** Active purchase reference when the customer already holds a purchase. */
|
|
4313
4321
|
purchaseRef?: string;
|
|
4314
4322
|
/** Purchase/plan status when the backend sent one. */
|
|
@@ -5300,6 +5308,12 @@ interface SolvaPay {
|
|
|
5300
5308
|
*/
|
|
5301
5309
|
paywall: {
|
|
5302
5310
|
decide<TArgs extends PaywallArgs>(args: TArgs, metadata?: PaywallMetadata, getCustomerRef?: (args: TArgs) => string): Promise<PaywallDecision<TArgs>>;
|
|
5311
|
+
/**
|
|
5312
|
+
* Drop cached `checkLimits` snapshots for this customer (optionally
|
|
5313
|
+
* scoped to one product). Call after `activate_plan`, a completed
|
|
5314
|
+
* checkout, or a top-up so the next payable call refetches.
|
|
5315
|
+
*/
|
|
5316
|
+
invalidateLimits(customerRef: string, product?: string): void;
|
|
5303
5317
|
};
|
|
5304
5318
|
/**
|
|
5305
5319
|
* Direct access to the API client for advanced operations.
|
|
@@ -6209,7 +6223,13 @@ declare function processTopupPaymentIntentCore(request: Request, body: {
|
|
|
6209
6223
|
declare function createCheckoutSessionCore(request: Request, body: {
|
|
6210
6224
|
productRef: string;
|
|
6211
6225
|
planRef?: string;
|
|
6212
|
-
|
|
6226
|
+
/**
|
|
6227
|
+
* URL to send the customer back to after payment. Pass `null` to omit
|
|
6228
|
+
* the field entirely — skips `options.returnUrl` and the request-origin
|
|
6229
|
+
* fallback. Use this when the caller has no browsable page to return to
|
|
6230
|
+
* (MCP servers whose public URL is an API endpoint, not a website).
|
|
6231
|
+
*/
|
|
6232
|
+
returnUrl?: string | null;
|
|
6213
6233
|
purpose?: 'credit_topup';
|
|
6214
6234
|
}, options?: {
|
|
6215
6235
|
solvaPay?: SolvaPay;
|
package/dist/index.d.ts
CHANGED
|
@@ -831,6 +831,10 @@ interface components {
|
|
|
831
831
|
currency: string;
|
|
832
832
|
/** @description How many metered items the credit balance still covers (`balance / creditsPerUnit`) */
|
|
833
833
|
remainingUnits?: number;
|
|
834
|
+
/** @description Credits per minor unit of `currency` (typically 100) */
|
|
835
|
+
creditsPerMinorUnit?: number;
|
|
836
|
+
/** @description USD → display-currency rate used with creditsPerMinorUnit */
|
|
837
|
+
displayExchangeRate?: number;
|
|
834
838
|
};
|
|
835
839
|
LimitPlanItemDto: {
|
|
836
840
|
/** @description Derived billing cycle */
|
|
@@ -4309,6 +4313,10 @@ type PaywallGateRecoveryFields = {
|
|
|
4309
4313
|
shortfallCredits?: number;
|
|
4310
4314
|
/** How many calls the current allowance or wallet still covers. */
|
|
4311
4315
|
remainingCalls?: number;
|
|
4316
|
+
/** Credits per minor currency unit, from the limits balance peg. */
|
|
4317
|
+
creditsPerMinorUnit?: number;
|
|
4318
|
+
/** USD → display-currency rate used with `creditsPerMinorUnit`. */
|
|
4319
|
+
displayExchangeRate?: number;
|
|
4312
4320
|
/** Active purchase reference when the customer already holds a purchase. */
|
|
4313
4321
|
purchaseRef?: string;
|
|
4314
4322
|
/** Purchase/plan status when the backend sent one. */
|
|
@@ -5300,6 +5308,12 @@ interface SolvaPay {
|
|
|
5300
5308
|
*/
|
|
5301
5309
|
paywall: {
|
|
5302
5310
|
decide<TArgs extends PaywallArgs>(args: TArgs, metadata?: PaywallMetadata, getCustomerRef?: (args: TArgs) => string): Promise<PaywallDecision<TArgs>>;
|
|
5311
|
+
/**
|
|
5312
|
+
* Drop cached `checkLimits` snapshots for this customer (optionally
|
|
5313
|
+
* scoped to one product). Call after `activate_plan`, a completed
|
|
5314
|
+
* checkout, or a top-up so the next payable call refetches.
|
|
5315
|
+
*/
|
|
5316
|
+
invalidateLimits(customerRef: string, product?: string): void;
|
|
5303
5317
|
};
|
|
5304
5318
|
/**
|
|
5305
5319
|
* Direct access to the API client for advanced operations.
|
|
@@ -6209,7 +6223,13 @@ declare function processTopupPaymentIntentCore(request: Request, body: {
|
|
|
6209
6223
|
declare function createCheckoutSessionCore(request: Request, body: {
|
|
6210
6224
|
productRef: string;
|
|
6211
6225
|
planRef?: string;
|
|
6212
|
-
|
|
6226
|
+
/**
|
|
6227
|
+
* URL to send the customer back to after payment. Pass `null` to omit
|
|
6228
|
+
* the field entirely — skips `options.returnUrl` and the request-origin
|
|
6229
|
+
* fallback. Use this when the caller has no browsable page to return to
|
|
6230
|
+
* (MCP servers whose public URL is an API endpoint, not a website).
|
|
6231
|
+
*/
|
|
6232
|
+
returnUrl?: string | null;
|
|
6213
6233
|
purpose?: 'credit_topup';
|
|
6214
6234
|
}, options?: {
|
|
6215
6235
|
solvaPay?: SolvaPay;
|
package/dist/index.js
CHANGED
|
@@ -635,9 +635,8 @@ function createSolvaPayClient(opts) {
|
|
|
635
635
|
}
|
|
636
636
|
|
|
637
637
|
// src/paywall-state.ts
|
|
638
|
-
import { minorUnitsPerMajor } from "@solvapay/core";
|
|
638
|
+
import { creditsToDisplayMinorUnits, minorUnitsPerMajor } from "@solvapay/core";
|
|
639
639
|
var CHECKOUT_SESSION_TTL_MINUTES = 15;
|
|
640
|
-
var DOCS_HINT = "See docs://solvapay/overview.md.";
|
|
641
640
|
function creditSignals(limits) {
|
|
642
641
|
if (!limits) return { isCreditBased: false };
|
|
643
642
|
const creditBalance = limits.balance?.creditBalance ?? limits.creditBalance;
|
|
@@ -721,6 +720,24 @@ function formatMinor(amountMinor, currency) {
|
|
|
721
720
|
function formatCredits(value) {
|
|
722
721
|
return value.toLocaleString("en-US");
|
|
723
722
|
}
|
|
723
|
+
function formatCreditsWithMoney(credits, gate) {
|
|
724
|
+
const amount = formatCredits(credits);
|
|
725
|
+
const peg = gate.creditsPerMinorUnit;
|
|
726
|
+
const currency = gate.currency;
|
|
727
|
+
const rate = gate.displayExchangeRate;
|
|
728
|
+
if (peg && peg > 0 && currency && rate != null && rate > 0) {
|
|
729
|
+
const minor = creditsToDisplayMinorUnits({
|
|
730
|
+
credits,
|
|
731
|
+
creditsPerMinorUnit: peg,
|
|
732
|
+
displayExchangeRate: rate,
|
|
733
|
+
displayCurrency: currency
|
|
734
|
+
});
|
|
735
|
+
if (minor != null) {
|
|
736
|
+
return `${amount} credits (~${formatMinor(minor, currency)})`;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return `${amount} credits`;
|
|
740
|
+
}
|
|
724
741
|
function meterLabel(gate) {
|
|
725
742
|
if (!gate.meterName) return "units";
|
|
726
743
|
return gate.meterName.replace(/_/g, " ");
|
|
@@ -764,49 +781,51 @@ function buildGateMessage(state, gate) {
|
|
|
764
781
|
const nextLine = price ? ` The next call is ${price}.` : "";
|
|
765
782
|
const planName = gate.planName ?? "This plan";
|
|
766
783
|
const antiTrap = gate.creditBalance !== void 0 && gate.creditBalance > 0 && (gate.creditsPerCall === void 0 || gate.creditsPerCall === 0) ? ` Adding credits will not help, because ${planName} does not spend them.` : "";
|
|
767
|
-
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : recoverClause(url, "continue", "checkout");
|
|
768
|
-
return `${usedLine}${nextLine}${antiTrap}${switchLine}
|
|
784
|
+
const switchLine = ladder ? ` Or switch plan: ${ladder}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.` : recoverClause(url, "continue", "checkout");
|
|
785
|
+
return `${usedLine}${nextLine}${antiTrap}${switchLine}`;
|
|
769
786
|
}
|
|
770
787
|
case "activation_required":
|
|
771
|
-
return `Your plan needs activation.${recoverClause(url, "activate", "checkout")} Or call \`activate_plan\` with a \`planRef
|
|
788
|
+
return `Your plan needs activation.${recoverClause(url, "activate", "checkout")} Or call \`activate_plan\` with a \`planRef\`.`;
|
|
772
789
|
case "topup_required": {
|
|
773
790
|
const balance = gate.creditBalance;
|
|
774
791
|
const cost = gate.creditsPerCall;
|
|
775
792
|
const shortfall = gate.shortfallCredits;
|
|
776
793
|
let lead;
|
|
777
794
|
if (balance !== void 0 && cost !== void 0 && shortfall !== void 0) {
|
|
778
|
-
lead = `Out of credits for this call. Balance ${
|
|
795
|
+
lead = `Out of credits for this call. Balance ${formatCreditsWithMoney(balance, gate)}; this call costs ${formatCreditsWithMoney(cost, gate)} \u2014 ${formatCredits(shortfall)} short.`;
|
|
779
796
|
} else {
|
|
780
797
|
lead = "Included usage is exhausted.";
|
|
781
798
|
}
|
|
782
799
|
const topup = recoverClause(url, "add credits", "topup", "Add credits");
|
|
783
|
-
const auto = autoRechargeDisabled(gate) ?
|
|
784
|
-
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : ""
|
|
785
|
-
return `${lead}${topup}${auto}${switchLine}
|
|
800
|
+
const auto = autoRechargeDisabled(gate) ? ` Auto-recharge is off \u2014 ${callViewer("account")} to turn it on and avoid this next time.` : "";
|
|
801
|
+
const switchLine = ladder ? ` Or switch plan: ${ladder}.` : hasActivePlan(gate) ? "" : ` Or ${callViewer("checkout")} to pick a plan.`;
|
|
802
|
+
return `${lead}${topup}${auto}${switchLine}`;
|
|
786
803
|
}
|
|
787
804
|
case "upgrade_required": {
|
|
788
805
|
if (hasActivePlan(gate)) {
|
|
789
806
|
const planName = gate.planName ?? "This plan";
|
|
790
807
|
const lead = `${planName} is active but its included usage is exhausted, and the automatic switch to the next plan did not complete.`;
|
|
791
808
|
if (ladder) {
|
|
792
|
-
return `${lead} Switch here: ${ladder}. ${
|
|
809
|
+
return `${lead} Switch here: ${ladder}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.`;
|
|
793
810
|
}
|
|
794
|
-
return `${lead}${recoverClause(url, "switch plan", "checkout")}
|
|
811
|
+
return `${lead}${recoverClause(url, "switch plan", "checkout")}`;
|
|
795
812
|
}
|
|
796
813
|
if (ladder) {
|
|
797
|
-
return `You don't have an active plan for this tool. Pick a plan to use this tool: ${ladder} (links expire in ${CHECKOUT_SESSION_TTL_MINUTES} minutes), or ${callViewer("checkout")}. ${
|
|
814
|
+
return `You don't have an active plan for this tool. Pick a plan to use this tool: ${ladder} (links expire in ${CHECKOUT_SESSION_TTL_MINUTES} minutes), or ${callViewer("checkout")}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.`;
|
|
798
815
|
}
|
|
799
|
-
return `You don't have an active plan for this tool.${recoverClause(url, "pick a plan", "checkout")}
|
|
816
|
+
return `You don't have an active plan for this tool.${recoverClause(url, "pick a plan", "checkout")}`;
|
|
800
817
|
}
|
|
801
818
|
case "reactivation_required":
|
|
802
|
-
return `Your previous plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it, or ${callViewer("checkout")} to pick a new plan
|
|
819
|
+
return `Your previous plan is no longer active. ${callViewer("account").replace(/^c/, "C")} to reactivate it, or ${callViewer("checkout")} to pick a new plan.`;
|
|
803
820
|
}
|
|
804
821
|
}
|
|
805
822
|
function buildNudgeMessage(state, limits) {
|
|
806
823
|
const url = limits?.checkoutUrl && limits.checkoutUrl.length > 0 ? limits.checkoutUrl : null;
|
|
807
824
|
const visitClause = url ? `, or ${namedCheckoutMarkdown(url)}` : "";
|
|
808
|
-
const
|
|
809
|
-
|
|
825
|
+
const signals = creditSignals(limits);
|
|
826
|
+
const remaining = signals.remainingCalls;
|
|
827
|
+
const kind = state.kind === "topup_required" && limits && !signals.isCreditBased ? "limit_reached" : state.kind;
|
|
828
|
+
switch (kind) {
|
|
810
829
|
case "topup_required":
|
|
811
830
|
if (remaining === 0) {
|
|
812
831
|
return `Heads up \u2014 0 calls left \u2014 the next call needs a top-up. ${callViewer("topup").replace(/^c/, "C")} to add more${visitClause}.`;
|
|
@@ -891,6 +910,8 @@ function recoveryFields(limits, state) {
|
|
|
891
910
|
...signals.creditsPerCall !== void 0 ? { creditsPerCall: signals.creditsPerCall } : {},
|
|
892
911
|
...signals.shortfallCredits !== void 0 ? { shortfallCredits: signals.shortfallCredits } : {},
|
|
893
912
|
...signals.remainingCalls !== void 0 ? { remainingCalls: signals.remainingCalls } : {},
|
|
913
|
+
...limits.balance?.creditsPerMinorUnit !== void 0 ? { creditsPerMinorUnit: limits.balance.creditsPerMinorUnit } : {},
|
|
914
|
+
...limits.balance?.displayExchangeRate !== void 0 ? { displayExchangeRate: limits.balance.displayExchangeRate } : {},
|
|
894
915
|
...limits.purchaseRef !== void 0 ? { purchaseRef: limits.purchaseRef } : {},
|
|
895
916
|
...limits.autoRecharge !== void 0 ? { autoRecharge: limits.autoRecharge } : {},
|
|
896
917
|
...links ? { links } : {}
|
|
@@ -1147,6 +1168,23 @@ var SolvaPayPaywall = class {
|
|
|
1147
1168
|
console.log(...args);
|
|
1148
1169
|
}
|
|
1149
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Drop cached limits for `customerRef`. When `product` is omitted every
|
|
1173
|
+
* meter/product entry for that customer is removed. Matches both the
|
|
1174
|
+
* backend `cus_…` ref and an unmapped external ref used as a cache key.
|
|
1175
|
+
*/
|
|
1176
|
+
invalidateLimits(customerRef, product) {
|
|
1177
|
+
const aliases = /* @__PURE__ */ new Set([customerRef]);
|
|
1178
|
+
const mapped = this.customerRefMapping.get(customerRef);
|
|
1179
|
+
if (mapped) aliases.add(mapped);
|
|
1180
|
+
if (!customerRef.startsWith("cus_")) aliases.add(`cus_${customerRef}`);
|
|
1181
|
+
for (const key of [...this.limitsCache.keys()]) {
|
|
1182
|
+
const [ref, prod] = key.split(":");
|
|
1183
|
+
if (!ref || !aliases.has(ref)) continue;
|
|
1184
|
+
if (product && prod !== product) continue;
|
|
1185
|
+
this.limitsCache.delete(key);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1150
1188
|
resolveProduct(metadata) {
|
|
1151
1189
|
return requireProductRef(metadata.product);
|
|
1152
1190
|
}
|
|
@@ -2167,6 +2205,9 @@ function createSolvaPay(config) {
|
|
|
2167
2205
|
paywall: {
|
|
2168
2206
|
decide(args, metadata, getCustomerRef) {
|
|
2169
2207
|
return paywall.decide(args, metadata, getCustomerRef);
|
|
2208
|
+
},
|
|
2209
|
+
invalidateLimits(customerRef, product) {
|
|
2210
|
+
paywall.invalidateLimits(customerRef, product);
|
|
2170
2211
|
}
|
|
2171
2212
|
},
|
|
2172
2213
|
// Common API methods exposed directly for convenience
|
|
@@ -2235,7 +2276,7 @@ function createSolvaPay(config) {
|
|
|
2235
2276
|
customerRef: params.customerRef,
|
|
2236
2277
|
productRef: params.productRef,
|
|
2237
2278
|
planRef: params.planRef,
|
|
2238
|
-
returnUrl: params.returnUrl,
|
|
2279
|
+
...params.returnUrl ? { returnUrl: params.returnUrl } : {},
|
|
2239
2280
|
...params.purpose ? { purpose: params.purpose } : {}
|
|
2240
2281
|
});
|
|
2241
2282
|
},
|
|
@@ -2983,12 +3024,15 @@ async function createCheckoutSessionCore(request, body, options = {}) {
|
|
|
2983
3024
|
return customerResult;
|
|
2984
3025
|
}
|
|
2985
3026
|
const customerRef = customerResult;
|
|
2986
|
-
let returnUrl
|
|
2987
|
-
if (
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
3027
|
+
let returnUrl;
|
|
3028
|
+
if (body.returnUrl !== null) {
|
|
3029
|
+
returnUrl = body.returnUrl || options.returnUrl;
|
|
3030
|
+
if (!returnUrl) {
|
|
3031
|
+
try {
|
|
3032
|
+
const url = new URL(request.url);
|
|
3033
|
+
returnUrl = url.origin;
|
|
3034
|
+
} catch {
|
|
3035
|
+
}
|
|
2992
3036
|
}
|
|
2993
3037
|
}
|
|
2994
3038
|
const solvaPay = options.solvaPay || createSolvaPay();
|
|
@@ -2996,7 +3040,7 @@ async function createCheckoutSessionCore(request, body, options = {}) {
|
|
|
2996
3040
|
productRef: body.productRef,
|
|
2997
3041
|
customerRef,
|
|
2998
3042
|
planRef: body.planRef || void 0,
|
|
2999
|
-
returnUrl,
|
|
3043
|
+
...returnUrl ? { returnUrl } : {},
|
|
3000
3044
|
...body.purpose ? { purpose: body.purpose } : {}
|
|
3001
3045
|
});
|
|
3002
3046
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solvapay/server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0-preview-09731417a0a7b4fd3afb4783fdf20b5ad293f63d",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"build": "tsup",
|
|
71
71
|
"dev": "tsup --watch",
|
|
72
72
|
"generate:types": "tsx scripts/generate-types.ts",
|
|
73
|
-
"test": "vitest run __tests__/paywall.unit.test.ts __tests__/paywall-state.unit.test.ts __tests__/paywall-gate.unit.test.ts __tests__/payable-gate.unit.test.ts __tests__/bootstrap-mcp.unit.test.ts __tests__/verify-webhook.unit.test.ts __tests__/edge-exports.unit.test.ts __tests__/credits-usage.unit.test.ts __tests__/limit-outcome-flags.unit.test.ts __tests__/client-error.unit.test.ts src/__tests__/edge-exports.test.ts src/helpers/payment.test.ts src/helpers/usage.test.ts src/resolve-product-ref.test.ts src/verify-product-configuration.test.ts __tests__/auth-core.unit.test.ts __tests__/ensure-customer.unit.test.ts __tests__/create-customer.unit.test.ts __tests__/fetch/cors.test.ts __tests__/fetch/handlers.test.ts __tests__/fetch/utils.test.ts",
|
|
74
|
-
"test:unit": "vitest run __tests__/paywall.unit.test.ts __tests__/paywall-state.unit.test.ts __tests__/paywall-gate.unit.test.ts __tests__/payable-gate.unit.test.ts __tests__/bootstrap-mcp.unit.test.ts __tests__/verify-webhook.unit.test.ts __tests__/edge-exports.unit.test.ts __tests__/credits-usage.unit.test.ts __tests__/limit-outcome-flags.unit.test.ts __tests__/client-error.unit.test.ts src/__tests__/edge-exports.test.ts src/helpers/payment.test.ts src/helpers/usage.test.ts src/resolve-product-ref.test.ts src/verify-product-configuration.test.ts __tests__/auth-core.unit.test.ts __tests__/ensure-customer.unit.test.ts __tests__/create-customer.unit.test.ts __tests__/fetch/cors.test.ts __tests__/fetch/handlers.test.ts __tests__/fetch/utils.test.ts",
|
|
73
|
+
"test": "vitest run __tests__/paywall.unit.test.ts __tests__/paywall-state.unit.test.ts __tests__/paywall-gate.unit.test.ts __tests__/payable-gate.unit.test.ts __tests__/bootstrap-mcp.unit.test.ts __tests__/verify-webhook.unit.test.ts __tests__/edge-exports.unit.test.ts __tests__/credits-usage.unit.test.ts __tests__/limit-outcome-flags.unit.test.ts __tests__/client-error.unit.test.ts __tests__/checkout-core.unit.test.ts src/__tests__/edge-exports.test.ts src/helpers/payment.test.ts src/helpers/usage.test.ts src/resolve-product-ref.test.ts src/verify-product-configuration.test.ts __tests__/auth-core.unit.test.ts __tests__/ensure-customer.unit.test.ts __tests__/create-customer.unit.test.ts __tests__/fetch/cors.test.ts __tests__/fetch/handlers.test.ts __tests__/fetch/utils.test.ts",
|
|
74
|
+
"test:unit": "vitest run __tests__/paywall.unit.test.ts __tests__/paywall-state.unit.test.ts __tests__/paywall-gate.unit.test.ts __tests__/payable-gate.unit.test.ts __tests__/bootstrap-mcp.unit.test.ts __tests__/verify-webhook.unit.test.ts __tests__/edge-exports.unit.test.ts __tests__/credits-usage.unit.test.ts __tests__/limit-outcome-flags.unit.test.ts __tests__/client-error.unit.test.ts __tests__/checkout-core.unit.test.ts src/__tests__/edge-exports.test.ts src/helpers/payment.test.ts src/helpers/usage.test.ts src/resolve-product-ref.test.ts src/verify-product-configuration.test.ts __tests__/auth-core.unit.test.ts __tests__/ensure-customer.unit.test.ts __tests__/create-customer.unit.test.ts __tests__/fetch/cors.test.ts __tests__/fetch/handlers.test.ts __tests__/fetch/utils.test.ts",
|
|
75
75
|
"test:integration": "vitest run --no-file-parallelism __tests__/backend.integration.test.ts __tests__/customer-update.integration.test.ts __tests__/multi-currency-plans.integration.test.ts",
|
|
76
76
|
"test:integration:backend": "vitest run __tests__/backend.integration.test.ts",
|
|
77
77
|
"test:integration:customer": "vitest run __tests__/customer-update.integration.test.ts",
|