@solvapay/server 1.0.7 → 1.0.8-preview.10

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/index.d.ts CHANGED
@@ -2488,6 +2488,26 @@ interface ProcessPaymentResult {
2488
2488
  status: 'completed';
2489
2489
  }
2490
2490
  type ActivatePlanResult = components['schemas']['ActivatePlanResponseDto'];
2491
+ /**
2492
+ * SDK-facing merchant identity (source: GET /v1/sdk/merchant).
2493
+ *
2494
+ * Hand-typed here until the OpenAPI generator picks up the new endpoint.
2495
+ * Fields match `SdkMerchantResponseDto` on the backend.
2496
+ */
2497
+ type SdkMerchantResponse = {
2498
+ displayName: string;
2499
+ legalName: string;
2500
+ supportEmail?: string;
2501
+ supportUrl?: string;
2502
+ termsUrl?: string;
2503
+ privacyUrl?: string;
2504
+ country?: string;
2505
+ defaultCurrency?: string;
2506
+ statementDescriptor?: string;
2507
+ logoUrl?: string;
2508
+ };
2509
+ /** SDK-facing product projection. Sourced from the existing OpenAPI spec. */
2510
+ type SdkProductResponse = components['schemas']['SdkProductResponse'];
2491
2511
  type McpBootstrapPlanInput = NonNullable<components['schemas']['McpBootstrapDto']['plans']>[number];
2492
2512
  type ToolPlanMappingInput = NonNullable<components['schemas']['McpBootstrapDto']['tools']>[number];
2493
2513
  type McpBootstrapRequest = components['schemas']['McpBootstrapDto'];
@@ -2556,6 +2576,13 @@ interface SolvaPayClient {
2556
2576
  externalRef?: string;
2557
2577
  email?: string;
2558
2578
  }): Promise<CustomerResponseMapped>;
