@solvapay/server 2.5.0 → 2.6.0-preview-253dcb103840555309a5ff5e4ccfa6a07f7ca6ae
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 +50 -48
- package/dist/edge.js +68 -24
- package/dist/fetch/index.cjs +63 -21
- package/dist/fetch/index.d.cts +18 -14
- package/dist/fetch/index.d.ts +18 -14
- package/dist/fetch/index.js +64 -22
- package/dist/index.cjs +67 -23
- package/dist/index.d.cts +50 -48
- package/dist/index.d.ts +50 -48
- package/dist/index.js +68 -24
- package/package.json +3 -3
package/dist/edge.d.ts
CHANGED
|
@@ -826,9 +826,13 @@ interface components {
|
|
|
826
826
|
LimitBalanceDto: {
|
|
827
827
|
/** @description Credit balance in credits (100 credits = 1 minor currency unit) */
|
|
828
828
|
creditBalance: number;
|
|
829
|
+
/** @description Credits per minor unit of `currency` (typically 100) */
|
|
830
|
+
creditsPerMinorUnit?: number;
|
|
829
831
|
/** @description Credits deducted per metered item (wallet coverage: remainingUnits = balance / creditsPerUnit) */
|
|
830
832
|
creditsPerUnit: number;
|
|
831
833
|
currency: string;
|
|
834
|
+
/** @description USD → display-currency rate used with creditsPerMinorUnit */
|
|
835
|
+
displayExchangeRate?: number;
|
|
832
836
|
/** @description How many metered items the credit balance still covers (`balance / creditsPerUnit`) */
|
|
833
837
|
remainingUnits?: number;
|
|
834
838
|
};
|
|
@@ -1201,17 +1205,17 @@ interface components {
|
|
|
1201
1205
|
};
|
|
1202
1206
|
ProcessPaymentCancelled: {
|
|
1203
1207
|
/**
|
|
1204
|
-
*
|
|
1208
|
+
* @example cancelled
|
|
1205
1209
|
* @enum {string}
|
|
1206
1210
|
*/
|
|
1207
|
-
status: '
|
|
1211
|
+
status: 'cancelled';
|
|
1208
1212
|
};
|
|
1209
1213
|
ProcessPaymentFailed: {
|
|
1210
1214
|
/**
|
|
1211
|
-
*
|
|
1215
|
+
* @example failed
|
|
1212
1216
|
* @enum {string}
|
|
1213
1217
|
*/
|
|
1214
|
-
status: '
|
|
1218
|
+
status: 'failed';
|
|
1215
1219
|
};
|
|
1216
1220
|
ProcessPaymentIntentDto: {
|
|
1217
1221
|
customerRef: string;
|
|
@@ -1220,25 +1224,25 @@ interface components {
|
|
|
1220
1224
|
};
|
|
1221
1225
|
ProcessPaymentProcessing: {
|
|
1222
1226
|
/**
|
|
1223
|
-
*
|
|
1227
|
+
* @example processing
|
|
1224
1228
|
* @enum {string}
|
|
1225
1229
|
*/
|
|
1226
|
-
status: '
|
|
1230
|
+
status: 'processing';
|
|
1227
1231
|
};
|
|
1228
1232
|
ProcessPaymentSucceededBare: {
|
|
1229
1233
|
/**
|
|
1230
|
-
*
|
|
1234
|
+
* @example succeeded
|
|
1231
1235
|
* @enum {string}
|
|
1232
1236
|
*/
|
|
1233
|
-
status: '
|
|
1237
|
+
status: 'succeeded';
|
|
1234
1238
|
};
|
|
1235
1239
|
ProcessPaymentSucceededOneTime: {
|
|
1236
1240
|
oneTimePurchase: components['schemas']['OneTimePurchaseInfo'];
|
|
1237
1241
|
/**
|
|
1238
|
-
*
|
|
1242
|
+
* @example succeeded
|
|
1239
1243
|
* @enum {string}
|
|
1240
1244
|
*/
|
|
1241
|
-
status: '
|
|
1245
|
+
status: 'succeeded';
|
|
1242
1246
|
/**
|
|
1243
1247
|
* @example one-time
|
|
1244
1248
|
* @enum {string}
|
|
@@ -1248,10 +1252,10 @@ interface components {
|
|
|
1248
1252
|
ProcessPaymentSucceededRecurring: {
|
|
1249
1253
|
purchase: components['schemas']['SdkPurchaseResponse'];
|
|
1250
1254
|
/**
|
|
1251
|
-
*
|
|
1255
|
+
* @example succeeded
|
|
1252
1256
|
* @enum {string}
|
|
1253
1257
|
*/
|
|
1254
|
-
status: '
|
|
1258
|
+
status: 'succeeded';
|
|
1255
1259
|
/**
|
|
1256
1260
|
* @example recurring
|
|
1257
1261
|
* @enum {string}
|
|
@@ -1262,10 +1266,10 @@ interface components {
|
|
|
1262
1266
|
/** @description Detail message describing the timeout */
|
|
1263
1267
|
message?: string;
|
|
1264
1268
|
/**
|
|
1265
|
-
*
|
|
1269
|
+
* @example timeout
|
|
1266
1270
|
* @enum {string}
|
|
1267
1271
|
*/
|
|
1268
|
-
status: '
|
|
1272
|
+
status: 'timeout';
|
|
1269
1273
|
};
|
|
1270
1274
|
ProductConfigDto: {
|
|
1271
1275
|
/**
|
|
@@ -3207,10 +3211,10 @@ interface operations {
|
|
|
3207
3211
|
content: {
|
|
3208
3212
|
'application/json': {
|
|
3209
3213
|
/**
|
|
3210
|
-
* Outcome of the delete:
|
|
3214
|
+
* Outcome of the delete: deactivation when purchases exist, otherwise soft-delete. Same in sandbox and live.
|
|
3211
3215
|
* @enum {string}
|
|
3212
3216
|
*/
|
|
3213
|
-
action?: '
|
|
3217
|
+
action?: 'deactivated' | 'soft_deleted';
|
|
3214
3218
|
success?: boolean;
|
|
3215
3219
|
};
|
|
3216
3220
|
};
|
|
@@ -3949,38 +3953,20 @@ type OneTimePurchaseInfo = components['schemas']['OneTimePurchaseInfo'];
|
|
|
3949
3953
|
/**
|
|
3950
3954
|
* Result from processing a payment intent.
|
|
3951
3955
|
*
|
|
3952
|
-
*
|
|
3953
|
-
* branches
|
|
3954
|
-
* recurring vs one-time
|
|
3955
|
-
* `purchase === undefined`. A bare `{ status: 'succeeded' }` is
|
|
3956
|
-
* when the webhook race means the backend can't yet enrich the
|
|
3957
|
-
* with the created purchase — callers should fall back to
|
|
3958
|
-
*
|
|
3959
|
-
*
|
|
3960
|
-
*
|
|
3961
|
-
*
|
|
3962
|
-
*
|
|
3956
|
+
* Derived from the generated `/v1/sdk/payment-intents/{id}/process` 200
|
|
3957
|
+
* response. The `succeeded` branches further discriminate on `type` so
|
|
3958
|
+
* consumers can route to recurring vs one-time handling without guarding
|
|
3959
|
+
* against `purchase === undefined`. A bare `{ status: 'succeeded' }` is
|
|
3960
|
+
* returned when the webhook race means the backend can't yet enrich the
|
|
3961
|
+
* response with the created purchase — callers should fall back to
|
|
3962
|
+
* refetching.
|
|
3963
|
+
*
|
|
3964
|
+
* `failed` and `cancelled` are returned when the Stripe PaymentIntent is
|
|
3965
|
+
* in a terminal non-success state and are routed to `onError` by
|
|
3966
|
+
* `reconcilePayment`. `timeout` carries a retry hint and is routed to
|
|
3967
|
+
* the timeout branch.
|
|
3963
3968
|
*/
|
|
3964
|
-
type ProcessPaymentResult =
|
|
3965
|
-
status: 'succeeded';
|
|
3966
|
-
type: 'recurring';
|
|
3967
|
-
purchase: PurchaseInfo;
|
|
3968
|
-
} | {
|
|
3969
|
-
status: 'succeeded';
|
|
3970
|
-
type: 'one-time';
|
|
3971
|
-
oneTimePurchase: OneTimePurchaseInfo;
|
|
3972
|
-
} | {
|
|
3973
|
-
status: 'succeeded';
|
|
3974
|
-
} | {
|
|
3975
|
-
status: 'processing';
|
|
3976
|
-
} | {
|
|
3977
|
-
status: 'timeout';
|
|
3978
|
-
message?: string;
|
|
3979
|
-
} | {
|
|
3980
|
-
status: 'failed';
|
|
3981
|
-
} | {
|
|
3982
|
-
status: 'cancelled';
|
|
3983
|
-
};
|
|
3969
|
+
type ProcessPaymentResult = operations['PaymentIntentSdkController_processPaymentIntent']['responses']['200']['content']['application/json'];
|
|
3984
3970
|
/**
|
|
3985
3971
|
* Result from processing a credit-topup payment intent.
|
|
3986
3972
|
*
|
|
@@ -4291,6 +4277,10 @@ type PaywallGateRecoveryFields = {
|
|
|
4291
4277
|
shortfallCredits?: number;
|
|
4292
4278
|
/** How many calls the current allowance or wallet still covers. */
|
|
4293
4279
|
remainingCalls?: number;
|
|
4280
|
+
/** Credits per minor currency unit, from the limits balance peg. */
|
|
4281
|
+
creditsPerMinorUnit?: number;
|
|
4282
|
+
/** USD → display-currency rate used with `creditsPerMinorUnit`. */
|
|
4283
|
+
displayExchangeRate?: number;
|
|
4294
4284
|
/** Active purchase reference when the customer already holds a purchase. */
|
|
4295
4285
|
purchaseRef?: string;
|
|
4296
4286
|
/** Purchase/plan status when the backend sent one. */
|
|
@@ -5354,6 +5344,12 @@ interface SolvaPay {
|
|
|
5354
5344
|
*/
|
|
5355
5345
|
paywall: {
|
|
5356
5346
|
decide<TArgs extends PaywallArgs>(args: TArgs, metadata?: PaywallMetadata, getCustomerRef?: (args: TArgs) => string): Promise<PaywallDecision<TArgs>>;
|
|
5347
|
+
/**
|
|
5348
|
+
* Drop cached `checkLimits` snapshots for this customer (optionally
|
|
5349
|
+
* scoped to one product). Call after `activate_plan`, a completed
|
|
5350
|
+
* checkout, or a top-up so the next payable call refetches.
|
|
5351
|
+
*/
|
|
5352
|
+
invalidateLimits(customerRef: string, product?: string): void;
|
|
5357
5353
|
};
|
|
5358
5354
|
/**
|
|
5359
5355
|
* Direct access to the API client for advanced operations.
|
|
@@ -6144,7 +6140,13 @@ declare function processTopupPaymentIntentCore(request: Request, body: {
|
|
|
6144
6140
|
declare function createCheckoutSessionCore(request: Request, body: {
|
|
6145
6141
|
productRef: string;
|
|
6146
6142
|
planRef?: string;
|
|
6147
|
-
|
|
6143
|
+
/**
|
|
6144
|
+
* URL to send the customer back to after payment. Pass `null` to omit
|
|
6145
|
+
* the field entirely — skips `options.returnUrl` and the request-origin
|
|
6146
|
+
* fallback. Use this when the caller has no browsable page to return to
|
|
6147
|
+
* (MCP servers whose public URL is an API endpoint, not a website).
|
|
6148
|
+
*/
|
|
6149
|
+
returnUrl?: string | null;
|
|
6148
6150
|
purpose?: 'credit_topup';
|
|
6149
6151
|
}, options?: {
|
|
6150
6152
|
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
|
@@ -823,9 +823,13 @@ interface components {
|
|
|
823
823
|
LimitBalanceDto: {
|
|
824
824
|
/** @description Credit balance in credits (100 credits = 1 minor currency unit) */
|
|
825
825
|
creditBalance: number;
|
|
826
|
+
/** @description Credits per minor unit of `currency` (typically 100) */
|
|
827
|
+
creditsPerMinorUnit?: number;
|
|
826
828
|
/** @description Credits deducted per metered item (wallet coverage: remainingUnits = balance / creditsPerUnit) */
|
|
827
829
|
creditsPerUnit: number;
|
|
828
830
|
currency: string;
|
|
831
|
+
/** @description USD → display-currency rate used with creditsPerMinorUnit */
|
|
832
|
+
displayExchangeRate?: number;
|
|
829
833
|
/** @description How many metered items the credit balance still covers (`balance / creditsPerUnit`) */
|
|
830
834
|
remainingUnits?: number;
|
|
831
835
|
};
|
|
@@ -1198,17 +1202,17 @@ interface components {
|
|
|
1198
1202
|
};
|
|
1199
1203
|
ProcessPaymentCancelled: {
|
|
1200
1204
|
/**
|
|
1201
|
-
*
|
|
1205
|
+
* @example cancelled
|
|
1202
1206
|
* @enum {string}
|
|
1203
1207
|
*/
|
|
1204
|
-
status: '
|
|
1208
|
+
status: 'cancelled';
|
|
1205
1209
|
};
|
|
1206
1210
|
ProcessPaymentFailed: {
|
|
1207
1211
|
/**
|
|
1208
|
-
*
|
|
1212
|
+
* @example failed
|
|
1209
1213
|
* @enum {string}
|
|
1210
1214
|
*/
|
|
1211
|
-
status: '
|
|
1215
|
+
status: 'failed';
|
|
1212
1216
|
};
|
|
1213
1217
|
ProcessPaymentIntentDto: {
|
|
1214
1218
|
customerRef: string;
|
|
@@ -1217,25 +1221,25 @@ interface components {
|
|
|
1217
1221
|
};
|
|
1218
1222
|
ProcessPaymentProcessing: {
|
|
1219
1223
|
/**
|
|
1220
|
-
*
|
|
1224
|
+
* @example processing
|
|
1221
1225
|
* @enum {string}
|
|
1222
1226
|
*/
|
|
1223
|
-
status: '
|
|
1227
|
+
status: 'processing';
|
|
1224
1228
|
};
|
|
1225
1229
|
ProcessPaymentSucceededBare: {
|
|
1226
1230
|
/**
|
|
1227
|
-
*
|
|
1231
|
+
* @example succeeded
|
|
1228
1232
|
* @enum {string}
|
|
1229
1233
|
*/
|
|
1230
|
-
status: '
|
|
1234
|
+
status: 'succeeded';
|
|
1231
1235
|
};
|
|
1232
1236
|
ProcessPaymentSucceededOneTime: {
|
|
1233
1237
|
oneTimePurchase: components['schemas']['OneTimePurchaseInfo'];
|
|
1234
1238
|
/**
|
|
1235
|
-
*
|
|
1239
|
+
* @example succeeded
|
|
1236
1240
|
* @enum {string}
|
|
1237
1241
|
*/
|
|
1238
|
-
status: '
|
|
1242
|
+
status: 'succeeded';
|
|
1239
1243
|
/**
|
|
1240
1244
|
* @example one-time
|
|
1241
1245
|
* @enum {string}
|
|
@@ -1245,10 +1249,10 @@ interface components {
|
|
|
1245
1249
|
ProcessPaymentSucceededRecurring: {
|
|
1246
1250
|
purchase: components['schemas']['SdkPurchaseResponse'];
|
|
1247
1251
|
/**
|
|
1248
|
-
*
|
|
1252
|
+
* @example succeeded
|
|
1249
1253
|
* @enum {string}
|
|
1250
1254
|
*/
|
|
1251
|
-
status: '
|
|
1255
|
+
status: 'succeeded';
|
|
1252
1256
|
/**
|
|
1253
1257
|
* @example recurring
|
|
1254
1258
|
* @enum {string}
|
|
@@ -1259,10 +1263,10 @@ interface components {
|
|
|
1259
1263
|
/** @description Detail message describing the timeout */
|
|
1260
1264
|
message?: string;
|
|
1261
1265
|
/**
|
|
1262
|
-
*
|
|
1266
|
+
* @example timeout
|
|
1263
1267
|
* @enum {string}
|
|
1264
1268
|
*/
|
|
1265
|
-
status: '
|
|
1269
|
+
status: 'timeout';
|
|
1266
1270
|
};
|
|
1267
1271
|
ProductConfigDto: {
|
|
1268
1272
|
/**
|