@mixrpay/merchant-sdk 0.3.0 → 0.3.2

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/README.md CHANGED
@@ -225,9 +225,35 @@ interface MixrPayment {
225
225
 
226
226
  /** Error message if invalid */
227
227
  error?: string;
228
+
229
+ /** Whether payment was pre-charged by the agent (session only) */
230
+ preCharged?: boolean;
228
231
  }
229
232
  ```
230
233
 
234
+ ### Pre-Charged Sessions
235
+
236
+ When AI agents use the Agent SDK, they can pre-charge before calling your API by passing `X-Mixr-Charged: <amount>`. The middleware detects this and validates the session without double-charging:
237
+
238
+ ```typescript
239
+ // Agent SDK calls your API with pre-charged session
240
+ // Headers: { 'X-Mixr-Session': 'sess_abc', 'X-Mixr-Charged': '0.05' }
241
+
242
+ app.post('/api/query', mixrpay({ priceUsd: 0.05 }), (req, res) => {
243
+ const { preCharged, amountUsd, payer } = req.mixrPayment!;
244
+
245
+ if (preCharged) {
246
+ // Agent pre-charged - session validated only, no additional charge made
247
+ console.log(`Pre-charged payment of $${amountUsd} from ${payer}`);
248
+ } else {
249
+ // Middleware charged the session
250
+ console.log(`Charged $${amountUsd} from ${payer}`);
251
+ }
252
+
253
+ res.json({ result: 'success' });
254
+ });
255
+ ```
256
+
231
257
  ## Advanced Usage
232
258
 
233
259
  ### Dynamic Pricing
@@ -301,6 +327,55 @@ if (result.valid) {
301
327
  }
302
328
  ```
303
329
 
