@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/edge.js CHANGED
@@ -109,6 +109,36 @@ function createSolvaPayClient(opts) {
109
109
  purchases: customer.purchases || []
110
110
  };
111
111
  },
112
+ // GET: /v1/sdk/merchant
113
+ async getMerchant() {
114
+ const url = `${base}/v1/sdk/merchant`;
115
+ const res = await fetch(url, {
116
+ method: "GET",
117
+ headers
118
+ });
119
+ if (!res.ok) {
120
+ const error = await res.text();
121
+ log(`\u274C API Error: ${res.status} - ${error}`);
122
+ throw new SolvaPayError(`Get merchant failed (${res.status}): ${error}`);
123
+ }
124
+ return res.json();
125
+ },
126
+ // GET: /v1/sdk/products/{productRef}
127
+ async getProduct(productRef) {
128
+ const url = `${base}/v1/sdk/products/${encodeURIComponent(productRef)}`;
129
+ const res = await fetch(url, {
130
+ method: "GET",
131
+ headers
132
+ });
133
+ if (!res.ok) {
134
+ const error = await res.text();
135
+ log(`\u274C API Error: ${res.status} - ${error}`);
136
+ throw new SolvaPayError(`Get product failed (${res.status}): ${error}`);
137
+ }
138
+ const result = await res.json();
139
+ const data = result.data || {};
140
+ return { ...data, ...result };
141
+ },
112
142
  // Product management methods (primarily for integration tests)
113
143
  // GET: /v1/sdk/products
