@solvapay/server 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/edge.d.ts +40 -15
- package/dist/edge.js +77 -35
- package/dist/fetch/index.cjs +84 -42
- package/dist/fetch/index.d.cts +10 -1
- package/dist/fetch/index.d.ts +10 -1
- package/dist/fetch/index.js +75 -33
- package/dist/index.cjs +93 -51
- package/dist/index.d.cts +41 -16
- package/dist/index.d.ts +41 -16
- package/dist/index.js +81 -39
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -254,6 +254,15 @@ interface components {
|
|
|
254
254
|
};
|
|
255
255
|
CheckLimitRequest: {
|
|
256
256
|
customerRef: string;
|
|
257
|
+
/** @description SDK-only free-tool allowance. Authenticated by the provider secret key, so `cap` is the provider's own declaration, not customer input. */
|
|
258
|
+
freeAllowance?: {
|
|
259
|
+
cap?: number;
|
|
260
|
+
/** @description Free meter name. Must match `/^free-[a-z0-9-]+$/`. */
|
|
261
|
+
meter?: string;
|
|
262
|
+
/** @enum {string} */
|
|
263
|
+
scope?: 'rolling_window' | 'lifetime';
|
|
264
|
+
windowDays?: number;
|
|
265
|
+
};
|
|
257
266
|
includeCheckoutSession?: boolean;
|
|
258
267
|
meterName?: string;
|
|
259
268
|
productRef: string;
|
|
@@ -909,7 +918,7 @@ interface components {
|
|
|
909
918
|
* Authoritative paywall classification shared with Managed MCP. Present on denial responses only.
|
|
910
919
|
* @enum {string}
|
|
911
920
|
*/
|
|
912
|
-
paywallReason?: 'activation_required' | 'topup_required' | 'payment_required';
|
|
921
|
+
paywallReason?: 'activation_required' | 'topup_required' | 'payment_required' | 'limit_reached';
|
|
913
922
|
/** @description Display name of the active or default plan */
|
|
914
923
|
planName?: string;
|
|
915
924
|
/** @description Active plan reference when the customer already holds a purchase */
|
|
@@ -3919,6 +3928,16 @@ type AttachBusinessDetailsParams = {
|
|
|
3919
3928
|
} & components['schemas']['BusinessDetailsDto'];
|
|
3920
3929
|
type AttachBusinessDetailsResult = components['schemas']['AttachBusinessDetailsResponse'];
|
|
3921
3930
|
type CheckLimitsRequest = components['schemas']['CheckLimitRequest'];
|
|
3931
|
+
/**
|
|
3932
|
+
* Per-customer cap declared in code on a `registerFree` tool. Not a
|
|
3933
|
+
* registered `Meter` — the name must match `/^free-[a-z0-9-]+$/`.
|
|
3934
|
+
*
|
|
3935
|
+
* Derived from `CheckLimitRequest.freeAllowance`. nestjs-zod omits
|
|
3936
|
+
* nested `required`, so OpenAPI marks every property optional; the
|
|
3937
|
+
* object schema only treats `windowDays` as optional.
|
|
3938
|
+
*/
|
|
3939
|
+
type GeneratedFreeAllowance = NonNullable<CheckLimitsRequest['freeAllowance']>;
|
|
3940
|
+
type FreeLimit = Required<Omit<GeneratedFreeAllowance, 'windowDays'>> & Pick<GeneratedFreeAllowance, 'windowDays'>;
|
|
3922
3941
|
/**
|
|
3923
3942
|
* `LimitResponse` plus a deprecated SDK-only `plan` alias.
|
|
3924
3943
|
*
|
|
@@ -4242,6 +4261,11 @@ interface PaywallMetadata {
|
|
|
4242
4261
|
* attributable. Set by `registerPayable` / `buildPayableHandler`.
|
|
4243
4262
|
*/
|
|
4244
4263
|
toolName?: string;
|
|
4264
|
+
/**
|
|
4265
|
+
* SDK-only free-tool allowance. When set, `decide()` sends this as
|
|
4266
|
+
* `freeAllowance` and the free meter wins over `meterName`.
|
|
4267
|
+
*/
|
|
4268
|
+
freeLimit?: FreeLimit;
|
|
4245
4269
|
}
|
|
4246
4270
|
/**
|
|
4247
4271
|
* Structured content for paywall errors (MCP structuredContent and manual handling).
|
|
@@ -4278,9 +4302,9 @@ type PaywallGateRecoveryFields = {
|
|
|
4278
4302
|
creditBalance?: number;
|
|
4279
4303
|
/**
|
|
4280
4304
|
* `PaywallState.kind` — same vocabulary as the classifier, not a third enum.
|
|
4281
|
-
* `upgrade_required` / `
|
|
4282
|
-
*
|
|
4283
|
-
*
|
|
4305
|
+
* `upgrade_required` / `reactivation_required` are SDK-only. The
|
|
4306
|
+
* backend's `paywallReason` covers `activation_required` /
|
|
4307
|
+
* `topup_required` / `payment_required` / `limit_reached`.
|
|
4284
4308
|
*/
|
|
4285
4309
|
reason?: PaywallReason;
|
|
4286
4310
|
/** Single primary recovery the agent should take. */
|
|
@@ -4404,6 +4428,7 @@ type PaywallDecision<T> = {
|
|
|
4404
4428
|
* Types for configuring various aspects of the SDK including retry behavior,
|
|
4405
4429
|
* payable protection, and framework adapters.
|
|
4406
4430
|
*/
|
|
4431
|
+
|
|
4407
4432
|
/**
|
|
4408
4433
|
* Retry configuration options
|
|
4409
4434
|
*/
|
|
@@ -4482,6 +4507,11 @@ interface PayableOptions {
|
|
|
4482
4507
|
* Product reference (alias for product, preferred for consistency with backend API)
|
|
4483
4508
|
*/
|
|
4484
4509
|
productRef?: string;
|
|
4510
|
+
/**
|
|
4511
|
+
* SDK-only free-tool allowance. When set, this meter's name wins over
|
|
4512
|
+
* `meterName` / `usageType`, and `checkLimits` sends a `freeAllowance` block.
|
|
4513
|
+
*/
|
|
4514
|
+
freeLimit?: FreeLimit;
|
|
4485
4515
|
/**
|
|
4486
4516
|
* Meter to charge against (defaults to `requests`).
|
|
4487
4517
|
*/
|
|
@@ -5042,14 +5072,7 @@ interface SolvaPay {
|
|
|
5042
5072
|
* }
|
|
5043
5073
|
* ```
|
|
5044
5074
|
*/
|
|
5045
|
-
checkLimits(params:
|
|
5046
|
-
customerRef: string;
|
|
5047
|
-
productRef: string;
|
|
5048
|
-
planRef?: string;
|
|
5049
|
-
meterName?: string;
|
|
5050
|
-
/** @deprecated Use `meterName`. */
|
|
5051
|
-
usageType?: string;
|
|
5052
|
-
}): Promise<LimitResponseWithPlan>;
|
|
5075
|
+
checkLimits(params: CheckLimitsRequest): Promise<LimitResponseWithPlan>;
|
|
5053
5076
|
/**
|
|
5054
5077
|
* Track usage for a customer action.
|
|
5055
5078
|
*
|
|
@@ -5597,8 +5620,8 @@ declare const PaywallStructuredContentSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
5597
5620
|
topup_required: "topup_required";
|
|
5598
5621
|
payment_required: "payment_required";
|
|
5599
5622
|
activation_required: "activation_required";
|
|
5600
|
-
upgrade_required: "upgrade_required";
|
|
5601
5623
|
limit_reached: "limit_reached";
|
|
5624
|
+
upgrade_required: "upgrade_required";
|
|
5602
5625
|
reactivation_required: "reactivation_required";
|
|
5603
5626
|
}>>;
|
|
5604
5627
|
nextAction: z.ZodOptional<z.ZodEnum<{
|
|
@@ -5646,8 +5669,8 @@ declare const PaywallStructuredContentSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
5646
5669
|
topup_required: "topup_required";
|
|
5647
5670
|
payment_required: "payment_required";
|
|
5648
5671
|
activation_required: "activation_required";
|
|
5649
|
-
upgrade_required: "upgrade_required";
|
|
5650
5672
|
limit_reached: "limit_reached";
|
|
5673
|
+
upgrade_required: "upgrade_required";
|
|
5651
5674
|
reactivation_required: "reactivation_required";
|
|
5652
5675
|
}>>;
|
|
5653
5676
|
nextAction: z.ZodOptional<z.ZodEnum<{
|
|
@@ -5728,7 +5751,9 @@ declare function nextActionFor(state: PaywallState): PaywallNextAction;
|
|
|
5728
5751
|
*
|
|
5729
5752
|
* Precedence:
|
|
5730
5753
|
* 1. `activationRequired` / `paywallReason === 'activation_required'`.
|
|
5731
|
-
* 2. `paywallReason === '
|
|
5754
|
+
* 2. `paywallReason === 'limit_reached'` — free-allowance exhaustion
|
|
5755
|
+
* (no purchaseRef / planRef) must not fall through to upgrade_required.
|
|
5756
|
+
* 3. `paywallReason === 'topup_required'` — backend stays authoritative
|
|
5732
5757
|
* for credit-based denials (same rule as Managed MCP).
|
|
5733
5758
|
* 3. Authoritative `needsTopUp` / `needsUpgrade` flags from `decideLimit`.
|
|
5734
5759
|
* 4. Credit-field presence + a real shortfall (`balance < cost`).
|
|
@@ -6555,4 +6580,4 @@ declare function verifyWebhook({ body, signature, secret, }: {
|
|
|
6555
6580
|
secret: string;
|
|
6556
6581
|
}): WebhookEvent;
|
|
6557
6582
|
|
|
6558
|
-
export { type ActivatePlanResult, type AssignCreditsRequest, type AssignCreditsResponse, type AttachBusinessDetailsParams, type AttachBusinessDetailsResult, type AuthenticatedUser, type AutoRechargeConfig, type AutoRechargeDisplayBlock, type AutoRechargeInput, type AutoRechargeResponse, BALANCE_RECONCILE_DELAYS_MS, type ConfigureMcpPlansRequest, type ConfigureMcpPlansResponse, type CreateSolvaPayConfig, type CreditActivityEntry, type CreditActivityResult, type CreditActivityType, type CreditDebitResult, type CreditDebitSkipReason, type CreditDisplayBlock, type CreditSignals, type CustomerBalanceResult, type CustomerResponseMapped, type CustomerWebhookObject, type ErrorResult, type GetHistoryResult, type GetUsageResult, type HttpAdapterOptions, type LimitActivationBalance, type LimitActivationProduct, type LimitAutoRechargeDto, type LimitPlanSummary, type LimitResponseWithPlan, type McpBootstrapPlanInput, type McpBootstrapRequest, type McpBootstrapResponse, type McpServerLike, type McpToolPlanMappingInput, type NextAdapterOptions, type OneTimePurchaseInfo, type PayableAllowResult, type PayableFunction, type PayableGateOptions, type PayableGateResult, type PayableOptions, type PayablePaywallResult, type PaymentMethodInfo, type PaywallArgs, type PaywallDecision, PaywallError, type PaywallGateRecoveryFields, type PaywallMetadata, type PaywallNextAction, type PaywallReason, type PaywallState, type PaywallStructuredContent, PaywallStructuredContentSchema, type ProcessPaymentResult, type ProductConfigurationStatus, type ProtectHandlerContext, type PurchaseCheckResult, type PurchaseInfo, type RegisterVirtualToolsMcpOptions, type RetryOptions, type SaveAutoRechargeInput, type SaveAutoRechargeResponse, type SdkMerchantResponse, type SdkProductResponse, type ServerClientOptions, type SolvaPay, type SolvaPayClient, TOPUP_BALANCE_POLL_DELAYS_MS, type ToolPlanMappingInput, type TopupProcessResult, type TrackUsageBulkRequest, type TrackUsageBulkResponse, type TrackUsageRequest, type TrackUsageResponse, type UsageLimitsInput, VIRTUAL_TOOL_DEFINITIONS, type VerifyProductConfigurationOptions, type VirtualToolDefinition, type VirtualToolsOptions, type WebhookEvent, type WebhookEventForType, type WebhookEventObjectMap, type WebhookEventType, type WebhookProduct, activatePlanCore, attachBusinessDetailsCore, buildGateMessage, buildNudgeMessage, buildPaywallGate, cancelPurchaseCore, checkLimitsCore, checkPurchaseCore, classifyPaywallState, type components, createCheckoutSessionCore, createCustomerSessionCore, createPaymentIntentCore, createSolvaPay, createSolvaPayClient, createTopupPaymentIntentCore, createVirtualTools, creditSignals, deriveUsageSnapshot, disableAutoRechargeCore, getAuthenticatedUserCore, getAutoRechargeCore, getCustomerBalanceCore, getHistoryCore, getMerchantCore, getPaymentMethodCore, getProductCore, getUsageCore, handleRouteError, isErrorResult, isPaywallStructuredContent, jsonSchemaToZodRawShape, linkLabel, listPlansCore, nextActionFor, paywallErrorToClientPayload, planLadder, pollBalanceUntilIncreased, processPaymentIntentCore, processTopupPaymentIntentCore, reactivatePurchaseCore, registerVirtualToolsMcpImpl, saveAutoRechargeCore, syncCustomerCore, trackUsageCore, verifyProductConfiguration, verifyWebhook, withRetry };
|
|
6583
|
+
export { type ActivatePlanResult, type AssignCreditsRequest, type AssignCreditsResponse, type AttachBusinessDetailsParams, type AttachBusinessDetailsResult, type AuthenticatedUser, type AutoRechargeConfig, type AutoRechargeDisplayBlock, type AutoRechargeInput, type AutoRechargeResponse, BALANCE_RECONCILE_DELAYS_MS, type ConfigureMcpPlansRequest, type ConfigureMcpPlansResponse, type CreateSolvaPayConfig, type CreditActivityEntry, type CreditActivityResult, type CreditActivityType, type CreditDebitResult, type CreditDebitSkipReason, type CreditDisplayBlock, type CreditSignals, type CustomerBalanceResult, type CustomerResponseMapped, type CustomerWebhookObject, type ErrorResult, type FreeLimit, type GetHistoryResult, type GetUsageResult, type HttpAdapterOptions, type LimitActivationBalance, type LimitActivationProduct, type LimitAutoRechargeDto, type LimitPlanSummary, type LimitResponseWithPlan, type McpBootstrapPlanInput, type McpBootstrapRequest, type McpBootstrapResponse, type McpServerLike, type McpToolPlanMappingInput, type NextAdapterOptions, type OneTimePurchaseInfo, type PayableAllowResult, type PayableFunction, type PayableGateOptions, type PayableGateResult, type PayableOptions, type PayablePaywallResult, type PaymentMethodInfo, type PaywallArgs, type PaywallDecision, PaywallError, type PaywallGateRecoveryFields, type PaywallMetadata, type PaywallNextAction, type PaywallReason, type PaywallState, type PaywallStructuredContent, PaywallStructuredContentSchema, type ProcessPaymentResult, type ProductConfigurationStatus, type ProtectHandlerContext, type PurchaseCheckResult, type PurchaseInfo, type RegisterVirtualToolsMcpOptions, type RetryOptions, type SaveAutoRechargeInput, type SaveAutoRechargeResponse, type SdkMerchantResponse, type SdkProductResponse, type ServerClientOptions, type SolvaPay, type SolvaPayClient, TOPUP_BALANCE_POLL_DELAYS_MS, type ToolPlanMappingInput, type TopupProcessResult, type TrackUsageBulkRequest, type TrackUsageBulkResponse, type TrackUsageRequest, type TrackUsageResponse, type UsageLimitsInput, VIRTUAL_TOOL_DEFINITIONS, type VerifyProductConfigurationOptions, type VirtualToolDefinition, type VirtualToolsOptions, type WebhookEvent, type WebhookEventForType, type WebhookEventObjectMap, type WebhookEventType, type WebhookProduct, activatePlanCore, attachBusinessDetailsCore, buildGateMessage, buildNudgeMessage, buildPaywallGate, cancelPurchaseCore, checkLimitsCore, checkPurchaseCore, classifyPaywallState, type components, createCheckoutSessionCore, createCustomerSessionCore, createPaymentIntentCore, createSolvaPay, createSolvaPayClient, createTopupPaymentIntentCore, createVirtualTools, creditSignals, deriveUsageSnapshot, disableAutoRechargeCore, getAuthenticatedUserCore, getAutoRechargeCore, getCustomerBalanceCore, getHistoryCore, getMerchantCore, getPaymentMethodCore, getProductCore, getUsageCore, handleRouteError, isErrorResult, isPaywallStructuredContent, jsonSchemaToZodRawShape, linkLabel, listPlansCore, nextActionFor, paywallErrorToClientPayload, planLadder, pollBalanceUntilIncreased, processPaymentIntentCore, processTopupPaymentIntentCore, reactivatePurchaseCore, registerVirtualToolsMcpImpl, saveAutoRechargeCore, syncCustomerCore, trackUsageCore, verifyProductConfiguration, verifyWebhook, withRetry };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import crypto from "crypto";
|
|
3
|
-
import { SolvaPayError as
|
|
3
|
+
import { SolvaPayError as SolvaPayError8 } from "@solvapay/core";
|
|
4
4
|
|
|
5
5
|
// src/client.ts
|
|
6
6
|
import { SolvaPayError } from "@solvapay/core";
|
|
@@ -675,6 +675,9 @@ function classifyPaywallState(limits) {
|
|
|
675
675
|
if (limits.activationRequired === true || limits.paywallReason === "activation_required") {
|
|
676
676
|
return { kind: "activation_required" };
|
|
677
677
|
}
|
|
678
|
+
if (limits.paywallReason === "limit_reached") {
|
|
679
|
+
return { kind: "limit_reached" };
|
|
680
|
+
}
|
|
678
681
|
if (limits.paywallReason === "topup_required") {
|
|
679
682
|
return { kind: "topup_required" };
|
|
680
683
|
}
|
|
@@ -738,9 +741,12 @@ function formatCreditsWithMoney(credits, gate) {
|
|
|
738
741
|
}
|
|
739
742
|
return `${amount} credits`;
|
|
740
743
|
}
|
|
744
|
+
function isFreeMeter(name) {
|
|
745
|
+
return typeof name === "string" && name.startsWith("free-");
|
|
746
|
+
}
|
|
741
747
|
function meterLabel(gate) {
|
|
742
748
|
if (!gate.meterName) return "units";
|
|
743
|
-
return gate.meterName.replace(/_/g, " ");
|
|
749
|
+
return gate.meterName.replace(/[_-]/g, " ");
|
|
744
750
|
}
|
|
745
751
|
function namedCheckoutMarkdown(url, label = "Open checkout") {
|
|
746
752
|
return `[${label}](${url})`;
|
|
@@ -776,6 +782,12 @@ function buildGateMessage(state, gate) {
|
|
|
776
782
|
switch (state.kind) {
|
|
777
783
|
case "limit_reached": {
|
|
778
784
|
const included = gate.included;
|
|
785
|
+
if (isFreeMeter(gate.meterName)) {
|
|
786
|
+
const total = included?.total;
|
|
787
|
+
const usedLine2 = total !== void 0 ? `You've used all ${total} ${meterLabel(gate)} for this period.` : `You've used all ${meterLabel(gate)} for this period.`;
|
|
788
|
+
const switchLine2 = ladder ? ` Pick a plan to keep going: ${ladder}. ${callViewer("account").replace(/^c/, "C")} for usage and recovery.` : recoverClause(url, "keep going", "checkout");
|
|
789
|
+
return `${usedLine2}${switchLine2}`;
|
|
790
|
+
}
|
|
779
791
|
const price = gate.unitPriceMinor != null && gate.currency ? formatMinor(gate.unitPriceMinor, gate.currency) : null;
|
|
780
792
|
const usedLine = included ? `You've used ${included.used} of ${included.total} included ${meterLabel(gate)} this period.` : `You've reached the included usage for this period.`;
|
|
781
793
|
const nextLine = price ? ` The next call is ${price}.` : "";
|
|
@@ -1102,6 +1114,7 @@ function createRequestDeduplicator(options = {}) {
|
|
|
1102
1114
|
}
|
|
1103
1115
|
|
|
1104
1116
|
// src/paywall.ts
|
|
1117
|
+
import { SolvaPayError as SolvaPayError3 } from "@solvapay/core";
|
|
1105
1118
|
var PaywallError = class extends Error {
|
|
1106
1119
|
/**
|
|
1107
1120
|
* Creates a new PaywallError instance.
|
|
@@ -1152,6 +1165,15 @@ var sharedLimitsFetchDeduplicator = createRequestDeduplicator({
|
|
|
1152
1165
|
});
|
|
1153
1166
|
var sharedLimitsFetchClaims = /* @__PURE__ */ new Map();
|
|
1154
1167
|
var EXTRA_FORWARD_KEY = "__solvapayExtra";
|
|
1168
|
+
function trackUsageExtra(metadata, outcome, consequence) {
|
|
1169
|
+
if (metadata.freeLimit) {
|
|
1170
|
+
return { meterName: metadata.freeLimit.meter };
|
|
1171
|
+
}
|
|
1172
|
+
if (outcome === "success") {
|
|
1173
|
+
return { usageClass: consequence === "overage" ? "overage" : "included" };
|
|
1174
|
+
}
|
|
1175
|
+
return void 0;
|
|
1176
|
+
}
|
|
1155
1177
|
var SolvaPayPaywall = class {
|
|
1156
1178
|
constructor(apiClient, options = {}) {
|
|
1157
1179
|
this.apiClient = apiClient;
|
|
@@ -1211,10 +1233,19 @@ var SolvaPayPaywall = class {
|
|
|
1211
1233
|
*/
|
|
1212
1234
|
async decide(args, metadata = {}, getCustomerRef) {
|
|
1213
1235
|
const product = this.resolveProduct(metadata);
|
|
1214
|
-
const usageType = metadata.meterName || metadata.usageType || "requests";
|
|
1236
|
+
const usageType = metadata.freeLimit?.meter || metadata.meterName || metadata.usageType || "requests";
|
|
1215
1237
|
const requestId = this.generateRequestId();
|
|
1216
1238
|
const startTime = Date.now();
|
|
1217
1239
|
const inputCustomerRef = getCustomerRef ? getCustomerRef(args) : args.auth?.customer_ref || "anonymous";
|
|
1240
|
+
if (metadata.freeLimit) {
|
|
1241
|
+
const resolved = typeof inputCustomerRef === "string" ? inputCustomerRef.trim() : "";
|
|
1242
|
+
if (!resolved || resolved === "anonymous") {
|
|
1243
|
+
throw new SolvaPayError3("identity required", {
|
|
1244
|
+
status: 401,
|
|
1245
|
+
code: "identity_required"
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1218
1249
|
let backendCustomerRef;
|
|
1219
1250
|
if (inputCustomerRef.startsWith("cus_")) {
|
|
1220
1251
|
backendCustomerRef = inputCustomerRef;
|
|
@@ -1265,7 +1296,8 @@ var SolvaPayPaywall = class {
|
|
|
1265
1296
|
// `checkLimitsCore`, which powers the React `useLimits` hook)
|
|
1266
1297
|
// leave this unset and the backend skips the session-creation
|
|
1267
1298
|
// side effect.
|
|
1268
|
-
includeCheckoutSession: true
|
|
1299
|
+
includeCheckoutSession: true,
|
|
1300
|
+
...metadata.freeLimit ? { freeAllowance: metadata.freeLimit } : {}
|
|
1269
1301
|
});
|
|
1270
1302
|
}
|
|
1271
1303
|
);
|
|
@@ -1309,7 +1341,8 @@ var SolvaPayPaywall = class {
|
|
|
1309
1341
|
"paywall",
|
|
1310
1342
|
requestId,
|
|
1311
1343
|
latencyMs,
|
|
1312
|
-
metadata.toolName
|
|
1344
|
+
metadata.toolName,
|
|
1345
|
+
metadata.freeLimit ? { meterName: metadata.freeLimit.meter } : void 0
|
|
1313
1346
|
).catch(() => void 0);
|
|
1314
1347
|
const gate = buildPaywallGate(
|
|
1315
1348
|
product,
|
|
@@ -1356,7 +1389,7 @@ var SolvaPayPaywall = class {
|
|
|
1356
1389
|
*/
|
|
1357
1390
|
async runAllow(decision, handler, metadata, args) {
|
|
1358
1391
|
const product = this.resolveProduct(metadata);
|
|
1359
|
-
const usageType = metadata.meterName || metadata.usageType || "requests";
|
|
1392
|
+
const usageType = metadata.freeLimit?.meter || metadata.meterName || metadata.usageType || "requests";
|
|
1360
1393
|
const requestId = decision.requestId;
|
|
1361
1394
|
const startTime = Date.now();
|
|
1362
1395
|
const forwardedExtra = args[EXTRA_FORWARD_KEY];
|
|
@@ -1375,7 +1408,8 @@ var SolvaPayPaywall = class {
|
|
|
1375
1408
|
"success",
|
|
1376
1409
|
requestId,
|
|
1377
1410
|
latencyMs,
|
|
1378
|
-
metadata.toolName
|
|
1411
|
+
metadata.toolName,
|
|
1412
|
+
trackUsageExtra(metadata, "success", decision.consequence)
|
|
1379
1413
|
).catch(() => void 0);
|
|
1380
1414
|
return result;
|
|
1381
1415
|
} catch (error) {
|
|
@@ -1394,7 +1428,8 @@ var SolvaPayPaywall = class {
|
|
|
1394
1428
|
"fail",
|
|
1395
1429
|
requestId,
|
|
1396
1430
|
latencyMs,
|
|
1397
|
-
metadata.toolName
|
|
1431
|
+
metadata.toolName,
|
|
1432
|
+
trackUsageExtra(metadata, "fail")
|
|
1398
1433
|
).catch(() => void 0);
|
|
1399
1434
|
}
|
|
1400
1435
|
throw error;
|
|
@@ -1585,7 +1620,7 @@ var SolvaPayPaywall = class {
|
|
|
1585
1620
|
}
|
|
1586
1621
|
return backendRef;
|
|
1587
1622
|
}
|
|
1588
|
-
async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration, toolName) {
|
|
1623
|
+
async trackUsage(customerRef, productRef, action, outcome, requestId, actionDuration, toolName, extra) {
|
|
1589
1624
|
await withRetry(
|
|
1590
1625
|
() => this.apiClient.trackUsage({
|
|
1591
1626
|
customerRef,
|
|
@@ -1598,7 +1633,9 @@ var SolvaPayPaywall = class {
|
|
|
1598
1633
|
metadata: {
|
|
1599
1634
|
action: action || "api_requests",
|
|
1600
1635
|
requestId,
|
|
1601
|
-
...toolName ? { toolName } : {}
|
|
1636
|
+
...toolName ? { toolName } : {},
|
|
1637
|
+
...extra?.meterName ? { meterName: extra.meterName } : {},
|
|
1638
|
+
...extra?.usageClass ? { usageClass: extra.usageClass } : {}
|
|
1602
1639
|
},
|
|
1603
1640
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1604
1641
|
}),
|
|
@@ -1938,7 +1975,7 @@ var McpAdapter = class {
|
|
|
1938
1975
|
};
|
|
1939
1976
|
|
|
1940
1977
|
// src/factory.ts
|
|
1941
|
-
import { SolvaPayError as
|
|
1978
|
+
import { SolvaPayError as SolvaPayError4, getSolvaPayConfig } from "@solvapay/core";
|
|
1942
1979
|
|
|
1943
1980
|
// src/virtual-tools.ts
|
|
1944
1981
|
var TOOL_GET_USER_INFO = {
|
|
@@ -2216,25 +2253,25 @@ function createSolvaPay(config) {
|
|
|
2216
2253
|
},
|
|
2217
2254
|
createPaymentIntent(params) {
|
|
2218
2255
|
if (!apiClient.createPaymentIntent) {
|
|
2219
|
-
throw new
|
|
2256
|
+
throw new SolvaPayError4("createPaymentIntent is not available on this API client");
|
|
2220
2257
|
}
|
|
2221
2258
|
return apiClient.createPaymentIntent(params);
|
|
2222
2259
|
},
|
|
2223
2260
|
createTopupPaymentIntent(params) {
|
|
2224
2261
|
if (!apiClient.createTopupPaymentIntent) {
|
|
2225
|
-
throw new
|
|
2262
|
+
throw new SolvaPayError4("createTopupPaymentIntent is not available on this API client");
|
|
2226
2263
|
}
|
|
2227
2264
|
return apiClient.createTopupPaymentIntent(params);
|
|
2228
2265
|
},
|
|
2229
2266
|
processPaymentIntent(params) {
|
|
2230
2267
|
if (!apiClient.processPaymentIntent) {
|
|
2231
|
-
throw new
|
|
2268
|
+
throw new SolvaPayError4("processPaymentIntent is not available on this API client");
|
|
2232
2269
|
}
|
|
2233
2270
|
return apiClient.processPaymentIntent(params);
|
|
2234
2271
|
},
|
|
2235
2272
|
attachBusinessDetails(params) {
|
|
2236
2273
|
if (!apiClient.attachBusinessDetails) {
|
|
2237
|
-
throw new
|
|
2274
|
+
throw new SolvaPayError4("attachBusinessDetails is not available on this API client");
|
|
2238
2275
|
}
|
|
2239
2276
|
return apiClient.attachBusinessDetails(params);
|
|
2240
2277
|
},
|
|
@@ -2246,13 +2283,13 @@ function createSolvaPay(config) {
|
|
|
2246
2283
|
},
|
|
2247
2284
|
trackUsageBulk(params) {
|
|
2248
2285
|
if (!apiClient.trackUsageBulk) {
|
|
2249
|
-
throw new
|
|
2286
|
+
throw new SolvaPayError4("trackUsageBulk is not available on this API client");
|
|
2250
2287
|
}
|
|
2251
2288
|
return apiClient.trackUsageBulk(params);
|
|
2252
2289
|
},
|
|
2253
2290
|
createCustomer(params) {
|
|
2254
2291
|
if (!apiClient.createCustomer) {
|
|
2255
|
-
throw new
|
|
2292
|
+
throw new SolvaPayError4("createCustomer is not available on this API client");
|
|
2256
2293
|
}
|
|
2257
2294
|
return apiClient.createCustomer({ ...params, metadata: params.metadata ?? {} });
|
|
2258
2295
|
},
|
|
@@ -2261,13 +2298,13 @@ function createSolvaPay(config) {
|
|
|
2261
2298
|
},
|
|
2262
2299
|
assignCredits(params) {
|
|
2263
2300
|
if (!apiClient.assignCredits) {
|
|
2264
|
-
throw new
|
|
2301
|
+
throw new SolvaPayError4("assignCredits is not available on this API client");
|
|
2265
2302
|
}
|
|
2266
2303
|
return apiClient.assignCredits(params);
|
|
2267
2304
|
},
|
|
2268
2305
|
getCustomerBalance(params) {
|
|
2269
2306
|
if (!apiClient.getCustomerBalance) {
|
|
2270
|
-
throw new
|
|
2307
|
+
throw new SolvaPayError4("getCustomerBalance is not available on this API client");
|
|
2271
2308
|
}
|
|
2272
2309
|
return apiClient.getCustomerBalance(params);
|
|
2273
2310
|
},
|
|
@@ -2285,19 +2322,19 @@ function createSolvaPay(config) {
|
|
|
2285
2322
|
},
|
|
2286
2323
|
activatePlan(params) {
|
|
2287
2324
|
if (!apiClient.activatePlan) {
|
|
2288
|
-
throw new
|
|
2325
|
+
throw new SolvaPayError4("activatePlan is not available on this API client");
|
|
2289
2326
|
}
|
|
2290
2327
|
return apiClient.activatePlan(params);
|
|
2291
2328
|
},
|
|
2292
2329
|
bootstrapMcpProduct(params) {
|
|
2293
2330
|
if (!apiClient.bootstrapMcpProduct) {
|
|
2294
|
-
throw new
|
|
2331
|
+
throw new SolvaPayError4("bootstrapMcpProduct is not available on this API client");
|
|
2295
2332
|
}
|
|
2296
2333
|
return apiClient.bootstrapMcpProduct(params);
|
|
2297
2334
|
},
|
|
2298
2335
|
configureMcpPlans(productRef, params) {
|
|
2299
2336
|
if (!apiClient.configureMcpPlans) {
|
|
2300
|
-
throw new
|
|
2337
|
+
throw new SolvaPayError4("configureMcpPlans is not available on this API client");
|
|
2301
2338
|
}
|
|
2302
2339
|
return apiClient.configureMcpPlans(productRef, params);
|
|
2303
2340
|
},
|
|
@@ -2310,12 +2347,13 @@ function createSolvaPay(config) {
|
|
|
2310
2347
|
// Payable API for framework-specific handlers
|
|
2311
2348
|
payable(options = {}) {
|
|
2312
2349
|
const product = resolveProductRef(options.productRef || options.product);
|
|
2313
|
-
const usageType = options.meterName || options.usageType || "requests";
|
|
2350
|
+
const usageType = options.freeLimit?.meter || options.meterName || options.usageType || "requests";
|
|
2314
2351
|
const metadata = {
|
|
2315
2352
|
product,
|
|
2316
2353
|
meterName: usageType,
|
|
2317
2354
|
usageType,
|
|
2318
|
-
...options.toolName ? { toolName: options.toolName } : {}
|
|
2355
|
+
...options.toolName ? { toolName: options.toolName } : {},
|
|
2356
|
+
...options.freeLimit ? { freeLimit: options.freeLimit } : {}
|
|
2319
2357
|
};
|
|
2320
2358
|
return {
|
|
2321
2359
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -2404,6 +2442,8 @@ function createSolvaPay(config) {
|
|
|
2404
2442
|
const errMeta = opts?.error !== void 0 ? {
|
|
2405
2443
|
error: opts.error instanceof Error ? opts.error.message : String(opts.error)
|
|
2406
2444
|
} : {};
|
|
2445
|
+
const isFree = Boolean(decideMetadata.freeLimit);
|
|
2446
|
+
const usageClass = !isFree && outcome === "success" ? decision.consequence === "overage" ? "overage" : "included" : void 0;
|
|
2407
2447
|
const trackPromise = apiClient.trackUsage({
|
|
2408
2448
|
customerRef,
|
|
2409
2449
|
productRef,
|
|
@@ -2416,6 +2456,8 @@ function createSolvaPay(config) {
|
|
|
2416
2456
|
action: meterName,
|
|
2417
2457
|
requestId,
|
|
2418
2458
|
...decideMetadata.toolName ? { toolName: decideMetadata.toolName } : {},
|
|
2459
|
+
...isFree && decideMetadata.freeLimit ? { meterName: decideMetadata.freeLimit.meter } : {},
|
|
2460
|
+
...usageClass ? { usageClass } : {},
|
|
2419
2461
|
...errMeta,
|
|
2420
2462
|
...opts?.metadata ?? {}
|
|
2421
2463
|
},
|
|
@@ -2448,7 +2490,7 @@ async function resolveCustomerRefFromRequest(req, options) {
|
|
|
2448
2490
|
}
|
|
2449
2491
|
|
|
2450
2492
|
// src/verify-product-configuration.ts
|
|
2451
|
-
import { evaluateProductReadiness, SolvaPayError as
|
|
2493
|
+
import { evaluateProductReadiness, SolvaPayError as SolvaPayError5 } from "@solvapay/core";
|
|
2452
2494
|
async function verifyProductConfiguration(options) {
|
|
2453
2495
|
const { apiClient, productRef } = options;
|
|
2454
2496
|
const apiBaseUrl = options.apiBaseUrl ?? "(unknown API base URL)";
|
|
@@ -2472,8 +2514,8 @@ async function verifyProductConfiguration(options) {
|
|
|
2472
2514
|
issues: readiness.issues
|
|
2473
2515
|
};
|
|
2474
2516
|
} catch (error) {
|
|
2475
|
-
const status = error instanceof
|
|
2476
|
-
const code = error instanceof
|
|
2517
|
+
const status = error instanceof SolvaPayError5 ? error.status : void 0;
|
|
2518
|
+
const code = error instanceof SolvaPayError5 ? error.code : void 0;
|
|
2477
2519
|
if (status === 404 && code === "non_json_response") {
|
|
2478
2520
|
throw new Error(
|
|
2479
2521
|
`Could not reach ${apiBaseUrl} while verifying SOLVAPAY_PRODUCT_REF "${productRef}": the server returned a non-JSON response (often an offline ngrok tunnel or proxy error page). Check SOLVAPAY_API_BASE_URL and that the API tunnel is running.`
|
|
@@ -2570,13 +2612,13 @@ var PaywallStructuredContentSchema = z.discriminatedUnion("kind", [
|
|
|
2570
2612
|
]);
|
|
2571
2613
|
|
|
2572
2614
|
// src/helpers/error.ts
|
|
2573
|
-
import { SolvaPayError as
|
|
2615
|
+
import { SolvaPayError as SolvaPayError6 } from "@solvapay/core";
|
|
2574
2616
|
function isErrorResult(result) {
|
|
2575
2617
|
return typeof result === "object" && result !== null && "error" in result && "status" in result;
|
|
2576
2618
|
}
|
|
2577
2619
|
function handleRouteError(error, operationName, defaultMessage) {
|
|
2578
2620
|
console.error(`[${operationName}] Error:`, error);
|
|
2579
|
-
if (error instanceof
|
|
2621
|
+
if (error instanceof SolvaPayError6) {
|
|
2580
2622
|
const errorMessage2 = error.message;
|
|
2581
2623
|
return {
|
|
2582
2624
|
error: errorMessage2,
|
|
@@ -3075,7 +3117,7 @@ async function createCustomerSessionCore(request, options = {}) {
|
|
|
3075
3117
|
}
|
|
3076
3118
|
|
|
3077
3119
|
// src/helpers/renewal.ts
|
|
3078
|
-
import { SolvaPayError as
|
|
3120
|
+
import { SolvaPayError as SolvaPayError7 } from "@solvapay/core";
|
|
3079
3121
|
async function cancelPurchaseCore(request, body, options = {}) {
|
|
3080
3122
|
try {
|
|
3081
3123
|
if (!body.purchaseRef) {
|
|
@@ -3121,7 +3163,7 @@ async function cancelPurchaseCore(request, body, options = {}) {
|
|
|
3121
3163
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3122
3164
|
return cancelledPurchase;
|
|
3123
3165
|
} catch (error) {
|
|
3124
|
-
if (error instanceof
|
|
3166
|
+
if (error instanceof SolvaPayError7) {
|
|
3125
3167
|
const errorMessage = error.message;
|
|
3126
3168
|
if (errorMessage.includes("not found")) {
|
|
3127
3169
|
return {
|
|
@@ -3189,7 +3231,7 @@ async function reactivatePurchaseCore(request, body, options = {}) {
|
|
|
3189
3231
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3190
3232
|
return reactivatedPurchase;
|
|
3191
3233
|
} catch (error) {
|
|
3192
|
-
if (error instanceof
|
|
3234
|
+
if (error instanceof SolvaPayError7) {
|
|
3193
3235
|
const errorMessage = error.message;
|
|
3194
3236
|
if (errorMessage.includes("not found")) {
|
|
3195
3237
|
return {
|
|
@@ -3655,34 +3697,34 @@ function verifyWebhook({
|
|
|
3655
3697
|
secret
|
|
3656
3698
|
}) {
|
|
3657
3699
|
const toleranceSec = 300;
|
|
3658
|
-
if (!signature) throw new
|
|
3700
|
+
if (!signature) throw new SolvaPayError8("Missing webhook signature");
|
|
3659
3701
|
const parts = signature.split(",");
|
|
3660
3702
|
const tPart = parts.find((p) => p.startsWith("t="));
|
|
3661
3703
|
const v1Part = parts.find((p) => p.startsWith("v1="));
|
|
3662
3704
|
if (!tPart || !v1Part) {
|
|
3663
|
-
throw new
|
|
3705
|
+
throw new SolvaPayError8("Malformed webhook signature");
|
|
3664
3706
|
}
|
|
3665
3707
|
const timestamp = parseInt(tPart.slice(2), 10);
|
|
3666
3708
|
const receivedHmac = v1Part.slice(3);
|
|
3667
3709
|
if (Number.isNaN(timestamp) || !receivedHmac) {
|
|
3668
|
-
throw new
|
|
3710
|
+
throw new SolvaPayError8("Malformed webhook signature");
|
|
3669
3711
|
}
|
|
3670
3712
|
if (toleranceSec > 0) {
|
|
3671
3713
|
const age = Math.abs(Math.floor(Date.now() / 1e3) - timestamp);
|
|
3672
3714
|
if (age > toleranceSec) {
|
|
3673
|
-
throw new
|
|
3715
|
+
throw new SolvaPayError8("Webhook signature timestamp too old");
|
|
3674
3716
|
}
|
|
3675
3717
|
}
|
|
3676
3718
|
const expectedHmac = crypto.createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
|
|
3677
3719
|
if (receivedHmac.length !== expectedHmac.length) {
|
|
3678
|
-
throw new
|
|
3720
|
+
throw new SolvaPayError8("Invalid webhook signature");
|
|
3679
3721
|
}
|
|
3680
3722
|
const ok = crypto.timingSafeEqual(Buffer.from(expectedHmac), Buffer.from(receivedHmac));
|
|
3681
|
-
if (!ok) throw new
|
|
3723
|
+
if (!ok) throw new SolvaPayError8("Invalid webhook signature");
|
|
3682
3724
|
try {
|
|
3683
3725
|
return JSON.parse(body);
|
|
3684
3726
|
} catch {
|
|
3685
|
-
throw new
|
|
3727
|
+
throw new SolvaPayError8("Invalid webhook payload: body is not valid JSON");
|
|
3686
3728
|
}
|
|
3687
3729
|
}
|
|
3688
3730
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solvapay/server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
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 __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",
|
|
73
|
+
"test": "vitest run __tests__/paywall.unit.test.ts __tests__/paywall-state.unit.test.ts __tests__/free-limit.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__/free-limit.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",
|