feat: swap Stripe for Mollie payment integration

Replace Stripe checkout with Mollie payments API. Mollie handles
payment method selection on their hosted checkout (card, SEPA, iDEAL,
PayPal, etc). Simpler auth model — single API key, no webhook secrets.

- Rewrite /api/create-checkout-session for Mollie payment creation
- Rewrite /api/webhook for Mollie status verification flow
- Update google-sheets.ts: stripeSessionId → paymentSessionId
- Remove payment method radio buttons (Mollie shows all methods)
- Update docker-compose env vars
- Swap stripe npm package for @mollie/api-client

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-04 14:30:55 -08:00
parent 3b63d62b95
commit 8f59233918
9 changed files with 249 additions and 254 deletions

View File

@ -1,9 +1,7 @@
# Stripe Payment Integration # Mollie Payment Integration
STRIPE_SECRET_KEY=sk_live_xxx MOLLIE_API_KEY=live_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_xxx
# Public URL (used for Stripe redirect URLs) # Public URL (used for payment redirect URLs)
NEXT_PUBLIC_BASE_URL=https://cryptocommonsgather.ing NEXT_PUBLIC_BASE_URL=https://cryptocommonsgather.ing
# Google Sheets Integration # Google Sheets Integration

View File

@ -1,138 +1,86 @@
import { type NextRequest, NextResponse } from "next/server" import { type NextRequest, NextResponse } from "next/server"
import Stripe from "stripe" import createMollieClient from "@mollie/api-client"
// Lazy initialization to avoid build-time errors // Lazy initialization to avoid build-time errors
let stripe: Stripe | null = null let mollieClient: ReturnType<typeof createMollieClient> | null = null
function getStripe() { function getMollie() {
if (!stripe) { if (!mollieClient) {
stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { mollieClient = createMollieClient({ apiKey: process.env.MOLLIE_API_KEY! })
apiVersion: "2024-12-18.acacia",
})
} }
return stripe return mollieClient
} }
// Dynamic pricing configuration (in EUR cents) // Dynamic pricing configuration (in EUR)
const TICKET_PRICE_CENTS = 8000 // €80 early bird const TICKET_PRICE = 80 // €80 early bird
const DORM_PRICE = 235.2 // €235.20 (€39.20/night x 6)
const DOUBLE_PRICE = 301.2 // €301.20 (€50.20/night x 6)
const FOOD_PRICE = 135 // €135 (6 days)
// Public base URL (needed because request.nextUrl.origin returns internal Docker address) // Public base URL
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || "https://cryptocommonsgather.ing" const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || "https://cryptocommonsgather.ing"
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
const formData = await request.formData() const formData = await request.formData()
const paymentMethod = formData.get("paymentMethod") as string
const registrationDataStr = formData.get("registrationData") as string const registrationDataStr = formData.get("registrationData") as string
const includeAccommodation = formData.get("includeAccommodation") === "true" const includeAccommodation = formData.get("includeAccommodation") === "true"
const accommodationType = formData.get("accommodationType") as string || "dorm" const accommodationType = (formData.get("accommodationType") as string) || "dorm"
const includeFood = formData.get("includeFood") === "true" const includeFood = formData.get("includeFood") === "true"
const registrationData = registrationDataStr ? JSON.parse(registrationDataStr) : null const registrationData = registrationDataStr ? JSON.parse(registrationDataStr) : null
// Accommodation pricing (in EUR cents) // Calculate total
const DORM_PRICE_CENTS = 23520 // €235.20 (€39.20/night x 6) let total = TICKET_PRICE
const DOUBLE_PRICE_CENTS = 30120 // €301.20 (€50.20/night x 6) const descriptionParts = ["CCG 2026 Ticket (€80)"]
const FOOD_PRICE_CENTS = 13500 // €135 (6 days)
// Build line items dynamically
const lineItems: Stripe.Checkout.SessionCreateParams.LineItem[] = [
{
price_data: {
currency: "eur",
product_data: {
name: "CCG 2026 Ticket",
description: "Crypto Commons Gathering 2026 - August 16-22, Austria",
},
unit_amount: TICKET_PRICE_CENTS,
},
quantity: 1,
},
]
if (includeAccommodation) { if (includeAccommodation) {
const isDorm = accommodationType === "dorm" const isDorm = accommodationType === "dorm"
lineItems.push({ const accomPrice = isDorm ? DORM_PRICE : DOUBLE_PRICE
price_data: { total += accomPrice
currency: "eur", descriptionParts.push(`${isDorm ? "Dorm" : "Double Room"} (€${accomPrice.toFixed(2)})`)
product_data: {
name: isDorm ? "Accommodation - Dorm (6 nights)" : "Accommodation - Double Room (6 nights)",
description: `Commons Hub, ${isDorm ? "dorm bed" : "double room"}${isDorm ? "€39.20" : "€50.20"}/night`,
},
unit_amount: isDorm ? DORM_PRICE_CENTS : DOUBLE_PRICE_CENTS,
},
quantity: 1,
})
} }
if (includeFood) { if (includeFood) {
lineItems.push({ total += FOOD_PRICE
price_data: { descriptionParts.push(`Food Package (€${FOOD_PRICE})`)
currency: "eur",
product_data: {
name: "Food Package (6 days)",
description: "Breakfast, catered lunches & dinners, coffee & tea",
},
unit_amount: FOOD_PRICE_CENTS,
},
quantity: 1,
})
} }
let paymentMethodTypes: Stripe.Checkout.SessionCreateParams.PaymentMethodType[] = ["card"] // Build metadata for webhook
const metadata: Record<string, string> = {}
if (paymentMethod === "sepa_debit") { if (registrationData) {
paymentMethodTypes = ["sepa_debit"] metadata.name = registrationData.name || ""
} else if (paymentMethod === "crypto") { metadata.contact = registrationData.contact || ""
paymentMethodTypes = ["customer_balance"] metadata.contributions = (registrationData.contributions || "").substring(0, 500)
metadata.expectations = (registrationData.expectations || "").substring(0, 500)
metadata.howHeard = registrationData.howHeard || ""
metadata.dietary =
(registrationData.dietary || []).join(", ") +
(registrationData.dietaryOther ? `, ${registrationData.dietaryOther}` : "")
metadata.crewConsent = registrationData.crewConsent || ""
metadata.accommodation = includeAccommodation ? accommodationType : "none"
metadata.food = includeFood ? "yes" : "no"
} }
const session = await getStripe().checkout.sessions.create({ const payment = await getMollie().payments.create({
payment_method_types: paymentMethodTypes, amount: {
line_items: lineItems, value: total.toFixed(2),
mode: "payment", currency: "EUR",
success_url: `${BASE_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${BASE_URL}/register`,
metadata: registrationData
? {
name: registrationData.name,
contact: registrationData.contact,
contributions: registrationData.contributions.substring(0, 500),
expectations: registrationData.expectations.substring(0, 500),
howHeard: registrationData.howHeard || "",
dietary:
registrationData.dietary.join(", ") +
(registrationData.dietaryOther ? `, ${registrationData.dietaryOther}` : ""),
crewConsent: registrationData.crewConsent,
accommodation: includeAccommodation ? accommodationType : "none",
food: includeFood ? "yes" : "no",
}
: {},
...(paymentMethod === "crypto" && {
payment_method_options: {
customer_balance: {
funding_type: "bank_transfer",
bank_transfer: {
type: "us_bank_account",
},
},
},
}),
allow_promotion_codes: true,
billing_address_collection: "required",
phone_number_collection: {
enabled: true,
}, },
description: `CCG 2026 Registration — ${descriptionParts.join(" + ")}`,
redirectUrl: `${BASE_URL}/success`,
webhookUrl: `${BASE_URL}/api/webhook`,
metadata,
}) })
// Use 303 redirect for POST requests (tells browser to follow with GET) // Redirect to Mollie checkout
return new Response(null, { return new Response(null, {
status: 303, status: 303,
headers: { Location: session.url! }, headers: { Location: payment.getCheckoutUrl()! },
}) })
} catch (err) { } catch (err) {
console.error("Error creating checkout session:", err) console.error("Error creating Mollie payment:", err)
return NextResponse.json({ error: "Error creating checkout session" }, { status: 500 }) return NextResponse.json({ error: "Error creating payment" }, { status: 500 })
} }
} }