114
144
  async listProducts() {
@@ -1728,26 +1758,128 @@ function handleRouteError(error, operationName, defaultMessage) {
1728
1758
  }
1729
1759
 
1730
1760
  // src/helpers/auth.ts
1761
+ function base64UrlDecode(input) {
1762
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
1763
+ const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
1764
+ const base64 = padded + padding;
1765
+ if (typeof atob === "function") {
1766
+ const binary = atob(base64);
1767
+ let result = "";
1768
+ for (let i = 0; i < binary.length; i++) {
1769
+ result += String.fromCharCode(binary.charCodeAt(i));
1770
+ }
1771
+ try {
1772
+ return decodeURIComponent(escape(result));
1773
+ } catch {
1774
+ return result;
1775
+ }
1776
+ }
1777
+ const BufferCtor = globalThis.Buffer;
1778
+ if (BufferCtor) {
1779
+ return BufferCtor.from(base64, "base64").toString("utf-8");
1780
+ }
1781
+ throw new Error("No base64 decoder available in this runtime");
1782
+ }
1783
+ function decodeJwtUnverified(token) {
1784
+ const parts = token.split(".");
1785
+ if (parts.length !== 3) return null;
1786
+ try {
1787
+ const json = base64UrlDecode(parts[1]);
1788
+ const payload = JSON.parse(json);
1789
+ if (typeof payload !== "object" || payload === null) return null;
1790
+ return payload;
1791
+ } catch {
1792
+ return null;
1793
+ }
1794
+ }
1795
+ function extractBearerToken(request) {
1796
+ const authHeader = request.headers.get("authorization");
1797
+ if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) return null;
1798
+ const token = authHeader.slice(7).trim();
1799
+ return token.length > 0 ? token : null;
1800
+ }
1801
+ function readEnv(name) {
1802
+ const proc = globalThis.process;
1803
+ return proc?.env?.[name];
1804
+ }
1805
+ function isStrictMode() {
1806
+ return readEnv("SOLVAPAY_AUTH_STRICT") === "true";
1807
+ }
1808
+ function getConfiguredSecret() {
1809
+ return readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
1810
+ }
1811
+ async function verifyHs256(token, secret) {
1812
+ try {
1813
+ const { jwtVerify } = await import("./webapi-K5XBCEO6.js");
1814
+ const key = new TextEncoder().encode(secret);
1815
+ const { payload } = await jwtVerify(token, key, { algorithms: ["HS256"] });
1816
+ return payload;
1817
+ } catch {
1818
+ return null;
1819
+ }
1820
+ }
1821
+ function pickName(payload) {
1822
+ const metadataFullName = typeof payload.user_metadata?.full_name === "string" ? payload.user_metadata.full_name : null;
1823
+ const metadataName = typeof payload.user_metadata?.name === "string" ? payload.user_metadata.name : null;
1824
+ const claimName = typeof payload.name === "string" ? payload.name : null;
1825
+ return metadataFullName || metadataName || claimName || null;
1826
+ }
1827
+ function pickEmail(payload) {
1828
+ return typeof payload.email === "string" ? payload.email : null;
1829
+ }
1830
+ function unauthorized(details) {
1831
+ return { error: "Unauthorized", status: 401, details };
1832
+ }
1731
1833
  async function getAuthenticatedUserCore(request, options = {}) {
1732
1834
  try {
1733
- const { requireUserId, getUserEmailFromRequest, getUserNameFromRequest } = await import("@solvapay/auth");
1734
- const userIdOrError = requireUserId(request);
1735
- if (userIdOrError instanceof Response) {
1736
- const clonedResponse = userIdOrError.clone();
1737
- const body = await clonedResponse.json().catch(() => ({ error: "Unauthorized" }));
1738
- return {
1739
- error: body.error || "Unauthorized",
1740
- status: userIdOrError.status,
1741
- details: body.error || "Unauthorized"
1742
- };
1835
+ const includeEmail = options.includeEmail !== false;
1836
+ const includeName = options.includeName !== false;
1837
+ const headerUserId = request.headers.get("x-user-id");
1838
+ if (headerUserId) {
1839
+ let email = null;
1840
+ let name = null;
1841
+ if (includeEmail || includeName) {
1842
+ const token2 = extractBearerToken(request);
1843
+ if (token2) {
1844
+ const secret2 = getConfiguredSecret();
1845
+ const payload2 = secret2 ? await verifyHs256(token2, secret2) : isStrictMode() ? null : decodeJwtUnverified(token2);
1846
+ if (payload2) {
1847
+ if (includeEmail) email = pickEmail(payload2);
1848
+ if (includeName) name = pickName(payload2);
1849
+ }
1850
+ }
1851
+ }
1852
+ return { userId: headerUserId, email, name };
1853
+ }
1854
+ const token = extractBearerToken(request);
1855
+ if (!token) {
1856
+ return unauthorized("User ID not found. Ensure middleware is configured.");
1857
+ }
1858
+ const secret = getConfiguredSecret();
1859
+ let payload = null;
1860
+ if (secret) {
1861
+ payload = await verifyHs256(token, secret);
1862
+ if (!payload) {
1863
+ return unauthorized("Invalid or expired authentication token");
1864
+ }
1865
+ } else if (isStrictMode()) {
1866
+ return unauthorized(
1867
+ "Strict auth mode is enabled but no JWT secret is configured. Set SOLVAPAY_JWT_SECRET or SUPABASE_JWT_SECRET."
1868
+ );
1869
+ } else {
1870
+ payload = decodeJwtUnverified(token);
1871
+ if (!payload) {
1872
+ return unauthorized("Malformed authentication token");
1873
+ }
1874
+ }
1875
+ const userId = typeof payload.sub === "string" ? payload.sub : null;
1876
+ if (!userId) {
1877
+ return unauthorized("Authentication token missing subject (sub) claim");
1743
1878
  }
1744
- const userId = userIdOrError;
1745
- const email = options.includeEmail !== false ? await getUserEmailFromRequest(request) : null;
1746
- const name = options.includeName !== false ? await getUserNameFromRequest(request) : null;
1747
1879
  return {
1748
1880
  userId,
1749
- email,
1750
- name
1881
+ email: includeEmail ? pickEmail(payload) : null,
1882
+ name: includeName ? pickName(payload) : null
1751
1883
  };
1752
1884
  } catch (error) {
1753
1885
  return handleRouteError(error, "Get authenticated user", "Authentication failed");
@@ -1775,6 +1907,24 @@ async function syncCustomerCore(request, options = {}) {
1775
1907
  return handleRouteError(error, "Sync customer", "Failed to sync customer");
1776
1908
  }
1777
1909
  }
1910
+ async function getCustomerBalanceCore(request, options = {}) {
1911
+ try {
1912
+ const userResult = await getAuthenticatedUserCore(request);
1913
+ if (isErrorResult(userResult)) {
1914
+ return userResult;
1915
+ }
1916
+ const { userId, email, name } = userResult;
1917
+ const solvaPay = options.solvaPay || createSolvaPay();
1918
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
1919
+ email: email || void 0,
1920
+ name: name || void 0
1921
+ });
1922
+ const result = await solvaPay.getCustomerBalance({ customerRef });
1923
+ return result;
1924
+ } catch (error) {
1925
+ return handleRouteError(error, "Get customer credits", "Failed to get customer credits");
1926
+ }
1927
+ }
1778
1928
 
