@mixrpay/merchant-sdk 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -68,15 +68,109 @@ async function verifyPaymentReceipt(receipt, options) {
68
68
  }
69
69
 
70
70
  // src/charges.ts
71
- import crypto from "crypto";
71
+ import crypto2 from "crypto";
72
72
  function verifySessionWebhook(payload, signature, secret) {
73
- const expectedSignature = crypto.createHmac("sha256", secret).update(payload).digest("hex");
74
- return crypto.timingSafeEqual(
73
+ const expectedSignature = crypto2.createHmac("sha256", secret).update(payload).digest("hex");
74
+ return crypto2.timingSafeEqual(
75
75
  Buffer.from(signature),
76
76
  Buffer.from(expectedSignature)
77
77
  );
78
78
  }
79
+
80
+ // src/utils.ts
81
+ function getWidgetUrl() {
82
+ return "https://www.mixrpay.com/widget.js";
83
+ }
84
+ var MIXRPAY_API_URL = "https://www.mixrpay.com";
85
+ var WIDGET_SCRIPT_URL = "https://www.mixrpay.com/widget.js";
86
+ function usdToMinor(usd) {
87
+ return BigInt(Math.round(usd * 1e6));
88
+ }
89
+ function minorToUsd(minor) {
90
+ return Number(minor) / 1e6;
91
+ }
92
+ function isValidAddress(address) {
93
+ return /^0x[a-fA-F0-9]{40}$/.test(address);
94
+ }
95
+ function validateEnv(env) {
96
+ const e = env || (typeof process !== "undefined" ? process.env : {});
97
+ const result = {
98
+ valid: true,
99
+ errors: [],
100
+ warnings: [],
101
+ config: {
102
+ publicKey: e.MIXRPAY_PUBLIC_KEY || null,
103
+ secretKey: e.MIXRPAY_SECRET_KEY || null,
104
+ hmacSecret: e.MIXRPAY_HMAC_SECRET || null,
105
+ webhookSecret: e.MIXRPAY_WEBHOOK_SECRET || null,
106
+ merchantAddress: e.MIXRPAY_MERCHANT_ADDRESS || null
107
+ }
108
+ };
109
+ if (!result.config.publicKey) {
110
+ result.errors.push("MIXRPAY_PUBLIC_KEY is required. Get it from https://www.mixrpay.com/seller/developers");
111
+ result.valid = false;
112
+ } else if (!result.config.publicKey.startsWith("pk_")) {
113
+ result.errors.push(`MIXRPAY_PUBLIC_KEY must start with 'pk_'. Got: ${result.config.publicKey.slice(0, 10)}...`);
114
+ result.valid = false;
115
+ }
116
+ if (!result.config.secretKey) {
117
+ result.errors.push("MIXRPAY_SECRET_KEY is required. Get it from https://www.mixrpay.com/seller/developers");
118
+ result.valid = false;
119
+ } else if (!result.config.secretKey.startsWith("sk_")) {
120
+ result.errors.push(`MIXRPAY_SECRET_KEY must start with 'sk_'. Got: ${result.config.secretKey.slice(0, 10)}...`);
121
+ result.valid = false;
122
+ }
123
+ if (!result.config.webhookSecret) {
124
+ result.errors.push("MIXRPAY_WEBHOOK_SECRET is required. Get it from https://www.mixrpay.com/seller/developers");
125
+ result.valid = false;
126
+ }
127
+ if (!result.config.hmacSecret) {
128
+ result.warnings.push("MIXRPAY_HMAC_SECRET not set. Only required if using Widget with user linking (data-user-token).");
129
+ }
130
+ if (!result.config.merchantAddress) {
131
+ result.warnings.push("MIXRPAY_MERCHANT_ADDRESS not set. Required for x402 protocol support.");
132
+ } else if (!isValidAddress(result.config.merchantAddress)) {
133
+ result.errors.push(`MIXRPAY_MERCHANT_ADDRESS is not a valid Ethereum address: ${result.config.merchantAddress}`);
134
+ result.valid = false;
135
+ }
136
+ return result;
137
+ }
138
+ function logEnvValidation(result) {
139
+ const r = result || validateEnv();
140
+ console.log("\n\u{1F527} MixrPay Environment Check\n");
141
+ if (r.valid) {
142
+ console.log("\u2705 Configuration valid\n");
143
+ } else {
144
+ console.log("\u274C Configuration errors found\n");
145
+ }
146
+ if (r.errors.length > 0) {
147
+ console.log("Errors:");
148
+ r.errors.forEach((e) => console.log(` \u274C ${e}`));
149
+ console.log("");
150
+ }
151
+ if (r.warnings.length > 0) {
152
+ console.log("Warnings:");
153
+ r.warnings.forEach((w) => console.log(` \u26A0\uFE0F ${w}`));
154
+ console.log("");
155
+ }
156
+ console.log("Required credentials:");
157
+ console.log(` Public Key: ${r.config.publicKey ? "\u2713 Set" : "\u2717 Missing"}`);
158
+ console.log(` Secret Key: ${r.config.secretKey ? "\u2713 Set" : "\u2717 Missing"}`);
159
+ console.log(` Webhook Secret: ${r.config.webhookSecret ? "\u2713 Set" : "\u2717 Missing"}`);
160
+ console.log("");
161
+ console.log("Optional credentials:");
162
+ console.log(` HMAC Secret: ${r.config.hmacSecret ? "\u2713 Set" : "\u25CB Not set (widget only)"}`);
163
+ console.log(` Merchant Address: ${r.config.merchantAddress ? "\u2713 Set" : "\u25CB Not set (x402 only)"}`);
164
+ console.log("");
165
+ }
79
166
  export {
167
+ MIXRPAY_API_URL,
168
+ WIDGET_SCRIPT_URL,
169
+ getWidgetUrl,
170
+ logEnvValidation,
171
+ minorToUsd,
172
+ usdToMinor,
173
+ validateEnv,
80
174
  verifyPaymentReceipt,
81
175
  verifySessionWebhook
82
176
  };
@@ -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://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
+ {"version":3,"sources":["../src/receipt.ts","../src/charges.ts","../src/utils.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 (from database) */\n status: 'pending' | 'submitted' | 'confirmed' | 'failed';\n /** Real-time status (may differ from DB if not yet synced) */\n liveStatus?: 'pending' | 'submitted' | 'confirmed' | 'failed';\n /** Raw on-chain status */\n onChainStatus?: 'pending' | 'confirmed' | 'failed' | 'not_found' | null;\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 /** True if DB status differs from on-chain - call sync() to update */\n needsSync?: boolean;\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 /** Real-time status (may differ from DB status if not yet synced) */\n live_status?: string;\n /** Raw on-chain status */\n on_chain_status?: 'pending' | 'confirmed' | 'failed' | 'not_found' | null;\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 /** True if DB status differs from on-chain status */\n needs_sync?: boolean;\n}\n\ninterface SyncChargeApiResponse {\n success: boolean;\n charge?: {\n id: string;\n status: string;\n };\n error?: 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 * Note: The returned status is from the database. If `needsSync` is true,\n * the on-chain status differs and you should call `sync()` to update.\n * \n * @example\n * ```typescript\n * const charge = await charges.get('chg_xxx');\n * console.log(charge.status); // 'submitted'\n * console.log(charge.liveStatus); // 'confirmed' (real-time)\n * \n * if (charge.needsSync) {\n * await charges.sync(charge.id);\n * }\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 this.mapChargeResponse(data);\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 this.mapChargeResponse(data);\n }\n\n /**\n * Sync a charge's status from the blockchain.\n * \n * Call this when `get()` returns a charge with `needsSync: true`.\n * This will update the database with the on-chain status.\n * \n * @example\n * ```typescript\n * const charge = await charges.get('chg_xxx');\n * if (charge?.needsSync) {\n * const synced = await charges.sync(charge.id);\n * console.log(`Status updated to: ${synced.status}`);\n * }\n * ```\n */\n async sync(chargeId: string): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/charges/sync`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({ charge_id: chargeId }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(`Failed to sync charge: ${(error as { error?: string }).error || response.statusText}`);\n }\n\n const data = await response.json() as SyncChargeApiResponse;\n\n if (!data.success || !data.charge) {\n throw new Error(`Sync failed: ${data.error || 'Unknown error'}`);\n }\n\n return data.charge;\n }\n\n /**\n * Poll for charge confirmation.\n * \n * Convenience method that polls until the charge is confirmed or failed,\n * automatically syncing when needed.\n * \n * @example\n * ```typescript\n * const charge = await charges.waitForConfirmation('chg_xxx', {\n * timeoutMs: 60000,\n * pollIntervalMs: 2000,\n * });\n * console.log(`Final status: ${charge.status}`);\n * ```\n */\n async waitForConfirmation(\n chargeId: string,\n options: { timeoutMs?: number; pollIntervalMs?: number } = {}\n ): Promise<Charge> {\n const { timeoutMs = 60000, pollIntervalMs = 2000 } = options;\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeoutMs) {\n const charge = await this.get(chargeId);\n \n if (!charge) {\n throw new Error('Charge not found');\n }\n\n // If needs sync, sync it first\n if (charge.needsSync) {\n await this.sync(chargeId);\n // Re-fetch to get updated status\n const synced = await this.get(chargeId);\n if (synced && (synced.status === 'confirmed' || synced.status === 'failed')) {\n return synced;\n }\n }\n\n // Check if we've reached a terminal state\n if (charge.status === 'confirmed' || charge.status === 'failed') {\n return charge;\n }\n\n // Also check live status for early return\n if (charge.liveStatus === 'confirmed' || charge.liveStatus === 'failed') {\n // Sync and return\n await this.sync(chargeId);\n const final = await this.get(chargeId);\n if (final) return final;\n }\n\n // Wait before next poll\n await new Promise(resolve => setTimeout(resolve, pollIntervalMs));\n }\n\n throw new Error(`Timeout waiting for charge confirmation after ${timeoutMs}ms`);\n }\n\n private mapChargeResponse(data: ChargeApiResponse): Charge {\n return {\n id: data.id,\n status: data.status as Charge['status'],\n liveStatus: data.live_status as Charge['status'] | undefined,\n onChainStatus: data.on_chain_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 needsSync: data.needs_sync,\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","/**\n * MixrPay Merchant SDK - Utilities\n */\n\nimport type { EIP712Domain } from './types';\n\n// =============================================================================\n// Widget URLs\n// =============================================================================\n\n/**\n * Get the MixrPay widget script URL.\n * \n * @example\n * ```html\n * <script src=\"${getWidgetUrl()}\"\n * data-seller-public-key=\"pk_live_...\"\n * data-user-token=\"...\"></script>\n * ```\n */\nexport function getWidgetUrl(): string {\n return 'https://www.mixrpay.com/widget.js';\n}\n\n/**\n * MixrPay API base URL\n */\nexport const MIXRPAY_API_URL = 'https://www.mixrpay.com';\n\n/**\n * Widget script URL (for convenience)\n */\nexport const WIDGET_SCRIPT_URL = 'https://www.mixrpay.com/widget.js';\n\n// =============================================================================\n// USDC Constants by Chain\n// =============================================================================\n\nexport const USDC_CONTRACTS: Record<number, `0x${string}`> = {\n 8453: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // Base Mainnet\n 84532: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // Base Sepolia\n};\n\nexport const DEFAULT_FACILITATOR = 'https://x402.org/facilitator';\n\n// =============================================================================\n// EIP-712 Domain\n// =============================================================================\n\nexport function getUSDCDomain(chainId: number): EIP712Domain {\n const verifyingContract = USDC_CONTRACTS[chainId];\n if (!verifyingContract) {\n throw new Error(`Unsupported chain ID: ${chainId}. Supported: ${Object.keys(USDC_CONTRACTS).join(', ')}`);\n }\n\n return {\n name: 'USD Coin',\n version: '2',\n chainId,\n verifyingContract,\n };\n}\n\n// EIP-712 types for TransferWithAuthorization\nexport const TRANSFER_WITH_AUTHORIZATION_TYPES = {\n TransferWithAuthorization: [\n { name: 'from', type: 'address' },\n { name: 'to', type: 'address' },\n { name: 'value', type: 'uint256' },\n { name: 'validAfter', type: 'uint256' },\n { name: 'validBefore', type: 'uint256' },\n { name: 'nonce', type: 'bytes32' },\n ],\n} as const;\n\n// =============================================================================\n// Utility Functions\n// =============================================================================\n\n/**\n * Convert USD dollars to USDC minor units (6 decimals)\n */\nexport function usdToMinor(usd: number): bigint {\n return BigInt(Math.round(usd * 1_000_000));\n}\n\n/**\n * Convert USDC minor units to USD dollars\n */\nexport function minorToUsd(minor: bigint): number {\n return Number(minor) / 1_000_000;\n}\n\n/**\n * Generate a random nonce for x402 payments\n */\nexport function generateNonce(): `0x${string}` {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return `0x${Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')}` as `0x${string}`;\n}\n\n/**\n * Check if an address is valid\n */\nexport function isValidAddress(address: string): address is `0x${string}` {\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n}\n\n/**\n * Normalize an address to lowercase checksum format\n */\nexport function normalizeAddress(address: string): `0x${string}` {\n if (!isValidAddress(address)) {\n throw new Error(`Invalid address: ${address}`);\n }\n return address.toLowerCase() as `0x${string}`;\n}\n\n/**\n * Safe base64 decode that works in both Node.js and browsers\n */\nexport function base64Decode(str: string): string {\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'base64').toString('utf-8');\n }\n return atob(str);\n}\n\n/**\n * Safe base64 encode that works in both Node.js and browsers\n */\nexport function base64Encode(str: string): string {\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'utf-8').toString('base64');\n }\n return btoa(str);\n}\n\n// =============================================================================\n// Environment Validation\n// =============================================================================\n\nexport interface EnvValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n config: {\n publicKey: string | null;\n secretKey: string | null;\n hmacSecret: string | null;\n webhookSecret: string | null;\n merchantAddress: string | null;\n };\n}\n\n/**\n * Validate MixrPay environment variables and provide helpful feedback.\n * \n * @example\n * ```typescript\n * import { validateEnv } from '@mixrpay/merchant-sdk';\n * \n * const result = validateEnv();\n * if (!result.valid) {\n * console.error('MixrPay configuration errors:', result.errors);\n * }\n * ```\n */\nexport function validateEnv(env?: Record<string, string | undefined>): EnvValidationResult {\n const e = env || (typeof process !== 'undefined' ? process.env : {});\n \n const result: EnvValidationResult = {\n valid: true,\n errors: [],\n warnings: [],\n config: {\n publicKey: (e.MIXRPAY_PUBLIC_KEY as string) || null,\n secretKey: (e.MIXRPAY_SECRET_KEY as string) || null,\n hmacSecret: (e.MIXRPAY_HMAC_SECRET as string) || null,\n webhookSecret: (e.MIXRPAY_WEBHOOK_SECRET as string) || null,\n merchantAddress: (e.MIXRPAY_MERCHANT_ADDRESS as string) || null,\n },\n };\n \n // Check public key\n if (!result.config.publicKey) {\n result.errors.push('MIXRPAY_PUBLIC_KEY is required. Get it from https://www.mixrpay.com/seller/developers');\n result.valid = false;\n } else if (!result.config.publicKey.startsWith('pk_')) {\n result.errors.push(`MIXRPAY_PUBLIC_KEY must start with 'pk_'. Got: ${result.config.publicKey.slice(0, 10)}...`);\n result.valid = false;\n }\n \n // Check secret key\n if (!result.config.secretKey) {\n result.errors.push('MIXRPAY_SECRET_KEY is required. Get it from https://www.mixrpay.com/seller/developers');\n result.valid = false;\n } else if (!result.config.secretKey.startsWith('sk_')) {\n result.errors.push(`MIXRPAY_SECRET_KEY must start with 'sk_'. Got: ${result.config.secretKey.slice(0, 10)}...`);\n result.valid = false;\n }\n \n // Check webhook secret (required)\n if (!result.config.webhookSecret) {\n result.errors.push('MIXRPAY_WEBHOOK_SECRET is required. Get it from https://www.mixrpay.com/seller/developers');\n result.valid = false;\n }\n \n // Check HMAC secret (warning only - only needed for widget with user linking)\n if (!result.config.hmacSecret) {\n result.warnings.push('MIXRPAY_HMAC_SECRET not set. Only required if using Widget with user linking (data-user-token).');\n }\n \n // Check merchant address (warning only - needed for x402)\n if (!result.config.merchantAddress) {\n result.warnings.push('MIXRPAY_MERCHANT_ADDRESS not set. Required for x402 protocol support.');\n } else if (!isValidAddress(result.config.merchantAddress)) {\n result.errors.push(`MIXRPAY_MERCHANT_ADDRESS is not a valid Ethereum address: ${result.config.merchantAddress}`);\n result.valid = false;\n }\n \n return result;\n}\n\n/**\n * Log environment validation results to console with formatting.\n */\nexport function logEnvValidation(result?: EnvValidationResult): void {\n const r = result || validateEnv();\n \n console.log('\\n🔧 MixrPay Environment Check\\n');\n \n if (r.valid) {\n console.log('✅ Configuration valid\\n');\n } else {\n console.log('❌ Configuration errors found\\n');\n }\n \n if (r.errors.length > 0) {\n console.log('Errors:');\n r.errors.forEach(e => console.log(` ❌ ${e}`));\n console.log('');\n }\n \n if (r.warnings.length > 0) {\n console.log('Warnings:');\n r.warnings.forEach(w => console.log(` ⚠️ ${w}`));\n console.log('');\n }\n \n console.log('Required credentials:');\n console.log(` Public Key: ${r.config.publicKey ? '✓ Set' : '✗ Missing'}`);\n console.log(` Secret Key: ${r.config.secretKey ? '✓ Set' : '✗ Missing'}`);\n console.log(` Webhook Secret: ${r.config.webhookSecret ? '✓ Set' : '✗ Missing'}`);\n console.log('');\n console.log('Optional credentials:');\n console.log(` HMAC Secret: ${r.config.hmacSecret ? '✓ Set' : '○ Not set (widget only)'}`);\n console.log(` Merchant Address: ${r.config.merchantAddress ? '✓ Set' : '○ Not set (x402 only)'}`);\n console.log('');\n}\n\n"],"mappings":"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","names":["crypto","crypto"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/middleware/express.ts","../../src/verify.ts","../../src/utils.ts","../../src/receipt-fetcher.ts"],"sourcesContent":["/**\n * MixrPay Merchant SDK - Express Middleware\n * \n * Add x402 payment requirement to Express routes with a single line of code.\n * \n * @example\n * ```typescript\n * import express from 'express';\n * import { x402 } from '@mixrpay/merchant-sdk/express';\n * \n * const app = express();\n * \n * // Fixed price\n * app.post('/api/query', x402({ price: 0.05 }), (req, res) => {\n * // This handler only runs after payment is verified\n * res.json({ result: 'success', payer: req.x402Payment?.payer });\n * });\n * \n * // Dynamic pricing\n * app.post('/api/generate', x402({ \n * getPrice: (ctx) => ctx.body?.premium ? 0.10 : 0.05 \n * }), handler);\n * \n * // With JWT receipts\n * app.post('/api/premium', x402({ price: 0.10, receiptMode: 'jwt' }), (req, res) => {\n * // Receipt is set in X-Payment-Receipt header\n * res.json({ receipt: req.x402Payment?.receipt });\n * });\n * ```\n */\n\nimport type { Request, Response, NextFunction } from 'express';\nimport { verifyX402Payment } from '../verify';\nimport { usdToMinor, generateNonce, DEFAULT_FACILITATOR } from '../utils';\nimport { fetchPaymentReceipt } from '../receipt-fetcher';\nimport type { X402Options, X402PaymentResult, X402PaymentRequired, PriceContext, MixrPayOptions, MixrPayPaymentResult, PaymentMethod } from '../types';\n\n// Extend Express Request type\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n /** Payment result after x402 verification */\n x402Payment?: X402PaymentResult;\n /** Unified payment result (session, widget, or x402) */\n mixrPayment?: MixrPayPaymentResult;\n }\n }\n}\n\n/**\n * Express middleware that adds x402 payment requirement to a route.\n * \n * When a request comes in without a valid X-PAYMENT header, it returns\n * a 402 Payment Required response with the payment details.\n * \n * When a valid X-PAYMENT header is provided, it verifies the payment\n * and calls the next handler with `req.x402Payment` containing the result.\n * \n * @param options - x402 configuration options\n * @returns Express middleware function\n */\nexport function x402(options: X402Options) {\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n // Build context for callbacks\n const context: PriceContext = {\n path: req.path,\n method: req.method,\n headers: req.headers as Record<string, string | string[] | undefined>,\n body: req.body,\n };\n\n // Check if we should skip payment\n if (options.skip) {\n const shouldSkip = await options.skip(context);\n if (shouldSkip) {\n return next();\n }\n }\n\n // Get recipient address\n const recipient = options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;\n if (!recipient) {\n console.error('[x402] No recipient address configured. Set MIXRPAY_MERCHANT_ADDRESS env var or pass recipient option.');\n return res.status(500).json({ \n error: 'Payment configuration error',\n message: 'Merchant wallet address not configured',\n });\n }\n\n // Determine price\n const price = options.getPrice \n ? await options.getPrice(context) \n : options.price;\n \n const priceMinor = usdToMinor(price);\n const chainId = options.chainId || 8453;\n const facilitator = options.facilitator || DEFAULT_FACILITATOR;\n\n // Check for X-PAYMENT header\n const paymentHeader = req.headers['x-payment'] as string | undefined;\n\n if (!paymentHeader) {\n // Return 402 with payment requirements\n const nonce = generateNonce();\n const expiresAt = Math.floor(Date.now() / 1000) + 300; // 5 minutes\n\n const paymentRequired: X402PaymentRequired = {\n recipient,\n amount: priceMinor.toString(),\n currency: 'USDC',\n chainId,\n facilitator,\n nonce,\n expiresAt,\n description: options.description,\n };\n\n // Set headers per x402 spec\n res.setHeader('X-Payment-Required', JSON.stringify(paymentRequired));\n res.setHeader('WWW-Authenticate', `X-402 ${Buffer.from(JSON.stringify(paymentRequired)).toString('base64')}`);\n\n return res.status(402).json({\n error: 'Payment required',\n payment: paymentRequired,\n });\n }\n\n // Verify the payment\n const result = await verifyX402Payment(paymentHeader, {\n expectedAmount: priceMinor,\n expectedRecipient: recipient,\n chainId,\n facilitator,\n skipSettlement: options.testMode,\n });\n\n if (!result.valid) {\n return res.status(402).json({\n error: 'Invalid payment',\n reason: result.error,\n });\n }\n\n // Payment verified! Attach to request\n req.x402Payment = result;\n\n // Set response header with tx hash\n if (result.txHash) {\n res.setHeader('X-Payment-TxHash', result.txHash);\n }\n\n // Handle receipt mode\n const receiptMode = options.receiptMode || 'webhook';\n \n if ((receiptMode === 'jwt' || receiptMode === 'both') && result.txHash && result.payer) {\n try {\n const receipt = await fetchPaymentReceipt({\n txHash: result.txHash,\n payer: result.payer,\n recipient,\n amount: priceMinor,\n chainId,\n apiUrl: options.mixrpayApiUrl,\n });\n \n if (receipt) {\n result.receipt = receipt;\n res.setHeader('X-Payment-Receipt', receipt);\n }\n } catch (receiptError) {\n console.warn('[x402] Failed to fetch JWT receipt:', receiptError);\n // Continue without receipt - payment was still successful\n }\n }\n\n // Call onPayment callback\n if (options.onPayment) {\n await options.onPayment(result);\n }\n\n next();\n } catch (error) {\n console.error('[x402] Middleware error:', error);\n return res.status(500).json({\n error: 'Payment processing error',\n message: (error as Error).message,\n });\n }\n };\n}\n\n// =============================================================================\n// Unified MixrPay Middleware\n// =============================================================================\n\n/**\n * Unified Express middleware that accepts payments from multiple sources:\n * - Session Authorizations (X-Mixr-Session header)\n * - Widget payments (X-Mixr-Payment header)\n * - x402 Protocol (X-PAYMENT header)\n * \n * This is the recommended middleware for new integrations.\n * \n * @example\n * ```typescript\n * import express from 'express';\n * import { mixrpay } from '@mixrpay/merchant-sdk/express';\n * \n * const app = express();\n * \n * // Accept payments from any MixrPay client\n * app.post('/api/query', mixrpay({ priceUsd: 0.05 }), (req, res) => {\n * const { payer, amountUsd, method } = req.mixrPayment!;\n * console.log(`Paid $${amountUsd} via ${method} from ${payer}`);\n * res.json({ result: 'success' });\n * });\n * ```\n */\n// Cache for feature prices (in-memory, refreshes every 5 minutes)\nconst featurePriceCache = new Map<string, { price: number; expiresAt: number }>();\nconst FEATURE_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nasync function getFeaturePriceExpress(feature: string, baseUrl: string): Promise<number | null> {\n const publicKey = process.env.MIXRPAY_PUBLIC_KEY;\n const cacheKey = `${publicKey}:${feature}`;\n const cached = featurePriceCache.get(cacheKey);\n \n if (cached && cached.expiresAt > Date.now()) {\n return cached.price;\n }\n \n try {\n const url = new URL('/api/v1/features/price', baseUrl);\n url.searchParams.set('feature', feature);\n if (publicKey) {\n url.searchParams.set('public_key', publicKey);\n }\n \n const response = await fetch(url.toString(), {\n headers: {\n 'Authorization': `Bearer ${process.env.MIXRPAY_SECRET_KEY || ''}`,\n },\n });\n \n if (!response.ok) {\n console.warn(`[mixrpay] Failed to fetch feature price for \"${feature}\": ${response.status}`);\n return null;\n }\n \n const data = await response.json() as Record<string, unknown>;\n const price = data.price_usd;\n \n if (typeof price === 'number') {\n featurePriceCache.set(cacheKey, { price, expiresAt: Date.now() + FEATURE_CACHE_TTL_MS });\n return price;\n }\n \n return null;\n } catch (err) {\n console.error(`[mixrpay] Error fetching feature price:`, err);\n return null;\n }\n}\n\nexport function mixrpay(options: MixrPayOptions) {\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || 'https://www.mixrpay.com';\n \n // Build context for callbacks\n const context: PriceContext = {\n path: req.path,\n method: req.method,\n headers: req.headers as Record<string, string | string[] | undefined>,\n body: req.body,\n };\n\n // Check if we should skip payment\n if (options.skip) {\n const shouldSkip = await options.skip(context);\n if (shouldSkip) {\n return next();\n }\n }\n\n // Determine price - priority: getPrice() > priceUsd > feature lookup\n let priceUsd: number | undefined;\n \n if (options.getPrice) {\n priceUsd = await options.getPrice(context);\n } else if (options.priceUsd !== undefined) {\n priceUsd = options.priceUsd;\n } else if (options.feature) {\n // Feature-based pricing: lookup from dashboard\n const featurePrice = await getFeaturePriceExpress(options.feature, baseUrl);\n if (featurePrice !== null) {\n priceUsd = featurePrice;\n } else {\n console.error(`[mixrpay] Could not determine price for feature \"${options.feature}\". Configure price in dashboard or provide priceUsd.`);\n return res.status(500).json({ error: 'Price configuration error', feature: options.feature });\n }\n } else {\n console.error('[mixrpay] No price configured. Provide priceUsd, getPrice, or feature.');\n return res.status(500).json({ error: 'Price not configured' });\n }\n\n // Check headers in priority order\n const sessionHeader = req.headers['x-mixr-session'] as string | undefined;\n const widgetHeader = req.headers['x-mixr-payment'] as string | undefined;\n const x402Header = req.headers['x-payment'] as string | undefined;\n\n let result: MixrPayPaymentResult;\n\n // 1. Session Authorization (highest priority - agent SDK)\n if (sessionHeader) {\n result = await verifySessionPayment(sessionHeader, priceUsd, options, req);\n }\n // 2. Widget Payment (human users)\n else if (widgetHeader) {\n result = await verifyWidgetPayment(widgetHeader, priceUsd, options, req);\n }\n // 3. x402 Protocol (external agents)\n else if (x402Header) {\n result = await verifyX402PaymentUnified(x402Header, priceUsd, options, res);\n }\n // No payment header - return 402\n else {\n return returnPaymentRequired(priceUsd, options, res);\n }\n\n if (!result.valid) {\n return res.status(402).json({\n error: 'Invalid payment',\n reason: result.error,\n method: result.method,\n });\n }\n\n // Payment verified! Attach to request\n req.mixrPayment = result;\n\n // Also set x402Payment for backwards compatibility if using x402\n if (result.method === 'x402' && result.x402Result) {\n req.x402Payment = result.x402Result;\n }\n\n // Set response headers\n if (result.txHash) {\n res.setHeader('X-Payment-TxHash', result.txHash);\n }\n if (result.chargeId) {\n res.setHeader('X-Mixr-Charge-Id', result.chargeId);\n }\n if (result.amountUsd !== undefined) {\n res.setHeader('X-Mixr-Charged', result.amountUsd.toString());\n }\n\n // Call onPayment callback\n if (options.onPayment) {\n await options.onPayment(result);\n }\n\n next();\n } catch (error) {\n console.error('[mixrpay] Middleware error:', error);\n return res.status(500).json({\n error: 'Payment processing error',\n message: (error as Error).message,\n });\n }\n };\n}\n\n/**\n * Verify a session authorization payment.\n * \n * Supports two flows:\n * 1. Pre-charged: Agent already charged before calling merchant API (X-Mixr-Charged header)\n * - Validates session only, no charge\n * 2. Merchant-initiated: Middleware charges the session\n * - Charges the session via /api/v2/charge\n */\nasync function verifySessionPayment(\n sessionId: string,\n priceUsd: number,\n options: MixrPayOptions,\n req: Request\n): Promise<MixrPayPaymentResult> {\n const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || 'https://www.mixrpay.com';\n const secretKey = process.env.MIXRPAY_SECRET_KEY;\n\n if (!secretKey) {\n return {\n valid: false,\n method: 'session',\n error: 'MIXRPAY_SECRET_KEY not configured',\n };\n }\n\n // Check if agent pre-charged (X-Mixr-Charged header indicates amount already charged)\n const preChargedHeader = req.headers['x-mixr-charged'] as string | undefined;\n const isPreCharged = preChargedHeader && preChargedHeader !== '0' && preChargedHeader.toLowerCase() !== 'false';\n\n try {\n if (isPreCharged) {\n // Pre-charged flow: Validate session only (agent already charged)\n return await validatePreChargedSession(sessionId, priceUsd, preChargedHeader!, baseUrl, secretKey, options);\n } else {\n // Merchant-initiated flow: Charge the session\n return await chargeSession(sessionId, priceUsd, baseUrl, secretKey, options, req);\n }\n } catch (error) {\n return {\n valid: false,\n method: 'session',\n error: `Session verification failed: ${(error as Error).message}`,\n sessionId,\n };\n }\n}\n\n/**\n * Validate a pre-charged session without charging.\n * Used when agent SDK charges before calling merchant API.\n */\nasync function validatePreChargedSession(\n sessionId: string,\n expectedPriceUsd: number,\n chargedAmount: string,\n baseUrl: string,\n secretKey: string,\n options: MixrPayOptions\n): Promise<MixrPayPaymentResult> {\n // Verify the charged amount matches expected price\n const chargedUsd = parseFloat(chargedAmount);\n if (isNaN(chargedUsd) || chargedUsd < expectedPriceUsd) {\n return {\n valid: false,\n method: 'session',\n error: `Insufficient payment: charged $${chargedUsd}, required $${expectedPriceUsd}`,\n sessionId,\n };\n }\n\n // Validate the session is still active\n // Use redirect: 'manual' to prevent Authorization header from being stripped\n const response = await fetch(`${baseUrl}/api/v2/session/validate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${secretKey}`,\n },\n body: JSON.stringify({ session_id: sessionId }),\n redirect: 'manual',\n });\n\n // Check for redirects - these will strip the Authorization header\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location');\n return {\n valid: false,\n method: 'session',\n error: `API URL caused redirect (${response.status}). Update MIXRPAY_API_URL to: ${location || baseUrl}. Redirects strip Authorization headers.`,\n sessionId,\n };\n }\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({})) as Record<string, unknown>;\n return {\n valid: false,\n method: 'session',\n error: (errorData.error || `Validation failed: ${response.status}`) as string,\n sessionId,\n };\n }\n\n const data = await response.json() as Record<string, unknown>;\n\n if (!data.valid) {\n return {\n valid: false,\n method: 'session',\n error: (data.error || data.code || 'Session invalid') as string,\n sessionId,\n };\n }\n\n return {\n valid: true,\n method: 'session',\n payer: data.wallet_address as string,\n amountUsd: chargedUsd,\n sessionId,\n feature: options.feature,\n settledAt: new Date(),\n preCharged: true,\n };\n}\n\n/**\n * Charge a session via /api/v2/charge.\n */\nasync function chargeSession(\n sessionId: string,\n priceUsd: number,\n baseUrl: string,\n secretKey: string,\n options: MixrPayOptions,\n req: Request\n): Promise<MixrPayPaymentResult> {\n // IMPORTANT: Use redirect: 'manual' to prevent Authorization header from being stripped\n // during redirects (e.g., http→https, www→non-www). This is a common security measure\n // that can silently break API calls.\n const response = await fetch(`${baseUrl}/api/v2/charge`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${secretKey}`,\n },\n body: JSON.stringify({\n session_id: sessionId,\n price_usd: priceUsd,\n feature: options.feature || req.headers['x-mixr-feature'],\n idempotency_key: req.headers['x-idempotency-key'],\n }),\n redirect: 'manual', // Prevent redirect from stripping Authorization header\n });\n\n // Check for redirects - these will strip the Authorization header\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location');\n return {\n valid: false,\n method: 'session',\n error: `API URL caused redirect (${response.status}). Update MIXRPAY_API_URL to: ${location || baseUrl}. Redirects strip Authorization headers.`,\n sessionId,\n };\n }\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({})) as Record<string, unknown>;\n return {\n valid: false,\n method: 'session',\n error: (errorData.message || errorData.error || `Charge failed: ${response.status}`) as string,\n sessionId,\n };\n }\n\n const data = await response.json() as Record<string, unknown>;\n\n return {\n valid: true,\n method: 'session',\n payer: (data.payer || data.walletAddress) as string,\n amountUsd: (data.charged_usd as number) || priceUsd,\n txHash: data.tx_hash as string | undefined,\n chargeId: data.idempotency_key as string | undefined,\n sessionId,\n feature: options.feature,\n settledAt: data.settledAt ? new Date(data.settledAt as string) : new Date(),\n };\n}\n\n/**\n * Verify a widget payment JWT.\n */\nasync function verifyWidgetPayment(\n paymentJwt: string,\n priceUsd: number,\n options: MixrPayOptions,\n req: Request\n): Promise<MixrPayPaymentResult> {\n const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || 'https://www.mixrpay.com';\n const secretKey = process.env.MIXRPAY_SECRET_KEY;\n\n if (!secretKey) {\n return {\n valid: false,\n method: 'widget',\n error: 'MIXRPAY_SECRET_KEY not configured',\n };\n }\n\n try {\n // Verify the widget payment JWT\n // Use redirect: 'manual' to prevent Authorization header from being stripped\n const response = await fetch(`${baseUrl}/api/widget/verify`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${secretKey}`,\n },\n body: JSON.stringify({\n paymentJwt,\n expectedAmountUsd: priceUsd,\n feature: options.feature || req.headers['x-mixr-feature'],\n }),\n redirect: 'manual',\n });\n\n // Check for redirects - these will strip the Authorization header\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location');\n return {\n valid: false,\n method: 'widget',\n error: `API URL caused redirect (${response.status}). Update MIXRPAY_API_URL to: ${location || baseUrl}. Redirects strip Authorization headers.`,\n };\n }\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({})) as Record<string, unknown>;\n return {\n valid: false,\n method: 'widget',\n error: (errorData.message || errorData.error || `Widget verification failed: ${response.status}`) as string,\n };\n }\n\n const data = await response.json() as Record<string, unknown>;\n\n return {\n valid: true,\n method: 'widget',\n payer: (data.payer || data.walletAddress) as string,\n amountUsd: (data.amountUsd as number) || priceUsd,\n txHash: data.txHash as string | undefined,\n chargeId: data.chargeId as string | undefined,\n feature: options.feature,\n settledAt: data.settledAt ? new Date(data.settledAt as string) : new Date(),\n };\n } catch (error) {\n return {\n valid: false,\n method: 'widget',\n error: `Widget verification failed: ${(error as Error).message}`,\n };\n }\n}\n\n/**\n * Verify an x402 payment header.\n */\nasync function verifyX402PaymentUnified(\n paymentHeader: string,\n priceUsd: number,\n options: MixrPayOptions,\n res: Response\n): Promise<MixrPayPaymentResult> {\n const recipient = options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;\n\n if (!recipient) {\n return {\n valid: false,\n method: 'x402',\n error: 'MIXRPAY_MERCHANT_ADDRESS not configured for x402 payments',\n };\n }\n\n const priceMinor = usdToMinor(priceUsd);\n const chainId = options.chainId || 8453;\n const facilitator = options.facilitator || DEFAULT_FACILITATOR;\n\n const x402Result = await verifyX402Payment(paymentHeader, {\n expectedAmount: priceMinor,\n expectedRecipient: recipient,\n chainId,\n facilitator,\n skipSettlement: options.testMode,\n });\n\n if (!x402Result.valid) {\n return {\n valid: false,\n method: 'x402',\n error: x402Result.error,\n x402Result,\n };\n }\n\n // Fetch JWT receipt if needed\n const receiptMode = options.receiptMode || 'webhook';\n if ((receiptMode === 'jwt' || receiptMode === 'both') && x402Result.txHash && x402Result.payer) {\n try {\n const receipt = await fetchPaymentReceipt({\n txHash: x402Result.txHash,\n payer: x402Result.payer,\n recipient,\n amount: priceMinor,\n chainId,\n apiUrl: options.mixrpayApiUrl,\n });\n\n if (receipt) {\n x402Result.receipt = receipt;\n res.setHeader('X-Payment-Receipt', receipt);\n }\n } catch (receiptError) {\n console.warn('[mixrpay] Failed to fetch JWT receipt:', receiptError);\n }\n }\n\n return {\n valid: true,\n method: 'x402',\n payer: x402Result.payer,\n amountUsd: x402Result.amount,\n txHash: x402Result.txHash,\n receipt: x402Result.receipt,\n settledAt: x402Result.settledAt,\n x402Result,\n };\n}\n\n/**\n * Return 402 Payment Required response with multiple payment options.\n */\nfunction returnPaymentRequired(\n priceUsd: number,\n options: MixrPayOptions,\n res: Response\n): Response {\n const priceMinor = usdToMinor(priceUsd);\n const recipient = options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;\n const chainId = options.chainId || 8453;\n const facilitator = options.facilitator || DEFAULT_FACILITATOR;\n const nonce = generateNonce();\n const expiresAt = Math.floor(Date.now() / 1000) + 300; // 5 minutes\n\n // x402 payment requirements (for x402-enabled agents)\n const x402PaymentRequired: X402PaymentRequired | null = recipient ? {\n recipient,\n amount: priceMinor.toString(),\n currency: 'USDC',\n chainId,\n facilitator,\n nonce,\n expiresAt,\n description: options.description,\n } : null;\n\n // Set headers\n if (x402PaymentRequired) {\n res.setHeader('X-Payment-Required', JSON.stringify(x402PaymentRequired));\n res.setHeader('WWW-Authenticate', `X-402 ${Buffer.from(JSON.stringify(x402PaymentRequired)).toString('base64')}`);\n }\n\n return res.status(402).json({\n error: 'Payment required',\n priceUsd,\n acceptedMethods: [\n { method: 'session', header: 'X-Mixr-Session', description: 'Session authorization ID' },\n { method: 'widget', header: 'X-Mixr-Payment', description: 'Widget payment JWT' },\n ...(recipient ? [{ method: 'x402', header: 'X-PAYMENT', description: 'x402 protocol payment' }] : []),\n ],\n x402: x402PaymentRequired,\n description: options.description,\n });\n}\n\n// Re-export for convenience\nexport { x402 as default };\nexport type { X402Options, X402PaymentResult, MixrPayOptions, MixrPayPaymentResult };\n\n","/**\n * MixrPay Merchant SDK - Payment Verification\n */\n\nimport { recoverTypedDataAddress } from 'viem';\nimport type { \n X402PaymentResult, \n X402PaymentPayload, \n VerifyOptions,\n TransferWithAuthorizationMessage,\n} from './types';\nimport { \n getUSDCDomain, \n TRANSFER_WITH_AUTHORIZATION_TYPES, \n base64Decode,\n minorToUsd,\n DEFAULT_FACILITATOR,\n} from './utils';\n\n// =============================================================================\n// Payment Verification\n// =============================================================================\n\n/**\n * Verify an X-PAYMENT header and optionally settle the payment.\n * \n * @param paymentHeader - The X-PAYMENT header value (base64 encoded JSON)\n * @param options - Verification options\n * @returns Payment verification result\n * \n * @example\n * ```typescript\n * const result = await verifyX402Payment(req.headers['x-payment'], {\n * expectedAmount: 50000n, // 0.05 USDC\n * expectedRecipient: '0x...',\n * });\n * \n * if (result.valid) {\n * console.log(`Payment from ${result.payer}: $${result.amount}`);\n * }\n * ```\n */\nexport async function verifyX402Payment(\n paymentHeader: string,\n options: VerifyOptions\n): Promise<X402PaymentResult> {\n try {\n // 1. Decode the X-PAYMENT header\n let decoded: X402PaymentPayload;\n try {\n const jsonStr = base64Decode(paymentHeader);\n decoded = JSON.parse(jsonStr);\n } catch (e) {\n return { valid: false, error: 'Invalid payment header encoding' };\n }\n\n // 2. Validate payload structure\n if (!decoded.payload?.authorization || !decoded.payload?.signature) {\n return { valid: false, error: 'Missing authorization or signature in payment' };\n }\n\n const { authorization, signature } = decoded.payload;\n const chainId = options.chainId || 8453;\n\n // 3. Build EIP-712 message for verification\n const message: TransferWithAuthorizationMessage = {\n from: authorization.from as `0x${string}`,\n to: authorization.to as `0x${string}`,\n value: BigInt(authorization.value),\n validAfter: BigInt(authorization.validAfter),\n validBefore: BigInt(authorization.validBefore),\n nonce: authorization.nonce as `0x${string}`,\n };\n\n // 4. Recover signer address\n const domain = getUSDCDomain(chainId);\n let signerAddress: string;\n \n try {\n signerAddress = await recoverTypedDataAddress({\n domain,\n types: TRANSFER_WITH_AUTHORIZATION_TYPES,\n primaryType: 'TransferWithAuthorization',\n message,\n signature: signature as `0x${string}`,\n });\n } catch (e) {\n return { valid: false, error: 'Failed to recover signer from signature' };\n }\n\n // 5. Verify signer matches the 'from' address\n if (signerAddress.toLowerCase() !== authorization.from.toLowerCase()) {\n return { \n valid: false, \n error: `Signature mismatch: expected ${authorization.from}, got ${signerAddress}` \n };\n }\n\n // 6. Verify payment amount\n const paymentAmount = BigInt(authorization.value);\n if (paymentAmount < options.expectedAmount) {\n return { \n valid: false, \n error: `Insufficient payment: expected ${options.expectedAmount}, got ${paymentAmount}` \n };\n }\n\n // 7. Verify recipient\n if (authorization.to.toLowerCase() !== options.expectedRecipient.toLowerCase()) {\n return { \n valid: false, \n error: `Wrong recipient: expected ${options.expectedRecipient}, got ${authorization.to}` \n };\n }\n\n // 8. Check expiration\n const now = Math.floor(Date.now() / 1000);\n if (Number(authorization.validBefore) < now) {\n return { valid: false, error: 'Payment authorization has expired' };\n }\n\n // 9. Check validAfter\n if (Number(authorization.validAfter) > now) {\n return { valid: false, error: 'Payment authorization is not yet valid' };\n }\n\n // 10. Submit to facilitator for settlement (unless skipped)\n let txHash: string | undefined;\n let settledAt: Date | undefined;\n\n if (!options.skipSettlement) {\n const facilitatorUrl = options.facilitator || DEFAULT_FACILITATOR;\n \n try {\n const settlementResponse = await fetch(`${facilitatorUrl}/settle`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ \n authorization, \n signature,\n chainId,\n }),\n });\n\n if (!settlementResponse.ok) {\n const errorBody = await settlementResponse.text();\n let errorMessage = 'Settlement failed';\n try {\n const errorJson = JSON.parse(errorBody);\n errorMessage = errorJson.message || errorJson.error || errorMessage;\n } catch {\n errorMessage = errorBody || errorMessage;\n }\n return { valid: false, error: `Settlement failed: ${errorMessage}` };\n }\n\n const settlement = await settlementResponse.json() as { txHash?: string; tx_hash?: string };\n txHash = settlement.txHash || settlement.tx_hash;\n settledAt = new Date();\n } catch (e) {\n return { \n valid: false, \n error: `Settlement request failed: ${(e as Error).message}` \n };\n }\n }\n\n // Success!\n return {\n valid: true,\n payer: authorization.from,\n amount: minorToUsd(paymentAmount),\n amountMinor: paymentAmount,\n txHash,\n settledAt: settledAt || new Date(),\n nonce: authorization.nonce,\n };\n\n } catch (error) {\n return { \n valid: false, \n error: `Verification error: ${(error as Error).message}` \n };\n }\n}\n\n/**\n * Parse and validate an X-PAYMENT header without settlement.\n * Useful for checking if a payment is structurally valid before processing.\n */\nexport async function parseX402Payment(\n paymentHeader: string,\n chainId: number = 8453\n): Promise<{\n valid: boolean;\n error?: string;\n payer?: string;\n recipient?: string;\n amount?: number;\n amountMinor?: bigint;\n expiresAt?: Date;\n}> {\n try {\n const jsonStr = base64Decode(paymentHeader);\n const decoded: X402PaymentPayload = JSON.parse(jsonStr);\n\n if (!decoded.payload?.authorization) {\n return { valid: false, error: 'Missing authorization in payment' };\n }\n\n const { authorization, signature } = decoded.payload;\n\n // Verify signature\n const domain = getUSDCDomain(chainId);\n const message: TransferWithAuthorizationMessage = {\n from: authorization.from as `0x${string}`,\n to: authorization.to as `0x${string}`,\n value: BigInt(authorization.value),\n validAfter: BigInt(authorization.validAfter),\n validBefore: BigInt(authorization.validBefore),\n nonce: authorization.nonce as `0x${string}`,\n };\n\n const signerAddress = await recoverTypedDataAddress({\n domain,\n types: TRANSFER_WITH_AUTHORIZATION_TYPES,\n primaryType: 'TransferWithAuthorization',\n message,\n signature: signature as `0x${string}`,\n });\n\n if (signerAddress.toLowerCase() !== authorization.from.toLowerCase()) {\n return { valid: false, error: 'Signature mismatch' };\n }\n\n const amountMinor = BigInt(authorization.value);\n \n return {\n valid: true,\n payer: authorization.from,\n recipient: authorization.to,\n amount: minorToUsd(amountMinor),\n amountMinor,\n expiresAt: new Date(Number(authorization.validBefore) * 1000),\n };\n } catch (error) {\n return { valid: false, error: `Parse error: ${(error as Error).message}` };\n }\n}\n\n","/**\n * MixrPay Merchant SDK - Utilities\n */\n\nimport type { EIP712Domain } from './types';\n\n// =============================================================================\n// USDC Constants by Chain\n// =============================================================================\n\nexport const USDC_CONTRACTS: Record<number, `0x${string}`> = {\n 8453: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // Base Mainnet\n 84532: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // Base Sepolia\n};\n\nexport const DEFAULT_FACILITATOR = 'https://x402.org/facilitator';\n\n// =============================================================================\n// EIP-712 Domain\n// =============================================================================\n\nexport function getUSDCDomain(chainId: number): EIP712Domain {\n const verifyingContract = USDC_CONTRACTS[chainId];\n if (!verifyingContract) {\n throw new Error(`Unsupported chain ID: ${chainId}. Supported: ${Object.keys(USDC_CONTRACTS).join(', ')}`);\n }\n\n return {\n name: 'USD Coin',\n version: '2',\n chainId,\n verifyingContract,\n };\n}\n\n// EIP-712 types for TransferWithAuthorization\nexport const TRANSFER_WITH_AUTHORIZATION_TYPES = {\n TransferWithAuthorization: [\n { name: 'from', type: 'address' },\n { name: 'to', type: 'address' },\n { name: 'value', type: 'uint256' },\n { name: 'validAfter', type: 'uint256' },\n { name: 'validBefore', type: 'uint256' },\n { name: 'nonce', type: 'bytes32' },\n ],\n} as const;\n\n// =============================================================================\n// Utility Functions\n// =============================================================================\n\n/**\n * Convert USD dollars to USDC minor units (6 decimals)\n */\nexport function usdToMinor(usd: number): bigint {\n return BigInt(Math.round(usd * 1_000_000));\n}\n\n/**\n * Convert USDC minor units to USD dollars\n */\nexport function minorToUsd(minor: bigint): number {\n return Number(minor) / 1_000_000;\n}\n\n/**\n * Generate a random nonce for x402 payments\n */\nexport function generateNonce(): `0x${string}` {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return `0x${Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')}` as `0x${string}`;\n}\n\n/**\n * Check if an address is valid\n */\nexport function isValidAddress(address: string): address is `0x${string}` {\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n}\n\n/**\n * Normalize an address to lowercase checksum format\n */\nexport function normalizeAddress(address: string): `0x${string}` {\n if (!isValidAddress(address)) {\n throw new Error(`Invalid address: ${address}`);\n }\n return address.toLowerCase() as `0x${string}`;\n}\n\n/**\n * Safe base64 decode that works in both Node.js and browsers\n */\nexport function base64Decode(str: string): string {\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'base64').toString('utf-8');\n }\n return atob(str);\n}\n\n/**\n * Safe base64 encode that works in both Node.js and browsers\n */\nexport function base64Encode(str: string): string {\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'utf-8').toString('base64');\n }\n return btoa(str);\n}\n\n","/**\n * MixrPay Merchant SDK - Receipt Fetcher\n * \n * Internal utility to fetch JWT receipts from MixrPay API after settlement.\n * Used by x402 middleware to get receipts for the X-Payment-Receipt header.\n */\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULT_MIXRPAY_API_URL = process.env.MIXRPAY_BASE_URL || 'https://www.mixrpay.com';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface FetchReceiptParams {\n /** Settlement transaction hash */\n txHash: string;\n /** Payer address */\n payer: string;\n /** Recipient address */\n recipient: string;\n /** Amount in USDC minor units */\n amount: bigint;\n /** Chain ID */\n chainId: number;\n /** Custom MixrPay API URL (optional) */\n apiUrl?: string;\n}\n\ninterface ReceiptResponse {\n receipt: string;\n expiresAt: string;\n}\n\n// =============================================================================\n// Receipt Fetching\n// =============================================================================\n\n/**\n * Fetch a JWT payment receipt from MixrPay API.\n * \n * This is called by the x402 middleware after successful payment verification\n * when receiptMode is 'jwt' or 'both'.\n * \n * @param params - Receipt request parameters\n * @returns JWT receipt string, or null if not available\n */\nexport async function fetchPaymentReceipt(params: FetchReceiptParams): Promise<string | null> {\n const apiUrl = params.apiUrl || DEFAULT_MIXRPAY_API_URL;\n const endpoint = `${apiUrl}/api/v1/receipts`;\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n tx_hash: params.txHash,\n payer: params.payer,\n recipient: params.recipient,\n amount: params.amount.toString(),\n chain_id: params.chainId,\n }),\n });\n\n if (!response.ok) {\n // Log but don't throw - receipt is optional\n console.warn(`[x402] Receipt fetch failed: ${response.status} ${response.statusText}`);\n return null;\n }\n\n const data = await response.json() as ReceiptResponse;\n return data.receipt || null;\n } catch (error) {\n // Log but don't throw - payment was successful, receipt is optional\n console.warn('[x402] Receipt fetch error:', (error as Error).message);\n return null;\n }\n}\n\n/**\n * Generate a local receipt for testing purposes.\n * \n * WARNING: This generates an unsigned receipt that will NOT verify.\n * Use only for local development and testing.\n * \n * @param params - Receipt parameters\n * @returns Mock JWT-like string (not cryptographically signed)\n */\nexport function generateMockReceipt(params: FetchReceiptParams): string {\n const header = {\n alg: 'none',\n typ: 'JWT',\n };\n\n const payload = {\n paymentId: `mock_${Date.now()}`,\n amount: params.amount.toString(),\n amountUsd: Number(params.amount) / 1_000_000,\n payer: params.payer,\n recipient: params.recipient,\n chainId: params.chainId,\n txHash: params.txHash,\n settledAt: new Date().toISOString(),\n iat: Math.floor(Date.now() / 1000),\n exp: Math.floor(Date.now() / 1000) + 3600,\n _mock: true,\n };\n\n const base64Header = Buffer.from(JSON.stringify(header)).toString('base64url');\n const base64Payload = Buffer.from(JSON.stringify(payload)).toString('base64url');\n\n // Mock signature (not cryptographically valid)\n return `${base64Header}.${base64Payload}.mock_signature_for_testing_only`;\n}\n\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/middleware/express.ts","../../src/verify.ts","../../src/utils.ts","../../src/receipt-fetcher.ts"],"sourcesContent":["/**\n * MixrPay Merchant SDK - Express Middleware\n * \n * Add x402 payment requirement to Express routes with a single line of code.\n * \n * @example\n * ```typescript\n * import express from 'express';\n * import { x402 } from '@mixrpay/merchant-sdk/express';\n * \n * const app = express();\n * \n * // Fixed price\n * app.post('/api/query', x402({ price: 0.05 }), (req, res) => {\n * // This handler only runs after payment is verified\n * res.json({ result: 'success', payer: req.x402Payment?.payer });\n * });\n * \n * // Dynamic pricing\n * app.post('/api/generate', x402({ \n * getPrice: (ctx) => ctx.body?.premium ? 0.10 : 0.05 \n * }), handler);\n * \n * // With JWT receipts\n * app.post('/api/premium', x402({ price: 0.10, receiptMode: 'jwt' }), (req, res) => {\n * // Receipt is set in X-Payment-Receipt header\n * res.json({ receipt: req.x402Payment?.receipt });\n * });\n * ```\n */\n\nimport type { Request, Response, NextFunction } from 'express';\nimport { verifyX402Payment } from '../verify';\nimport { usdToMinor, generateNonce, DEFAULT_FACILITATOR } from '../utils';\nimport { fetchPaymentReceipt } from '../receipt-fetcher';\nimport type { X402Options, X402PaymentResult, X402PaymentRequired, PriceContext, MixrPayOptions, MixrPayPaymentResult, PaymentMethod } from '../types';\n\n// Extend Express Request type\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n /** Payment result after x402 verification */\n x402Payment?: X402PaymentResult;\n /** Unified payment result (session, widget, or x402) */\n mixrPayment?: MixrPayPaymentResult;\n }\n }\n}\n\n/**\n * Express middleware that adds x402 payment requirement to a route.\n * \n * When a request comes in without a valid X-PAYMENT header, it returns\n * a 402 Payment Required response with the payment details.\n * \n * When a valid X-PAYMENT header is provided, it verifies the payment\n * and calls the next handler with `req.x402Payment` containing the result.\n * \n * @param options - x402 configuration options\n * @returns Express middleware function\n */\nexport function x402(options: X402Options) {\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n // Build context for callbacks\n const context: PriceContext = {\n path: req.path,\n method: req.method,\n headers: req.headers as Record<string, string | string[] | undefined>,\n body: req.body,\n };\n\n // Check if we should skip payment\n if (options.skip) {\n const shouldSkip = await options.skip(context);\n if (shouldSkip) {\n return next();\n }\n }\n\n // Get recipient address\n const recipient = options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;\n if (!recipient) {\n console.error('[x402] No recipient address configured. Set MIXRPAY_MERCHANT_ADDRESS env var or pass recipient option.');\n return res.status(500).json({ \n error: 'Payment configuration error',\n message: 'Merchant wallet address not configured',\n });\n }\n\n // Determine price\n const price = options.getPrice \n ? await options.getPrice(context) \n : options.price;\n \n const priceMinor = usdToMinor(price);\n const chainId = options.chainId || 8453;\n const facilitator = options.facilitator || DEFAULT_FACILITATOR;\n\n // Check for X-PAYMENT header\n const paymentHeader = req.headers['x-payment'] as string | undefined;\n\n if (!paymentHeader) {\n // Return 402 with payment requirements\n const nonce = generateNonce();\n const expiresAt = Math.floor(Date.now() / 1000) + 300; // 5 minutes\n\n const paymentRequired: X402PaymentRequired = {\n recipient,\n amount: priceMinor.toString(),\n currency: 'USDC',\n chainId,\n facilitator,\n nonce,\n expiresAt,\n description: options.description,\n };\n\n // Set headers per x402 spec\n res.setHeader('X-Payment-Required', JSON.stringify(paymentRequired));\n res.setHeader('WWW-Authenticate', `X-402 ${Buffer.from(JSON.stringify(paymentRequired)).toString('base64')}`);\n\n return res.status(402).json({\n error: 'Payment required',\n payment: paymentRequired,\n });\n }\n\n // Verify the payment\n const result = await verifyX402Payment(paymentHeader, {\n expectedAmount: priceMinor,\n expectedRecipient: recipient,\n chainId,\n facilitator,\n skipSettlement: options.testMode,\n });\n\n if (!result.valid) {\n return res.status(402).json({\n error: 'Invalid payment',\n reason: result.error,\n });\n }\n\n // Payment verified! Attach to request\n req.x402Payment = result;\n\n // Set response header with tx hash\n if (result.txHash) {\n res.setHeader('X-Payment-TxHash', result.txHash);\n }\n\n // Handle receipt mode\n const receiptMode = options.receiptMode || 'webhook';\n \n if ((receiptMode === 'jwt' || receiptMode === 'both') && result.txHash && result.payer) {\n try {\n const receipt = await fetchPaymentReceipt({\n txHash: result.txHash,\n payer: result.payer,\n recipient,\n amount: priceMinor,\n chainId,\n apiUrl: options.mixrpayApiUrl,\n });\n \n if (receipt) {\n result.receipt = receipt;\n res.setHeader('X-Payment-Receipt', receipt);\n }\n } catch (receiptError) {\n console.warn('[x402] Failed to fetch JWT receipt:', receiptError);\n // Continue without receipt - payment was still successful\n }\n }\n\n // Call onPayment callback\n if (options.onPayment) {\n await options.onPayment(result);\n }\n\n next();\n } catch (error) {\n console.error('[x402] Middleware error:', error);\n return res.status(500).json({\n error: 'Payment processing error',\n message: (error as Error).message,\n });\n }\n };\n}\n\n// =============================================================================\n// Unified MixrPay Middleware\n// =============================================================================\n\n/**\n * Unified Express middleware that accepts payments from multiple sources:\n * - Session Authorizations (X-Mixr-Session header)\n * - Widget payments (X-Mixr-Payment header)\n * - x402 Protocol (X-PAYMENT header)\n * \n * This is the recommended middleware for new integrations.\n * \n * @example\n * ```typescript\n * import express from 'express';\n * import { mixrpay } from '@mixrpay/merchant-sdk/express';\n * \n * const app = express();\n * \n * // Accept payments from any MixrPay client\n * app.post('/api/query', mixrpay({ priceUsd: 0.05 }), (req, res) => {\n * const { payer, amountUsd, method } = req.mixrPayment!;\n * console.log(`Paid $${amountUsd} via ${method} from ${payer}`);\n * res.json({ result: 'success' });\n * });\n * ```\n */\n// Cache for feature prices (in-memory, refreshes every 5 minutes)\nconst featurePriceCache = new Map<string, { price: number; expiresAt: number }>();\nconst FEATURE_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nasync function getFeaturePriceExpress(feature: string, baseUrl: string): Promise<number | null> {\n const publicKey = process.env.MIXRPAY_PUBLIC_KEY;\n const cacheKey = `${publicKey}:${feature}`;\n const cached = featurePriceCache.get(cacheKey);\n \n if (cached && cached.expiresAt > Date.now()) {\n return cached.price;\n }\n \n try {\n const url = new URL('/api/v1/features/price', baseUrl);\n url.searchParams.set('feature', feature);\n if (publicKey) {\n url.searchParams.set('public_key', publicKey);\n }\n \n const response = await fetch(url.toString(), {\n headers: {\n 'Authorization': `Bearer ${process.env.MIXRPAY_SECRET_KEY || ''}`,\n },\n });\n \n if (!response.ok) {\n console.warn(`[mixrpay] Failed to fetch feature price for \"${feature}\": ${response.status}`);\n return null;\n }\n \n const data = await response.json() as Record<string, unknown>;\n const price = data.price_usd;\n \n if (typeof price === 'number') {\n featurePriceCache.set(cacheKey, { price, expiresAt: Date.now() + FEATURE_CACHE_TTL_MS });\n return price;\n }\n \n return null;\n } catch (err) {\n console.error(`[mixrpay] Error fetching feature price:`, err);\n return null;\n }\n}\n\nexport function mixrpay(options: MixrPayOptions) {\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || 'https://www.mixrpay.com';\n \n // Build context for callbacks\n const context: PriceContext = {\n path: req.path,\n method: req.method,\n headers: req.headers as Record<string, string | string[] | undefined>,\n body: req.body,\n };\n\n // Check if we should skip payment\n if (options.skip) {\n const shouldSkip = await options.skip(context);\n if (shouldSkip) {\n return next();\n }\n }\n\n // Determine price - priority: getPrice() > priceUsd > feature lookup\n let priceUsd: number | undefined;\n \n if (options.getPrice) {\n priceUsd = await options.getPrice(context);\n } else if (options.priceUsd !== undefined) {\n priceUsd = options.priceUsd;\n } else if (options.feature) {\n // Feature-based pricing: lookup from dashboard\n const featurePrice = await getFeaturePriceExpress(options.feature, baseUrl);\n if (featurePrice !== null) {\n priceUsd = featurePrice;\n } else {\n console.error(`[mixrpay] Could not determine price for feature \"${options.feature}\". Configure price in dashboard or provide priceUsd.`);\n return res.status(500).json({ error: 'Price configuration error', feature: options.feature });\n }\n } else {\n console.error('[mixrpay] No price configured. Provide priceUsd, getPrice, or feature.');\n return res.status(500).json({ error: 'Price not configured' });\n }\n\n // Check headers in priority order\n const sessionHeader = req.headers['x-mixr-session'] as string | undefined;\n const widgetHeader = req.headers['x-mixr-payment'] as string | undefined;\n const x402Header = req.headers['x-payment'] as string | undefined;\n\n let result: MixrPayPaymentResult;\n\n // 1. Session Authorization (highest priority - agent SDK)\n if (sessionHeader) {\n result = await verifySessionPayment(sessionHeader, priceUsd, options, req);\n }\n // 2. Widget Payment (human users)\n else if (widgetHeader) {\n result = await verifyWidgetPayment(widgetHeader, priceUsd, options, req);\n }\n // 3. x402 Protocol (external agents)\n else if (x402Header) {\n result = await verifyX402PaymentUnified(x402Header, priceUsd, options, res);\n }\n // No payment header - return 402\n else {\n return returnPaymentRequired(priceUsd, options, res);\n }\n\n if (!result.valid) {\n return res.status(402).json({\n error: 'Invalid payment',\n reason: result.error,\n method: result.method,\n });\n }\n\n // Payment verified! Attach to request\n req.mixrPayment = result;\n\n // Also set x402Payment for backwards compatibility if using x402\n if (result.method === 'x402' && result.x402Result) {\n req.x402Payment = result.x402Result;\n }\n\n // Set response headers\n if (result.txHash) {\n res.setHeader('X-Payment-TxHash', result.txHash);\n }\n if (result.chargeId) {\n res.setHeader('X-Mixr-Charge-Id', result.chargeId);\n }\n if (result.amountUsd !== undefined) {\n res.setHeader('X-Mixr-Charged', result.amountUsd.toString());\n }\n\n // Call onPayment callback\n if (options.onPayment) {\n await options.onPayment(result);\n }\n\n next();\n } catch (error) {\n console.error('[mixrpay] Middleware error:', error);\n return res.status(500).json({\n error: 'Payment processing error',\n message: (error as Error).message,\n });\n }\n };\n}\n\n/**\n * Verify a session authorization payment.\n * \n * Supports two flows:\n * 1. Pre-charged: Agent already charged before calling merchant API (X-Mixr-Charged header)\n * - Validates session only, no charge\n * 2. Merchant-initiated: Middleware charges the session\n * - Charges the session via /api/v2/charge\n */\nasync function verifySessionPayment(\n sessionId: string,\n priceUsd: number,\n options: MixrPayOptions,\n req: Request\n): Promise<MixrPayPaymentResult> {\n const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || 'https://www.mixrpay.com';\n const secretKey = process.env.MIXRPAY_SECRET_KEY;\n\n if (!secretKey) {\n return {\n valid: false,\n method: 'session',\n error: 'MIXRPAY_SECRET_KEY not configured',\n };\n }\n\n // Check if agent pre-charged (X-Mixr-Charged header indicates amount already charged)\n const preChargedHeader = req.headers['x-mixr-charged'] as string | undefined;\n const isPreCharged = preChargedHeader && preChargedHeader !== '0' && preChargedHeader.toLowerCase() !== 'false';\n\n try {\n if (isPreCharged) {\n // Pre-charged flow: Validate session only (agent already charged)\n return await validatePreChargedSession(sessionId, priceUsd, preChargedHeader!, baseUrl, secretKey, options);\n } else {\n // Merchant-initiated flow: Charge the session\n return await chargeSession(sessionId, priceUsd, baseUrl, secretKey, options, req);\n }\n } catch (error) {\n return {\n valid: false,\n method: 'session',\n error: `Session verification failed: ${(error as Error).message}`,\n sessionId,\n };\n }\n}\n\n/**\n * Validate a pre-charged session without charging.\n * Used when agent SDK charges before calling merchant API.\n */\nasync function validatePreChargedSession(\n sessionId: string,\n expectedPriceUsd: number,\n chargedAmount: string,\n baseUrl: string,\n secretKey: string,\n options: MixrPayOptions\n): Promise<MixrPayPaymentResult> {\n // Verify the charged amount matches expected price\n const chargedUsd = parseFloat(chargedAmount);\n if (isNaN(chargedUsd) || chargedUsd < expectedPriceUsd) {\n return {\n valid: false,\n method: 'session',\n error: `Insufficient payment: charged $${chargedUsd}, required $${expectedPriceUsd}`,\n sessionId,\n };\n }\n\n // Validate the session is still active\n // Use redirect: 'manual' to prevent Authorization header from being stripped\n const response = await fetch(`${baseUrl}/api/v2/session/validate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${secretKey}`,\n },\n body: JSON.stringify({ session_id: sessionId }),\n redirect: 'manual',\n });\n\n // Check for redirects - these will strip the Authorization header\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location');\n return {\n valid: false,\n method: 'session',\n error: `API URL caused redirect (${response.status}). Update MIXRPAY_API_URL to: ${location || baseUrl}. Redirects strip Authorization headers.`,\n sessionId,\n };\n }\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({})) as Record<string, unknown>;\n return {\n valid: false,\n method: 'session',\n error: (errorData.error || `Validation failed: ${response.status}`) as string,\n sessionId,\n };\n }\n\n const data = await response.json() as Record<string, unknown>;\n\n if (!data.valid) {\n return {\n valid: false,\n method: 'session',\n error: (data.error || data.code || 'Session invalid') as string,\n sessionId,\n };\n }\n\n return {\n valid: true,\n method: 'session',\n payer: data.wallet_address as string,\n amountUsd: chargedUsd,\n sessionId,\n feature: options.feature,\n settledAt: new Date(),\n preCharged: true,\n };\n}\n\n/**\n * Charge a session via /api/v2/charge.\n */\nasync function chargeSession(\n sessionId: string,\n priceUsd: number,\n baseUrl: string,\n secretKey: string,\n options: MixrPayOptions,\n req: Request\n): Promise<MixrPayPaymentResult> {\n // IMPORTANT: Use redirect: 'manual' to prevent Authorization header from being stripped\n // during redirects (e.g., http→https, www→non-www). This is a common security measure\n // that can silently break API calls.\n const response = await fetch(`${baseUrl}/api/v2/charge`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${secretKey}`,\n },\n body: JSON.stringify({\n session_id: sessionId,\n price_usd: priceUsd,\n feature: options.feature || req.headers['x-mixr-feature'],\n idempotency_key: req.headers['x-idempotency-key'],\n }),\n redirect: 'manual', // Prevent redirect from stripping Authorization header\n });\n\n // Check for redirects - these will strip the Authorization header\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location');\n return {\n valid: false,\n method: 'session',\n error: `API URL caused redirect (${response.status}). Update MIXRPAY_API_URL to: ${location || baseUrl}. Redirects strip Authorization headers.`,\n sessionId,\n };\n }\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({})) as Record<string, unknown>;\n return {\n valid: false,\n method: 'session',\n error: (errorData.message || errorData.error || `Charge failed: ${response.status}`) as string,\n sessionId,\n };\n }\n\n const data = await response.json() as Record<string, unknown>;\n\n return {\n valid: true,\n method: 'session',\n payer: (data.payer || data.walletAddress) as string,\n amountUsd: (data.charged_usd as number) || priceUsd,\n txHash: data.tx_hash as string | undefined,\n chargeId: data.idempotency_key as string | undefined,\n sessionId,\n feature: options.feature,\n settledAt: data.settledAt ? new Date(data.settledAt as string) : new Date(),\n };\n}\n\n/**\n * Verify a widget payment JWT.\n */\nasync function verifyWidgetPayment(\n paymentJwt: string,\n priceUsd: number,\n options: MixrPayOptions,\n req: Request\n): Promise<MixrPayPaymentResult> {\n const baseUrl = options.mixrpayApiUrl || process.env.MIXRPAY_API_URL || 'https://www.mixrpay.com';\n const secretKey = process.env.MIXRPAY_SECRET_KEY;\n\n if (!secretKey) {\n return {\n valid: false,\n method: 'widget',\n error: 'MIXRPAY_SECRET_KEY not configured',\n };\n }\n\n try {\n // Verify the widget payment JWT\n // Use redirect: 'manual' to prevent Authorization header from being stripped\n const response = await fetch(`${baseUrl}/api/widget/verify`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${secretKey}`,\n },\n body: JSON.stringify({\n paymentJwt,\n expectedAmountUsd: priceUsd,\n feature: options.feature || req.headers['x-mixr-feature'],\n }),\n redirect: 'manual',\n });\n\n // Check for redirects - these will strip the Authorization header\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location');\n return {\n valid: false,\n method: 'widget',\n error: `API URL caused redirect (${response.status}). Update MIXRPAY_API_URL to: ${location || baseUrl}. Redirects strip Authorization headers.`,\n };\n }\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({})) as Record<string, unknown>;\n return {\n valid: false,\n method: 'widget',\n error: (errorData.message || errorData.error || `Widget verification failed: ${response.status}`) as string,\n };\n }\n\n const data = await response.json() as Record<string, unknown>;\n\n return {\n valid: true,\n method: 'widget',\n payer: (data.payer || data.walletAddress) as string,\n amountUsd: (data.amountUsd as number) || priceUsd,\n txHash: data.txHash as string | undefined,\n chargeId: data.chargeId as string | undefined,\n feature: options.feature,\n settledAt: data.settledAt ? new Date(data.settledAt as string) : new Date(),\n };\n } catch (error) {\n return {\n valid: false,\n method: 'widget',\n error: `Widget verification failed: ${(error as Error).message}`,\n };\n }\n}\n\n/**\n * Verify an x402 payment header.\n */\nasync function verifyX402PaymentUnified(\n paymentHeader: string,\n priceUsd: number,\n options: MixrPayOptions,\n res: Response\n): Promise<MixrPayPaymentResult> {\n const recipient = options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;\n\n if (!recipient) {\n return {\n valid: false,\n method: 'x402',\n error: 'MIXRPAY_MERCHANT_ADDRESS not configured for x402 payments',\n };\n }\n\n const priceMinor = usdToMinor(priceUsd);\n const chainId = options.chainId || 8453;\n const facilitator = options.facilitator || DEFAULT_FACILITATOR;\n\n const x402Result = await verifyX402Payment(paymentHeader, {\n expectedAmount: priceMinor,\n expectedRecipient: recipient,\n chainId,\n facilitator,\n skipSettlement: options.testMode,\n });\n\n if (!x402Result.valid) {\n return {\n valid: false,\n method: 'x402',\n error: x402Result.error,\n x402Result,\n };\n }\n\n // Fetch JWT receipt if needed\n const receiptMode = options.receiptMode || 'webhook';\n if ((receiptMode === 'jwt' || receiptMode === 'both') && x402Result.txHash && x402Result.payer) {\n try {\n const receipt = await fetchPaymentReceipt({\n txHash: x402Result.txHash,\n payer: x402Result.payer,\n recipient,\n amount: priceMinor,\n chainId,\n apiUrl: options.mixrpayApiUrl,\n });\n\n if (receipt) {\n x402Result.receipt = receipt;\n res.setHeader('X-Payment-Receipt', receipt);\n }\n } catch (receiptError) {\n console.warn('[mixrpay] Failed to fetch JWT receipt:', receiptError);\n }\n }\n\n return {\n valid: true,\n method: 'x402',\n payer: x402Result.payer,\n amountUsd: x402Result.amount,\n txHash: x402Result.txHash,\n receipt: x402Result.receipt,\n settledAt: x402Result.settledAt,\n x402Result,\n };\n}\n\n/**\n * Return 402 Payment Required response with multiple payment options.\n */\nfunction returnPaymentRequired(\n priceUsd: number,\n options: MixrPayOptions,\n res: Response\n): Response {\n const priceMinor = usdToMinor(priceUsd);\n const recipient = options.recipient || process.env.MIXRPAY_MERCHANT_ADDRESS;\n const chainId = options.chainId || 8453;\n const facilitator = options.facilitator || DEFAULT_FACILITATOR;\n const nonce = generateNonce();\n const expiresAt = Math.floor(Date.now() / 1000) + 300; // 5 minutes\n\n // x402 payment requirements (for x402-enabled agents)\n const x402PaymentRequired: X402PaymentRequired | null = recipient ? {\n recipient,\n amount: priceMinor.toString(),\n currency: 'USDC',\n chainId,\n facilitator,\n nonce,\n expiresAt,\n description: options.description,\n } : null;\n\n // Set headers\n if (x402PaymentRequired) {\n res.setHeader('X-Payment-Required', JSON.stringify(x402PaymentRequired));\n res.setHeader('WWW-Authenticate', `X-402 ${Buffer.from(JSON.stringify(x402PaymentRequired)).toString('base64')}`);\n }\n\n return res.status(402).json({\n error: 'Payment required',\n priceUsd,\n acceptedMethods: [\n { method: 'session', header: 'X-Mixr-Session', description: 'Session authorization ID' },\n { method: 'widget', header: 'X-Mixr-Payment', description: 'Widget payment JWT' },\n ...(recipient ? [{ method: 'x402', header: 'X-PAYMENT', description: 'x402 protocol payment' }] : []),\n ],\n x402: x402PaymentRequired,\n description: options.description,\n });\n}\n\n// Re-export for convenience\nexport { x402 as default };\nexport type { X402Options, X402PaymentResult, MixrPayOptions, MixrPayPaymentResult };\n\n","/**\n * MixrPay Merchant SDK - Payment Verification\n */\n\nimport { recoverTypedDataAddress } from 'viem';\nimport type { \n X402PaymentResult, \n X402PaymentPayload, \n VerifyOptions,\n TransferWithAuthorizationMessage,\n} from './types';\nimport { \n getUSDCDomain, \n TRANSFER_WITH_AUTHORIZATION_TYPES, \n base64Decode,\n minorToUsd,\n DEFAULT_FACILITATOR,\n} from './utils';\n\n// =============================================================================\n// Payment Verification\n// =============================================================================\n\n/**\n * Verify an X-PAYMENT header and optionally settle the payment.\n * \n * @param paymentHeader - The X-PAYMENT header value (base64 encoded JSON)\n * @param options - Verification options\n * @returns Payment verification result\n * \n * @example\n * ```typescript\n * const result = await verifyX402Payment(req.headers['x-payment'], {\n * expectedAmount: 50000n, // 0.05 USDC\n * expectedRecipient: '0x...',\n * });\n * \n * if (result.valid) {\n * console.log(`Payment from ${result.payer}: $${result.amount}`);\n * }\n * ```\n */\nexport async function verifyX402Payment(\n paymentHeader: string,\n options: VerifyOptions\n): Promise<X402PaymentResult> {\n try {\n // 1. Decode the X-PAYMENT header\n let decoded: X402PaymentPayload;\n try {\n const jsonStr = base64Decode(paymentHeader);\n decoded = JSON.parse(jsonStr);\n } catch (e) {\n return { valid: false, error: 'Invalid payment header encoding' };\n }\n\n // 2. Validate payload structure\n if (!decoded.payload?.authorization || !decoded.payload?.signature) {\n return { valid: false, error: 'Missing authorization or signature in payment' };\n }\n\n const { authorization, signature } = decoded.payload;\n const chainId = options.chainId || 8453;\n\n // 3. Build EIP-712 message for verification\n const message: TransferWithAuthorizationMessage = {\n from: authorization.from as `0x${string}`,\n to: authorization.to as `0x${string}`,\n value: BigInt(authorization.value),\n validAfter: BigInt(authorization.validAfter),\n validBefore: BigInt(authorization.validBefore),\n nonce: authorization.nonce as `0x${string}`,\n };\n\n // 4. Recover signer address\n const domain = getUSDCDomain(chainId);\n let signerAddress: string;\n \n try {\n signerAddress = await recoverTypedDataAddress({\n domain,\n types: TRANSFER_WITH_AUTHORIZATION_TYPES,\n primaryType: 'TransferWithAuthorization',\n message,\n signature: signature as `0x${string}`,\n });\n } catch (e) {\n return { valid: false, error: 'Failed to recover signer from signature' };\n }\n\n // 5. Verify signer matches the 'from' address\n if (signerAddress.toLowerCase() !== authorization.from.toLowerCase()) {\n return { \n valid: false, \n error: `Signature mismatch: expected ${authorization.from}, got ${signerAddress}` \n };\n }\n\n // 6. Verify payment amount\n const paymentAmount = BigInt(authorization.value);\n if (paymentAmount < options.expectedAmount) {\n return { \n valid: false, \n error: `Insufficient payment: expected ${options.expectedAmount}, got ${paymentAmount}` \n };\n }\n\n // 7. Verify recipient\n if (authorization.to.toLowerCase() !== options.expectedRecipient.toLowerCase()) {\n return { \n valid: false, \n error: `Wrong recipient: expected ${options.expectedRecipient}, got ${authorization.to}` \n };\n }\n\n // 8. Check expiration\n const now = Math.floor(Date.now() / 1000);\n if (Number(authorization.validBefore) < now) {\n return { valid: false, error: 'Payment authorization has expired' };\n }\n\n // 9. Check validAfter\n if (Number(authorization.validAfter) > now) {\n return { valid: false, error: 'Payment authorization is not yet valid' };\n }\n\n // 10. Submit to facilitator for settlement (unless skipped)\n let txHash: string | undefined;\n let settledAt: Date | undefined;\n\n if (!options.skipSettlement) {\n const facilitatorUrl = options.facilitator || DEFAULT_FACILITATOR;\n \n try {\n const settlementResponse = await fetch(`${facilitatorUrl}/settle`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ \n authorization, \n signature,\n chainId,\n }),\n });\n\n if (!settlementResponse.ok) {\n const errorBody = await settlementResponse.text();\n let errorMessage = 'Settlement failed';\n try {\n const errorJson = JSON.parse(errorBody);\n errorMessage = errorJson.message || errorJson.error || errorMessage;\n } catch {\n errorMessage = errorBody || errorMessage;\n }\n return { valid: false, error: `Settlement failed: ${errorMessage}` };\n }\n\n const settlement = await settlementResponse.json() as { txHash?: string; tx_hash?: string };\n txHash = settlement.txHash || settlement.tx_hash;\n settledAt = new Date();\n } catch (e) {\n return { \n valid: false, \n error: `Settlement request failed: ${(e as Error).message}` \n };\n }\n }\n\n // Success!\n return {\n valid: true,\n payer: authorization.from,\n amount: minorToUsd(paymentAmount),\n amountMinor: paymentAmount,\n txHash,\n settledAt: settledAt || new Date(),\n nonce: authorization.nonce,\n };\n\n } catch (error) {\n return { \n valid: false, \n error: `Verification error: ${(error as Error).message}` \n };\n }\n}\n\n/**\n * Parse and validate an X-PAYMENT header without settlement.\n * Useful for checking if a payment is structurally valid before processing.\n */\nexport async function parseX402Payment(\n paymentHeader: string,\n chainId: number = 8453\n): Promise<{\n valid: boolean;\n error?: string;\n payer?: string;\n recipient?: string;\n amount?: number;\n amountMinor?: bigint;\n expiresAt?: Date;\n}> {\n try {\n const jsonStr = base64Decode(paymentHeader);\n const decoded: X402PaymentPayload = JSON.parse(jsonStr);\n\n if (!decoded.payload?.authorization) {\n return { valid: false, error: 'Missing authorization in payment' };\n }\n\n const { authorization, signature } = decoded.payload;\n\n // Verify signature\n const domain = getUSDCDomain(chainId);\n const message: TransferWithAuthorizationMessage = {\n from: authorization.from as `0x${string}`,\n to: authorization.to as `0x${string}`,\n value: BigInt(authorization.value),\n validAfter: BigInt(authorization.validAfter),\n validBefore: BigInt(authorization.validBefore),\n nonce: authorization.nonce as `0x${string}`,\n };\n\n const signerAddress = await recoverTypedDataAddress({\n domain,\n types: TRANSFER_WITH_AUTHORIZATION_TYPES,\n primaryType: 'TransferWithAuthorization',\n message,\n signature: signature as `0x${string}`,\n });\n\n if (signerAddress.toLowerCase() !== authorization.from.toLowerCase()) {\n return { valid: false, error: 'Signature mismatch' };\n }\n\n const amountMinor = BigInt(authorization.value);\n \n return {\n valid: true,\n payer: authorization.from,\n recipient: authorization.to,\n amount: minorToUsd(amountMinor),\n amountMinor,\n expiresAt: new Date(Number(authorization.validBefore) * 1000),\n };\n } catch (error) {\n return { valid: false, error: `Parse error: ${(error as Error).message}` };\n }\n}\n\n","/**\n * MixrPay Merchant SDK - Utilities\n */\n\nimport type { EIP712Domain } from './types';\n\n// =============================================================================\n// Widget URLs\n// =============================================================================\n\n/**\n * Get the MixrPay widget script URL.\n * \n * @example\n * ```html\n * <script src=\"${getWidgetUrl()}\"\n * data-seller-public-key=\"pk_live_...\"\n * data-user-token=\"...\"></script>\n * ```\n */\nexport function getWidgetUrl(): string {\n return 'https://www.mixrpay.com/widget.js';\n}\n\n/**\n * MixrPay API base URL\n */\nexport const MIXRPAY_API_URL = 'https://www.mixrpay.com';\n\n/**\n * Widget script URL (for convenience)\n */\nexport const WIDGET_SCRIPT_URL = 'https://www.mixrpay.com/widget.js';\n\n// =============================================================================\n// USDC Constants by Chain\n// =============================================================================\n\nexport const USDC_CONTRACTS: Record<number, `0x${string}`> = {\n 8453: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // Base Mainnet\n 84532: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // Base Sepolia\n};\n\nexport const DEFAULT_FACILITATOR = 'https://x402.org/facilitator';\n\n// =============================================================================\n// EIP-712 Domain\n// =============================================================================\n\nexport function getUSDCDomain(chainId: number): EIP712Domain {\n const verifyingContract = USDC_CONTRACTS[chainId];\n if (!verifyingContract) {\n throw new Error(`Unsupported chain ID: ${chainId}. Supported: ${Object.keys(USDC_CONTRACTS).join(', ')}`);\n }\n\n return {\n name: 'USD Coin',\n version: '2',\n chainId,\n verifyingContract,\n };\n}\n\n// EIP-712 types for TransferWithAuthorization\nexport const TRANSFER_WITH_AUTHORIZATION_TYPES = {\n TransferWithAuthorization: [\n { name: 'from', type: 'address' },\n { name: 'to', type: 'address' },\n { name: 'value', type: 'uint256' },\n { name: 'validAfter', type: 'uint256' },\n { name: 'validBefore', type: 'uint256' },\n { name: 'nonce', type: 'bytes32' },\n ],\n} as const;\n\n// =============================================================================\n// Utility Functions\n// =============================================================================\n\n/**\n * Convert USD dollars to USDC minor units (6 decimals)\n */\nexport function usdToMinor(usd: number): bigint {\n return BigInt(Math.round(usd * 1_000_000));\n}\n\n/**\n * Convert USDC minor units to USD dollars\n */\nexport function minorToUsd(minor: bigint): number {\n return Number(minor) / 1_000_000;\n}\n\n/**\n * Generate a random nonce for x402 payments\n */\nexport function generateNonce(): `0x${string}` {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return `0x${Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')}` as `0x${string}`;\n}\n\n/**\n * Check if an address is valid\n */\nexport function isValidAddress(address: string): address is `0x${string}` {\n return /^0x[a-fA-F0-9]{40}$/.test(address);\n}\n\n/**\n * Normalize an address to lowercase checksum format\n */\nexport function normalizeAddress(address: string): `0x${string}` {\n if (!isValidAddress(address)) {\n throw new Error(`Invalid address: ${address}`);\n }\n return address.toLowerCase() as `0x${string}`;\n}\n\n/**\n * Safe base64 decode that works in both Node.js and browsers\n */\nexport function base64Decode(str: string): string {\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'base64').toString('utf-8');\n }\n return atob(str);\n}\n\n/**\n * Safe base64 encode that works in both Node.js and browsers\n */\nexport function base64Encode(str: string): string {\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'utf-8').toString('base64');\n }\n return btoa(str);\n}\n\n// =============================================================================\n// Environment Validation\n// =============================================================================\n\nexport interface EnvValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n config: {\n publicKey: string | null;\n secretKey: string | null;\n hmacSecret: string | null;\n webhookSecret: string | null;\n merchantAddress: string | null;\n };\n}\n\n/**\n * Validate MixrPay environment variables and provide helpful feedback.\n * \n * @example\n * ```typescript\n * import { validateEnv } from '@mixrpay/merchant-sdk';\n * \n * const result = validateEnv();\n * if (!result.valid) {\n * console.error('MixrPay configuration errors:', result.errors);\n * }\n * ```\n */\nexport function validateEnv(env?: Record<string, string | undefined>): EnvValidationResult {\n const e = env || (typeof process !== 'undefined' ? process.env : {});\n \n const result: EnvValidationResult = {\n valid: true,\n errors: [],\n warnings: [],\n config: {\n publicKey: (e.MIXRPAY_PUBLIC_KEY as string) || null,\n secretKey: (e.MIXRPAY_SECRET_KEY as string) || null,\n hmacSecret: (e.MIXRPAY_HMAC_SECRET as string) || null,\n webhookSecret: (e.MIXRPAY_WEBHOOK_SECRET as string) || null,\n merchantAddress: (e.MIXRPAY_MERCHANT_ADDRESS as string) || null,\n },\n };\n \n // Check public key\n if (!result.config.publicKey) {\n result.errors.push('MIXRPAY_PUBLIC_KEY is required. Get it from https://www.mixrpay.com/seller/developers');\n result.valid = false;\n } else if (!result.config.publicKey.startsWith('pk_')) {\n result.errors.push(`MIXRPAY_PUBLIC_KEY must start with 'pk_'. Got: ${result.config.publicKey.slice(0, 10)}...`);\n result.valid = false;\n }\n \n // Check secret key\n if (!result.config.secretKey) {\n result.errors.push('MIXRPAY_SECRET_KEY is required. Get it from https://www.mixrpay.com/seller/developers');\n result.valid = false;\n } else if (!result.config.secretKey.startsWith('sk_')) {\n result.errors.push(`MIXRPAY_SECRET_KEY must start with 'sk_'. Got: ${result.config.secretKey.slice(0, 10)}...`);\n result.valid = false;\n }\n \n // Check webhook secret (required)\n if (!result.config.webhookSecret) {\n result.errors.push('MIXRPAY_WEBHOOK_SECRET is required. Get it from https://www.mixrpay.com/seller/developers');\n result.valid = false;\n }\n \n // Check HMAC secret (warning only - only needed for widget with user linking)\n if (!result.config.hmacSecret) {\n result.warnings.push('MIXRPAY_HMAC_SECRET not set. Only required if using Widget with user linking (data-user-token).');\n }\n \n // Check merchant address (warning only - needed for x402)\n if (!result.config.merchantAddress) {\n result.warnings.push('MIXRPAY_MERCHANT_ADDRESS not set. Required for x402 protocol support.');\n } else if (!isValidAddress(result.config.merchantAddress)) {\n result.errors.push(`MIXRPAY_MERCHANT_ADDRESS is not a valid Ethereum address: ${result.config.merchantAddress}`);\n result.valid = false;\n }\n \n return result;\n}\n\n/**\n * Log environment validation results to console with formatting.\n */\nexport function logEnvValidation(result?: EnvValidationResult): void {\n const r = result || validateEnv();\n \n console.log('\\n🔧 MixrPay Environment Check\\n');\n \n if (r.valid) {\n console.log('✅ Configuration valid\\n');\n } else {\n console.log('❌ Configuration errors found\\n');\n }\n \n if (r.errors.length > 0) {\n console.log('Errors:');\n r.errors.forEach(e => console.log(` ❌ ${e}`));\n console.log('');\n }\n \n if (r.warnings.length > 0) {\n console.log('Warnings:');\n r.warnings.forEach(w => console.log(` ⚠️ ${w}`));\n console.log('');\n }\n \n console.log('Required credentials:');\n console.log(` Public Key: ${r.config.publicKey ? '✓ Set' : '✗ Missing'}`);\n console.log(` Secret Key: ${r.config.secretKey ? '✓ Set' : '✗ Missing'}`);\n console.log(` Webhook Secret: ${r.config.webhookSecret ? '✓ Set' : '✗ Missing'}`);\n console.log('');\n console.log('Optional credentials:');\n console.log(` HMAC Secret: ${r.config.hmacSecret ? '✓ Set' : '○ Not set (widget only)'}`);\n console.log(` Merchant Address: ${r.config.merchantAddress ? '✓ Set' : '○ Not set (x402 only)'}`);\n console.log('');\n}\n\n","/**\n * MixrPay Merchant SDK - Receipt Fetcher\n * \n * Internal utility to fetch JWT receipts from MixrPay API after settlement.\n * Used by x402 middleware to get receipts for the X-Payment-Receipt header.\n */\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULT_MIXRPAY_API_URL = process.env.MIXRPAY_BASE_URL || 'https://www.mixrpay.com';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface FetchReceiptParams {\n /** Settlement transaction hash */\n txHash: string;\n /** Payer address */\n payer: string;\n /** Recipient address */\n recipient: string;\n /** Amount in USDC minor units */\n amount: bigint;\n /** Chain ID */\n chainId: number;\n /** Custom MixrPay API URL (optional) */\n apiUrl?: string;\n}\n\ninterface ReceiptResponse {\n receipt: string;\n expiresAt: string;\n}\n\n// =============================================================================\n// Receipt Fetching\n// =============================================================================\n\n/**\n * Fetch a JWT payment receipt from MixrPay API.\n * \n * This is called by the x402 middleware after successful payment verification\n * when receiptMode is 'jwt' or 'both'.\n * \n * @param params - Receipt request parameters\n * @returns JWT receipt string, or null if not available\n */\nexport async function fetchPaymentReceipt(params: FetchReceiptParams): Promise<string | null> {\n const apiUrl = params.apiUrl || DEFAULT_MIXRPAY_API_URL;\n const endpoint = `${apiUrl}/api/v1/receipts`;\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n tx_hash: params.txHash,\n payer: params.payer,\n recipient: params.recipient,\n amount: params.amount.toString(),\n chain_id: params.chainId,\n }),\n });\n\n if (!response.ok) {\n // Log but don't throw - receipt is optional\n console.warn(`[x402] Receipt fetch failed: ${response.status} ${response.statusText}`);\n return null;\n }\n\n const data = await response.json() as ReceiptResponse;\n return data.receipt || null;\n } catch (error) {\n // Log but don't throw - payment was successful, receipt is optional\n console.warn('[x402] Receipt fetch error:', (error as Error).message);\n return null;\n }\n}\n\n/**\n * Generate a local receipt for testing purposes.\n * \n * WARNING: This generates an unsigned receipt that will NOT verify.\n * Use only for local development and testing.\n * \n * @param params - Receipt parameters\n * @returns Mock JWT-like string (not cryptographically signed)\n */\nexport function generateMockReceipt(params: FetchReceiptParams): string {\n const header = {\n alg: 'none',\n typ: 'JWT',\n };\n\n const payload = {\n paymentId: `mock_${Date.now()}`,\n amount: params.amount.toString(),\n amountUsd: Number(params.amount) / 1_000_000,\n payer: params.payer,\n recipient: params.recipient,\n chainId: params.chainId,\n txHash: params.txHash,\n settledAt: new Date().toISOString(),\n iat: Math.floor(Date.now() / 1000),\n exp: Math.floor(Date.now() / 1000) + 3600,\n _mock: true,\n };\n\n const base64Header = Buffer.from(JSON.stringify(header)).toString('base64url');\n const base64Payload = Buffer.from(JSON.stringify(payload)).toString('base64url');\n\n // Mock signature (not cryptographically valid)\n return `${base64Header}.${base64Payload}.mock_signature_for_testing_only`;\n}\n\n"],"mappings":"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","names":[]}