stripe 1/2
This commit is contained in:
parent
5fbffdee5e
commit
8731ea7cf9
@ -26,5 +26,7 @@ export interface EcommerceBundleDependencies {
|
||||
stripeReturnUrl?: string;
|
||||
/** Currency code for Stripe (default: "eur"). */
|
||||
currency?: string;
|
||||
/** Returns the current auth token for authenticated API calls. */
|
||||
getAuthToken?: () => Promise<string | null>;
|
||||
}
|
||||
export declare const EcommerceBundle: React.FC<EcommerceBundleDependencies>;
|
||||
|
||||
@ -36,5 +36,7 @@ export interface CheckoutFlowProps {
|
||||
stripeReturnUrl?: string;
|
||||
/** Currency code for Stripe payments (default: "eur"). */
|
||||
currency?: string;
|
||||
/** Returns the current auth token for authenticated API calls. */
|
||||
getAuthToken?: () => Promise<string | null>;
|
||||
}
|
||||
export declare function CheckoutFlow({ userId, userDisplayName, userEmail, onFetchAddresses, onSaveAddress, onPlaceOrder, onBackToCart, onOrderSuccess, toast, stripePublishableKey, apiBaseUrl, stripeReturnUrl, currency, }: CheckoutFlowProps): import("react/jsx-runtime").JSX.Element;
|
||||
export declare function CheckoutFlow({ userId, userDisplayName, userEmail, onFetchAddresses, onSaveAddress, onPlaceOrder, onBackToCart, onOrderSuccess, toast, stripePublishableKey, apiBaseUrl, stripeReturnUrl, currency, getAuthToken, }: CheckoutFlowProps): import("react/jsx-runtime").JSX.Element;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -6,6 +6,9 @@ export interface Transaction {
|
||||
currency: string;
|
||||
product_info: any[];
|
||||
shipping_info: any;
|
||||
payment_provider?: string;
|
||||
external_order_id?: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
export interface PurchasesListProps {
|
||||
/** Async function to fetch user transactions. */
|
||||
|
||||
@ -36,6 +36,8 @@ export interface EcommerceBundleDependencies {
|
||||
stripeReturnUrl?: string;
|
||||
/** Currency code for Stripe (default: "eur"). */
|
||||
currency?: string;
|
||||
/** Returns the current auth token for authenticated API calls. */
|
||||
getAuthToken?: () => Promise<string | null>;
|
||||
}
|
||||
|
||||
export const EcommerceBundle: React.FC<EcommerceBundleDependencies> = (props) => {
|
||||
@ -61,6 +63,7 @@ export const EcommerceBundle: React.FC<EcommerceBundleDependencies> = (props) =>
|
||||
apiBaseUrl={props.apiBaseUrl}
|
||||
stripeReturnUrl={props.stripeReturnUrl}
|
||||
currency={props.currency}
|
||||
getAuthToken={props.getAuthToken}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -40,6 +40,8 @@ export interface CheckoutFlowProps {
|
||||
stripeReturnUrl?: string;
|
||||
/** Currency code for Stripe payments (default: "eur"). */
|
||||
currency?: string;
|
||||
/** Returns the current auth token for authenticated API calls. */
|
||||
getAuthToken?: () => Promise<string | null>;
|
||||
}
|
||||
|
||||
export function CheckoutFlow({
|
||||
@ -56,6 +58,7 @@ export function CheckoutFlow({
|
||||
apiBaseUrl = "",
|
||||
stripeReturnUrl,
|
||||
currency = "eur",
|
||||
getAuthToken,
|
||||
}: CheckoutFlowProps) {
|
||||
const [savedAddresses, setSavedAddresses] = useState<SavedAddress[]>([]);
|
||||
const [stripePromise, setStripePromise] = useState<Promise<StripeJS | null> | null>(null);
|
||||
@ -88,12 +91,22 @@ export function CheckoutFlow({
|
||||
// Stripe expects amount in smallest currency unit (cents for USD)
|
||||
const amountInCents = Math.round(subtotal * 100);
|
||||
|
||||
fetch(`${apiBaseUrl}/api/stripe/create-payment-intent`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ amount: amountInCents, currency }),
|
||||
})
|
||||
.then((r) => r.json())
|
||||
// Get auth token for the API call (needed for Stripe Customer creation)
|
||||
const makeRequest = async () => {
|
||||
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
||||
if (getAuthToken) {
|
||||
const token = await getAuthToken();
|
||||
if (token) headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
const r = await fetch(`${apiBaseUrl}/api/stripe/create-payment-intent`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({ amount: amountInCents, currency }),
|
||||
});
|
||||
return r.json();
|
||||
};
|
||||
|
||||
makeRequest()
|
||||
.then((data) => {
|
||||
if (mounted && data.clientSecret) {
|
||||
setClientSecret(data.clientSecret);
|
||||
@ -161,6 +174,9 @@ export function CheckoutFlow({
|
||||
},
|
||||
});
|
||||
toast?.success("Payment successful!");
|
||||
// Brief delay to let the Stripe webhook update the transaction
|
||||
// with receipt URL and status before navigating
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
onOrderSuccess();
|
||||
} catch (err) {
|
||||
console.error("Failed to record Stripe transaction:", err);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Package, Clock, CheckCircle, XCircle, ArrowLeft, ExternalLink, RefreshCw, Store } from "lucide-react";
|
||||
import { Package, Clock, CheckCircle, XCircle, ArrowLeft, ExternalLink, RefreshCw, Store, Receipt, FileText } from "lucide-react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@ -18,6 +18,9 @@ export interface Transaction {
|
||||
currency: string;
|
||||
product_info: any[];
|
||||
shipping_info: any;
|
||||
payment_provider?: string;
|
||||
external_order_id?: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface PurchasesListProps {
|
||||
@ -34,6 +37,7 @@ export interface PurchasesListProps {
|
||||
const statusConfig: Record<string, { label: string; variant: "default" | "secondary" | "destructive" | "outline"; icon: React.ReactNode }> = {
|
||||
pending: { label: "Pending", variant: "secondary", icon: <Clock className="h-3.5 w-3.5" /> },
|
||||
processing: { label: "Processing", variant: "default", icon: <RefreshCw className="h-3.5 w-3.5" /> },
|
||||
paid: { label: "Paid", variant: "default", icon: <CheckCircle className="h-3.5 w-3.5" /> },
|
||||
completed: { label: "Completed", variant: "default", icon: <CheckCircle className="h-3.5 w-3.5" /> },
|
||||
failed: { label: "Failed", variant: "destructive", icon: <XCircle className="h-3.5 w-3.5" /> },
|
||||
refunded: { label: "Refunded", variant: "outline", icon: <RefreshCw className="h-3.5 w-3.5" /> },
|
||||
@ -182,6 +186,34 @@ export function PurchasesList({ onFetchTransactions, onNavigate, toast }: Purcha
|
||||
Ships to: {(tx.shipping_info as any).fullName}, {(tx.shipping_info as any).city}, {(tx.shipping_info as any).country}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Receipt / Invoice links */}
|
||||
{tx.metadata && (tx.metadata.stripe_receipt_url || tx.metadata.stripe_invoice_url) && (
|
||||
<div className="mt-3 pt-3 border-t flex items-center gap-4">
|
||||
{tx.metadata.stripe_receipt_url && (
|
||||
<a
|
||||
href={tx.metadata.stripe_receipt_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-primary hover:underline flex items-center gap-1"
|
||||
>
|
||||
<Receipt className="h-3.5 w-3.5" />
|
||||
View Receipt
|
||||
</a>
|
||||
)}
|
||||
{tx.metadata.stripe_invoice_url && (
|
||||
<a
|
||||
href={tx.metadata.stripe_invoice_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-primary hover:underline flex items-center gap-1"
|
||||
>
|
||||
<FileText className="h-3.5 w-3.5" />
|
||||
View Invoice
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user