1779
1929
  // src/helpers/payment.ts
1780
1930
  async function createPaymentIntentCore(request, body, options = {}) {
@@ -2024,10 +2174,107 @@ async function cancelPurchaseCore(request, body, options = {}) {
2024
2174
  return handleRouteError(error, "Cancel purchase", "Failed to cancel purchase");
2025
2175
  }
2026
2176
  }
2177
+ async function reactivatePurchaseCore(request, body, options = {}) {
2178
+ try {
2179
+ if (!body.purchaseRef) {
2180
+ return {
2181
+ error: "Missing required parameter: purchaseRef is required",
2182
+ status: 400
2183
+ };
2184
+ }
2185
+ const solvaPay = options.solvaPay || createSolvaPay();
2186
+ if (!solvaPay.apiClient.reactivatePurchase) {
2187
+ return {
2188
+ error: "Reactivate purchase method not available on SDK client",
2189
+ status: 500
2190
+ };
2191
+ }
2192
+ let reactivatedPurchase = await solvaPay.apiClient.reactivatePurchase({
2193
+ purchaseRef: body.purchaseRef
2194
+ });
2195
+ if (!reactivatedPurchase || typeof reactivatedPurchase !== "object") {
2196
+ return {
2197
+ error: "Invalid response from reactivate purchase endpoint",
2198
+ status: 500
2199
+ };
2200
+ }
2201
+ const responseObj = reactivatedPurchase;
2202
+ if (responseObj.purchase && typeof responseObj.purchase === "object") {
2203
+ reactivatedPurchase = responseObj.purchase;
2204
+ }
2205
+ if (!reactivatedPurchase.reference) {
2206
+ return {
2207
+ error: "Reactivate purchase response missing required fields",
2208
+ status: 500
2209
+ };
2210
+ }
2211
+ if (reactivatedPurchase.cancelledAt) {
2212
+ return {
2213
+ error: `Purchase reactivation failed: cancelledAt is still set`,
2214
+ status: 500
2215
+ };
2216
+ }
2217
+ await new Promise((resolve) => setTimeout(resolve, 500));
2218
+ return reactivatedPurchase;
2219
+ } catch (error) {
2220
+ if (error instanceof SolvaPayError4) {
2221
+ const errorMessage = error.message;
2222
+ if (errorMessage.includes("not found")) {
2223
+ return {
2224
+ error: "Purchase not found",
2225
+ status: 404,
2226
+ details: errorMessage
2227
+ };
2228
+ }
2229
+ if (errorMessage.includes("cannot be reactivated") || errorMessage.includes("not pending cancellation") || errorMessage.includes("already been fully cancelled") || errorMessage.includes("already ended")) {
2230
+ return {
2231
+ error: "Purchase cannot be reactivated",
2232
+ status: 400,
2233
+ details: errorMessage
2234
+ };
2235
+ }
2236
+ return {
2237
+ error: errorMessage,
2238
+ status: 500,
2239
+ details: errorMessage
2240
+ };
2241
+ }
2242
+ return handleRouteError(error, "Reactivate purchase", "Failed to reactivate purchase");
2243
+ }
2244
+ }
2245
+
2246
+ // src/helpers/activation.ts
2247
+ async function activatePlanCore(request, body, options = {}) {
2248
+ try {
2249
+ if (!body.productRef || !body.planRef) {
2250
+ return {
2251
+ error: "Missing required parameters: productRef and planRef are required",
2252
+ status: 400
2253
+ };
2254
+ }
2255
+ const customerResult = await syncCustomerCore(request, {
2256
+ solvaPay: options.solvaPay,
2257
+ includeEmail: options.includeEmail,
2258
+ includeName: options.includeName
2259
+ });
2260
+ if (isErrorResult(customerResult)) {
2261
+ return customerResult;
2262
+ }
2263
+ const customerRef = customerResult;
2264
+ const solvaPay = options.solvaPay || createSolvaPay();
2265
+ return await solvaPay.activatePlan({
2266
+ customerRef,
2267
+ productRef: body.productRef,
2268
+ planRef: body.planRef
2269
+ });
2270
+ } catch (error) {
2271
+ return handleRouteError(error, "Activate plan", "Plan activation failed");
2272
+ }
2273
+ }
2027
2274
 