330
+ ### Webhook Verification
331
+
332
+ Verify webhook signatures to ensure events are from MixrPay:
333
+
334
+ ```typescript
335
+ import { verifySessionWebhook } from '@mixrpay/merchant-sdk';
336
+
337
+ app.post('/webhooks/mixrpay', (req, res) => {
338
+ const signature = req.headers['x-mixrpay-signature'] as string;
339
+ const payload = JSON.stringify(req.body);
340
+
341
+ // Verify signature using your webhook secret from the dashboard
342
+ if (!verifySessionWebhook(payload, signature, process.env.WEBHOOK_SECRET!)) {
343
+ return res.status(401).json({ error: 'Invalid signature' });
344
+ }
345
+
346
+ const event = req.body;
347
+
348
+ switch (event.event) {
349
+ case 'session.created':
350
+ // New session authorized
351
+ console.log(`Session created: ${event.session_id} from ${event.user_wallet}`);
352
+ console.log(`Spending limit: $${event.spending_limit_usd}`);
353
+ break;
354
+
355
+ case 'session.charged':
356
+ // Successful charge against a session
357
+ console.log(`Charged $${event.amount_usd} from ${event.user_wallet}`);
358
+ console.log(`Remaining: $${event.remaining_usd}`);
359
+ // Update your database, trigger fulfillment, etc.
360
+ break;
361
+
362
+ case 'session.revoked':
363
+ // User revoked their session
364
+ console.log(`Session revoked: ${event.session_id}`);
365
+ break;
366
+
367
+ case 'session.expired':
368
+ // Session expired
369
+ console.log(`Session expired: ${event.session_id}`);
370
+ break;
371
+ }
372
+
373
+ res.json({ received: true });
374
+ });
375
+ ```
376
+
377
+ Configure webhook URLs in your [MixrPay Dashboard](https://www.mixrpay.com/seller/developers).
378
+
304
379
  ## Testing
305
380
 
306
381
  Enable test mode to accept test payments during development:
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { V as VerifyReceiptOptions, P as PaymentReceipt } from './types-jelXkTp9.mjs';
2
- export { M as MixrPayOptions, a as MixrPayPaymentResult, b as PaymentMethod, c as PriceContext } from './types-jelXkTp9.mjs';
1
+ import { V as VerifyReceiptOptions, P as PaymentReceipt } from './types-BwiuIaOu.mjs';
2
+ export { M as MixrPayOptions, a as MixrPayPaymentResult, b as PaymentMethod, c as PriceContext } from './types-BwiuIaOu.mjs';
3
3
 
4
4
  /**
5
5
  * MixrPay Merchant SDK - Payment Receipt Verification
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { V as VerifyReceiptOptions, P as PaymentReceipt } from './types-jelXkTp9.js';
2
- export { M as MixrPayOptions, a as MixrPayPaymentResult, b as PaymentMethod, c as PriceContext } from './types-jelXkTp9.js';
1
+ import { V as VerifyReceiptOptions, P as PaymentReceipt } from './types-BwiuIaOu.js';
2
+ export { M as MixrPayOptions, a as MixrPayPaymentResult, b as PaymentMethod, c as PriceContext } from './types-BwiuIaOu.js';
3
3
 
4
4
  /**
5
5
  * MixrPay Merchant SDK - Payment Receipt Verification
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ module.exports = __toCommonJS(src_exports);
37
37
 
38
38
  // src/receipt.ts
39
39
  var jose = __toESM(require("jose"));
40
- var DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || "https://mixrpay.com/.well-known/jwks";
40
+ var DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || "https://www.mixrpay.com/.well-known/jwks";
41
41
  var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
42
42
  var jwksCache = /* @__PURE__ */ new Map();
43
43
  async function getJWKS(jwksUrl) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/receipt.ts","../src/charges.ts"],"sourcesContent":["/**\n * MixrPay Merchant SDK\n * \n * Accept payments from AI agents and web apps with one middleware.\n * \n * @example Express\n * ```typescript\n * import { mixrpay } from '@mixrpay/merchant-sdk/express';\n * \n * app.post('/api/query', mixrpay({ priceUsd: 0.05 }), (req, res) => {\n * console.log(`Paid by ${req.mixrPayment?.payer}`);\n * res.json({ result: 'success' });\n * });\n * ```\n * \n * @example Next.js\n * ```typescript\n * import { withMixrPay } from '@mixrpay/merchant-sdk/nextjs';\n * \n * export const POST = withMixrPay({ priceUsd: 0.05 }, async (req, payment) => {\n * return NextResponse.json({ result: 'success' });\n * });\n * ```\n * \n * @packageDocumentation\n */\n\n// Receipt verification (for custom integrations)\nexport { verifyPaymentReceipt } from './receipt';\n\n// Session webhook verification\nexport { verifySessionWebhook } from './charges';\n\n// Types for TypeScript users\nexport type {\n MixrPayOptions,\n MixrPayPaymentResult,\n PaymentMethod,\n PriceContext,\n PaymentReceipt,\n} from './types';\n\nexport type {\n SessionGrant,\n} from './charges';\n\n// Widget element types (for TypeScript JSX support)\nexport type {} from './widget-elements';\n","/**\n * MixrPay Merchant SDK - Payment Receipt Verification\n * \n * Verify JWT payment receipts issued by MixrPay after successful x402 payments.\n * \n * @example\n * ```typescript\n * import { verifyPaymentReceipt } from '@mixrpay/merchant-sdk';\n * \n * const receipt = req.headers['x-payment-receipt'];\n * const payment = await verifyPaymentReceipt(receipt);\n * \n * console.log(`Payment received: $${payment.amountUsd} from ${payment.payer}`);\n * console.log(`Transaction: ${payment.txHash}`);\n * ```\n */\n\nimport * as jose from 'jose';\nimport type { PaymentReceipt, VerifyReceiptOptions } from './types';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || 'https://mixrpay.com/.well-known/jwks';\nconst JWKS_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\n\n// =============================================================================\n// JWKS Cache\n// =============================================================================\n\ninterface CachedJWKS {\n jwks: jose.JSONWebKeySet;\n fetchedAt: number;\n}\n\nconst jwksCache = new Map<string, CachedJWKS>();\n\n/**\n * Fetch and cache JWKS from the specified URL.\n */\nasync function getJWKS(jwksUrl: string): Promise<jose.JSONWebKeySet> {\n const cached = jwksCache.get(jwksUrl);\n const now = Date.now();\n\n // Return cached JWKS if still valid\n if (cached && (now - cached.fetchedAt) < JWKS_CACHE_TTL_MS) {\n return cached.jwks;\n }\n\n // Fetch fresh JWKS\n const response = await fetch(jwksUrl);\n \n if (!response.ok) {\n throw new Error(`Failed to fetch JWKS from ${jwksUrl}: ${response.status} ${response.statusText}`);\n }\n\n const jwks = await response.json() as jose.JSONWebKeySet;\n\n // Validate JWKS structure\n if (!jwks.keys || !Array.isArray(jwks.keys) || jwks.keys.length === 0) {\n throw new Error('Invalid JWKS: missing or empty keys array');\n }\n\n // Cache the JWKS\n jwksCache.set(jwksUrl, { jwks, fetchedAt: now });\n\n return jwks;\n}\n\n/**\n * Create a JWKS key set from a fetched JWKS.\n */\nasync function createKeySet(jwksUrl: string): Promise<jose.JWTVerifyGetKey> {\n const jwks = await getJWKS(jwksUrl);\n return jose.createLocalJWKSet(jwks);\n}\n\n// =============================================================================\n// Receipt Verification\n// =============================================================================\n\n/**\n * Verify a JWT payment receipt from MixrPay.\n * \n * This function:\n * 1. Fetches the JWKS from MixrPay (cached for 1 hour)\n * 2. Verifies the JWT signature using RS256\n * 3. Validates standard JWT claims (exp, iat)\n * 4. Returns the typed payment receipt\n * \n * @param receipt - The JWT receipt string from X-Payment-Receipt header\n * @param options - Optional configuration (custom JWKS URL, issuer validation)\n * @returns Verified payment receipt\n * @throws Error if verification fails\n * \n * @example\n * ```typescript\n * // Basic usage\n * const payment = await verifyPaymentReceipt(receipt);\n * \n * // With custom JWKS URL (for testing or self-hosted)\n * const payment = await verifyPaymentReceipt(receipt, {\n * jwksUrl: 'https://your-mixrpay.com/.well-known/jwks'\n * });\n * ```\n */\nexport async function verifyPaymentReceipt(\n receipt: string,\n options?: VerifyReceiptOptions\n): Promise<PaymentReceipt> {\n const jwksUrl = options?.jwksUrl || DEFAULT_JWKS_URL;\n\n try {\n // Get the key set\n const keySet = await createKeySet(jwksUrl);\n\n // Build verification options\n const verifyOptions: jose.JWTVerifyOptions = {\n algorithms: ['RS256'],\n };\n\n // Add issuer validation if specified\n if (options?.issuer) {\n verifyOptions.issuer = options.issuer;\n }\n\n // Verify the JWT\n const { payload } = await jose.jwtVerify(receipt, keySet, verifyOptions);\n\n // Validate required payment fields\n const requiredFields = ['paymentId', 'amount', 'amountUsd', 'payer', 'recipient', 'chainId', 'txHash', 'settledAt'];\n for (const field of requiredFields) {\n if (!(field in payload)) {\n throw new Error(`Missing required field in receipt: ${field}`);\n }\n }\n\n // Return typed receipt\n return {\n paymentId: payload.paymentId as string,\n amount: payload.amount as string,\n amountUsd: payload.amountUsd as number,\n payer: payload.payer as string,\n recipient: payload.recipient as string,\n chainId: payload.chainId as number,\n txHash: payload.txHash as string,\n settledAt: payload.settledAt as string,\n issuedAt: new Date((payload.iat as number) * 1000),\n expiresAt: new Date((payload.exp as number) * 1000),\n };\n } catch (error) {\n if (error instanceof jose.errors.JWTExpired) {\n throw new Error('Payment receipt has expired');\n }\n if (error instanceof jose.errors.JWTClaimValidationFailed) {\n throw new Error(`Receipt validation failed: ${error.message}`);\n }\n if (error instanceof jose.errors.JWSSignatureVerificationFailed) {\n throw new Error('Invalid receipt signature');\n }\n throw error;\n }\n}\n\n/**\n * Parse a JWT payment receipt without verification.\n * \n * WARNING: This does NOT verify the signature. Use only for debugging\n * or when you've already verified the receipt elsewhere.\n * \n * @param receipt - The JWT receipt string\n * @returns Decoded payload (unverified)\n */\nexport function parsePaymentReceipt(receipt: string): PaymentReceipt {\n const decoded = jose.decodeJwt(receipt);\n\n return {\n paymentId: decoded.paymentId as string,\n amount: decoded.amount as string,\n amountUsd: decoded.amountUsd as number,\n payer: decoded.payer as string,\n recipient: decoded.recipient as string,\n chainId: decoded.chainId as number,\n txHash: decoded.txHash as string,\n settledAt: decoded.settledAt as string,\n issuedAt: decoded.iat ? new Date(decoded.iat * 1000) : undefined,\n expiresAt: decoded.exp ? new Date(decoded.exp * 1000) : undefined,\n };\n}\n\n/**\n * Check if a receipt is expired.\n * \n * @param receipt - The JWT receipt string or parsed PaymentReceipt\n * @returns true if expired, false otherwise\n */\nexport function isReceiptExpired(receipt: string | PaymentReceipt): boolean {\n const parsed = typeof receipt === 'string' ? parsePaymentReceipt(receipt) : receipt;\n \n if (!parsed.expiresAt) {\n return false; // No expiration = never expires\n }\n\n return parsed.expiresAt.getTime() < Date.now();\n}\n\n/**\n * Clear the JWKS cache.\n * Useful for testing or when keys have rotated.\n */\nexport function clearJWKSCache(): void {\n jwksCache.clear();\n}\n\n/**\n * Get the default JWKS URL.\n */\nexport function getDefaultJWKSUrl(): string {\n return DEFAULT_JWKS_URL;\n}\n\n","/**\n * MixrPay Charges Module\n * \n * Create and manage charges using session signers.\n * Session signers allow you to charge user wallets without\n * requiring approval for each transaction.\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface CreateChargeParams {\n /** Session key ID granted by the user */\n sessionId: string;\n /** Amount to charge in USD (e.g., 0.05 for 5 cents) */\n amountUsd: number;\n /** Unique reference for idempotency (e.g., \"order_123\") */\n reference: string;\n /** Optional metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface Charge {\n /** Unique charge ID */\n id: string;\n /** Current status */\n status: 'pending' | 'submitted' | 'confirmed' | 'failed';\n /** Amount in USD */\n amountUsd: number;\n /** Formatted USDC amount */\n amountUsdc: string;\n /** Transaction hash (if submitted) */\n txHash?: string;\n /** Block explorer URL */\n explorerUrl?: string;\n /** From wallet address */\n fromAddress: string;\n /** To wallet address */\n toAddress: string;\n /** Your reference */\n reference: string;\n /** When the charge was created */\n createdAt: string;\n /** When the charge was confirmed */\n confirmedAt?: string;\n /** Session name */\n sessionName?: string;\n /** User wallet address */\n userWallet?: string;\n /** Error message if failed */\n error?: string;\n}\n\nexport interface CreateChargeResult {\n success: boolean;\n charge?: Charge;\n /** True if this is a replay of an existing charge */\n idempotentReplay?: boolean;\n error?: string;\n details?: string;\n}\n\nexport interface SessionGrant {\n /** Session key ID */\n sessionId: string;\n /** User's wallet address */\n userWallet: string;\n /** Your merchant wallet address */\n merchantWallet: string;\n /** Spending limits */\n limits: {\n maxTotalUsd?: number;\n maxPerTxUsd?: number;\n expiresAt: string;\n };\n /** When the session was granted */\n grantedAt: string;\n}\n\n// Internal types for API responses\ninterface CreateChargeApiResponse {\n success: boolean;\n charge_id?: string;\n status?: string;\n amount_usd?: number;\n amount_usdc?: string;\n tx_hash?: string;\n explorer_url?: string;\n idempotent_replay?: boolean;\n error?: string;\n details?: string;\n}\n\ninterface ChargeApiResponse {\n id: string;\n status: string;\n amount_usd: number;\n amount_usdc: string;\n tx_hash?: string;\n explorer_url?: string;\n from_address: string;\n to_address: string;\n reference: string;\n created_at: string;\n confirmed_at?: string;\n session_name?: string;\n user_wallet?: string;\n}\n\nexport interface ChargesClientOptions {\n /** Your API key */\n apiKey: string;\n /** MixrPay API base URL */\n baseUrl?: string;\n}\n\n// =============================================================================\n// Client\n// =============================================================================\n\nexport class ChargesClient {\n private apiKey: string;\n private baseUrl: string;\n\n constructor(options: ChargesClientOptions) {\n this.apiKey = options.apiKey;\n this.baseUrl = options.baseUrl || process.env.MIXRPAY_BASE_URL || 'https://mixrpay.com';\n }\n\n /**\n * Create a new charge.\n * \n * @example\n * ```typescript\n * const result = await charges.create({\n * sessionId: 'sk_xxx',\n * amountUsd: 0.05,\n * reference: 'image_gen_123',\n * metadata: { feature: 'image_generation' }\n * });\n * \n * if (result.success) {\n * console.log(`Charged ${result.charge.amountUsdc}`);\n * console.log(`TX: ${result.charge.explorerUrl}`);\n * }\n * ```\n */\n async create(params: CreateChargeParams): Promise<CreateChargeResult> {\n const response = await fetch(`${this.baseUrl}/api/v1/charges`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({\n session_id: params.sessionId,\n amount_usd: params.amountUsd,\n reference: params.reference,\n metadata: params.metadata,\n }),\n });\n\n const data = await response.json() as CreateChargeApiResponse;\n\n if (!response.ok) {\n return {\n success: false,\n error: data.error,\n details: data.details,\n };\n }\n\n return {\n success: data.success,\n charge: data.charge_id ? {\n id: data.charge_id,\n status: data.status as Charge['status'],\n amountUsd: data.amount_usd ?? 0,\n amountUsdc: data.amount_usdc ?? '0',\n txHash: data.tx_hash,\n explorerUrl: data.explorer_url,\n fromAddress: '',\n toAddress: '',\n reference: params.reference,\n createdAt: new Date().toISOString(),\n } : undefined,\n idempotentReplay: data.idempotent_replay,\n error: data.error,\n };\n }\n\n /**\n * Get a charge by ID.\n * \n * @example\n * ```typescript\n * const charge = await charges.get('chg_xxx');\n * console.log(charge.status); // 'confirmed'\n * ```\n */\n async get(chargeId: string): Promise<Charge | null> {\n const response = await fetch(`${this.baseUrl}/api/v1/charges?id=${chargeId}`, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`Failed to get charge: ${response.statusText}`);\n }\n\n const data = await response.json() as ChargeApiResponse;\n\n return {\n id: data.id,\n status: data.status as Charge['status'],\n amountUsd: data.amount_usd,\n amountUsdc: data.amount_usdc,\n txHash: data.tx_hash,\n explorerUrl: data.explorer_url,\n fromAddress: data.from_address,\n toAddress: data.to_address,\n reference: data.reference,\n createdAt: data.created_at,\n confirmedAt: data.confirmed_at,\n sessionName: data.session_name,\n userWallet: data.user_wallet,\n };\n }\n\n /**\n * Get a charge by transaction hash.\n */\n async getByTxHash(txHash: string): Promise<Charge | null> {\n const response = await fetch(`${this.baseUrl}/api/v1/charges?tx_hash=${txHash}`, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`Failed to get charge: ${response.statusText}`);\n }\n\n const data = await response.json() as ChargeApiResponse;\n\n return {\n id: data.id,\n status: data.status as Charge['status'],\n amountUsd: data.amount_usd,\n amountUsdc: data.amount_usdc,\n txHash: data.tx_hash,\n explorerUrl: data.explorer_url,\n fromAddress: data.from_address,\n toAddress: data.to_address,\n reference: data.reference,\n createdAt: data.created_at,\n confirmedAt: data.confirmed_at,\n sessionName: data.session_name,\n userWallet: data.user_wallet,\n };\n }\n}\n\n// =============================================================================\n// Webhook Verification\n// =============================================================================\n\nimport crypto from 'crypto';\n\n/**\n * Verify a session.granted webhook signature.\n * \n * @example\n * ```typescript\n * const payload = req.body;\n * const signature = req.headers['x-mixrpay-signature'];\n * \n * if (verifySessionWebhook(JSON.stringify(payload), signature, webhookSecret)) {\n * const grant = parseSessionGrant(payload);\n * console.log(`User ${grant.userWallet} granted access`);\n * }\n * ```\n */\nexport function verifySessionWebhook(\n payload: string,\n signature: string,\n secret: string\n): boolean {\n const expectedSignature = crypto\n .createHmac('sha256', secret)\n .update(payload)\n .digest('hex');\n \n return crypto.timingSafeEqual(\n Buffer.from(signature),\n Buffer.from(expectedSignature)\n );\n}\n\n/**\n * Parse a session.granted webhook payload.\n */\nexport function parseSessionGrant(payload: {\n event: string;\n session_id: string;\n user_wallet: string;\n merchant_wallet: string;\n limits: {\n max_total_usd?: number;\n max_per_tx_usd?: number;\n expires_at: string;\n };\n granted_at: string;\n}): SessionGrant {\n if (payload.event !== 'session.granted') {\n throw new Error(`Unexpected event type: ${payload.event}`);\n }\n\n return {\n sessionId: payload.session_id,\n userWallet: payload.user_wallet,\n merchantWallet: payload.merchant_wallet,\n limits: {\n maxTotalUsd: payload.limits.max_total_usd,\n maxPerTxUsd: payload.limits.max_per_tx_usd,\n expiresAt: payload.limits.expires_at,\n },\n grantedAt: payload.granted_at,\n };\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACiBA,WAAsB;AAOtB,IAAM,mBAAmB,QAAQ,IAAI,oBAAoB;AACzD,IAAM,oBAAoB,KAAK,KAAK;AAWpC,IAAM,YAAY,oBAAI,IAAwB;AAK9C,eAAe,QAAQ,SAA8C;AACnE,QAAM,SAAS,UAAU,IAAI,OAAO;AACpC,QAAM,MAAM,KAAK,IAAI;AAGrB,MAAI,UAAW,MAAM,OAAO,YAAa,mBAAmB;AAC1D,WAAO,OAAO;AAAA,EAChB;AAGA,QAAM,WAAW,MAAM,MAAM,OAAO;AAEpC,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,6BAA6B,OAAO,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,EACnG;AAEA,QAAM,OAAO,MAAM,SAAS,KAAK;AAGjC,MAAI,CAAC,KAAK,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAAI,KAAK,KAAK,KAAK,WAAW,GAAG;AACrE,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AAGA,YAAU,IAAI,SAAS,EAAE,MAAM,WAAW,IAAI,CAAC;AAE/C,SAAO;AACT;AAKA,eAAe,aAAa,SAAgD;AAC1E,QAAM,OAAO,MAAM,QAAQ,OAAO;AAClC,SAAY,uBAAkB,IAAI;AACpC;AA+BA,eAAsB,qBACpB,SACA,SACyB;AACzB,QAAM,UAAU,SAAS,WAAW;AAEpC,MAAI;AAEF,UAAM,SAAS,MAAM,aAAa,OAAO;AAGzC,UAAM,gBAAuC;AAAA,MAC3C,YAAY,CAAC,OAAO;AAAA,IACtB;AAGA,QAAI,SAAS,QAAQ;AACnB,oBAAc,SAAS,QAAQ;AAAA,IACjC;AAGA,UAAM,EAAE,QAAQ,IAAI,MAAW,eAAU,SAAS,QAAQ,aAAa;AAGvE,UAAM,iBAAiB,CAAC,aAAa,UAAU,aAAa,SAAS,aAAa,WAAW,UAAU,WAAW;AAClH,eAAW,SAAS,gBAAgB;AAClC,UAAI,EAAE,SAAS,UAAU;AACvB,cAAM,IAAI,MAAM,sCAAsC,KAAK,EAAE;AAAA,MAC/D;AAAA,IACF;AAGA,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,MACnB,OAAO,QAAQ;AAAA,MACf,WAAW,QAAQ;AAAA,MACnB,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,MACnB,UAAU,IAAI,KAAM,QAAQ,MAAiB,GAAI;AAAA,MACjD,WAAW,IAAI,KAAM,QAAQ,MAAiB,GAAI;AAAA,IACpD;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAsB,YAAO,YAAY;AAC3C,YAAM,IAAI,MAAM,6BAA6B;AAAA,IAC/C;AACA,QAAI,iBAAsB,YAAO,0BAA0B;AACzD,YAAM,IAAI,MAAM,8BAA8B,MAAM,OAAO,EAAE;AAAA,IAC/D;AACA,QAAI,iBAAsB,YAAO,gCAAgC;AAC/D,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AACA,UAAM;AAAA,EACR;AACF;;;ACgHA,oBAAmB;AAgBZ,SAAS,qBACd,SACA,WACA,QACS;AACT,QAAM,oBAAoB,cAAAA,QACvB,WAAW,UAAU,MAAM,EAC3B,OAAO,OAAO,EACd,OAAO,KAAK;AAEf,SAAO,cAAAA,QAAO;AAAA,IACZ,OAAO,KAAK,SAAS;AAAA,IACrB,OAAO,KAAK,iBAAiB;AAAA,EAC/B;AACF;","names":["crypto"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/receipt.ts","../src/charges.ts"],"sourcesContent":["/**\n * MixrPay Merchant SDK\n * \n * Accept payments from AI agents and web apps with one middleware.\n * \n * @example Express\n * ```typescript\n * import { mixrpay } from '@mixrpay/merchant-sdk/express';\n * \n * app.post('/api/query', mixrpay({ priceUsd: 0.05 }), (req, res) => {\n * console.log(`Paid by ${req.mixrPayment?.payer}`);\n * res.json({ result: 'success' });\n * });\n * ```\n * \n * @example Next.js\n * ```typescript\n * import { withMixrPay } from '@mixrpay/merchant-sdk/nextjs';\n * \n * export const POST = withMixrPay({ priceUsd: 0.05 }, async (req, payment) => {\n * return NextResponse.json({ result: 'success' });\n * });\n * ```\n * \n * @packageDocumentation\n */\n\n// Receipt verification (for custom integrations)\nexport { verifyPaymentReceipt } from './receipt';\n\n// Session webhook verification\nexport { verifySessionWebhook } from './charges';\n\n// Types for TypeScript users\nexport type {\n MixrPayOptions,\n MixrPayPaymentResult,\n PaymentMethod,\n PriceContext,\n PaymentReceipt,\n} from './types';\n\nexport type {\n SessionGrant,\n} from './charges';\n\n// Widget element types (for TypeScript JSX support)\nexport type {} from './widget-elements';\n","/**\n * MixrPay Merchant SDK - Payment Receipt Verification\n * \n * Verify JWT payment receipts issued by MixrPay after successful x402 payments.\n * \n * @example\n * ```typescript\n * import { verifyPaymentReceipt } from '@mixrpay/merchant-sdk';\n * \n * const receipt = req.headers['x-payment-receipt'];\n * const payment = await verifyPaymentReceipt(receipt);\n * \n * console.log(`Payment received: $${payment.amountUsd} from ${payment.payer}`);\n * console.log(`Transaction: ${payment.txHash}`);\n * ```\n */\n\nimport * as jose from 'jose';\nimport type { PaymentReceipt, VerifyReceiptOptions } from './types';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || 'https://www.mixrpay.com/.well-known/jwks';\nconst JWKS_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\n\n// =============================================================================\n// JWKS Cache\n// =============================================================================\n\ninterface CachedJWKS {\n jwks: jose.JSONWebKeySet;\n fetchedAt: number;\n}\n\nconst jwksCache = new Map<string, CachedJWKS>();\n\n/**\n * Fetch and cache JWKS from the specified URL.\n */\nasync function getJWKS(jwksUrl: string): Promise<jose.JSONWebKeySet> {\n const cached = jwksCache.get(jwksUrl);\n const now = Date.now();\n\n // Return cached JWKS if still valid\n if (cached && (now - cached.fetchedAt) < JWKS_CACHE_TTL_MS) {\n return cached.jwks;\n }\n\n // Fetch fresh JWKS\n const response = await fetch(jwksUrl);\n \n if (!response.ok) {\n throw new Error(`Failed to fetch JWKS from ${jwksUrl}: ${response.status} ${response.statusText}`);\n }\n\n const jwks = await response.json() as jose.JSONWebKeySet;\n\n // Validate JWKS structure\n if (!jwks.keys || !Array.isArray(jwks.keys) || jwks.keys.length === 0) {\n throw new Error('Invalid JWKS: missing or empty keys array');\n }\n\n // Cache the JWKS\n jwksCache.set(jwksUrl, { jwks, fetchedAt: now });\n\n return jwks;\n}\n\n/**\n * Create a JWKS key set from a fetched JWKS.\n */\nasync function createKeySet(jwksUrl: string): Promise<jose.JWTVerifyGetKey> {\n const jwks = await getJWKS(jwksUrl);\n return jose.createLocalJWKSet(jwks);\n}\n\n// =============================================================================\n// Receipt Verification\n// =============================================================================\n\n/**\n * Verify a JWT payment receipt from MixrPay.\n * \n * This function:\n * 1. Fetches the JWKS from MixrPay (cached for 1 hour)\n * 2. Verifies the JWT signature using RS256\n * 3. Validates standard JWT claims (exp, iat)\n * 4. Returns the typed payment receipt\n * \n * @param receipt - The JWT receipt string from X-Payment-Receipt header\n * @param options - Optional configuration (custom JWKS URL, issuer validation)\n * @returns Verified payment receipt\n * @throws Error if verification fails\n * \n * @example\n * ```typescript\n * // Basic usage\n * const payment = await verifyPaymentReceipt(receipt);\n * \n * // With custom JWKS URL (for testing or self-hosted)\n * const payment = await verifyPaymentReceipt(receipt, {\n * jwksUrl: 'https://your-mixrpay.com/.well-known/jwks'\n * });\n * ```\n */\nexport async function verifyPaymentReceipt(\n receipt: string,\n options?: VerifyReceiptOptions\n): Promise<PaymentReceipt> {\n const jwksUrl = options?.jwksUrl || DEFAULT_JWKS_URL;\n\n try {\n // Get the key set\n const keySet = await createKeySet(jwksUrl);\n\n // Build verification options\n const verifyOptions: jose.JWTVerifyOptions = {\n algorithms: ['RS256'],\n };\n\n // Add issuer validation if specified\n if (options?.issuer) {\n verifyOptions.issuer = options.issuer;\n }\n\n // Verify the JWT\n const { payload } = await jose.jwtVerify(receipt, keySet, verifyOptions);\n\n // Validate required payment fields\n const requiredFields = ['paymentId', 'amount', 'amountUsd', 'payer', 'recipient', 'chainId', 'txHash', 'settledAt'];\n for (const field of requiredFields) {\n if (!(field in payload)) {\n throw new Error(`Missing required field in receipt: ${field}`);\n }\n }\n\n // Return typed receipt\n return {\n paymentId: payload.paymentId as string,\n amount: payload.amount as string,\n amountUsd: payload.amountUsd as number,\n payer: payload.payer as string,\n recipient: payload.recipient as string,\n chainId: payload.chainId as number,\n txHash: payload.txHash as string,\n settledAt: payload.settledAt as string,\n issuedAt: new Date((payload.iat as number) * 1000),\n expiresAt: new Date((payload.exp as number) * 1000),\n };\n } catch (error) {\n if (error instanceof jose.errors.JWTExpired) {\n throw new Error('Payment receipt has expired');\n }\n if (error instanceof jose.errors.JWTClaimValidationFailed) {\n throw new Error(`Receipt validation failed: ${error.message}`);\n }\n if (error instanceof jose.errors.JWSSignatureVerificationFailed) {\n throw new Error('Invalid receipt signature');\n }\n throw error;\n }\n}\n\n/**\n * Parse a JWT payment receipt without verification.\n * \n * WARNING: This does NOT verify the signature. Use only for debugging\n * or when you've already verified the receipt elsewhere.\n * \n * @param receipt - The JWT receipt string\n * @returns Decoded payload (unverified)\n */\nexport function parsePaymentReceipt(receipt: string): PaymentReceipt {\n const decoded = jose.decodeJwt(receipt);\n\n return {\n paymentId: decoded.paymentId as string,\n amount: decoded.amount as string,\n amountUsd: decoded.amountUsd as number,\n payer: decoded.payer as string,\n recipient: decoded.recipient as string,\n chainId: decoded.chainId as number,\n txHash: decoded.txHash as string,\n settledAt: decoded.settledAt as string,\n issuedAt: decoded.iat ? new Date(decoded.iat * 1000) : undefined,\n expiresAt: decoded.exp ? new Date(decoded.exp * 1000) : undefined,\n };\n}\n\n/**\n * Check if a receipt is expired.\n * \n * @param receipt - The JWT receipt string or parsed PaymentReceipt\n * @returns true if expired, false otherwise\n */\nexport function isReceiptExpired(receipt: string | PaymentReceipt): boolean {\n const parsed = typeof receipt === 'string' ? parsePaymentReceipt(receipt) : receipt;\n \n if (!parsed.expiresAt) {\n return false; // No expiration = never expires\n }\n\n return parsed.expiresAt.getTime() < Date.now();\n}\n\n/**\n * Clear the JWKS cache.\n * Useful for testing or when keys have rotated.\n */\nexport function clearJWKSCache(): void {\n jwksCache.clear();\n}\n\n/**\n * Get the default JWKS URL.\n */\nexport function getDefaultJWKSUrl(): string {\n return DEFAULT_JWKS_URL;\n}\n\n","/**\n * MixrPay Charges Module\n * \n * Create and manage charges using session signers.\n * Session signers allow you to charge user wallets without\n * requiring approval for each transaction.\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface CreateChargeParams {\n /** Session key ID granted by the user */\n sessionId: string;\n /** Amount to charge in USD (e.g., 0.05 for 5 cents) */\n amountUsd: number;\n /** Unique reference for idempotency (e.g., \"order_123\") */\n reference: string;\n /** Optional metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface Charge {\n /** Unique charge ID */\n id: string;\n /** Current status */\n status: 'pending' | 'submitted' | 'confirmed' | 'failed';\n /** Amount in USD */\n amountUsd: number;\n /** Formatted USDC amount */\n amountUsdc: string;\n /** Transaction hash (if submitted) */\n txHash?: string;\n /** Block explorer URL */\n explorerUrl?: string;\n /** From wallet address */\n fromAddress: string;\n /** To wallet address */\n toAddress: string;\n /** Your reference */\n reference: string;\n /** When the charge was created */\n createdAt: string;\n /** When the charge was confirmed */\n confirmedAt?: string;\n /** Session name */\n sessionName?: string;\n /** User wallet address */\n userWallet?: string;\n /** Error message if failed */\n error?: string;\n}\n\nexport interface CreateChargeResult {\n success: boolean;\n charge?: Charge;\n /** True if this is a replay of an existing charge */\n idempotentReplay?: boolean;\n error?: string;\n details?: string;\n}\n\nexport interface SessionGrant {\n /** Session key ID */\n sessionId: string;\n /** User's wallet address */\n userWallet: string;\n /** Your merchant wallet address */\n merchantWallet: string;\n /** Spending limits */\n limits: {\n maxTotalUsd?: number;\n maxPerTxUsd?: number;\n expiresAt: string;\n };\n /** When the session was granted */\n grantedAt: string;\n}\n\n// Internal types for API responses\ninterface CreateChargeApiResponse {\n success: boolean;\n charge_id?: string;\n status?: string;\n amount_usd?: number;\n amount_usdc?: string;\n tx_hash?: string;\n explorer_url?: string;\n idempotent_replay?: boolean;\n error?: string;\n details?: string;\n}\n\ninterface ChargeApiResponse {\n id: string;\n status: string;\n amount_usd: number;\n amount_usdc: string;\n tx_hash?: string;\n explorer_url?: string;\n from_address: string;\n to_address: string;\n reference: string;\n created_at: string;\n confirmed_at?: string;\n session_name?: string;\n user_wallet?: string;\n}\n\nexport interface ChargesClientOptions {\n /** Your API key */\n apiKey: string;\n /** MixrPay API base URL */\n baseUrl?: string;\n}\n\n// =============================================================================\n// Client\n// =============================================================================\n\nexport class ChargesClient {\n private apiKey: string;\n private baseUrl: string;\n\n constructor(options: ChargesClientOptions) {\n this.apiKey = options.apiKey;\n this.baseUrl = options.baseUrl || process.env.MIXRPAY_BASE_URL || 'https://www.mixrpay.com';\n }\n\n /**\n * Create a new charge.\n * \n * @example\n * ```typescript\n * const result = await charges.create({\n * sessionId: 'sk_xxx',\n * amountUsd: 0.05,\n * reference: 'image_gen_123',\n * metadata: { feature: 'image_generation' }\n * });\n * \n * if (result.success) {\n * console.log(`Charged ${result.charge.amountUsdc}`);\n * console.log(`TX: ${result.charge.explorerUrl}`);\n * }\n * ```\n */\n async create(params: CreateChargeParams): Promise<CreateChargeResult> {\n const response = await fetch(`${this.baseUrl}/api/v1/charges`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({\n session_id: params.sessionId,\n amount_usd: params.amountUsd,\n reference: params.reference,\n metadata: params.metadata,\n }),\n });\n\n const data = await response.json() as CreateChargeApiResponse;\n\n if (!response.ok) {\n return {\n success: false,\n error: data.error,\n details: data.details,\n };\n }\n\n return {\n success: data.success,\n charge: data.charge_id ? {\n id: data.charge_id,\n status: data.status as Charge['status'],\n amountUsd: data.amount_usd ?? 0,\n amountUsdc: data.amount_usdc ?? '0',\n txHash: data.tx_hash,\n explorerUrl: data.explorer_url,\n fromAddress: '',\n toAddress: '',\n reference: params.reference,\n createdAt: new Date().toISOString(),\n } : undefined,\n idempotentReplay: data.idempotent_replay,\n error: data.error,\n };\n }\n\n /**\n * Get a charge by ID.\n * \n * @example\n * ```typescript\n * const charge = await charges.get('chg_xxx');\n * console.log(charge.status); // 'confirmed'\n * ```\n */\n async get(chargeId: string): Promise<Charge | null> {\n const response = await fetch(`${this.baseUrl}/api/v1/charges?id=${chargeId}`, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`Failed to get charge: ${response.statusText}`);\n }\n\n const data = await response.json() as ChargeApiResponse;\n\n return {\n id: data.id,\n status: data.status as Charge['status'],\n amountUsd: data.amount_usd,\n amountUsdc: data.amount_usdc,\n txHash: data.tx_hash,\n explorerUrl: data.explorer_url,\n fromAddress: data.from_address,\n toAddress: data.to_address,\n reference: data.reference,\n createdAt: data.created_at,\n confirmedAt: data.confirmed_at,\n sessionName: data.session_name,\n userWallet: data.user_wallet,\n };\n }\n\n /**\n * Get a charge by transaction hash.\n */\n async getByTxHash(txHash: string): Promise<Charge | null> {\n const response = await fetch(`${this.baseUrl}/api/v1/charges?tx_hash=${txHash}`, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`Failed to get charge: ${response.statusText}`);\n }\n\n const data = await response.json() as ChargeApiResponse;\n\n return {\n id: data.id,\n status: data.status as Charge['status'],\n amountUsd: data.amount_usd,\n amountUsdc: data.amount_usdc,\n txHash: data.tx_hash,\n explorerUrl: data.explorer_url,\n fromAddress: data.from_address,\n toAddress: data.to_address,\n reference: data.reference,\n createdAt: data.created_at,\n confirmedAt: data.confirmed_at,\n sessionName: data.session_name,\n userWallet: data.user_wallet,\n };\n }\n}\n\n// =============================================================================\n// Webhook Verification\n// =============================================================================\n\nimport crypto from 'crypto';\n\n/**\n * Verify a session.granted webhook signature.\n * \n * @example\n * ```typescript\n * const payload = req.body;\n * const signature = req.headers['x-mixrpay-signature'];\n * \n * if (verifySessionWebhook(JSON.stringify(payload), signature, webhookSecret)) {\n * const grant = parseSessionGrant(payload);\n * console.log(`User ${grant.userWallet} granted access`);\n * }\n * ```\n */\nexport function verifySessionWebhook(\n payload: string,\n signature: string,\n secret: string\n): boolean {\n const expectedSignature = crypto\n .createHmac('sha256', secret)\n .update(payload)\n .digest('hex');\n \n return crypto.timingSafeEqual(\n Buffer.from(signature),\n Buffer.from(expectedSignature)\n );\n}\n\n/**\n * Parse a session.granted webhook payload.\n */\nexport function parseSessionGrant(payload: {\n event: string;\n session_id: string;\n user_wallet: string;\n merchant_wallet: string;\n limits: {\n max_total_usd?: number;\n max_per_tx_usd?: number;\n expires_at: string;\n };\n granted_at: string;\n}): SessionGrant {\n if (payload.event !== 'session.granted') {\n throw new Error(`Unexpected event type: ${payload.event}`);\n }\n\n return {\n sessionId: payload.session_id,\n userWallet: payload.user_wallet,\n merchantWallet: payload.merchant_wallet,\n limits: {\n maxTotalUsd: payload.limits.max_total_usd,\n maxPerTxUsd: payload.limits.max_per_tx_usd,\n expiresAt: payload.limits.expires_at,\n },\n grantedAt: payload.granted_at,\n };\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACiBA,WAAsB;AAOtB,IAAM,mBAAmB,QAAQ,IAAI,oBAAoB;AACzD,IAAM,oBAAoB,KAAK,KAAK;AAWpC,IAAM,YAAY,oBAAI,IAAwB;AAK9C,eAAe,QAAQ,SAA8C;AACnE,QAAM,SAAS,UAAU,IAAI,OAAO;AACpC,QAAM,MAAM,KAAK,IAAI;AAGrB,MAAI,UAAW,MAAM,OAAO,YAAa,mBAAmB;AAC1D,WAAO,OAAO;AAAA,EAChB;AAGA,QAAM,WAAW,MAAM,MAAM,OAAO;AAEpC,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,6BAA6B,OAAO,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,EACnG;AAEA,QAAM,OAAO,MAAM,SAAS,KAAK;AAGjC,MAAI,CAAC,KAAK,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAAI,KAAK,KAAK,KAAK,WAAW,GAAG;AACrE,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AAGA,YAAU,IAAI,SAAS,EAAE,MAAM,WAAW,IAAI,CAAC;AAE/C,SAAO;AACT;AAKA,eAAe,aAAa,SAAgD;AAC1E,QAAM,OAAO,MAAM,QAAQ,OAAO;AAClC,SAAY,uBAAkB,IAAI;AACpC;AA+BA,eAAsB,qBACpB,SACA,SACyB;AACzB,QAAM,UAAU,SAAS,WAAW;AAEpC,MAAI;AAEF,UAAM,SAAS,MAAM,aAAa,OAAO;AAGzC,UAAM,gBAAuC;AAAA,MAC3C,YAAY,CAAC,OAAO;AAAA,IACtB;AAGA,QAAI,SAAS,QAAQ;AACnB,oBAAc,SAAS,QAAQ;AAAA,IACjC;AAGA,UAAM,EAAE,QAAQ,IAAI,MAAW,eAAU,SAAS,QAAQ,aAAa;AAGvE,UAAM,iBAAiB,CAAC,aAAa,UAAU,aAAa,SAAS,aAAa,WAAW,UAAU,WAAW;AAClH,eAAW,SAAS,gBAAgB;AAClC,UAAI,EAAE,SAAS,UAAU;AACvB,cAAM,IAAI,MAAM,sCAAsC,KAAK,EAAE;AAAA,MAC/D;AAAA,IACF;AAGA,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,MACnB,OAAO,QAAQ;AAAA,MACf,WAAW,QAAQ;AAAA,MACnB,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,MACnB,UAAU,IAAI,KAAM,QAAQ,MAAiB,GAAI;AAAA,MACjD,WAAW,IAAI,KAAM,QAAQ,MAAiB,GAAI;AAAA,IACpD;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAsB,YAAO,YAAY;AAC3C,YAAM,IAAI,MAAM,6BAA6B;AAAA,IAC/C;AACA,QAAI,iBAAsB,YAAO,0BAA0B;AACzD,YAAM,IAAI,MAAM,8BAA8B,MAAM,OAAO,EAAE;AAAA,IAC/D;AACA,QAAI,iBAAsB,YAAO,gCAAgC;AAC/D,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AACA,UAAM;AAAA,EACR;AACF;;;ACgHA,oBAAmB;AAgBZ,SAAS,qBACd,SACA,WACA,QACS;AACT,QAAM,oBAAoB,cAAAA,QACvB,WAAW,UAAU,MAAM,EAC3B,OAAO,OAAO,EACd,OAAO,KAAK;AAEf,SAAO,cAAAA,QAAO;AAAA,IACZ,OAAO,KAAK,SAAS;AAAA,IACrB,OAAO,KAAK,iBAAiB;AAAA,EAC/B;AACF;","names":["crypto"]}
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/receipt.ts
2
2
  import * as jose from "jose";
3
- var DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || "https://mixrpay.com/.well-known/jwks";
3
+ var DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || "https://www.mixrpay.com/.well-known/jwks";
4
4
  var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
5
5
  var jwksCache = /* @__PURE__ */ new Map();
6
6
  async function getJWKS(jwksUrl) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/receipt.ts","../src/charges.ts"],"sourcesContent":["/**\n * MixrPay Merchant SDK - Payment Receipt Verification\n * \n * Verify JWT payment receipts issued by MixrPay after successful x402 payments.\n * \n * @example\n * ```typescript\n * import { verifyPaymentReceipt } from '@mixrpay/merchant-sdk';\n * \n * const receipt = req.headers['x-payment-receipt'];\n * const payment = await verifyPaymentReceipt(receipt);\n * \n * console.log(`Payment received: $${payment.amountUsd} from ${payment.payer}`);\n * console.log(`Transaction: ${payment.txHash}`);\n * ```\n */\n\nimport * as jose from 'jose';\nimport type { PaymentReceipt, VerifyReceiptOptions } from './types';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || 'https://mixrpay.com/.well-known/jwks';\nconst JWKS_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\n\n// =============================================================================\n// JWKS Cache\n// =============================================================================\n\ninterface CachedJWKS {\n jwks: jose.JSONWebKeySet;\n fetchedAt: number;\n}\n\nconst jwksCache = new Map<string, CachedJWKS>();\n\n/**\n * Fetch and cache JWKS from the specified URL.\n */\nasync function getJWKS(jwksUrl: string): Promise<jose.JSONWebKeySet> {\n const cached = jwksCache.get(jwksUrl);\n const now = Date.now();\n\n // Return cached JWKS if still valid\n if (cached && (now - cached.fetchedAt) < JWKS_CACHE_TTL_MS) {\n return cached.jwks;\n }\n\n // Fetch fresh JWKS\n const response = await fetch(jwksUrl);\n \n if (!response.ok) {\n throw new Error(`Failed to fetch JWKS from ${jwksUrl}: ${response.status} ${response.statusText}`);\n }\n\n const jwks = await response.json() as jose.JSONWebKeySet;\n\n // Validate JWKS structure\n if (!jwks.keys || !Array.isArray(jwks.keys) || jwks.keys.length === 0) {\n throw new Error('Invalid JWKS: missing or empty keys array');\n }\n\n // Cache the JWKS\n jwksCache.set(jwksUrl, { jwks, fetchedAt: now });\n\n return jwks;\n}\n\n/**\n * Create a JWKS key set from a fetched JWKS.\n */\nasync function createKeySet(jwksUrl: string): Promise<jose.JWTVerifyGetKey> {\n const jwks = await getJWKS(jwksUrl);\n return jose.createLocalJWKSet(jwks);\n}\n\n// =============================================================================\n// Receipt Verification\n// =============================================================================\n\n/**\n * Verify a JWT payment receipt from MixrPay.\n * \n * This function:\n * 1. Fetches the JWKS from MixrPay (cached for 1 hour)\n * 2. Verifies the JWT signature using RS256\n * 3. Validates standard JWT claims (exp, iat)\n * 4. Returns the typed payment receipt\n * \n * @param receipt - The JWT receipt string from X-Payment-Receipt header\n * @param options - Optional configuration (custom JWKS URL, issuer validation)\n * @returns Verified payment receipt\n * @throws Error if verification fails\n * \n * @example\n * ```typescript\n * // Basic usage\n * const payment = await verifyPaymentReceipt(receipt);\n * \n * // With custom JWKS URL (for testing or self-hosted)\n * const payment = await verifyPaymentReceipt(receipt, {\n * jwksUrl: 'https://your-mixrpay.com/.well-known/jwks'\n * });\n * ```\n */\nexport async function verifyPaymentReceipt(\n receipt: string,\n options?: VerifyReceiptOptions\n): Promise<PaymentReceipt> {\n const jwksUrl = options?.jwksUrl || DEFAULT_JWKS_URL;\n\n try {\n // Get the key set\n const keySet = await createKeySet(jwksUrl);\n\n // Build verification options\n const verifyOptions: jose.JWTVerifyOptions = {\n algorithms: ['RS256'],\n };\n\n // Add issuer validation if specified\n if (options?.issuer) {\n verifyOptions.issuer = options.issuer;\n }\n\n // Verify the JWT\n const { payload } = await jose.jwtVerify(receipt, keySet, verifyOptions);\n\n // Validate required payment fields\n const requiredFields = ['paymentId', 'amount', 'amountUsd', 'payer', 'recipient', 'chainId', 'txHash', 'settledAt'];\n for (const field of requiredFields) {\n if (!(field in payload)) {\n throw new Error(`Missing required field in receipt: ${field}`);\n }\n }\n\n // Return typed receipt\n return {\n paymentId: payload.paymentId as string,\n amount: payload.amount as string,\n amountUsd: payload.amountUsd as number,\n payer: payload.payer as string,\n recipient: payload.recipient as string,\n chainId: payload.chainId as number,\n txHash: payload.txHash as string,\n settledAt: payload.settledAt as string,\n issuedAt: new Date((payload.iat as number) * 1000),\n expiresAt: new Date((payload.exp as number) * 1000),\n };\n } catch (error) {\n if (error instanceof jose.errors.JWTExpired) {\n throw new Error('Payment receipt has expired');\n }\n if (error instanceof jose.errors.JWTClaimValidationFailed) {\n throw new Error(`Receipt validation failed: ${error.message}`);\n }\n if (error instanceof jose.errors.JWSSignatureVerificationFailed) {\n throw new Error('Invalid receipt signature');\n }\n throw error;\n }\n}\n\n/**\n * Parse a JWT payment receipt without verification.\n * \n * WARNING: This does NOT verify the signature. Use only for debugging\n * or when you've already verified the receipt elsewhere.\n * \n * @param receipt - The JWT receipt string\n * @returns Decoded payload (unverified)\n */\nexport function parsePaymentReceipt(receipt: string): PaymentReceipt {\n const decoded = jose.decodeJwt(receipt);\n\n return {\n paymentId: decoded.paymentId as string,\n amount: decoded.amount as string,\n amountUsd: decoded.amountUsd as number,\n payer: decoded.payer as string,\n recipient: decoded.recipient as string,\n chainId: decoded.chainId as number,\n txHash: decoded.txHash as string,\n settledAt: decoded.settledAt as string,\n issuedAt: decoded.iat ? new Date(decoded.iat * 1000) : undefined,\n expiresAt: decoded.exp ? new Date(decoded.exp * 1000) : undefined,\n };\n}\n\n/**\n * Check if a receipt is expired.\n * \n * @param receipt - The JWT receipt string or parsed PaymentReceipt\n * @returns true if expired, false otherwise\n */\nexport function isReceiptExpired(receipt: string | PaymentReceipt): boolean {\n const parsed = typeof receipt === 'string' ? parsePaymentReceipt(receipt) : receipt;\n \n if (!parsed.expiresAt) {\n return false; // No expiration = never expires\n }\n\n return parsed.expiresAt.getTime() < Date.now();\n}\n\n/**\n * Clear the JWKS cache.\n * Useful for testing or when keys have rotated.\n */\nexport function clearJWKSCache(): void {\n jwksCache.clear();\n}\n\n/**\n * Get the default JWKS URL.\n */\nexport function getDefaultJWKSUrl(): string {\n return DEFAULT_JWKS_URL;\n}\n\n","/**\n * MixrPay Charges Module\n * \n * Create and manage charges using session signers.\n * Session signers allow you to charge user wallets without\n * requiring approval for each transaction.\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface CreateChargeParams {\n /** Session key ID granted by the user */\n sessionId: string;\n /** Amount to charge in USD (e.g., 0.05 for 5 cents) */\n amountUsd: number;\n /** Unique reference for idempotency (e.g., \"order_123\") */\n reference: string;\n /** Optional metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface Charge {\n /** Unique charge ID */\n id: string;\n /** Current status */\n status: 'pending' | 'submitted' | 'confirmed' | 'failed';\n /** Amount in USD */\n amountUsd: number;\n /** Formatted USDC amount */\n amountUsdc: string;\n /** Transaction hash (if submitted) */\n txHash?: string;\n /** Block explorer URL */\n explorerUrl?: string;\n /** From wallet address */\n fromAddress: string;\n /** To wallet address */\n toAddress: string;\n /** Your reference */\n reference: string;\n /** When the charge was created */\n createdAt: string;\n /** When the charge was confirmed */\n confirmedAt?: string;\n /** Session name */\n sessionName?: string;\n /** User wallet address */\n userWallet?: string;\n /** Error message if failed */\n error?: string;\n}\n\nexport interface CreateChargeResult {\n success: boolean;\n charge?: Charge;\n /** True if this is a replay of an existing charge */\n idempotentReplay?: boolean;\n error?: string;\n details?: string;\n}\n\nexport interface SessionGrant {\n /** Session key ID */\n sessionId: string;\n /** User's wallet address */\n userWallet: string;\n /** Your merchant wallet address */\n merchantWallet: string;\n /** Spending limits */\n limits: {\n maxTotalUsd?: number;\n maxPerTxUsd?: number;\n expiresAt: string;\n };\n /** When the session was granted */\n grantedAt: string;\n}\n\n// Internal types for API responses\ninterface CreateChargeApiResponse {\n success: boolean;\n charge_id?: string;\n status?: string;\n amount_usd?: number;\n amount_usdc?: string;\n tx_hash?: string;\n explorer_url?: string;\n idempotent_replay?: boolean;\n error?: string;\n details?: string;\n}\n\ninterface ChargeApiResponse {\n id: string;\n status: string;\n amount_usd: number;\n amount_usdc: string;\n tx_hash?: string;\n explorer_url?: string;\n from_address: string;\n to_address: string;\n reference: string;\n created_at: string;\n confirmed_at?: string;\n session_name?: string;\n user_wallet?: string;\n}\n\nexport interface ChargesClientOptions {\n /** Your API key */\n apiKey: string;\n /** MixrPay API base URL */\n baseUrl?: string;\n}\n\n// =============================================================================\n// Client\n// =============================================================================\n\nexport class ChargesClient {\n private apiKey: string;\n private baseUrl: string;\n\n constructor(options: ChargesClientOptions) {\n this.apiKey = options.apiKey;\n this.baseUrl = options.baseUrl || process.env.MIXRPAY_BASE_URL || 'https://mixrpay.com';\n }\n\n /**\n * Create a new charge.\n * \n * @example\n * ```typescript\n * const result = await charges.create({\n * sessionId: 'sk_xxx',\n * amountUsd: 0.05,\n * reference: 'image_gen_123',\n * metadata: { feature: 'image_generation' }\n * });\n * \n * if (result.success) {\n * console.log(`Charged ${result.charge.amountUsdc}`);\n * console.log(`TX: ${result.charge.explorerUrl}`);\n * }\n * ```\n */\n async create(params: CreateChargeParams): Promise<CreateChargeResult> {\n const response = await fetch(`${this.baseUrl}/api/v1/charges`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({\n session_id: params.sessionId,\n amount_usd: params.amountUsd,\n reference: params.reference,\n metadata: params.metadata,\n }),\n });\n\n const data = await response.json() as CreateChargeApiResponse;\n\n if (!response.ok) {\n return {\n success: false,\n error: data.error,\n details: data.details,\n };\n }\n\n return {\n success: data.success,\n charge: data.charge_id ? {\n id: data.charge_id,\n status: data.status as Charge['status'],\n amountUsd: data.amount_usd ?? 0,\n amountUsdc: data.amount_usdc ?? '0',\n txHash: data.tx_hash,\n explorerUrl: data.explorer_url,\n fromAddress: '',\n toAddress: '',\n reference: params.reference,\n createdAt: new Date().toISOString(),\n } : undefined,\n idempotentReplay: data.idempotent_replay,\n error: data.error,\n };\n }\n\n /**\n * Get a charge by ID.\n * \n * @example\n * ```typescript\n * const charge = await charges.get('chg_xxx');\n * console.log(charge.status); // 'confirmed'\n * ```\n */\n async get(chargeId: string): Promise<Charge | null> {\n const response = await fetch(`${this.baseUrl}/api/v1/charges?id=${chargeId}`, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`Failed to get charge: ${response.statusText}`);\n }\n\n const data = await response.json() as ChargeApiResponse;\n\n return {\n id: data.id,\n status: data.status as Charge['status'],\n amountUsd: data.amount_usd,\n amountUsdc: data.amount_usdc,\n txHash: data.tx_hash,\n explorerUrl: data.explorer_url,\n fromAddress: data.from_address,\n toAddress: data.to_address,\n reference: data.reference,\n createdAt: data.created_at,\n confirmedAt: data.confirmed_at,\n sessionName: data.session_name,\n userWallet: data.user_wallet,\n };\n }\n\n /**\n * Get a charge by transaction hash.\n */\n async getByTxHash(txHash: string): Promise<Charge | null> {\n const response = await fetch(`${this.baseUrl}/api/v1/charges?tx_hash=${txHash}`, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`Failed to get charge: ${response.statusText}`);\n }\n\n const data = await response.json() as ChargeApiResponse;\n\n return {\n id: data.id,\n status: data.status as Charge['status'],\n amountUsd: data.amount_usd,\n amountUsdc: data.amount_usdc,\n txHash: data.tx_hash,\n explorerUrl: data.explorer_url,\n fromAddress: data.from_address,\n toAddress: data.to_address,\n reference: data.reference,\n createdAt: data.created_at,\n confirmedAt: data.confirmed_at,\n sessionName: data.session_name,\n userWallet: data.user_wallet,\n };\n }\n}\n\n// =============================================================================\n// Webhook Verification\n// =============================================================================\n\nimport crypto from 'crypto';\n\n/**\n * Verify a session.granted webhook signature.\n * \n * @example\n * ```typescript\n * const payload = req.body;\n * const signature = req.headers['x-mixrpay-signature'];\n * \n * if (verifySessionWebhook(JSON.stringify(payload), signature, webhookSecret)) {\n * const grant = parseSessionGrant(payload);\n * console.log(`User ${grant.userWallet} granted access`);\n * }\n * ```\n */\nexport function verifySessionWebhook(\n payload: string,\n signature: string,\n secret: string\n): boolean {\n const expectedSignature = crypto\n .createHmac('sha256', secret)\n .update(payload)\n .digest('hex');\n \n return crypto.timingSafeEqual(\n Buffer.from(signature),\n Buffer.from(expectedSignature)\n );\n}\n\n/**\n * Parse a session.granted webhook payload.\n */\nexport function parseSessionGrant(payload: {\n event: string;\n session_id: string;\n user_wallet: string;\n merchant_wallet: string;\n limits: {\n max_total_usd?: number;\n max_per_tx_usd?: number;\n expires_at: string;\n };\n granted_at: string;\n}): SessionGrant {\n if (payload.event !== 'session.granted') {\n throw new Error(`Unexpected event type: ${payload.event}`);\n }\n\n return {\n sessionId: payload.session_id,\n userWallet: payload.user_wallet,\n merchantWallet: payload.merchant_wallet,\n limits: {\n maxTotalUsd: payload.limits.max_total_usd,\n maxPerTxUsd: payload.limits.max_per_tx_usd,\n expiresAt: payload.limits.expires_at,\n },\n grantedAt: payload.granted_at,\n };\n}\n\n"],"mappings":";AAiBA,YAAY,UAAU;AAOtB,IAAM,mBAAmB,QAAQ,IAAI,oBAAoB;AACzD,IAAM,oBAAoB,KAAK,KAAK;AAWpC,IAAM,YAAY,oBAAI,IAAwB;AAK9C,eAAe,QAAQ,SAA8C;AACnE,QAAM,SAAS,UAAU,IAAI,OAAO;AACpC,QAAM,MAAM,KAAK,IAAI;AAGrB,MAAI,UAAW,MAAM,OAAO,YAAa,mBAAmB;AAC1D,WAAO,OAAO;AAAA,EAChB;AAGA,QAAM,WAAW,MAAM,MAAM,OAAO;AAEpC,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,6BAA6B,OAAO,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,EACnG;AAEA,QAAM,OAAO,MAAM,SAAS,KAAK;AAGjC,MAAI,CAAC,KAAK,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAAI,KAAK,KAAK,KAAK,WAAW,GAAG;AACrE,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AAGA,YAAU,IAAI,SAAS,EAAE,MAAM,WAAW,IAAI,CAAC;AAE/C,SAAO;AACT;AAKA,eAAe,aAAa,SAAgD;AAC1E,QAAM,OAAO,MAAM,QAAQ,OAAO;AAClC,SAAY,uBAAkB,IAAI;AACpC;AA+BA,eAAsB,qBACpB,SACA,SACyB;AACzB,QAAM,UAAU,SAAS,WAAW;AAEpC,MAAI;AAEF,UAAM,SAAS,MAAM,aAAa,OAAO;AAGzC,UAAM,gBAAuC;AAAA,MAC3C,YAAY,CAAC,OAAO;AAAA,IACtB;AAGA,QAAI,SAAS,QAAQ;AACnB,oBAAc,SAAS,QAAQ;AAAA,IACjC;AAGA,UAAM,EAAE,QAAQ,IAAI,MAAW,eAAU,SAAS,QAAQ,aAAa;AAGvE,UAAM,iBAAiB,CAAC,aAAa,UAAU,aAAa,SAAS,aAAa,WAAW,UAAU,WAAW;AAClH,eAAW,SAAS,gBAAgB;AAClC,UAAI,EAAE,SAAS,UAAU;AACvB,cAAM,IAAI,MAAM,sCAAsC,KAAK,EAAE;AAAA,MAC/D;AAAA,IACF;AAGA,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,MACnB,OAAO,QAAQ;AAAA,MACf,WAAW,QAAQ;AAAA,MACnB,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,MACnB,UAAU,IAAI,KAAM,QAAQ,MAAiB,GAAI;AAAA,MACjD,WAAW,IAAI,KAAM,QAAQ,MAAiB,GAAI;AAAA,IACpD;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAsB,YAAO,YAAY;AAC3C,YAAM,IAAI,MAAM,6BAA6B;AAAA,IAC/C;AACA,QAAI,iBAAsB,YAAO,0BAA0B;AACzD,YAAM,IAAI,MAAM,8BAA8B,MAAM,OAAO,EAAE;AAAA,IAC/D;AACA,QAAI,iBAAsB,YAAO,gCAAgC;AAC/D,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AACA,UAAM;AAAA,EACR;AACF;;;ACgHA,OAAO,YAAY;AAgBZ,SAAS,qBACd,SACA,WACA,QACS;AACT,QAAM,oBAAoB,OACvB,WAAW,UAAU,MAAM,EAC3B,OAAO,OAAO,EACd,OAAO,KAAK;AAEf,SAAO,OAAO;AAAA,IACZ,OAAO,KAAK,SAAS;AAAA,IACrB,OAAO,KAAK,iBAAiB;AAAA,EAC/B;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/receipt.ts","../src/charges.ts"],"sourcesContent":["/**\n * MixrPay Merchant SDK - Payment Receipt Verification\n * \n * Verify JWT payment receipts issued by MixrPay after successful x402 payments.\n * \n * @example\n * ```typescript\n * import { verifyPaymentReceipt } from '@mixrpay/merchant-sdk';\n * \n * const receipt = req.headers['x-payment-receipt'];\n * const payment = await verifyPaymentReceipt(receipt);\n * \n * console.log(`Payment received: $${payment.amountUsd} from ${payment.payer}`);\n * console.log(`Transaction: ${payment.txHash}`);\n * ```\n */\n\nimport * as jose from 'jose';\nimport type { PaymentReceipt, VerifyReceiptOptions } from './types';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULT_JWKS_URL = process.env.MIXRPAY_JWKS_URL || 'https://www.mixrpay.com/.well-known/jwks';\nconst JWKS_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\n\n// =============================================================================\n// JWKS Cache\n// =============================================================================\n\ninterface CachedJWKS {\n jwks: jose.JSONWebKeySet;\n fetchedAt: number;\n}\n\nconst jwksCache = new Map<string, CachedJWKS>();\n\n/**\n * Fetch and cache JWKS from the specified URL.\n */\nasync function getJWKS(jwksUrl: string): Promise<jose.JSONWebKeySet> {\n const cached = jwksCache.get(jwksUrl);\n const now = Date.now();\n\n // Return cached JWKS if still valid\n if (cached && (now - cached.fetchedAt) < JWKS_CACHE_TTL_MS) {\n return cached.jwks;\n }\n\n // Fetch fresh JWKS\n const response = await fetch(jwksUrl);\n \n if (!response.ok) {\n throw new Error(`Failed to fetch JWKS from ${jwksUrl}: ${response.status} ${response.statusText}`);\n }\n\n const jwks = await response.json() as jose.JSONWebKeySet;\n\n // Validate JWKS structure\n if (!jwks.keys || !Array.isArray(jwks.keys) || jwks.keys.length === 0) {\n throw new Error('Invalid JWKS: missing or empty keys array');\n }\n\n // Cache the JWKS\n jwksCache.set(jwksUrl, { jwks, fetchedAt: now });\n\n return jwks;\n}\n\n/**\n * Create a JWKS key set from a fetched JWKS.\n */\nasync function createKeySet(jwksUrl: string): Promise<jose.JWTVerifyGetKey> {\n const jwks = await getJWKS(jwksUrl);\n return jose.createLocalJWKSet(jwks);\n}\n\n// =============================================================================\n// Receipt Verification\n// =============================================================================\n\n/**\n * Verify a JWT payment receipt from MixrPay.\n * \n * This function:\n * 1. Fetches the JWKS from MixrPay (cached for 1 hour)\n * 2. Verifies the JWT signature using RS256\n * 3. Validates standard JWT claims (exp, iat)\n * 4. Returns the typed payment receipt\n * \n * @param receipt - The JWT receipt string from X-Payment-Receipt header\n * @param options - Optional configuration (custom JWKS URL, issuer validation)\n * @returns Verified payment receipt\n * @throws Error if verification fails\n * \n * @example\n * ```typescript\n * // Basic usage\n * const payment = await verifyPaymentReceipt(receipt);\n * \n * // With custom JWKS URL (for testing or self-hosted)\n * const payment = await verifyPaymentReceipt(receipt, {\n * jwksUrl: 'https://your-mixrpay.com/.well-known/jwks'\n * });\n * ```\n */\nexport async function verifyPaymentReceipt(\n receipt: string,\n options?: VerifyReceiptOptions\n): Promise<PaymentReceipt> {\n const jwksUrl = options?.jwksUrl || DEFAULT_JWKS_URL;\n\n try {\n // Get the key set\n const keySet = await createKeySet(jwksUrl);\n\n // Build verification options\n const verifyOptions: jose.JWTVerifyOptions = {\n algorithms: ['RS256'],\n };\n\n // Add issuer validation if specified\n if (options?.issuer) {\n verifyOptions.issuer = options.issuer;\n }\n\n // Verify the JWT\n const { payload } = await jose.jwtVerify(receipt, keySet, verifyOptions);\n\n // Validate required payment fields\n const requiredFields = ['paymentId', 'amount', 'amountUsd', 'payer', 'recipient', 'chainId', 'txHash', 'settledAt'];\n for (const field of requiredFields) {\n if (!(field in payload)) {\n throw new Error(`Missing required field in receipt: ${field}`);\n }\n }\n\n // Return typed receipt\n return {\n paymentId: payload.paymentId as string,\n amount: payload.amount as string,\n amountUsd: payload.amountUsd as number,\n payer: payload.payer as string,\n recipient: payload.recipient as string,\n chainId: payload.chainId as number,\n txHash: payload.txHash as string,\n settledAt: payload.settledAt as string,\n issuedAt: new Date((payload.iat as number) * 1000),\n expiresAt: new Date((payload.exp as number) * 1000),\n };\n } catch (error) {\n if (error instanceof jose.errors.JWTExpired) {\n throw new Error('Payment receipt has expired');\n }\n if (error instanceof jose.errors.JWTClaimValidationFailed) {\n throw new Error(`Receipt validation failed: ${error.message}`);\n }\n if (error instanceof jose.errors.JWSSignatureVerificationFailed) {\n throw new Error('Invalid receipt signature');\n }\n throw error;\n }\n}\n\n/**\n * Parse a JWT payment receipt without verification.\n * \n * WARNING: This does NOT verify the signature. Use only for debugging\n * or when you've already verified the receipt elsewhere.\n * \n * @param receipt - The JWT receipt string\n * @returns Decoded payload (unverified)\n */\nexport function parsePaymentReceipt(receipt: string): PaymentReceipt {\n const decoded = jose.decodeJwt(receipt);\n\n return {\n paymentId: decoded.paymentId as string,\n amount: decoded.amount as string,\n amountUsd: decoded.amountUsd as number,\n payer: decoded.payer as string,\n recipient: decoded.recipient as string,\n chainId: decoded.chainId as number,\n txHash: decoded.txHash as string,\n settledAt: decoded.settledAt as string,\n issuedAt: decoded.iat ? new Date(decoded.iat * 1000) : undefined,\n expiresAt: decoded.exp ? new Date(decoded.exp * 1000) : undefined,\n };\n}\n\n/**\n * Check if a receipt is expired.\n * \n * @param receipt - The JWT receipt string or parsed PaymentReceipt\n * @returns true if expired, false otherwise\n */\nexport function isReceiptExpired(receipt: string | PaymentReceipt): boolean {\n const parsed = typeof receipt === 'string' ? parsePaymentReceipt(receipt) : receipt;\n \n if (!parsed.expiresAt) {\n return false; // No expiration = never expires\n }\n\n return parsed.expiresAt.getTime() < Date.now();\n}\n\n/**\n * Clear the JWKS cache.\n * Useful for testing or when keys have rotated.\n */\nexport function clearJWKSCache(): void {\n jwksCache.clear();\n}\n\n/**\n * Get the default JWKS URL.\n */\nexport function getDefaultJWKSUrl(): string {\n return DEFAULT_JWKS_URL;\n}\n\n","/**\n * MixrPay Charges Module\n * \n * Create and manage charges using session signers.\n * Session signers allow you to charge user wallets without\n * requiring approval for each transaction.\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface CreateChargeParams {\n /** Session key ID granted by the user */\n sessionId: string;\n /** Amount to charge in USD (e.g., 0.05 for 5 cents) */\n amountUsd: number;\n /** Unique reference for idempotency (e.g., \"order_123\") */\n reference: string;\n /** Optional metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface Charge {\n /** Unique charge ID */\n id: string;\n /** Current status */\n status: 'pending' | 'submitted' | 'confirmed' | 'failed';\n /** Amount in USD */\n amountUsd: number;\n /** Formatted USDC amount */\n amountUsdc: string;\n /** Transaction hash (if submitted) */\n txHash?: string;\n /** Block explorer URL */\n explorerUrl?: string;\n /** From wallet address */\n fromAddress: string;\n /** To wallet address */\n toAddress: string;\n /** Your reference */\n reference: string;\n /** When the charge was created */\n createdAt: string;\n /** When the charge was confirmed */\n confirmedAt?: string;\n /** Session name */\n sessionName?: string;\n /** User wallet address */\n userWallet?: string;\n /** Error message if failed */\n error?: string;\n}\n\nexport interface CreateChargeResult {\n success: boolean;\n charge?: Charge;\n /** True if this is a replay of an existing charge */\n idempotentReplay?: boolean;\n error?: string;\n details?: string;\n}\n\nexport interface SessionGrant {\n /** Session key ID */\n sessionId: string;\n /** User's wallet address */\n userWallet: string;\n /** Your merchant wallet address */\n merchantWallet: string;\n /** Spending limits */\n limits: {\n maxTotalUsd?: number;\n maxPerTxUsd?: number;\n expiresAt: string;\n };\n /** When the session was granted */\n grantedAt: string;\n}\n\n// Internal types for API responses\ninterface CreateChargeApiResponse {\n success: boolean;\n charge_id?: string;\n status?: string;\n amount_usd?: number;\n amount_usdc?: string;\n tx_hash?: string;\n explorer_url?: string;\n idempotent_replay?: boolean;\n error?: string;\n details?: string;\n}\n\ninterface ChargeApiResponse {\n id: string;\n status: string;\n amount_usd: number;\n amount_usdc: string;\n tx_hash?: string;\n explorer_url?: string;\n from_address: string;\n to_address: string;\n reference: string;\n created_at: string;\n confirmed_at?: string;\n session_name?: string;\n user_wallet?: string;\n}\n\nexport interface ChargesClientOptions {\n /** Your API key */\n apiKey: string;\n /** MixrPay API base URL */\n baseUrl?: string;\n}\n\n// =============================================================================\n// Client\n// =============================================================================\n\nexport class ChargesClient {\n private apiKey: string;\n private baseUrl: string;\n\n constructor(options: ChargesClientOptions) {\n this.apiKey = options.apiKey;\n this.baseUrl = options.baseUrl || process.env.MIXRPAY_BASE_URL || 'https://www.mixrpay.com';\n }\n\n /**\n * Create a new charge.\n * \n * @example\n * ```typescript\n * const result = await charges.create({\n * sessionId: 'sk_xxx',\n * amountUsd: 0.05,\n * reference: 'image_gen_123',\n * metadata: { feature: 'image_generation' }\n * });\n * \n * if (result.success) {\n * console.log(`Charged ${result.charge.amountUsdc}`);\n * console.log(`TX: ${result.charge.explorerUrl}`);\n * }\n * ```\n */\n async create(params: CreateChargeParams): Promise<CreateChargeResult> {\n const response = await fetch(`${this.baseUrl}/api/v1/charges`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({\n session_id: params.sessionId,\n amount_usd: params.amountUsd,\n reference: params.reference,\n metadata: params.metadata,\n }),\n });\n\n const data = await response.json() as CreateChargeApiResponse;\n\n if (!response.ok) {\n return {\n success: false,\n error: data.error,\n details: data.details,\n };\n }\n\n return {\n success: data.success,\n charge: data.charge_id ? {\n id: data.charge_id,\n status: data.status as Charge['status'],\n amountUsd: data.amount_usd ?? 0,\n amountUsdc: data.amount_usdc ?? '0',\n txHash: data.tx_hash,\n explorerUrl: data.explorer_url,\n fromAddress: '',\n toAddress: '',\n reference: params.reference,\n createdAt: new Date().toISOString(),\n } : undefined,\n idempotentReplay: data.idempotent_replay,\n error: data.error,\n };\n }\n\n /**\n * Get a charge by ID.\n * \n * @example\n * ```typescript\n * const charge = await charges.get('chg_xxx');\n * console.log(charge.status); // 'confirmed'\n * ```\n */\n async get(chargeId: string): Promise<Charge | null> {\n const response = await fetch(`${this.baseUrl}/api/v1/charges?id=${chargeId}`, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`Failed to get charge: ${response.statusText}`);\n }\n\n const data = await response.json() as ChargeApiResponse;\n\n return {\n id: data.id,\n status: data.status as Charge['status'],\n amountUsd: data.amount_usd,\n amountUsdc: data.amount_usdc,\n txHash: data.tx_hash,\n explorerUrl: data.explorer_url,\n fromAddress: data.from_address,\n toAddress: data.to_address,\n reference: data.reference,\n createdAt: data.created_at,\n confirmedAt: data.confirmed_at,\n sessionName: data.session_name,\n userWallet: data.user_wallet,\n };\n }\n\n /**\n * Get a charge by transaction hash.\n */\n async getByTxHash(txHash: string): Promise<Charge | null> {\n const response = await fetch(`${this.baseUrl}/api/v1/charges?tx_hash=${txHash}`, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`Failed to get charge: ${response.statusText}`);\n }\n\n const data = await response.json() as ChargeApiResponse;\n\n return {\n id: data.id,\n status: data.status as Charge['status'],\n amountUsd: data.amount_usd,\n amountUsdc: data.amount_usdc,\n txHash: data.tx_hash,\n explorerUrl: data.explorer_url,\n fromAddress: data.from_address,\n toAddress: data.to_address,\n reference: data.reference,\n createdAt: data.created_at,\n confirmedAt: data.confirmed_at,\n sessionName: data.session_name,\n userWallet: data.user_wallet,\n };\n }\n}\n\n// =============================================================================\n// Webhook Verification\n// =============================================================================\n\nimport crypto from 'crypto';\n\n/**\n * Verify a session.granted webhook signature.\n * \n * @example\n * ```typescript\n * const payload = req.body;\n * const signature = req.headers['x-mixrpay-signature'];\n * \n * if (verifySessionWebhook(JSON.stringify(payload), signature, webhookSecret)) {\n * const grant = parseSessionGrant(payload);\n * console.log(`User ${grant.userWallet} granted access`);\n * }\n * ```\n */\nexport function verifySessionWebhook(\n payload: string,\n signature: string,\n secret: string\n): boolean {\n const expectedSignature = crypto\n .createHmac('sha256', secret)\n .update(payload)\n .digest('hex');\n \n return crypto.timingSafeEqual(\n Buffer.from(signature),\n Buffer.from(expectedSignature)\n );\n}\n\n/**\n * Parse a session.granted webhook payload.\n */\nexport function parseSessionGrant(payload: {\n event: string;\n session_id: string;\n user_wallet: string;\n merchant_wallet: string;\n limits: {\n max_total_usd?: number;\n max_per_tx_usd?: number;\n expires_at: string;\n };\n granted_at: string;\n}): SessionGrant {\n if (payload.event !== 'session.granted') {\n throw new Error(`Unexpected event type: ${payload.event}`);\n }\n\n return {\n sessionId: payload.session_id,\n userWallet: payload.user_wallet,\n merchantWallet: payload.merchant_wallet,\n limits: {\n maxTotalUsd: payload.limits.max_total_usd,\n maxPerTxUsd: payload.limits.max_per_tx_usd,\n expiresAt: payload.limits.expires_at,\n },\n grantedAt: payload.granted_at,\n };\n}\n\n"],"mappings":";AAiBA,YAAY,UAAU;AAOtB,IAAM,mBAAmB,QAAQ,IAAI,oBAAoB;AACzD,IAAM,oBAAoB,KAAK,KAAK;AAWpC,IAAM,YAAY,oBAAI,IAAwB;AAK9C,eAAe,QAAQ,SAA8C;AACnE,QAAM,SAAS,UAAU,IAAI,OAAO;AACpC,QAAM,MAAM,KAAK,IAAI;AAGrB,MAAI,UAAW,MAAM,OAAO,YAAa,mBAAmB;AAC1D,WAAO,OAAO;AAAA,EAChB;AAGA,QAAM,WAAW,MAAM,MAAM,OAAO;AAEpC,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,6BAA6B,OAAO,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,EACnG;AAEA,QAAM,OAAO,MAAM,SAAS,KAAK;AAGjC,MAAI,CAAC,KAAK,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAAI,KAAK,KAAK,KAAK,WAAW,GAAG;AACrE,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AAGA,YAAU,IAAI,SAAS,EAAE,MAAM,WAAW,IAAI,CAAC;AAE/C,SAAO;AACT;AAKA,eAAe,aAAa,SAAgD;AAC1E,QAAM,OAAO,MAAM,QAAQ,OAAO;AAClC,SAAY,uBAAkB,IAAI;AACpC;AA+BA,eAAsB,qBACpB,SACA,SACyB;AACzB,QAAM,UAAU,SAAS,WAAW;AAEpC,MAAI;AAEF,UAAM,SAAS,MAAM,aAAa,OAAO;AAGzC,UAAM,gBAAuC;AAAA,MAC3C,YAAY,CAAC,OAAO;AAAA,IACtB;AAGA,QAAI,SAAS,QAAQ;AACnB,oBAAc,SAAS,QAAQ;AAAA,IACjC;AAGA,UAAM,EAAE,QAAQ,IAAI,MAAW,eAAU,SAAS,QAAQ,aAAa;AAGvE,UAAM,iBAAiB,CAAC,aAAa,UAAU,aAAa,SAAS,aAAa,WAAW,UAAU,WAAW;AAClH,eAAW,SAAS,gBAAgB;AAClC,UAAI,EAAE,SAAS,UAAU;AACvB,cAAM,IAAI,MAAM,sCAAsC,KAAK,EAAE;AAAA,MAC/D;AAAA,IACF;AAGA,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,MACnB,OAAO,QAAQ;AAAA,MACf,WAAW,QAAQ;AAAA,MACnB,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,MACnB,UAAU,IAAI,KAAM,QAAQ,MAAiB,GAAI;AAAA,MACjD,WAAW,IAAI,KAAM,QAAQ,MAAiB,GAAI;AAAA,IACpD;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAsB,YAAO,YAAY;AAC3C,YAAM,IAAI,MAAM,6BAA6B;AAAA,IAC/C;AACA,QAAI,iBAAsB,YAAO,0BAA0B;AACzD,YAAM,IAAI,MAAM,8BAA8B,MAAM,OAAO,EAAE;AAAA,IAC/D;AACA,QAAI,iBAAsB,YAAO,gCAAgC;AAC/D,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AACA,UAAM;AAAA,EACR;AACF;;;ACgHA,OAAO,YAAY;AAgBZ,SAAS,qBACd,SACA,WACA,QACS;AACT,QAAM,oBAAoB,OACvB,WAAW,UAAU,MAAM,EAC3B,OAAO,OAAO,EACd,OAAO,KAAK;AAEf,SAAO,OAAO;AAAA,IACZ,OAAO,KAAK,SAAS;AAAA,IACrB,OAAO,KAAK,iBAAiB;AAAA,EAC/B;AACF;","names":[]}
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
- import { X as X402PaymentResult, a as MixrPayPaymentResult, d as X402Options, M as MixrPayOptions } from '../types-jelXkTp9.mjs';
2
+ import { X as X402PaymentResult, a as MixrPayPaymentResult, d as X402Options, M as MixrPayOptions } from '../types-BwiuIaOu.mjs';
3
3
 
4
4
  /**
5
5
  * MixrPay Merchant SDK - Express Middleware
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
- import { X as X402PaymentResult, a as MixrPayPaymentResult, d as X402Options, M as MixrPayOptions } from '../types-jelXkTp9.js';
2
+ import { X as X402PaymentResult, a as MixrPayPaymentResult, d as X402Options, M as MixrPayOptions } from '../types-BwiuIaOu.js';
3
3
 
4
4
  /**
5
5
  * MixrPay Merchant SDK - Express Middleware
@@ -192,7 +192,7 @@ async function verifyX402Payment(paymentHeader, options) {
192
192
  }
193
193
 
194
194
  // src/receipt-fetcher.ts
195
- var DEFAULT_MIXRPAY_API_URL = process.env.MIXRPAY_BASE_URL || "https://mixrpay.com";
195
+ var DEFAULT_MIXRPAY_API_URL = process.env.MIXRPAY_BASE_URL || "https://www.mixrpay.com";
196
196
  async function fetchPaymentReceipt(params) {
197
197
  const apiUrl = params.apiUrl || DEFAULT_MIXRPAY_API_URL;
198
198
  const endpoint = `${apiUrl}/api/v1/receipts`;
@@ -359,7 +359,7 @@ async function getFeaturePriceExpress(feature, baseUrl) {
359
359
  function mixrpay(options) {
360
360
  return async (req, res, next) => {
361
361
  try {
362
- const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://mixrpay.com";
362
+ const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://www.mixrpay.com";
363
363
  const context = {
364
364
  path: req.path,
365
365
  method: req.method,
@@ -436,7 +436,7 @@ function mixrpay(options) {
436
436
  };
437
437
  }
438
438
  async function verifySessionPayment(sessionId, priceUsd, options, req) {
439
- const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://mixrpay.com";
439
+ const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://www.mixrpay.com";
440
440
  const secretKey = process.env.MIXRPAY_SECRET_KEY;
441
441
  if (!secretKey) {
442
442
  return {
@@ -445,52 +445,108 @@ async function verifySessionPayment(sessionId, priceUsd, options, req) {
445
445
  error: "MIXRPAY_SECRET_KEY not configured"
446
446
  };
447
447
  }
448
+ const preChargedHeader = req.headers["x-mixr-charged"];
449
+ const isPreCharged = preChargedHeader && preChargedHeader !== "0" && preChargedHeader.toLowerCase() !== "false";
448
450
  try {
449
- const response = await fetch(`${baseUrl}/api/v2/charge`, {
450
- method: "POST",
451
- headers: {
452
- "Content-Type": "application/json",
453
- "Authorization": `Bearer ${secretKey}`
454
- },
455
- body: JSON.stringify({
456
- sessionId,
457
- amountUsd: priceUsd,
458
- feature: options.feature || req.headers["x-mixr-feature"],
459
- idempotencyKey: req.headers["x-idempotency-key"]
460
- })
461
- });
462
- if (!response.ok) {
463
- const errorData = await response.json().catch(() => ({}));
464
- return {
465
- valid: false,
466
- method: "session",
467
- error: errorData.message || errorData.error || `Charge failed: ${response.status}`,
468
- sessionId
469
- };
451
+ if (isPreCharged) {
452
+ return await validatePreChargedSession(sessionId, priceUsd, preChargedHeader, baseUrl, secretKey, options);
453
+ } else {
454
+ return await chargeSession(sessionId, priceUsd, baseUrl, secretKey, options, req);
470
455
  }
471
- const data = await response.json();
456
+ } catch (error) {
472
457
  return {
473
- valid: true,
458
+ valid: false,
474
459
  method: "session",
475
- payer: data.payer || data.walletAddress,
476
- amountUsd: data.amountUsd || priceUsd,
477
- txHash: data.txHash,
478
- chargeId: data.chargeId,
479
- sessionId,
480
- feature: options.feature,
481
- settledAt: data.settledAt ? new Date(data.settledAt) : /* @__PURE__ */ new Date()
460
+ error: `Session verification failed: ${error.message}`,
461
+ sessionId
482
462
  };