View File

@ -1,121 +1,93 @@
import { type NextRequest, NextResponse } from "next/server" import { type NextRequest, NextResponse } from "next/server"
import Stripe from "stripe" import createMollieClient from "@mollie/api-client"
import { updatePaymentStatus } from "@/lib/google-sheets" import { updatePaymentStatus } from "@/lib/google-sheets"
import { sendPaymentConfirmation } from "@/lib/email" import { sendPaymentConfirmation } from "@/lib/email"
import { addToListmonk } from "@/lib/listmonk" import { addToListmonk } from "@/lib/listmonk"
// Lazy initialization to avoid build-time errors // Lazy initialization
let stripe: Stripe | null = null let mollieClient: ReturnType<typeof createMollieClient> | null = null
function getStripe() { function getMollie() {
if (!stripe) { if (!mollieClient) {
stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { mollieClient = createMollieClient({ apiKey: process.env.MOLLIE_API_KEY! })
apiVersion: "2024-12-18.acacia",
})
} }
return stripe return mollieClient
}
function getWebhookSecret() {
return process.env.STRIPE_WEBHOOK_SECRET!
} }
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
const body = await request.text() // Mollie sends payment ID in the body as form data
const signature = request.headers.get("stripe-signature")! const formData = await request.formData()
const paymentId = formData.get("id") as string
let event: Stripe.Event if (!paymentId) {
console.error("[Webhook] No payment ID received")
try { return NextResponse.json({ error: "Missing payment ID" }, { status: 400 })
event = getStripe().webhooks.constructEvent(body, signature, getWebhookSecret())
} catch (err) {
console.error("[Webhook] Signature verification failed:", err)
return NextResponse.json({ error: "Invalid signature" }, { status: 400 })
} }
// Handle the event // Fetch the full payment from Mollie API (this is how you verify — no signature needed)
switch (event.type) { const payment = await getMollie().payments.get(paymentId)
case "checkout.session.completed": { const metadata = (payment.metadata || {}) as Record<string, string>
const session = event.data.object as Stripe.Checkout.Session
console.log("[Webhook] Payment successful:", session.id)
// Extract registration data from metadata console.log(`[Webhook] Payment ${paymentId} status: ${payment.status}`)
const metadata = session.metadata || {}
const customerEmail = session.customer_details?.email || ""
// Update Google Sheet with payment confirmation if (payment.status === "paid") {
const updated = await updatePaymentStatus({ const customerEmail = payment.billingAddress?.email || ""
const amountPaid = `${payment.amount.value}`
// Update Google Sheet
const updated = await updatePaymentStatus({
name: metadata.name || "",
email: customerEmail,
paymentSessionId: paymentId,
paymentStatus: "Paid",
paymentMethod: payment.method || "unknown",
amountPaid,
paymentDate: new Date().toISOString(),
})
if (updated) {
console.log(`[Webhook] Google Sheet updated for ${metadata.name}`)
} else {
console.error(`[Webhook] Failed to update Google Sheet for ${metadata.name}`)
}
// Send confirmation email
if (customerEmail) {
await sendPaymentConfirmation({
name: metadata.name || "", name: metadata.name || "",
email: customerEmail, email: customerEmail,
stripeSessionId: session.id, amountPaid,
paymentStatus: "Paid", paymentMethod: payment.method || "card",
paymentMethod: session.payment_method_types?.[0] || "unknown", contributions: metadata.contributions || "",
amountPaid: session.amount_total dietary: metadata.dietary || "",
? `${(session.amount_total / 100).toFixed(2)}`
: "",
paymentDate: new Date().toISOString(),
}) })
if (updated) { // Add to Listmonk newsletter
console.log(`[Webhook] Google Sheet updated for ${metadata.name}`) addToListmonk({
} else { email: customerEmail,
console.error(`[Webhook] Failed to update Google Sheet for ${metadata.name}`) name: metadata.name || "",
} attribs: {
contact: metadata.contact,
// Send payment confirmation email contributions: metadata.contributions,
if (customerEmail) { expectations: metadata.expectations,
await sendPaymentConfirmation({ },
name: metadata.name || "", }).catch((err) => console.error("[Webhook] Listmonk sync failed:", err))
email: customerEmail,
amountPaid: session.amount_total
? `${(session.amount_total / 100).toFixed(2)}`
: "",
paymentMethod: session.payment_method_types?.[0] || "card",
contributions: metadata.contributions || "",
dietary: metadata.dietary || "",
})
// Add to Listmonk newsletter
addToListmonk({
email: customerEmail,
name: metadata.name || "",
attribs: {
contact: metadata.contact,
contributions: metadata.contributions,
expectations: metadata.expectations,
},
}).catch((err) => console.error("[Webhook] Listmonk sync failed:", err))
}
break
} }
case "payment_intent.succeeded": { } else if (payment.status === "failed" || payment.status === "canceled" || payment.status === "expired") {
const paymentIntent = event.data.object as Stripe.PaymentIntent console.log(`[Webhook] Payment ${payment.status}: ${paymentId}`)
console.log("[Webhook] PaymentIntent successful:", paymentIntent.id)
break
}
case "payment_intent.payment_failed": {
const paymentIntent = event.data.object as Stripe.PaymentIntent
console.error("[Webhook] Payment failed:", paymentIntent.id)
// Optionally update sheet to mark as failed if (metadata.name) {
const failedMetadata = paymentIntent.metadata || {} await updatePaymentStatus({
if (failedMetadata.name) { name: metadata.name,
await updatePaymentStatus({ paymentSessionId: paymentId,
name: failedMetadata.name, paymentStatus: "Failed",
stripeSessionId: paymentIntent.id, paymentDate: new Date().toISOString(),
paymentStatus: "Failed", })
paymentDate: new Date().toISOString(),
})
}
break
} }
default:
console.log("[Webhook] Unhandled event type:", event.type)
} }
// Mollie expects 200 OK
return NextResponse.json({ received: true }) return NextResponse.json({ received: true })
} catch (err) { } catch (err) {
console.error("[Webhook] Error:", err) console.error("[Webhook] Error:", err)

View File

@ -258,34 +258,10 @@ export default function RegisterPage() {
<input type="hidden" name="includeFood" value={includeFood ? "true" : "false"} /> <input type="hidden" name="includeFood" value={includeFood ? "true" : "false"} />
<div className="space-y-4"> <div className="space-y-4">
<div> <p className="text-sm text-muted-foreground">
<Label className="text-base font-semibold mb-3 block">Select Payment Method</Label> You'll be redirected to Mollie's secure checkout where you can pay by credit card,
<RadioGroup name="paymentMethod" defaultValue="card" className="space-y-3"> SEPA bank transfer, iDEAL, PayPal, or other methods.
<div className="flex items-center space-x-2 border rounded-lg p-4 hover:border-primary transition-colors"> </p>
<RadioGroupItem value="card" id="card" />
<Label htmlFor="card" className="flex-1 cursor-pointer">
<div className="font-medium">Credit Card</div>
<div className="text-sm text-muted-foreground">Visa, Mastercard, Amex</div>
</Label>
</div>
<div className="flex items-center space-x-2 border rounded-lg p-4 hover:border-primary transition-colors">
<RadioGroupItem value="sepa_debit" id="sepa" />
<Label htmlFor="sepa" className="flex-1 cursor-pointer">
<div className="font-medium">SEPA Bank Transfer</div>
<div className="text-sm text-muted-foreground">Direct debit from European banks</div>
</Label>
</div>
<div className="flex items-center space-x-2 border rounded-lg p-4 hover:border-primary transition-colors border-primary/30">
<RadioGroupItem value="crypto" id="crypto" />
<Label htmlFor="crypto" className="flex-1 cursor-pointer">
<div className="font-medium">Cryptocurrency</div>
<div className="text-sm text-muted-foreground">USDC Stablecoin (settles as EUR)</div>
</Label>
</div>
</RadioGroup>
</div>
<div className="flex gap-3 pt-4"> <div className="flex gap-3 pt-4">
<Button type="button" variant="outline" onClick={() => setStep("form")} className="flex-1"> <Button type="button" variant="outline" onClick={() => setStep("form")} className="flex-1">
@ -302,7 +278,7 @@ export default function RegisterPage() {
<div className="text-center text-sm text-muted-foreground"> <div className="text-center text-sm text-muted-foreground">
<p> <p>
All payments are processed securely through Stripe. You'll receive a confirmation email after successful All payments are processed securely through Mollie. You'll receive a confirmation email after successful
payment. payment.
</p> </p>
</div> </div>

View File

@ -5,9 +5,7 @@ services:
restart: unless-stopped restart: unless-stopped
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY} - MOLLIE_API_KEY=${MOLLIE_API_KEY}
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}
- NEXT_PUBLIC_BASE_URL=https://staging-ccg.jeffemmett.com - NEXT_PUBLIC_BASE_URL=https://staging-ccg.jeffemmett.com
- GOOGLE_SERVICE_ACCOUNT_KEY=${GOOGLE_SERVICE_ACCOUNT_KEY} - GOOGLE_SERVICE_ACCOUNT_KEY=${GOOGLE_SERVICE_ACCOUNT_KEY}
- GOOGLE_SHEET_ID=${GOOGLE_SHEET_ID} - GOOGLE_SHEET_ID=${GOOGLE_SHEET_ID}

View File

@ -5,9 +5,7 @@ services:
restart: unless-stopped restart: unless-stopped
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY} - MOLLIE_API_KEY=${MOLLIE_API_KEY}
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}
- NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL:-https://cryptocommonsgather.ing} - NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL:-https://cryptocommonsgather.ing}
- GOOGLE_SERVICE_ACCOUNT_KEY=${GOOGLE_SERVICE_ACCOUNT_KEY} - GOOGLE_SERVICE_ACCOUNT_KEY=${GOOGLE_SERVICE_ACCOUNT_KEY}
- GOOGLE_SHEET_ID=${GOOGLE_SHEET_ID} - GOOGLE_SHEET_ID=${GOOGLE_SHEET_ID}