2028
2275
  // src/helpers/plans.ts
2029
2276
  import { getSolvaPayConfig as getSolvaPayConfig2 } from "@solvapay/core";
2030
- async function listPlansCore(request) {
2277
+ async function listPlansCore(request, options = {}) {
2031
2278
  try {
2032
2279
  const url = new URL(request.url);
2033
2280
  const productRef = url.searchParams.get("productRef");
@@ -2037,19 +2284,20 @@ async function listPlansCore(request) {
2037
2284
  status: 400
2038
2285
  };
2039
2286
  }
2040
- const config = getSolvaPayConfig2();
2041
- const solvapaySecretKey = config.apiKey;
2042
- const solvapayApiBaseUrl = config.apiBaseUrl;
2043
- if (!solvapaySecretKey) {
2287
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2288
+ const config = getSolvaPayConfig2();
2289
+ if (!config.apiKey) return null;
2290
+ return createSolvaPayClient({
2291
+ apiKey: config.apiKey,
2292
+ apiBaseUrl: config.apiBaseUrl
2293
+ });
2294
+ })();
2295
+ if (!apiClient) {
2044
2296
  return {
2045
2297
  error: "Server configuration error: SolvaPay secret key not configured",
2046
2298
  status: 500
2047
2299
  };
2048
2300
  }
2049
- const apiClient = createSolvaPayClient({
2050
- apiKey: solvapaySecretKey,
2051
- apiBaseUrl: solvapayApiBaseUrl
2052
- });
2053
2301
  if (!apiClient.listPlans) {
2054
2302
  return {
2055
2303
  error: "List plans method not available",
@@ -2066,6 +2314,159 @@ async function listPlansCore(request) {
2066
2314
  }
2067
2315
  }
2068
2316
 
2317
+ // src/helpers/merchant.ts
2318
+ import { getSolvaPayConfig as getSolvaPayConfig3 } from "@solvapay/core";
2319
+ async function getMerchantCore(_request, options = {}) {
2320
+ try {
2321
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2322
+ const config = getSolvaPayConfig3();
2323
+ if (!config.apiKey) return null;
2324
+ return createSolvaPayClient({
2325
+ apiKey: config.apiKey,
2326
+ apiBaseUrl: config.apiBaseUrl
2327
+ });
2328
+ })();
2329
+ if (!apiClient) {
2330
+ return {
2331
+ error: "Server configuration error: SolvaPay secret key not configured",
2332
+ status: 500
2333
+ };
2334
+ }
2335
+ if (!apiClient.getMerchant) {
2336
+ return {
2337
+ error: "Get merchant method not available",
2338
+ status: 500
2339
+ };
2340
+ }
2341
+ const merchant = await apiClient.getMerchant();
2342
+ return merchant;
2343
+ } catch (error) {
2344
+ return handleRouteError(error, "Get merchant", "Failed to fetch merchant");
2345
+ }
2346
+ }
2347
+
2348
+ // src/helpers/product.ts
2349
+ import { getSolvaPayConfig as getSolvaPayConfig4 } from "@solvapay/core";
2350
+ async function getProductCore(request, options = {}) {
2351
+ try {
2352
+ const url = new URL(request.url);
2353
+ const productRef = url.searchParams.get("productRef");
2354
+ if (!productRef) {
2355
+ return {
2356
+ error: "Missing required parameter: productRef",
2357
+ status: 400
2358
+ };
2359
+ }
2360
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2361
+ const config = getSolvaPayConfig4();
2362
+ if (!config.apiKey) return null;
2363
+ return createSolvaPayClient({
2364
+ apiKey: config.apiKey,
2365
+ apiBaseUrl: config.apiBaseUrl
2366
+ });
2367
+ })();
2368
+ if (!apiClient) {
2369
+ return {
2370
+ error: "Server configuration error: SolvaPay secret key not configured",
2371
+ status: 500
2372
+ };
2373
+ }
2374
+ if (!apiClient.getProduct) {
2375
+ return {
2376
+ error: "Get product method not available",
2377
+ status: 500
2378
+ };
2379
+ }
2380
+ const product = await apiClient.getProduct(productRef);
2381
+ return product;
2382
+ } catch (error) {
2383
+ return handleRouteError(error, "Get product", "Failed to fetch product");
2384
+ }
2385
+ }
2386
+
2387
+ // src/helpers/purchase.ts
2388
+ async function checkPurchaseCore(request, options = {}) {
2389
+ try {
2390
+ const userResult = await getAuthenticatedUserCore(request, {
2391
+ includeEmail: options.includeEmail,
2392
+ includeName: options.includeName
2393
+ });
2394
+ if (isErrorResult(userResult)) {
2395
+ return userResult;
2396
+ }
2397
+ const { userId, email, name } = userResult;
2398
+ const solvaPay = options.solvaPay || createSolvaPay();
2399
+ const cachedCustomerRef = request.headers.get("x-solvapay-customer-ref");
2400
+ if (cachedCustomerRef) {
2401
+ try {
2402
+ const customer = await solvaPay.getCustomer({ customerRef: cachedCustomerRef });
2403
+ if (customer && customer.customerRef) {
2404
+ if (customer.externalRef && customer.externalRef === userId) {
2405
+ const filteredPurchases = (customer.purchases || []).filter(
2406
+ (p) => p.status === "active"
2407
+ );
2408
+ return {
2409
+ customerRef: customer.customerRef,
2410
+ email: customer.email,
2411
+ name: customer.name,
2412
+ purchases: filteredPurchases
2413
+ };
2414
+ }
2415
+ }
2416
+ } catch {
2417
+ }
2418
+ }
2419
+ try {
2420
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2421
+ email: email || void 0,
2422
+ name: name || void 0
2423
+ });
2424
+ const customer = await solvaPay.getCustomer({ customerRef });
2425
+ const filteredPurchases = (customer.purchases || []).filter((p) => p.status === "active");
2426
+ return {
2427
+ customerRef: customer.customerRef || userId,
2428
+ email: customer.email,
2429
+ name: customer.name,
2430
+ purchases: filteredPurchases
2431
+ };
2432
+ } catch {
2433
+ return {
2434
+ customerRef: userId,
2435
+ purchases: []
2436
+ };
2437
+ }
2438
+ } catch (error) {
2439
+ return handleRouteError(error, "Check purchase", "Failed to check purchase");
2440
+ }
2441
+ }
2442
+
2443
+ // src/helpers/usage.ts
2444
+ async function trackUsageCore(request, body, options = {}) {
2445
+ try {
2446
+ const userResult = await getAuthenticatedUserCore(request);
2447
+ if (isErrorResult(userResult)) {
2448
+ return userResult;
2449
+ }
2450
+ const { userId, email, name } = userResult;
2451
+ const solvaPay = options.solvaPay || createSolvaPay();
2452
+ const customerRef = await solvaPay.ensureCustomer(userId, userId, {
2453
+ email: email || void 0,
2454
+ name: name || void 0
2455
+ });
2456
+ await solvaPay.trackUsage({
2457
+ customerRef,
2458
+ actionType: body.actionType,
2459
+ units: body.units,
2460
+ productRef: body.productRef,
2461
+ description: body.description,
2462
+ metadata: body.metadata
2463
+ });
2464
+ return { success: true };
2465
+ } catch (error) {
2466
+ return handleRouteError(error, "Track usage", "Track usage failed");
2467
+ }
2468
+ }
2469
+
2069
2470
  // src/edge.ts
2070
2471
  function timingSafeEqual(a, b) {
2071
2472
  if (a.length !== b.length) return false;
@@ -2120,7 +2521,9 @@ async function verifyWebhook({
2120
2521
  }
2121
2522
  export {
2122
2523
  PaywallError,
2524
+ activatePlanCore,
2123
2525
  cancelPurchaseCore,
2526
+ checkPurchaseCore,
2124
2527
  createCheckoutSessionCore,
2125
2528
  createCustomerSessionCore,
2126
2529
  createPaymentIntentCore,
@@ -2128,12 +2531,17 @@ export {
2128
2531
  createSolvaPayClient,
2129
2532
  createTopupPaymentIntentCore,
2130
2533
  getAuthenticatedUserCore,
2534
+ getCustomerBalanceCore,
2535
+ getMerchantCore,
2536
+ getProductCore,
2131
2537
  handleRouteError,
2132
2538
  isErrorResult,
2133
2539
  listPlansCore,
2134
2540
  paywallErrorToClientPayload,
2135
2541
  processPaymentIntentCore,
2542
+ reactivatePurchaseCore,
2136
2543
  syncCustomerCore,
2544
+ trackUsageCore,
2137
2545
  verifyWebhook,
2138
2546
  withRetry
2139
2547
  };