puffin-app/src/api/checkoutClient.ts
Matt cfa7e88ed2
Some checks failed
Build and Push Docker Images / docker (push) Failing after 2m20s
Remove all build-time variables and secure Wren API
BREAKING CHANGE: All environment variables are now runtime-configurable

Changes:
- Removed ALL build-time NEXT_PUBLIC_* variables from Dockerfile and CI/CD
- Created server-side proxy routes for Wren API (/api/wren/*)
- Refactored wrenClient.ts to use proxy endpoints (reduced from 400+ to 200 lines)
- Updated checkoutClient.ts and emailClient.ts to remove NEXT_PUBLIC_ fallbacks
- Hardcoded metadataBase in layout.tsx (no longer depends on env var)
- Updated .env.local to use runtime-only variables (WREN_API_TOKEN, NocoDB config)

Security improvements:
- Wren API token never exposed to browser
- All secrets stay server-side
- No sensitive data baked into build

Configuration:
- Wren API: Set WREN_API_TOKEN in docker-compose or .env
- NocoDB: Set NOCODB_* variables in docker-compose or .env
- No Gitea secrets/variables needed for build (only registry credentials)

Docker build is now truly environment-agnostic - same image works in
any environment with different runtime configuration.
2025-11-03 11:03:42 +01:00

119 lines
3.4 KiB
TypeScript

import axios from 'axios';
import { logger } from '../utils/logger';
// Get API base URL from runtime config (window.env)
// IMPORTANT: Call this function at REQUEST TIME, not at module load time,
// to ensure window.env is populated by env-config.js
const getApiBaseUrl = (): string => {
// Check window.env first (runtime config from env.sh or docker-compose)
if (typeof window !== 'undefined' && window.env?.API_BASE_URL) {
return window.env.API_BASE_URL;
}
// Fall back to production default if not configured
// All configuration is now runtime-only via environment variables
return 'https://puffinoffset.com/api';
};
export interface CreateCheckoutSessionParams {
tons: number;
portfolioId: number;
pricePerTon: number; // Price per ton from Wren API
customerEmail?: string;
}
export interface CheckoutSessionResponse {
sessionId: string;
url: string;
orderId: string;
}
export interface OrderDetails {
order: {
id: string;
tons: number;
portfolioId: number;
baseAmount: number;
processingFee: number;
totalAmount: number;
currency: string;
status: string;
wrenOrderId: string | null;
stripeSessionId: string;
createdAt: string;
};
session: {
paymentStatus: string;
customerEmail?: string;
};
}
/**
* Create a Stripe checkout session
* @param params Checkout session parameters
* @returns Checkout session response with redirect URL
*/
export async function createCheckoutSession(
params: CreateCheckoutSessionParams
): Promise<CheckoutSessionResponse> {
try {
const apiBaseUrl = getApiBaseUrl(); // Lazy evaluate at request time
logger.info('Creating checkout session:', params);
logger.info('Using API base URL:', apiBaseUrl);
const response = await axios.post<CheckoutSessionResponse>(
`${apiBaseUrl}/api/checkout/create-session`,
params
);
logger.info('Checkout session created:', response.data.sessionId);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
logger.error('Checkout session creation failed:', error.response?.data || error.message);
throw new Error(error.response?.data?.message || 'Failed to create checkout session');
}
throw error;
}
}
/**
* Get order details by session ID
* @param sessionId Stripe session ID
* @returns Order details
*/
export async function getOrderDetails(sessionId: string): Promise<OrderDetails> {
try {
const apiBaseUrl = getApiBaseUrl(); // Lazy evaluate at request time
logger.info('Fetching order details for session:', sessionId);
const response = await axios.get<OrderDetails>(
`${apiBaseUrl}/api/checkout/session/${sessionId}`
);
logger.info('Order details retrieved:', response.data.order.id);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
logger.error('Order details retrieval failed:', error.response?.data || error.message);
throw new Error(error.response?.data?.message || 'Failed to retrieve order details');
}
throw error;
}
}
/**
* Health check for backend API
* @returns true if backend is healthy
*/
export async function checkBackendHealth(): Promise<boolean> {
try {
const apiBaseUrl = getApiBaseUrl(); // Lazy evaluate at request time
const response = await axios.get(`${apiBaseUrl}/health`);
return response.data.status === 'ok';
} catch (error) {
logger.error('Backend health check failed:', error);
return false;
}
}