2579
+ /**
2580
+ * SDK-facing merchant identity (GET /v1/sdk/merchant).
2581
+ * Returns the subset of provider fields safe for browser consumption —
2582
+ * used by `<MandateText>`, `<CheckoutSummary>`, and trust signals.
2583
+ */
2584
+ getMerchant?(): Promise<SdkMerchantResponse>;
2585
+ getProduct?(productRef: string): Promise<SdkProductResponse>;
2559
2586
  listProducts?(): Promise<Array<{
2560
2587
  reference: string;
2561
2588
  name: string;
@@ -3785,8 +3812,16 @@ declare function handleRouteError(error: unknown, operationName: string, default
3785
3812
  * and name from authenticated requests. Works with any framework that uses
3786
3813
  * the standard Web API Request (Express, Fastify, Next.js, Edge Functions, etc.).
3787
3814
  *
3788
- * Uses dynamic imports to avoid requiring @solvapay/auth at build time,
3789
- * making it suitable for edge runtime environments.
3815
+ * Resolution order:
3816
+ * 1. `x-user-id` header set by Next.js-style middleware (unchanged).
3817
+ * 2. `Authorization: Bearer <jwt>` — verified via HS256 when a secret is
3818
+ * configured (`SOLVAPAY_JWT_SECRET` or `SUPABASE_JWT_SECRET`), or
3819
+ * unverified-decoded when no secret is configured. The unverified path
3820
+ * covers platforms that verify JWTs at the gateway (e.g. Supabase Edge
3821
+ * with `verify_jwt = true`, which is the default) and asymmetric signing
3822
+ * keys (ES256/RS256) that the SDK does not have keys for.
3823
+ * 3. Set `SOLVAPAY_AUTH_STRICT=true` to require cryptographic verification
3824
+ * inside the handler (the unverified-decode fallback is then disabled).
3790
3825
  *
3791
3826
  * @param request - Standard Web API Request object
3792
3827
  * @param options - Configuration options
@@ -4116,13 +4151,85 @@ declare function activatePlanCore(request: Request, body: {
4116
4151
 
4117
4152
  type Plan = components['schemas']['Plan'];
4118
4153
  /**
4119
- * List plans - core implementation
4154
+ * List plans - core implementation.
4155
+ *
4156
+ * Pass `options.solvaPay` to route through a pre-configured SolvaPay instance
4157
+ * (e.g. stub-backed in examples). When omitted, the helper reads
4158
+ * `SOLVAPAY_SECRET_KEY` from environment and constructs a real API client.
4120
4159
  */
4121
- declare function listPlansCore(request: Request): Promise<{
4160
+ declare function listPlansCore(request: Request, options?: {
4161
+ solvaPay?: SolvaPay;
4162
+ }): Promise<{
4122
4163
  plans: Plan[];
4123
4164
  productRef: string;
4124
4165
  } | ErrorResult>;
4125
4166
 
4167
+ /**
4168
+ * Merchant Helper (Core)
4169
+ *
4170
+ * Generic helper for GET /api/merchant — returns the SDK-facing merchant
4171
+ * identity used by `<MandateText>`, `<CheckoutSummary>`, and trust signals.
4172
+ * Works with standard Web API Request (Express, Fastify, Next.js, Edge).
4173
+ */
4174
+
4175
+ declare function getMerchantCore(_request: Request, options?: {
4176
+ solvaPay?: SolvaPay;
4177
+ }): Promise<SdkMerchantResponse | ErrorResult>;
4178
+
4179
+ /**
4180
+ * Product Helper (Core)
4181
+ *
4182
+ * Generic helper for GET /api/get-product?productRef=...
4183
+ * Returns a single product by reference, used by the `useProduct` React hook.
4184
+ */
4185
+
4186
+ declare function getProductCore(request: Request, options?: {
4187
+ solvaPay?: SolvaPay;
4188
+ }): Promise<SdkProductResponse | ErrorResult>;
4189
+
4190
+ interface PurchaseCheckResult {
4191
+ customerRef: string;
4192
+ email?: string;
4193
+ name?: string;
4194
+ purchases: Array<{
4195
+ reference: string;
4196
+ productName?: string;
4197
+ productRef?: string;
4198
+ status?: string;
4199
+ startDate?: string;
4200
+ planSnapshot?: {
4201
+ meterId?: string;
4202
+ limit?: number;
4203
+ freeUnits?: number;
4204
+ };
4205
+ usage?: {
4206
+ used?: number;
4207
+ overageUnits?: number;
4208
+ overageCost?: number;
4209
+ periodStart?: string;
4210
+ periodEnd?: string;
4211
+ };
4212
+ [key: string]: unknown;
4213
+ }>;
4214
+ }
4215
+ declare function checkPurchaseCore(request: Request, options?: {
4216
+ solvaPay?: SolvaPay;
4217
+ includeEmail?: boolean;
4218
+ includeName?: boolean;
4219
+ }): Promise<PurchaseCheckResult | ErrorResult>;
4220
+
4221
+ declare function trackUsageCore(request: Request, body: {
4222
+ actionType?: 'transaction' | 'api_call' | 'hour' | 'email' | 'storage' | 'custom';
4223
+ units?: number;
4224
+ productRef?: string;
4225
+ description?: string;
4226
+ metadata?: Record<string, unknown>;
4227
+ }, options?: {
4228
+ solvaPay?: SolvaPay;
4229
+ }): Promise<{
4230
+ success: true;
4231
+ } | ErrorResult>;
4232
+
4126
4233
  /**
4127
4234
  * SolvaPay Server SDK
4128
4235
  *
@@ -4178,4 +4285,4 @@ declare function verifyWebhook({ body, signature, secret, }: {
4178
4285
  secret: string;
4179
4286
  }): WebhookEvent;
4180
4287
 
4181
- export { type ActivatePlanResult, type AuthenticatedUser, type ConfigureMcpPlansRequest, type ConfigureMcpPlansResponse, type CreateSolvaPayConfig, type CustomerBalanceResult, type CustomerResponseMapped, type CustomerWebhookObject, type ErrorResult, type HttpAdapterOptions, type LimitActivationBalance, type LimitActivationProduct, type LimitPlanSummary, type McpAdapterOptions, McpBearerAuthError, type McpBootstrapPlanInput, type McpBootstrapRequest, type McpBootstrapResponse, type McpServerLike, type McpToolExtra, type McpToolPlanMappingInput, type NextAdapterOptions, type OneTimePurchaseInfo, type PayableFunction, type PayableOptions, type PaywallArgs, PaywallError, type PaywallMetadata, type PaywallStructuredContent, type PaywallToolResult, type ProcessPaymentResult, type RegisterVirtualToolsMcpOptions, type RetryOptions, type ServerClientOptions, type SolvaPay, type SolvaPayClient, type ToolPlanMappingInput, VIRTUAL_TOOL_DEFINITIONS, type VirtualToolDefinition, type VirtualToolsOptions, type WebhookEvent, type WebhookEventForType, type WebhookEventObjectMap, type WebhookEventType, type WebhookProduct, activatePlanCore, buildAuthInfoFromBearer, cancelPurchaseCore, type components, createCheckoutSessionCore, createCustomerSessionCore, createMcpOAuthBridge, createPaymentIntentCore, createSolvaPay, createSolvaPayClient, createTopupPaymentIntentCore, createVirtualTools, decodeJwtPayload, extractBearerToken, getAuthenticatedUserCore, getCustomerBalanceCore, getCustomerRefFromBearerAuthHeader, getCustomerRefFromJwtPayload, getOAuthAuthorizationServerResponse, getOAuthProtectedResourceResponse, handleRouteError, isErrorResult, jsonSchemaToZodRawShape, listPlansCore, paywallErrorToClientPayload, processPaymentIntentCore, reactivatePurchaseCore, registerVirtualToolsMcpImpl, syncCustomerCore, verifyWebhook, withRetry };
4288
+ export { type ActivatePlanResult, type AuthenticatedUser, type ConfigureMcpPlansRequest, type ConfigureMcpPlansResponse, type CreateSolvaPayConfig, type CustomerBalanceResult, type CustomerResponseMapped, type CustomerWebhookObject, type ErrorResult, type HttpAdapterOptions, type LimitActivationBalance, type LimitActivationProduct, type LimitPlanSummary, type McpAdapterOptions, McpBearerAuthError, type McpBootstrapPlanInput, type McpBootstrapRequest, type McpBootstrapResponse, type McpServerLike, type McpToolExtra, type McpToolPlanMappingInput, type NextAdapterOptions, type OneTimePurchaseInfo, type PayableFunction, type PayableOptions, type PaywallArgs, PaywallError, type PaywallMetadata, type PaywallStructuredContent, type PaywallToolResult, type ProcessPaymentResult, type PurchaseCheckResult, type RegisterVirtualToolsMcpOptions, type RetryOptions, type SdkMerchantResponse, type SdkProductResponse, type ServerClientOptions, type SolvaPay, type SolvaPayClient, type ToolPlanMappingInput, VIRTUAL_TOOL_DEFINITIONS, type VirtualToolDefinition, type VirtualToolsOptions, type WebhookEvent, type WebhookEventForType, type WebhookEventObjectMap, type WebhookEventType, type WebhookProduct, activatePlanCore, buildAuthInfoFromBearer, cancelPurchaseCore, checkPurchaseCore, type components, createCheckoutSessionCore, createCustomerSessionCore, createMcpOAuthBridge, createPaymentIntentCore, createSolvaPay, createSolvaPayClient, createTopupPaymentIntentCore, createVirtualTools, decodeJwtPayload, extractBearerToken, getAuthenticatedUserCore, getCustomerBalanceCore, getCustomerRefFromBearerAuthHeader, getCustomerRefFromJwtPayload, getMerchantCore, getOAuthAuthorizationServerResponse, getOAuthProtectedResourceResponse, getProductCore, handleRouteError, isErrorResult, jsonSchemaToZodRawShape, listPlansCore, paywallErrorToClientPayload, processPaymentIntentCore, reactivatePurchaseCore, registerVirtualToolsMcpImpl, syncCustomerCore, trackUsageCore, verifyWebhook, withRetry };
package/dist/index.js CHANGED
@@ -110,6 +110,36 @@ function createSolvaPayClient(opts) {
110
110
  purchases: customer.purchases || []
111
111
  };
112
112
  },
113
+ // GET: /v1/sdk/merchant
114
+ async getMerchant() {
115
+ const url = `${base}/v1/sdk/merchant`;
116
+ const res = await fetch(url, {
117
+ method: "GET",
118
+ headers
119
+ });
120
+ if (!res.ok) {
121
+ const error = await res.text();
122
+ log(`\u274C API Error: ${res.status} - ${error}`);
123
+ throw new SolvaPayError(`Get merchant failed (${res.status}): ${error}`);
124
+ }
125
+ return res.json();
126
+ },
127
+ // GET: /v1/sdk/products/{productRef}
128
+ async getProduct(productRef) {
129
+ const url = `${base}/v1/sdk/products/${encodeURIComponent(productRef)}`;
130
+ const res = await fetch(url, {
131
+ method: "GET",
132
+ headers
133
+ });
134
+ if (!res.ok) {
135
+ const error = await res.text();
136
+ log(`\u274C API Error: ${res.status} - ${error}`);
137
+ throw new SolvaPayError(`Get product failed (${res.status}): ${error}`);
138
+ }
139
+ const result = await res.json();
140
+ const data = result.data || {};
141
+ return { ...data, ...result };
142
+ },
113
143
  // Product management methods (primarily for integration tests)
114
144
  // GET: /v1/sdk/products
115
145
  async listProducts() {
@@ -1715,7 +1745,8 @@ var McpBearerAuthError = class extends Error {
1715
1745
  function base64UrlDecode(input) {
1716
1746
  const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
1717
1747
  const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
1718
- return Buffer.from(padded, "base64").toString("utf8");
1748
+ const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
1749
+ return new TextDecoder().decode(bytes);
1719
1750
  }
1720
1751
  function extractBearerToken(authorization) {
1721
1752
  if (!authorization) return null;
@@ -1942,26 +1973,128 @@ function handleRouteError(error, operationName, defaultMessage) {
1942
1973
  }
1943
1974
 
1944
1975
  // src/helpers/auth.ts
1976
+ function base64UrlDecode2(input) {
1977
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
1978
+ const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
1979
+ const base64 = padded + padding;
1980
+ if (typeof atob === "function") {
1981
+ const binary = atob(base64);
1982
+ let result = "";
1983
+ for (let i = 0; i < binary.length; i++) {
1984
+ result += String.fromCharCode(binary.charCodeAt(i));
1985
+ }
1986
+ try {
1987
+ return decodeURIComponent(escape(result));
1988
+ } catch {
1989
+ return result;
1990
+ }
1991
+ }
1992
+ const BufferCtor = globalThis.Buffer;
1993
+ if (BufferCtor) {
1994
+ return BufferCtor.from(base64, "base64").toString("utf-8");
1995
+ }
1996
+ throw new Error("No base64 decoder available in this runtime");
1997
+ }
1998
+ function decodeJwtUnverified(token) {
1999
+ const parts = token.split(".");
2000
+ if (parts.length !== 3) return null;
2001
+ try {
2002
+ const json = base64UrlDecode2(parts[1]);
2003
+ const payload = JSON.parse(json);
2004
+ if (typeof payload !== "object" || payload === null) return null;
2005
+ return payload;
2006
+ } catch {
2007
+ return null;
2008
+ }
2009
+ }
2010
+ function extractBearerToken2(request) {
2011
+ const authHeader = request.headers.get("authorization");
2012
+ if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) return null;
2013
+ const token = authHeader.slice(7).trim();
2014
+ return token.length > 0 ? token : null;
2015
+ }
2016
+ function readEnv(name) {
2017
+ const proc = globalThis.process;
2018
+ return proc?.env?.[name];
2019
+ }
2020
+ function isStrictMode() {
2021
+ return readEnv("SOLVAPAY_AUTH_STRICT") === "true";
2022
+ }
2023
+ function getConfiguredSecret() {
2024
+ return readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
2025
+ }
2026
+ async function verifyHs256(token, secret) {
2027
+ try {
2028
+ const { jwtVerify } = await import("./webapi-K5XBCEO6.js");
2029
+ const key = new TextEncoder().encode(secret);
2030
+ const { payload } = await jwtVerify(token, key, { algorithms: ["HS256"] });
2031
+ return payload;
2032
+ } catch {
2033
+ return null;
2034
+ }
2035
+ }
2036
+ function pickName(payload) {
2037
+ const metadataFullName = typeof payload.user_metadata?.full_name === "string" ? payload.user_metadata.full_name : null;
2038
+ const metadataName = typeof payload.user_metadata?.name === "string" ? payload.user_metadata.name : null;
2039
+ const claimName = typeof payload.name === "string" ? payload.name : null;
2040
+ return metadataFullName || metadataName || claimName || null;
2041
+ }
2042
+ function pickEmail(payload) {
2043
+ return typeof payload.email === "string" ? payload.email : null;
2044
+ }
2045
+ function unauthorized(details) {
2046
+ return { error: "Unauthorized", status: 401, details };
2047
+ }
1945
2048
  async function getAuthenticatedUserCore(request, options = {}) {
1946
2049
  try {
1947
- const { requireUserId, getUserEmailFromRequest, getUserNameFromRequest } = await import("@solvapay/auth");
1948
- const userIdOrError = requireUserId(request);
1949
- if (userIdOrError instanceof Response) {
1950
- const clonedResponse = userIdOrError.clone();
1951
- const body = await clonedResponse.json().catch(() => ({ error: "Unauthorized" }));
1952
- return {
1953
- error: body.error || "Unauthorized",
1954
- status: userIdOrError.status,
1955
- details: body.error || "Unauthorized"
1956
- };
2050
+ const includeEmail = options.includeEmail !== false;
2051
+ const includeName = options.includeName !== false;
2052
+ const headerUserId = request.headers.get("x-user-id");
2053
+ if (headerUserId) {
2054
+ let email = null;
2055
+ let name = null;
2056
+ if (includeEmail || includeName) {
2057
+ const token2 = extractBearerToken2(request);
2058
+ if (token2) {
2059
+ const secret2 = getConfiguredSecret();
2060
+ const payload2 = secret2 ? await verifyHs256(token2, secret2) : isStrictMode() ? null : decodeJwtUnverified(token2);
2061
+ if (payload2) {
2062
+ if (includeEmail) email = pickEmail(payload2);
2063
+ if (includeName) name = pickName(payload2);
2064
+ }
2065
+ }
2066
+ }
2067
+ return { userId: headerUserId, email, name };
2068
+ }
2069
+ const token = extractBearerToken2(request);
2070
+ if (!token) {
2071
+ return unauthorized("User ID not found. Ensure middleware is configured.");
2072
+ }
2073
+ const secret = getConfiguredSecret();
2074
+ let payload = null;
2075
+ if (secret) {
2076
+ payload = await verifyHs256(token, secret);
2077
+ if (!payload) {
2078
+ return unauthorized("Invalid or expired authentication token");
2079
+ }
2080
+ } else if (isStrictMode()) {
2081
+ return unauthorized(
2082
+ "Strict auth mode is enabled but no JWT secret is configured. Set SOLVAPAY_JWT_SECRET or SUPABASE_JWT_SECRET."
2083
+ );
2084
+ } else {
2085
+ payload = decodeJwtUnverified(token);
2086
+ if (!payload) {
2087
+ return unauthorized("Malformed authentication token");
2088
+ }
2089
+ }
2090
+ const userId = typeof payload.sub === "string" ? payload.sub : null;
2091
+ if (!userId) {
2092
+ return unauthorized("Authentication token missing subject (sub) claim");
1957
2093
  }
1958
- const userId = userIdOrError;
1959
- const email = options.includeEmail !== false ? await getUserEmailFromRequest(request) : null;
1960
- const name = options.includeName !== false ? await getUserNameFromRequest(request) : null;
1961
2094
  return {
1962
2095
  userId,
1963
- email,
1964
- name
2096
+ email: includeEmail ? pickEmail(payload) : null,
2097
+ name: includeName ? pickName(payload) : null
1965
2098
  };
1966
2099
  } catch (error) {
1967
2100
  return handleRouteError(error, "Get authenticated user", "Authentication failed");
@@ -2356,7 +2489,7 @@ async function activatePlanCore(request, body, options = {}) {
2356
2489
 
2357
2490
  // src/helpers/plans.ts
2358
2491
  import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
2359
- async function listPlansCore(request) {
2492
+ async function listPlansCore(request, options = {}) {
2360
2493
  try {
2361
2494
  const url = new URL(request.url);
2362
2495
  const productRef = url.searchParams.get("productRef");
@@ -2366,19 +2499,20 @@ async function listPlansCore(request) {
2366
2499
  status: 400
2367
2500
  };
2368
2501
  }
2369
- const config = getSolvaPayConfig2();
2370
- const solvapaySecretKey = config.apiKey;
2371
- const solvapayApiBaseUrl = config.apiBaseUrl;
2372
- if (!solvapaySecretKey) {
2502
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2503
+ const config = getSolvaPayConfig2();
2504
+ if (!config.apiKey) return null;
2505
+ return createSolvaPayClient({
2506
+ apiKey: config.apiKey,
2507
+ apiBaseUrl: config.apiBaseUrl
2508
+ });
2509
+ })();
2510
+ if (!apiClient) {
2373
2511
  return {
2374
2512
  error: "Server configuration error: SolvaPay secret key not configured",
2375
2513
  status: 500
2376
2514
  };
2377
2515
  }
2378
- const apiClient = createSolvaPayClient({
2379
- apiKey: solvapaySecretKey,
2380
- apiBaseUrl: solvapayApiBaseUrl
2381
- });
2382
2516
  if (!apiClient.listPlans) {
2383
2517
  return {
2384
2518
  error: "List plans method not available",
@@ -2395,6 +2529,159 @@ async function listPlansCore(request) {
2395
2529
  }
2396
2530
  }
2397
2531
 
2532
+ // src/helpers/merchant.ts
2533
+ import { getSolvaPayConfig as getSolvaPayConfig3 } from "@solvapay/core";
2534
+ async function getMerchantCore(_request, options = {}) {
2535
+ try {
2536
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2537
+ const config = getSolvaPayConfig3();
2538
+ if (!config.apiKey) return null;
2539
+ return createSolvaPayClient({
2540
+ apiKey: config.apiKey,
2541
+ apiBaseUrl: config.apiBaseUrl
2542
+ });
2543
+ })();
2544
+ if (!apiClient) {
2545
+ return {
2546
+ error: "Server configuration error: SolvaPay secret key not configured",
2547
+ status: 500
2548
+ };
2549
+ }
2550
+ if (!apiClient.getMerchant) {
2551
+ return {
2552
+ error: "Get merchant method not available",
2553
+ status: 500
2554
+ };
2555
+ }
2556
+ const merchant = await apiClient.getMerchant();
2557
+ return merchant;
2558
+ } catch (error) {
2559
+ return handleRouteError(error, "Get merchant", "Failed to fetch merchant");
2560
+ }
2561
+ }
2562
+
2563
+ // src/helpers/product.ts
2564
+ import { getSolvaPayConfig as getSolvaPayConfig4 } from "@solvapay/core";
2565
+ async function getProductCore(request, options = {}) {
2566
+ try {
2567
+ const url = new URL(request.url);
2568
+ const productRef = url.searchParams.get("productRef");
2569
+ if (!productRef) {
2570
+ return {
2571
+ error: "Missing required parameter: productRef",
2572
+ status: 400
2573
+ };
2574
+ }
2575
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2576
+ const config = getSolvaPayConfig4();
2577
+ if (!config.apiKey) return null;
2578
+ return createSolvaPayClient({
2579
+ apiKey: config.apiKey,
2580
+ apiBaseUrl: config.apiBaseUrl
2581
+ });
2582
+ })();
2583
+ if (!apiClient) {
2584
+ return {
2585
+ error: "Server configuration error: SolvaPay secret key not configured",
2586
+ status: 500
2587
+ };
2588
+ }
2589
+ if (!apiClient.getProduct) {
2590
+ return {
2591
+ error: "Get product method not available",
2592
+ status: 500
2593
+ };
2594
+ }
2595
+ const product = await apiClient.getProduct(productRef);
2596
+ return product;
2597
+ } catch (error) {
2598
+ return handleRouteError(error, "Get product", "Failed to fetch product");
2599
+ }
2600
+ }
2601
+
2602
+ // src/helpers/purchase.ts
2603
+ async function checkPurchaseCore(request, options = {}) {
2604
+ try {
2605
+ const userResult = await getAuthenticatedUserCore(request, {
2606
+ includeEmail: options.includeEmail,
2607
+ includeName: options.includeName
2608
+ });
2609
+ if (isErrorResult(userResult)) {
2610
+ return userResult;
2611
+ }
2612
+ const { userId, email, name } = userResult;
2613
+ const solvaPay = options.solvaPay || createSolvaPay();
2614
+ const cachedCustomerRef = request.headers.get("x-solvapay-customer-ref");
2615
+ if (cachedCustomerRef) {
2616
+ try {
2617
+ const customer = await solvaPay.getCustomer({ customerRef: cachedCustomerRef });
2618
+ if (customer && customer.customerRef) {
2619
+ if (customer.externalRef && customer.externalRef === userId) {
2620
+ const filteredPurchases = (customer.purchases || []).filter(
2621
+ (p) => p.status === "active"
2622
+ );
2623
+ return {
2624
+ customerRef: customer.customerRef,
2625
+ email: customer.email,
2626
+ name: customer.name,
2627
+ purchases: filteredPurchases
2628
+ };
2629
+ }
2630
+ }
2631
+ } catch {
2632
+ }
2633
+ }
2634
+ try {
2635
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2636
+ email: email || void 0,
2637
+ name: name || void 0
2638
+ });
2639
+ const customer = await solvaPay.getCustomer({ customerRef });
2640
+ const filteredPurchases = (customer.purchases || []).filter((p) => p.status === "active");
2641
+ return {
2642
+ customerRef: customer.customerRef || userId,
2643
+ email: customer.email,
2644
+ name: customer.name,
2645
+ purchases: filteredPurchases
2646
+ };
2647
+ } catch {
2648
+ return {
2649
+ customerRef: userId,
2650
+ purchases: []
2651
+ };
2652
+ }
2653
+ } catch (error) {
2654
+ return handleRouteError(error, "Check purchase", "Failed to check purchase");
2655
+ }
2656
+ }
2657
+
2658
+ // src/helpers/usage.ts
2659
+ async function trackUsageCore(request, body, options = {}) {
2660
+ try {
2661
+ const userResult = await getAuthenticatedUserCore(request);
2662
+ if (isErrorResult(userResult)) {
2663
+ return userResult;
2664
+ }
2665
+ const { userId, email, name } = userResult;
2666
+ const solvaPay = options.solvaPay || createSolvaPay();
2667
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2668
+ email: email || void 0,
2669
+ name: name || void 0
2670
+ });
2671
+ await solvaPay.trackUsage({
2672
+ customerRef,
2673
+ actionType: body.actionType,
2674
+ units: body.units,
2675
+ productRef: body.productRef,
2676
+ description: body.description,
2677
+ metadata: body.metadata
2678
+ });
2679
+ return { success: true };
2680
+ } catch (error) {
2681
+ return handleRouteError(error, "Track usage", "Track usage failed");
2682
+ }
2683
+ }
2684
+
2398
2685
  // src/index.ts
2399
2686
  function verifyWebhook({
2400
2687
  body,
@@ -2439,6 +2726,7 @@ export {
2439
2726
  activatePlanCore,
2440
2727
  buildAuthInfoFromBearer,
2441
2728
  cancelPurchaseCore,
2729
+ checkPurchaseCore,
2442
2730
  createCheckoutSessionCore,
2443
2731
  createCustomerSessionCore,
2444
2732
  createMcpOAuthBridge,
@@ -2453,8 +2741,10 @@ export {
2453
2741
  getCustomerBalanceCore,
2454
2742
  getCustomerRefFromBearerAuthHeader,
2455
2743
  getCustomerRefFromJwtPayload,
2744
+ getMerchantCore,
2456
2745
  getOAuthAuthorizationServerResponse,
2457
2746
  getOAuthProtectedResourceResponse,
2747
+ getProductCore,
2458
2748
  handleRouteError,
2459
2749
  isErrorResult,
2460
2750
  jsonSchemaToZodRawShape,
@@ -2464,6 +2754,7 @@ export {
2464
2754
  reactivatePurchaseCore,
2465
2755
  registerVirtualToolsMcpImpl,
2466
2756
  syncCustomerCore,
2757
+ trackUsageCore,
2467
2758
  verifyWebhook,
2468
2759
  withRetry
2469
2760
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solvapay/server",
3
- "version": "1.0.7",
3
+ "version": "1.0.8-preview.10",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -37,12 +37,12 @@
37
37
  },
38
38
  "sideEffects": false,
39
39
  "dependencies": {
40
- "@solvapay/core": "1.0.7"
40
+ "@solvapay/core": "1.0.8-preview.10"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@modelcontextprotocol/sdk": "^1.28.0",
44
44
  "zod": "^3.25.0 || ^4.0.0",
45
- "@solvapay/auth": "1.0.7"
45
+ "@solvapay/auth": "1.0.8-preview.10"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "@modelcontextprotocol/sdk": {
@@ -60,15 +60,15 @@
60
60
  "tsx": "^4.21.0",
61
61
  "typescript": "^5.9.3",
62
62
  "vitest": "^4.1.2",
63
- "@solvapay/auth": "1.0.7",
63
+ "@solvapay/auth": "1.0.8-preview.10",
64
64
  "@solvapay/demo-services": "0.0.0",
65
65
  "@solvapay/test-utils": "^0.0.0"
66
66
  },
67
67
  "scripts": {
68
68
  "build": "tsup src/index.ts --format esm,cjs --dts --tsconfig tsconfig.build.json && tsup src/edge.ts --format esm --dts --tsconfig tsconfig.build.json",
69
69
  "generate:types": "tsx scripts/generate-types.ts",
70
- "test": "vitest run __tests__/paywall.unit.test.ts __tests__/mcp-auth.unit.test.ts __tests__/bootstrap-mcp.unit.test.ts __tests__/verify-webhook.unit.test.ts",
71
- "test:unit": "vitest run __tests__/paywall.unit.test.ts __tests__/mcp-auth.unit.test.ts __tests__/bootstrap-mcp.unit.test.ts __tests__/verify-webhook.unit.test.ts",
70
+ "test": "vitest run __tests__/paywall.unit.test.ts __tests__/mcp-auth.unit.test.ts __tests__/bootstrap-mcp.unit.test.ts __tests__/verify-webhook.unit.test.ts __tests__/edge-exports.unit.test.ts __tests__/auth-core.unit.test.ts",
71
+ "test:unit": "vitest run __tests__/paywall.unit.test.ts __tests__/mcp-auth.unit.test.ts __tests__/bootstrap-mcp.unit.test.ts __tests__/verify-webhook.unit.test.ts __tests__/edge-exports.unit.test.ts __tests__/auth-core.unit.test.ts",
72
72
  "test:integration": "vitest run __tests__/backend.integration.test.ts",
73
73
  "test:integration:payment": "vitest run __tests__/payment-stripe.integration.test.ts",
74
74
  "test:all": "vitest run",