483
- } catch (error) {
463
+ }
464
+ }
465
+ async function validatePreChargedSession(sessionId, expectedPriceUsd, chargedAmount, baseUrl, secretKey, options) {
466
+ const chargedUsd = parseFloat(chargedAmount);
467
+ if (isNaN(chargedUsd) || chargedUsd < expectedPriceUsd) {
484
468
  return {
485
469
  valid: false,
486
470
  method: "session",
487
- error: `Session verification failed: ${error.message}`,
471
+ error: `Insufficient payment: charged $${chargedUsd}, required $${expectedPriceUsd}`,
472
+ sessionId
473
+ };
474
+ }
475
+ const response = await fetch(`${baseUrl}/api/v2/session/validate`, {
476
+ method: "POST",
477
+ headers: {
478
+ "Content-Type": "application/json",
479
+ "Authorization": `Bearer ${secretKey}`
480
+ },
481
+ body: JSON.stringify({ session_id: sessionId })
482
+ });
483
+ if (!response.ok) {
484
+ const errorData = await response.json().catch(() => ({}));
485
+ return {
486
+ valid: false,
487
+ method: "session",
488
+ error: errorData.error || `Validation failed: ${response.status}`,
489
+ sessionId
490
+ };
491
+ }
492
+ const data = await response.json();
493
+ if (!data.valid) {
494
+ return {
495
+ valid: false,
496
+ method: "session",
497
+ error: data.error || data.code || "Session invalid",
488
498
  sessionId
489
499
  };
490
500
  }
501
+ return {
502
+ valid: true,
503
+ method: "session",
504
+ payer: data.wallet_address,
505
+ amountUsd: chargedUsd,
506
+ sessionId,
507
+ feature: options.feature,
508
+ settledAt: /* @__PURE__ */ new Date(),
509
+ preCharged: true
510
+ };
511
+ }
512
+ async function chargeSession(sessionId, priceUsd, baseUrl, secretKey, options, req) {
513
+ const response = await fetch(`${baseUrl}/api/v2/charge`, {
514
+ method: "POST",
515
+ headers: {
516
+ "Content-Type": "application/json",
517
+ "Authorization": `Bearer ${secretKey}`
518
+ },
519
+ body: JSON.stringify({
520
+ session_id: sessionId,
521
+ price_usd: priceUsd,
522
+ feature: options.feature || req.headers["x-mixr-feature"],
523
+ idempotency_key: req.headers["x-idempotency-key"]
524
+ })
525
+ });
526
+ if (!response.ok) {
527
+ const errorData = await response.json().catch(() => ({}));
528
+ return {
529
+ valid: false,
530
+ method: "session",
531
+ error: errorData.message || errorData.error || `Charge failed: ${response.status}`,
532
+ sessionId
533
+ };
534
+ }
535
+ const data = await response.json();
536
+ return {
537
+ valid: true,
538
+ method: "session",
539
+ payer: data.payer || data.walletAddress,
540
+ amountUsd: data.charged_usd || priceUsd,
541
+ txHash: data.tx_hash,
542
+ chargeId: data.idempotency_key,
543
+ sessionId,
544
+ feature: options.feature,
545
+ settledAt: data.settledAt ? new Date(data.settledAt) : /* @__PURE__ */ new Date()
546
+ };
491
547
  }
492
548
  async function verifyWidgetPayment(paymentJwt, priceUsd, options, req) {
493
- const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://mixrpay.com";
549
+ const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || "https://www.mixrpay.com";
494
550
  const secretKey = process.env.MIXRPAY_SECRET_KEY;
495
551
  if (!secretKey) {
496
552
  return {