View File

@ -29,7 +29,7 @@ export interface RegistrationData {
export interface PaymentUpdateData { export interface PaymentUpdateData {
email?: string email?: string
name?: string name?: string
stripeSessionId: string paymentSessionId: string
paymentStatus: "Paid" | "Failed" paymentStatus: "Paid" | "Failed"
paymentMethod?: string paymentMethod?: string
amountPaid?: string amountPaid?: string
@ -131,7 +131,7 @@ export async function updatePaymentStatus(data: PaymentUpdateData): Promise<bool
rows[targetRowIndex - 1][8], // I: Crew Consent (preserve) rows[targetRowIndex - 1][8], // I: Crew Consent (preserve)
data.paymentStatus, // J: Payment Status data.paymentStatus, // J: Payment Status
data.paymentMethod || "", // K: Payment Method data.paymentMethod || "", // K: Payment Method
data.stripeSessionId, // L: Stripe Session ID data.paymentSessionId, // L: Stripe Session ID
data.amountPaid || "", // M: Amount Paid data.amountPaid || "", // M: Amount Paid
data.paymentDate || new Date().toISOString(), // N: Payment Date data.paymentDate || new Date().toISOString(), // N: Payment Date
], ],
@ -180,7 +180,7 @@ export async function initializeSheetHeaders(): Promise<void> {
"Crew Consent", "Crew Consent",
"Payment Status", "Payment Status",
"Payment Method", "Payment Method",
"Stripe Session ID", "Payment Session ID",
"Amount Paid", "Amount Paid",
"Payment Date", "Payment Date",
], ],

