refactor: remove all Stripe references — Commerce is payment provider

Stripe banned Hanzo. Removed Stripe SDK, webhook handlers, hardcoded
keys, and StripePricingTable. Billing redirects to billing.hanzo.ai.
This commit is contained in:
2026-03-04 10:57:40 -08:00
parent 23448f36b5
commit c9ab3f7ce4
11 changed files with 98 additions and 708 deletions
+2 -15
View File
@@ -1,18 +1,5 @@
# Stripe Configuration
# Get these from your Stripe dashboard: https://dashboard.stripe.com/apikeys
# Publishable key (safe for client-side)
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key_here
# Price IDs for credit packs
# Create these in Stripe Dashboard > Products
VITE_STRIPE_STARTER_PRICE_ID=price_1234567890starter
VITE_STRIPE_PROFESSIONAL_PRICE_ID=price_1234567890professional
VITE_STRIPE_ENTERPRISE_PRICE_ID=price_1234567890enterprise
# Price IDs for services
VITE_STRIPE_AGENCY_PRICE_ID=price_1234567890agency
VITE_STRIPE_INSTANT_SITE_PRICE_ID=price_1234567890instant
# Commerce API
VITE_COMMERCE_API_URL=https://commerce.hanzo.ai/api/v1
# Other environment variables
VITE_API_URL=http://localhost:3000
-178
View File
@@ -1,178 +0,0 @@
# Stripe Payment Integration
This guide explains how to set up and use the Stripe payment integration for the Hanzo Agency website.
## Setup Instructions
### 1. Create a Stripe Account
1. Go to [https://stripe.com](https://stripe.com) and create an account
2. Once logged in, navigate to the [Stripe Dashboard](https://dashboard.stripe.com)
### 2. Get Your API Keys
1. In the Stripe Dashboard, go to **Developers** > **API keys**
2. Copy your **Publishable key** (starts with `pk_test_` or `pk_live_`)
3. Keep your **Secret key** safe (you'll need it for backend integration)
### 3. Create Products and Prices
In the Stripe Dashboard, go to **Products** and create the following:
#### Credit Packs:
1. **Starter Pack**
- Name: "100 Credits - Starter Pack"
- Price: $100.00 (one-time)
- Copy the price ID (starts with `price_`)
2. **Professional Pack**
- Name: "500 Credits - Professional Pack"
- Price: $450.00 (one-time)
- Copy the price ID
3. **Enterprise Pack**
- Name: "2000 Credits - Enterprise Pack"
- Price: $1,500.00 (one-time)
- Copy the price ID
#### Services:
1. **Agency Service**
- Name: "Full-Service Creative Agency"
- Price: $5,000.00/month (recurring)
- Copy the price ID
2. **Instant Site**
- Name: "Instant Site - 24hr Delivery"
- Price: $500.00 (one-time)
- Copy the price ID
### 4. Configure Environment Variables
1. Create a `.env` file in the project root (copy from `.env.example`)
2. Add your Stripe configuration:
```env
# Stripe Configuration
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_your_key_here
# Credit Pack Price IDs
VITE_STRIPE_STARTER_PRICE_ID=price_xxxxx
VITE_STRIPE_PROFESSIONAL_PRICE_ID=price_xxxxx
VITE_STRIPE_ENTERPRISE_PRICE_ID=price_xxxxx
# Service Price IDs
VITE_STRIPE_AGENCY_PRICE_ID=price_xxxxx
VITE_STRIPE_INSTANT_SITE_PRICE_ID=price_xxxxx
```
### 5. Install Dependencies
```bash
npm install
# or
pnpm install
```
## Testing the Integration
### Test Credit Cards
Use these test card numbers in test mode:
- **Success**: 4242 4242 4242 4242
- **Decline**: 4000 0000 0000 0002
- **Requires Authentication**: 4000 0025 0000 3155
Use any future date for expiry and any 3 digits for CVC.
### Testing Flow
1. Go to `/login` and create a test account
2. Navigate to `/dashboard`
3. Click "Purchase Credits"
4. Select a credit pack and quantity
5. Click "Continue to Payment"
6. You'll be redirected to Stripe Checkout
7. Use a test card to complete payment
8. You'll be redirected back to `/success` or `/cancel`
## Current Implementation
### Client-Side Only
The current implementation uses Stripe's client-side checkout, which is suitable for:
- Simple payment flows
- Fixed-price products
- Public pricing
### Limitations
- No server-side validation
- Credits are managed in localStorage (not secure for production)
- No webhook handling for payment confirmation
- No customer management
## Production Recommendations
For production use, you should:
1. **Add Backend API**
- Create checkout sessions server-side
- Validate payments with webhooks
- Store credits in a database
- Manage customer records
2. **Security Improvements**
- Never expose secret keys
- Validate all payments server-side
- Use webhooks to confirm payments
- Implement proper authentication
3. **Enhanced Features**
- Customer portal for managing subscriptions
- Invoice generation
- Email receipts
- Refund handling
## File Structure
```
src/
├── lib/
│ └── stripe.ts # Stripe configuration and helpers
├── pages/
│ ├── PurchaseCredits.tsx # Credit purchase page
│ ├── PaymentSuccess.tsx # Success redirect page
│ ├── PaymentCancel.tsx # Cancel redirect page
│ └── Payment.tsx # General payment page
└── App.tsx # Routes configuration
```
## Troubleshooting
### "Stripe not loaded" error
- Check that your publishable key is correct
- Ensure you're using the correct environment (test/live)
### Payment fails
- Check the browser console for errors
- Verify price IDs are correct
- Ensure products are active in Stripe Dashboard
### Success page shows error
- This demo uses localStorage, so make sure it's not blocked
- In production, implement proper backend validation
## Next Steps
1. Set up a backend server (Node.js, Python, etc.)
2. Implement Stripe webhooks
3. Create a database for user and credit management
4. Add customer portal integration
5. Implement subscription management for agency services
## Support
- [Stripe Documentation](https://stripe.com/docs)
- [Stripe Support](https://support.stripe.com)
- [API Reference](https://stripe.com/docs/api)
-78
View File
@@ -1,78 +0,0 @@
# Stripe Product Configuration
## Products Created in Stripe Dashboard
The following products have been created in your live Stripe account:
### Credit Packs (One-time payments)
1. **100 Credits - Starter Pack**
- Price ID: `price_1QvKqDJ03IK6WYmUxGzK5DXg`
- Amount: $100.00 USD
- Type: One-time payment
2. **500 Credits - Professional Pack**
- Price ID: `price_1QvKqWJ03IK6WYmUJX9PqvHM`
- Amount: $450.00 USD
- Type: One-time payment
- Savings: $50 (10% off)
3. **2000 Credits - Enterprise Pack**
- Price ID: `price_1QvKqiJ03IK6WYmU7vwMNGLC`
- Amount: $1,500.00 USD
- Type: One-time payment
- Savings: $500 (25% off)
### Services
4. **Agency Service**
- Price ID: `price_1QvKr3J03IK6WYmUbCwK4LFp`
- Amount: $5,000.00 USD
- Type: Monthly subscription
5. **Instant Site**
- Price ID: `price_1QvKrHJ03IK6WYmUYKq0Zx8n`
- Amount: $500.00 USD
- Type: One-time payment
## Webhook Configuration
Your webhook endpoint has been configured in Stripe:
- Endpoint URL: `https://your-domain.vercel.app/api/stripe-webhook`
- Webhook Secret: `PURGED`
## Environment Variables for Vercel
Add these to your Vercel project settings:
```
STRIPE_SECRET_KEY=PURGED
STRIPE_WEBHOOK_SECRET=PURGED
```
## Analytics Events Tracked
- Page views
- Product selection
- Add to cart
- Checkout initiation
- Purchase completion
- User registration
- User login
- Form submissions
## Testing
To test the integration:
1. Go to `/purchase-credits`
2. Select a credit pack
3. Click "Continue to Payment"
4. Complete the Stripe checkout
5. You'll be redirected back to `/success`
## Security Notes
- Secret keys are only used in serverless functions
- All payment processing happens server-side
- Webhook signatures are verified
- Analytics track all key conversion events
-82
View File
@@ -1,82 +0,0 @@
import { VercelRequest, VercelResponse } from '@vercel/node';
import Stripe from 'stripe';
// Initialize Stripe with your secret key
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || 'PURGED', {
apiVersion: '2023-10-16',
});
// Plan-to-price mapping (server-side source of truth)
const PLAN_PRICES: Record<string, { priceId: string; mode: 'payment' | 'subscription' }> = {
'agency': {
priceId: 'price_1QvKr3J03IK6WYmUbCwK4LFp',
mode: 'subscription',
},
'instant-site': {
priceId: 'price_1QvKrHJ03IK6WYmUYKq0Zx8n',
mode: 'payment',
},
'enterprise': {
priceId: 'price_1QvKrZJ03IK6WYmUeNtP9Kx2',
mode: 'subscription',
},
};
export default async function handler(req: VercelRequest, res: VercelResponse) {
// Handle CORS preflight
if (req.method === 'OPTIONS') {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
return res.status(200).json({});
}
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const { plan, priceId, mode, quantity, successUrl, cancelUrl, metadata, customerEmail } = req.body;
// Determine price ID and mode from plan name or explicit params
let resolvedPriceId = priceId;
let resolvedMode: 'payment' | 'subscription' = mode || 'payment';
if (plan && PLAN_PRICES[plan]) {
resolvedPriceId = PLAN_PRICES[plan].priceId;
resolvedMode = PLAN_PRICES[plan].mode;
}
if (!resolvedPriceId) {
return res.status(400).json({ error: 'Missing priceId or valid plan name' });
}
// Build session params
const sessionParams: Stripe.Checkout.SessionCreateParams = {
payment_method_types: ['card'],
line_items: [{
price: resolvedPriceId,
quantity: quantity || 1,
}],
mode: resolvedMode,
success_url: successUrl || `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: cancelUrl || `${req.headers.origin}/cancel`,
metadata: metadata || {},
billing_address_collection: 'required',
};
// Attach customer email if provided
if (customerEmail || metadata?.email) {
sessionParams.customer_email = customerEmail || metadata.email;
}
const session = await stripe.checkout.sessions.create(sessionParams);
res.setHeader('Access-Control-Allow-Origin', '*');
res.status(200).json({ sessionId: session.id, url: session.url });
} catch (error: any) {
console.error('Stripe error:', error);
res.setHeader('Access-Control-Allow-Origin', '*');
res.status(500).json({ error: error.message });
}
}
-64
View File
@@ -1,64 +0,0 @@
import { VercelRequest, VercelResponse } from '@vercel/node';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || 'PURGED', {
apiVersion: '2023-10-16',
});
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET || 'PURGED';
export default async function handler(req: VercelRequest, res: VercelResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const sig = req.headers['stripe-signature'] as string;
let event: Stripe.Event;
try {
// Verify webhook signature
const rawBody = JSON.stringify(req.body);
event = stripe.webhooks.constructEvent(rawBody, sig, endpointSecret);
} catch (err: any) {
console.error('Webhook signature verification failed:', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
switch (event.type) {
case 'checkout.session.completed':
const session = event.data.object as Stripe.Checkout.Session;
// Process the successful payment
console.log('Payment successful:', {
sessionId: session.id,
customerId: session.customer,
amount: session.amount_total,
metadata: session.metadata
});
// Here you would typically:
// 1. Update user credits in your database
// 2. Send confirmation email
// 3. Log the transaction
// For now, we'll just log it
break;
case 'payment_intent.succeeded':
const paymentIntent = event.data.object as Stripe.PaymentIntent;
console.log('Payment intent succeeded:', paymentIntent.id);
break;
case 'payment_intent.payment_failed':
const failedPayment = event.data.object as Stripe.PaymentIntent;
console.log('Payment failed:', failedPayment.id);
break;
default:
console.log(`Unhandled event type ${event.type}`);
}
// Return a 200 response to acknowledge receipt of the event
res.status(200).json({ received: true });
}
-2
View File
@@ -41,8 +41,6 @@
"@radix-ui/react-toggle-group": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@rollup/rollup-darwin-x64": "^4.37.0",
"@stripe/react-stripe-js": "^2.4.0",
"@stripe/stripe-js": "^2.4.0",
"@tanstack/react-query": "^5.69.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+88
View File
@@ -0,0 +1,88 @@
import { analytics } from '@/utils/analytics';
// --- Commerce API (Square, crypto, wire) ---
const COMMERCE_API = 'https://commerce.hanzo.ai/api/v1';
export type PaymentMethod = 'card' | 'crypto' | 'wire';
export type CommerceCheckoutResult =
| { type: 'redirect'; url: string; sessionId: string }
| { type: 'wire'; instructions: Record<string, unknown> };
// Plan configuration for agency checkout
const PLAN_CONFIG: Record<string, {
name: string;
price: number;
amount: number;
}> = {
'agency': {
name: 'Agency Service',
price: 9999,
amount: 999900,
},
'instant-site': {
name: 'Instant Site',
price: 500,
amount: 50000,
},
'enterprise': {
name: 'Enterprise',
price: 9999,
amount: 999900,
},
};
export async function createCommerceCheckout(
plan: string,
options: { email: string; name: string; paymentMethod?: PaymentMethod }
): Promise<CommerceCheckoutResult> {
const { email, name, paymentMethod = 'card' } = options;
if (paymentMethod === 'wire') {
const res = await fetch(`${COMMERCE_API}/checkout/wire/instructions?org=hanzo`);
if (!res.ok) throw new Error(`Wire instructions request failed: ${res.status}`);
const data = await res.json();
return { type: 'wire' as const, instructions: data };
}
const config = PLAN_CONFIG[plan];
const res = await fetch(`${COMMERCE_API}/checkout/sessions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
org: 'hanzo',
providerHint: paymentMethod === 'crypto' ? 'ethereum' : 'square',
currency: 'USD',
customer: { email, name },
items: [{
name: config?.name || plan,
amount: config?.amount || 999900,
quantity: 1,
}],
successUrl: `${window.location.origin}/onboarding-success`,
cancelUrl: `${window.location.origin}/pricing`,
}),
});
if (!res.ok) {
const body = await res.text();
throw new Error(`Commerce checkout failed: ${body}`);
}
const data = await res.json();
return { type: 'redirect' as const, url: data.checkoutUrl, sessionId: data.sessionId };
}
// Helper to format currency
export const formatCurrency = (amount: number): string => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount / 100);
};
// Track successful purchase (to be called on success page)
export const trackPurchaseSuccess = (sessionId: string, products: any[], totalAmount: number) => {
analytics.trackPurchase(sessionId, products, totalAmount);
};
-236
View File
@@ -1,236 +0,0 @@
import { loadStripe } from '@stripe/stripe-js';
import { analytics } from '@/utils/analytics';
// Initialize Stripe with your publishable key
const stripePromise = loadStripe(
import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY ||
'PURGED'
);
// API endpoint (will use relative URL in production)
const API_URL = import.meta.env.VITE_API_URL || '';
// Helper function to create a Stripe Checkout session via API
export const createCheckoutSession = async (
priceId: string,
quantity: number = 1,
metadata?: any
) => {
try {
const response = await fetch(`${API_URL}/api/create-checkout-session`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
priceId,
quantity,
metadata,
successUrl: `${window.location.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
cancelUrl: `${window.location.origin}/cancel`,
}),
});
if (!response.ok) {
throw new Error('Failed to create checkout session');
}
const { sessionId, url } = await response.json();
// Track checkout initiation
analytics.trackBeginCheckout([{
id: priceId,
name: metadata?.productName || 'Credit Pack',
category: 'credits',
price: metadata?.price || 0,
quantity: quantity
}], metadata?.totalPrice || 0);
// Redirect to Stripe Checkout
if (url) {
window.location.href = url;
} else {
// Fallback to client-side redirect
const stripe = await stripePromise;
if (!stripe) throw new Error('Stripe failed to load');
const { error } = await stripe.redirectToCheckout({ sessionId });
if (error) throw error;
}
} catch (error) {
console.error('Checkout error:', error);
throw error;
}
};
// Price IDs - Create these in your Stripe Dashboard
export const STRIPE_PRICE_IDS = {
// Credit Packs (create these as one-time products in Stripe)
STARTER_100_CREDITS: 'price_1QvKqDJ03IK6WYmUxGzK5DXg', // $100
PROFESSIONAL_500_CREDITS: 'price_1QvKqWJ03IK6WYmUJX9PqvHM', // $450
ENTERPRISE_2000_CREDITS: 'price_1QvKqiJ03IK6WYmU7vwMNGLC', // $1,500
// Services (create these in Stripe)
AGENCY_MONTHLY: 'price_1QvKr3J03IK6WYmUbCwK4LFp', // $9,999/month recurring
INSTANT_SITE: 'price_1QvKrHJ03IK6WYmUYKq0Zx8n', // $500 one-time
ENTERPRISE_MONTHLY: 'price_1QvKrZJ03IK6WYmUeNtP9Kx2', // $9,999/month recurring
};
// Plan configuration for agency checkout
const PLAN_CONFIG: Record<string, {
priceId: string;
mode: 'subscription' | 'payment';
name: string;
price: number;
amount: number;
}> = {
'agency': {
priceId: STRIPE_PRICE_IDS.AGENCY_MONTHLY,
mode: 'subscription' as const,
name: 'Agency Service',
price: 9999,
amount: 999900,
},
'instant-site': {
priceId: STRIPE_PRICE_IDS.INSTANT_SITE,
mode: 'payment' as const,
name: 'Instant Site',
price: 500,
amount: 50000,
},
'enterprise': {
priceId: STRIPE_PRICE_IDS.ENTERPRISE_MONTHLY,
mode: 'subscription' as const,
name: 'Enterprise',
price: 9999,
amount: 999900,
},
};
// --- Commerce API (Square, crypto, wire) ---
const COMMERCE_API = 'https://commerce.hanzo.ai/api/v1';
export type PaymentMethod = 'card' | 'crypto' | 'wire';
export type CommerceCheckoutResult =
| { type: 'redirect'; url: string; sessionId: string }
| { type: 'wire'; instructions: Record<string, unknown> };
export async function createCommerceCheckout(
plan: string,
options: { email: string; name: string; paymentMethod?: PaymentMethod }
): Promise<CommerceCheckoutResult> {
const { email, name, paymentMethod = 'card' } = options;
if (paymentMethod === 'wire') {
const res = await fetch(`${COMMERCE_API}/checkout/wire/instructions?org=hanzo`);
if (!res.ok) throw new Error(`Wire instructions request failed: ${res.status}`);
const data = await res.json();
return { type: 'wire' as const, instructions: data };
}
const config = PLAN_CONFIG[plan];
const res = await fetch(`${COMMERCE_API}/checkout/sessions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
org: 'hanzo',
providerHint: paymentMethod === 'crypto' ? 'ethereum' : 'square',
currency: 'USD',
customer: { email, name },
items: [{
name: config?.name || plan,
amount: config?.amount || 999900,
quantity: 1,
}],
successUrl: `${window.location.origin}/onboarding-success`,
cancelUrl: `${window.location.origin}/pricing`,
}),
});
if (!res.ok) {
const body = await res.text();
throw new Error(`Commerce checkout failed: ${body}`);
}
const data = await res.json();
return { type: 'redirect' as const, url: data.checkoutUrl, sessionId: data.sessionId };
}
// Create a Stripe Checkout session for agency plans
export const createAgencyCheckout = async (
plan: keyof typeof PLAN_CONFIG,
customer: { email: string; name: string }
) => {
const config = PLAN_CONFIG[plan];
if (!config) throw new Error(`Unknown plan: ${plan}`);
try {
const successPath = plan === 'instant-site' ? '/instant-site-form' : '/onboarding-success';
const response = await fetch(`${API_URL}/api/create-checkout-session`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
plan,
priceId: config.priceId,
mode: config.mode,
quantity: 1,
metadata: {
plan,
productName: config.name,
email: customer.email,
customerName: customer.name,
},
customerEmail: customer.email,
successUrl: `${window.location.origin}${successPath}?session_id={CHECKOUT_SESSION_ID}`,
cancelUrl: `${window.location.origin}/payment?plan=${plan}`,
}),
});
if (!response.ok) {
const errBody = await response.text();
throw new Error(`Checkout session failed: ${errBody}`);
}
const { sessionId, url } = await response.json();
// Track checkout initiation
analytics.trackBeginCheckout([{
id: config.priceId,
name: config.name,
category: 'agency_services',
price: config.price,
quantity: 1,
}], config.price);
// Redirect to Stripe Checkout
if (url) {
window.location.href = url;
} else {
const stripe = await stripePromise;
if (!stripe) throw new Error('Stripe failed to load');
const { error } = await stripe.redirectToCheckout({ sessionId });
if (error) throw error;
}
} catch (error) {
console.error('Agency checkout error:', error);
throw error;
}
};
// Helper to format currency
export const formatCurrency = (amount: number): string => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount / 100);
};
// Track successful purchase (to be called on success page)
export const trackPurchaseSuccess = (sessionId: string, products: any[], totalAmount: number) => {
analytics.trackPurchase(sessionId, products, totalAmount);
};
+1 -14
View File
@@ -5,8 +5,7 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Check, Lock, Mail, Loader2, CreditCard, Coins, Landmark } from 'lucide-react';
import { useLocation, Link } from 'react-router-dom';
import { createAgencyCheckout } from '@/lib/stripe';
import { createCommerceCheckout, type PaymentMethod, type CommerceCheckoutResult } from '@/lib/stripe';
import { createCommerceCheckout, type PaymentMethod, type CommerceCheckoutResult } from '@/lib/commerce';
import { useToast } from '@/hooks/use-toast';
const PAYMENT_METHODS: { value: PaymentMethod; label: string; icon: React.ElementType; desc: string }[] = [
@@ -58,18 +57,6 @@ const Payment = () => {
}
} catch (error) {
console.error('Checkout error:', error);
// Fall back to legacy Stripe checkout for card payments
if (paymentMethod === 'card') {
try {
await createAgencyCheckout(plan, {
email: formData.email,
name: formData.name,
});
return; // Stripe will redirect
} catch (fallbackError) {
console.error('Stripe fallback error:', fallbackError);
}
}
toast({
title: 'Checkout Failed',
description: 'Unable to start checkout. Please try again.',
+2 -2
View File
@@ -3,7 +3,7 @@ import { useNavigate, useSearchParams, Link } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import Footer from '@/components/Footer';
import { CheckCircle, ArrowRight, Loader2, AlertCircle } from 'lucide-react';
import { trackPurchaseSuccess } from '@/lib/stripe';
import { trackPurchaseSuccess } from '@/lib/commerce';
import { analytics } from '@/utils/analytics';
const PaymentSuccess = () => {
@@ -47,7 +47,7 @@ const PaymentSuccess = () => {
amount: details.amount,
description: `Purchased ${details.credits} credits`,
date: new Date().toISOString(),
stripeSessionId: sessionId
checkoutSessionId: sessionId
});
localStorage.setItem('creditHistory', JSON.stringify(history));
+4 -36
View File
@@ -10,7 +10,7 @@ import {
TrendingUp,
Shield
} from 'lucide-react';
import { createCheckoutSession, STRIPE_PRICE_IDS } from '@/lib/stripe';
import { formatCurrency } from '@/lib/commerce';
import { useToast } from '@/hooks/use-toast';
import { analytics } from '@/utils/analytics';
@@ -40,7 +40,6 @@ const PurchaseCredits = () => {
price: 100,
perCredit: 1.00,
icon: Zap,
priceId: STRIPE_PRICE_IDS.STARTER_100_CREDITS,
features: [
'100 credits',
'$1 per credit',
@@ -55,7 +54,6 @@ const PurchaseCredits = () => {
perCredit: 0.90,
savings: 50,
icon: TrendingUp,
priceId: STRIPE_PRICE_IDS.PROFESSIONAL_500_CREDITS,
features: [
'500 credits',
'$0.90 per credit',
@@ -70,7 +68,6 @@ const PurchaseCredits = () => {
perCredit: 0.75,
savings: 500,
icon: Star,
priceId: STRIPE_PRICE_IDS.ENTERPRISE_2000_CREDITS,
features: [
'2,000 credits',
'$0.75 per credit',
@@ -111,38 +108,9 @@ const PurchaseCredits = () => {
const pack = creditPacks.find(p => p.id === selectedPack);
if (!pack) return;
try {
// Save purchase info for the success page
const purchaseData = {
packId: selectedPack,
credits: pack.credits * quantity,
amount: pack.price * quantity,
quantity: quantity,
productName: `${pack.credits} Credits Pack`,
unitPrice: pack.price
};
localStorage.setItem('pendingPurchase', JSON.stringify(purchaseData));
// Create checkout session via API
await createCheckoutSession(pack.priceId, quantity, {
userId: localStorage.getItem('userId'),
email: localStorage.getItem('userEmail'),
packId: selectedPack,
credits: pack.credits * quantity,
productName: `${pack.credits} Credits Pack`,
price: pack.price,
totalPrice: pack.price * quantity
});
} catch (error) {
console.error('Checkout error:', error);
toast({
title: 'Checkout Failed',
description: 'Unable to process payment. Please try again.',
variant: 'destructive'
});
// Redirect to billing portal for credit purchases
window.location.href = `https://billing.hanzo.ai?pack=${selectedPack}&quantity=${quantity}`;
setIsLoading(false);
}
};
const selectedPackDetails = creditPacks.find(p => p.id === selectedPack);
@@ -294,7 +262,7 @@ const PurchaseCredits = () => {
<div className="mt-4 flex items-center justify-center gap-2 text-sm text-gray-400">
<Shield className="h-4 w-4" />
<span>Secure payment powered by Stripe</span>
<span>Secure payment via Hanzo Billing</span>
</div>
</div>
</div>