View File

@ -10,6 +10,7 @@
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.10.0", "@hookform/resolvers": "^3.10.0",
"@mollie/api-client": "^4.4.0",
"@radix-ui/react-accordion": "1.2.2", "@radix-ui/react-accordion": "1.2.2",
"@radix-ui/react-alert-dialog": "1.1.4", "@radix-ui/react-alert-dialog": "1.1.4",
"@radix-ui/react-aspect-ratio": "1.1.1", "@radix-ui/react-aspect-ratio": "1.1.1",
@ -58,7 +59,6 @@
"react-resizable-panels": "^2.1.7", "react-resizable-panels": "^2.1.7",
"recharts": "2.15.4", "recharts": "2.15.4",
"sonner": "^1.7.4", "sonner": "^1.7.4",
"stripe": "latest",
"tailwind-merge": "^2.5.5", "tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2", "vaul": "^1.1.2",

View File

@ -11,6 +11,9 @@ importers:
'@hookform/resolvers': '@hookform/resolvers':
specifier: ^3.10.0 specifier: ^3.10.0
version: 3.10.0(react-hook-form@7.69.0(react@19.2.0)) version: 3.10.0(react-hook-form@7.69.0(react@19.2.0))
'@mollie/api-client':
specifier: ^4.4.0
version: 4.4.0
'@radix-ui/react-accordion': '@radix-ui/react-accordion':
specifier: 1.2.2 specifier: 1.2.2
version: 1.2.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) version: 1.2.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@ -155,9 +158,6 @@ importers:
sonner: sonner:
specifier: ^1.7.4 specifier: ^1.7.4
version: 1.7.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) version: 1.7.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
stripe:
specifier: latest
version: 20.1.0(@types/node@22.19.3)
tailwind-merge: tailwind-merge:
specifier: ^2.5.5 specifier: ^2.5.5
version: 2.6.0 version: 2.6.0
@ -411,6 +411,10 @@ packages:
'@jridgewell/trace-mapping@0.3.31': '@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@mollie/api-client@4.4.0':
resolution: {integrity: sha512-V2OKBGS4TUKpbARV4JSjjtXqfRqfTQ5KCKGudEFEOEh/yQxrM2zXhkoq2gIbA6nGIVPh5nVaAzqLhp4C+e4aMQ==}
engines: {node: '>=8'}
'@next/env@16.0.10': '@next/env@16.0.10':
resolution: {integrity: sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==} resolution: {integrity: sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==}
@ -1375,6 +1379,9 @@ packages:
'@types/d3-timer@3.0.2': '@types/d3-timer@3.0.2':
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
'@types/node-fetch@2.6.13':
resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==}
'@types/node@22.19.3': '@types/node@22.19.3':
resolution: {integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==} resolution: {integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==}
@ -1416,6 +1423,9 @@ packages:
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
engines: {node: '>=10'} engines: {node: '>=10'}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
autoprefixer@10.4.23: autoprefixer@10.4.23:
resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==} resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
@ -1481,6 +1491,10 @@ packages:
color-name@1.1.4: color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
cross-spawn@7.0.6: cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -1554,6 +1568,10 @@ packages:
decimal.js-light@2.5.1: decimal.js-light@2.5.1:
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
detect-libc@2.1.2: detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -1612,6 +1630,10 @@ packages:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
es-set-tostringtag@2.1.0:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
escalade@3.2.0: escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -1634,6 +1656,10 @@ packages:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'} engines: {node: '>=14'}
form-data@4.0.5:
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'}
formdata-polyfill@4.0.10: formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'} engines: {node: '>=12.20.0'}
@ -1699,6 +1725,10 @@ packages:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
has-tostringtag@1.0.2:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
engines: {node: '>= 0.4'}
hasown@2.0.2: hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -1839,6 +1869,14 @@ packages:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
minimatch@9.0.5: minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
@ -1887,6 +1925,15 @@ packages:
engines: {node: '>=10.5.0'} engines: {node: '>=10.5.0'}
deprecated: Use your platform's native DOMException instead deprecated: Use your platform's native DOMException instead
node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
node-fetch@3.3.2: node-fetch@3.3.2:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -2083,6 +2130,9 @@ packages:
resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==}
hasBin: true hasBin: true
ruply@1.0.1:
resolution: {integrity: sha512-p39LnaaJyuucPGlgaB0KiyifpcuOkn24+Hq5y0ejAD/LlH+mRAbkHn2tckCLgHir+S+nis1WYG+TYEC4zHX0WQ==}
safe-buffer@5.2.1: safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
@ -2156,15 +2206,6 @@ packages:
resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
engines: {node: '>=12'} engines: {node: '>=12'}
stripe@20.1.0:
resolution: {integrity: sha512-o1VNRuMkY76ZCq92U3EH3/XHm/WHp7AerpzDs4Zyo8uE5mFL4QUcv/2SudWsSnhBSp4moO2+ZoGCZ7mT8crPmQ==}
engines: {node: '>=16'}
peerDependencies:
'@types/node': '>=16'
peerDependenciesMeta:
'@types/node':
optional: true
styled-jsx@5.1.6: styled-jsx@5.1.6:
resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
@ -2196,6 +2237,9 @@ packages:
tiny-invariant@1.3.3: tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
tslib@2.8.1: tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@ -2257,6 +2301,12 @@ packages:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
which@2.0.2: which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -2436,6 +2486,14 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
'@mollie/api-client@4.4.0':
dependencies:
'@types/node-fetch': 2.6.13
node-fetch: 2.7.0
ruply: 1.0.1
transitivePeerDependencies:
- encoding
'@next/env@16.0.10': {} '@next/env@16.0.10': {}
'@next/swc-darwin-arm64@16.0.10': '@next/swc-darwin-arm64@16.0.10':
@ -3357,6 +3415,11 @@ snapshots:
'@types/d3-timer@3.0.2': {} '@types/d3-timer@3.0.2': {}
'@types/node-fetch@2.6.13':
dependencies:
'@types/node': 22.19.3
form-data: 4.0.5
'@types/node@22.19.3': '@types/node@22.19.3':
dependencies: dependencies:
undici-types: 6.21.0 undici-types: 6.21.0
@ -3395,6 +3458,8 @@ snapshots:
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
asynckit@0.4.0: {}
autoprefixer@10.4.23(postcss@8.5.6): autoprefixer@10.4.23(postcss@8.5.6):
dependencies: dependencies:
browserslist: 4.28.1 browserslist: 4.28.1
@ -3464,6 +3529,10 @@ snapshots:
color-name@1.1.4: {} color-name@1.1.4: {}
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
cross-spawn@7.0.6: cross-spawn@7.0.6:
dependencies: dependencies:
path-key: 3.1.1 path-key: 3.1.1
@ -3522,6 +3591,8 @@ snapshots:
decimal.js-light@2.5.1: {} decimal.js-light@2.5.1: {}
delayed-stream@1.0.0: {}
detect-libc@2.1.2: {} detect-libc@2.1.2: {}
detect-node-es@1.1.0: {} detect-node-es@1.1.0: {}
@ -3574,6 +3645,13 @@ snapshots:
dependencies: dependencies:
es-errors: 1.3.0 es-errors: 1.3.0
es-set-tostringtag@2.1.0:
dependencies:
es-errors: 1.3.0
get-intrinsic: 1.3.0
has-tostringtag: 1.0.2
hasown: 2.0.2
escalade@3.2.0: {} escalade@3.2.0: {}
eventemitter3@4.0.7: {} eventemitter3@4.0.7: {}
@ -3592,6 +3670,14 @@ snapshots:
cross-spawn: 7.0.6 cross-spawn: 7.0.6
signal-exit: 4.1.0 signal-exit: 4.1.0
form-data@4.0.5:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
es-set-tostringtag: 2.1.0
hasown: 2.0.2
mime-types: 2.1.35
formdata-polyfill@4.0.10: formdata-polyfill@4.0.10:
dependencies: dependencies:
fetch-blob: 3.2.0 fetch-blob: 3.2.0
@ -3690,6 +3776,10 @@ snapshots:
has-symbols@1.1.0: {} has-symbols@1.1.0: {}
has-tostringtag@1.0.2:
dependencies:
has-symbols: 1.1.0
hasown@2.0.2: hasown@2.0.2:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
@ -3804,6 +3894,12 @@ snapshots:
math-intrinsics@1.1.0: {} math-intrinsics@1.1.0: {}
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
minimatch@9.0.5: minimatch@9.0.5:
dependencies: dependencies:
brace-expansion: 2.0.2 brace-expansion: 2.0.2
@ -3844,6 +3940,10 @@ snapshots:
node-domexception@1.0.0: {} node-domexception@1.0.0: {}
node-fetch@2.7.0:
dependencies:
whatwg-url: 5.0.0
node-fetch@3.3.2: node-fetch@3.3.2:
dependencies: dependencies:
data-uri-to-buffer: 4.0.1 data-uri-to-buffer: 4.0.1
@ -4036,6 +4136,8 @@ snapshots:
dependencies: dependencies:
glob: 10.5.0 glob: 10.5.0
ruply@1.0.1: {}
safe-buffer@5.2.1: {} safe-buffer@5.2.1: {}
scheduler@0.27.0: {} scheduler@0.27.0: {}
@ -4140,12 +4242,6 @@ snapshots:
dependencies: dependencies:
ansi-regex: 6.2.2 ansi-regex: 6.2.2
stripe@20.1.0(@types/node@22.19.3):
dependencies:
qs: 6.14.0
optionalDependencies:
'@types/node': 22.19.3
styled-jsx@5.1.6(react@19.2.0): styled-jsx@5.1.6(react@19.2.0):
dependencies: dependencies:
client-only: 0.0.1 client-only: 0.0.1
@ -4163,6 +4259,8 @@ snapshots:
tiny-invariant@1.3.3: {} tiny-invariant@1.3.3: {}
tr46@0.0.3: {}
tslib@2.8.1: {} tslib@2.8.1: {}
tw-animate-css@1.3.3: {} tw-animate-css@1.3.3: {}
@ -4226,6 +4324,13 @@ snapshots:
web-streams-polyfill@3.3.3: {} web-streams-polyfill@3.3.3: {}
webidl-conversions@3.0.1: {}
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
which@2.0.2: which@2.0.2:
dependencies: dependencies:
isexe: 2.0.0 isexe: 2.0.0