React frontend with: - Authentication UI - Trading dashboard - ML signals display - Portfolio management Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
394 lines
11 KiB
TypeScript
394 lines
11 KiB
TypeScript
/**
|
|
* Payment Service
|
|
* API client for subscriptions, payments, billing, and wallet
|
|
*/
|
|
|
|
import axios from 'axios';
|
|
import type {
|
|
PricingPlan,
|
|
Subscription,
|
|
SubscriptionWithPlan,
|
|
Payment,
|
|
PaymentMethod,
|
|
Invoice,
|
|
Wallet,
|
|
WalletTransaction,
|
|
CreateSubscriptionInput,
|
|
CheckoutSession,
|
|
SubscriptionPreview,
|
|
BillingInfo,
|
|
UsageStats,
|
|
ApiResponse,
|
|
SubscriptionResponse,
|
|
WalletResponse,
|
|
PlanInterval,
|
|
} from '../types/payment.types';
|
|
|
|
const API_BASE_URL = import.meta.env?.VITE_API_URL || '/api/v1';
|
|
|
|
const api = axios.create({
|
|
baseURL: API_BASE_URL,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
|
|
// Add auth token to requests
|
|
api.interceptors.request.use((config) => {
|
|
const token = localStorage.getItem('auth_token');
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`;
|
|
}
|
|
return config;
|
|
});
|
|
|
|
// ============================================================================
|
|
// Pricing Plans
|
|
// ============================================================================
|
|
|
|
export async function getPlans(): Promise<PricingPlan[]> {
|
|
const response = await api.get<ApiResponse<PricingPlan[]>>('/payments/plans');
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function getPlanBySlug(slug: string): Promise<PricingPlan> {
|
|
const response = await api.get<ApiResponse<PricingPlan>>(`/payments/plans/${slug}`);
|
|
return response.data.data;
|
|
}
|
|
|
|
// Alias for backwards compatibility - backend uses slug for lookup
|
|
export async function getPlanById(planId: string): Promise<PricingPlan> {
|
|
return getPlanBySlug(planId);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Subscriptions
|
|
// ============================================================================
|
|
|
|
export async function getCurrentSubscription(): Promise<SubscriptionWithPlan | null> {
|
|
try {
|
|
const response = await api.get<ApiResponse<SubscriptionWithPlan>>(
|
|
'/payments/subscription'
|
|
);
|
|
return response.data.data;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function getSubscriptionHistory(): Promise<Subscription[]> {
|
|
const response = await api.get<ApiResponse<Subscription[]>>('/payments/subscription/history');
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function createSubscription(
|
|
input: CreateSubscriptionInput
|
|
): Promise<SubscriptionResponse> {
|
|
const response = await api.post<ApiResponse<SubscriptionResponse>>(
|
|
'/payments/subscriptions',
|
|
input
|
|
);
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function cancelSubscription(
|
|
_subscriptionId: string,
|
|
immediately = false
|
|
): Promise<Subscription> {
|
|
const response = await api.post<ApiResponse<Subscription>>(
|
|
'/payments/subscription/cancel',
|
|
{ immediately }
|
|
);
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function reactivateSubscription(_subscriptionId: string): Promise<Subscription> {
|
|
const response = await api.post<ApiResponse<Subscription>>(
|
|
'/payments/subscription/resume'
|
|
);
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function changeSubscriptionPlan(
|
|
_subscriptionId: string,
|
|
newPlanId: string,
|
|
newInterval: PlanInterval
|
|
): Promise<Subscription> {
|
|
const response = await api.post<ApiResponse<Subscription>>(
|
|
'/payments/subscription/change-plan',
|
|
{ planId: newPlanId, billingCycle: newInterval === 'year' ? 'yearly' : 'monthly' }
|
|
);
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function previewSubscriptionChange(
|
|
planId: string,
|
|
interval: PlanInterval,
|
|
couponCode?: string
|
|
): Promise<SubscriptionPreview> {
|
|
// Note: Backend doesn't have a preview endpoint, so we return a mock for now
|
|
// This would need to be implemented in the backend
|
|
return {
|
|
subtotal: 0,
|
|
discount: 0,
|
|
tax: 0,
|
|
total: 0,
|
|
currency: 'USD',
|
|
interval,
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// Checkout
|
|
// ============================================================================
|
|
|
|
export async function createCheckoutSession(
|
|
planId: string,
|
|
interval: PlanInterval,
|
|
successUrl?: string,
|
|
cancelUrl?: string
|
|
): Promise<CheckoutSession> {
|
|
const response = await api.post<ApiResponse<CheckoutSession>>('/payments/checkout', {
|
|
planId,
|
|
billingCycle: interval === 'year' ? 'yearly' : 'monthly',
|
|
successUrl: successUrl || `${window.location.origin}/settings/billing?success=true`,
|
|
cancelUrl: cancelUrl || `${window.location.origin}/pricing?canceled=true`,
|
|
});
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function createPortalSession(returnUrl?: string): Promise<{ url: string }> {
|
|
const response = await api.post<ApiResponse<{ url: string }>>('/payments/billing-portal', {
|
|
returnUrl: returnUrl || `${window.location.origin}/settings/billing`,
|
|
});
|
|
return response.data.data;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Payment Methods
|
|
// ============================================================================
|
|
|
|
export async function getPaymentMethods(): Promise<PaymentMethod[]> {
|
|
const response = await api.get<ApiResponse<PaymentMethod[]>>('/payments/methods');
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function addPaymentMethod(paymentMethodId: string): Promise<PaymentMethod> {
|
|
const response = await api.post<ApiResponse<PaymentMethod>>('/payments/methods', {
|
|
paymentMethodId,
|
|
});
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function setDefaultPaymentMethod(paymentMethodId: string): Promise<PaymentMethod> {
|
|
const response = await api.post<ApiResponse<PaymentMethod>>(
|
|
'/payments/methods/default',
|
|
{ paymentMethodId }
|
|
);
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function removePaymentMethod(paymentMethodId: string): Promise<void> {
|
|
await api.delete(`/payments/methods/${paymentMethodId}`);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Payments & Invoices
|
|
// ============================================================================
|
|
|
|
export async function getPaymentHistory(
|
|
limit = 20,
|
|
offset = 0
|
|
): Promise<{ payments: Payment[]; total: number }> {
|
|
const response = await api.get<ApiResponse<{ payments: Payment[]; total: number }>>(
|
|
'/payments/history',
|
|
{ params: { limit, offset } }
|
|
);
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function getInvoices(
|
|
limit = 20,
|
|
offset = 0
|
|
): Promise<{ invoices: Invoice[]; total: number }> {
|
|
const response = await api.get<ApiResponse<{ invoices: Invoice[]; total: number }>>(
|
|
'/payments/invoices',
|
|
{ params: { limit, offset } }
|
|
);
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function getInvoiceById(invoiceId: string): Promise<Invoice> {
|
|
const response = await api.get<ApiResponse<Invoice>>(`/payments/invoices/${invoiceId}`);
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function downloadInvoice(invoiceId: string): Promise<Blob> {
|
|
const response = await api.get(`/payments/invoices/${invoiceId}/pdf`, {
|
|
responseType: 'blob',
|
|
});
|
|
return response.data;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Billing Info
|
|
// ============================================================================
|
|
|
|
export async function getBillingInfo(): Promise<BillingInfo | null> {
|
|
try {
|
|
const response = await api.get<ApiResponse<BillingInfo>>('/payments/billing-info');
|
|
return response.data.data;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function updateBillingInfo(info: BillingInfo): Promise<BillingInfo> {
|
|
const response = await api.put<ApiResponse<BillingInfo>>('/payments/billing-info', info);
|
|
return response.data.data;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Usage Stats
|
|
// ============================================================================
|
|
|
|
export async function getUsageStats(): Promise<UsageStats> {
|
|
const response = await api.get<ApiResponse<UsageStats>>('/payments/usage');
|
|
return response.data.data;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Wallet
|
|
// ============================================================================
|
|
|
|
export async function getWallet(): Promise<WalletResponse> {
|
|
const response = await api.get<ApiResponse<WalletResponse>>('/payments/wallet');
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function getWalletTransactions(
|
|
limit = 20,
|
|
offset = 0,
|
|
type?: string
|
|
): Promise<{ transactions: WalletTransaction[]; total: number }> {
|
|
const response = await api.get<
|
|
ApiResponse<{ transactions: WalletTransaction[]; total: number }>
|
|
>('/payments/wallet/transactions', { params: { limit, offset, type } });
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function depositToWallet(
|
|
amount: number,
|
|
paymentMethodId: string
|
|
): Promise<{ transaction: WalletTransaction; clientSecret?: string }> {
|
|
const response = await api.post<
|
|
ApiResponse<{ transaction: WalletTransaction; clientSecret?: string }>
|
|
>('/payments/wallet/deposit', { amount, paymentMethodId });
|
|
return response.data.data;
|
|
}
|
|
|
|
export async function withdrawFromWallet(
|
|
amount: number,
|
|
destination: { type: 'bank_account'; accountId: string }
|
|
): Promise<WalletTransaction> {
|
|
const response = await api.post<ApiResponse<WalletTransaction>>('/payments/wallet/withdraw', {
|
|
amount,
|
|
destination,
|
|
});
|
|
return response.data.data;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Coupons
|
|
// ============================================================================
|
|
|
|
export async function validateCoupon(
|
|
code: string,
|
|
planId: string
|
|
): Promise<{
|
|
valid: boolean;
|
|
discount: number;
|
|
discountType: 'percent' | 'amount';
|
|
message?: string;
|
|
}> {
|
|
const response = await api.post<
|
|
ApiResponse<{
|
|
valid: boolean;
|
|
discount: number;
|
|
discountType: 'percent' | 'amount';
|
|
message?: string;
|
|
}>
|
|
>('/payments/coupons/validate', { code, planId });
|
|
return response.data.data;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Summary & Dashboard
|
|
// ============================================================================
|
|
|
|
export async function getBillingSummary(): Promise<{
|
|
subscription: SubscriptionWithPlan | null;
|
|
nextBillingDate: string | null;
|
|
nextBillingAmount: number | null;
|
|
usage: UsageStats;
|
|
recentPayments: Payment[];
|
|
wallet: Wallet | null;
|
|
}> {
|
|
const response = await api.get<
|
|
ApiResponse<{
|
|
subscription: SubscriptionWithPlan | null;
|
|
nextBillingDate: string | null;
|
|
nextBillingAmount: number | null;
|
|
usage: UsageStats;
|
|
recentPayments: Payment[];
|
|
wallet: Wallet | null;
|
|
}>
|
|
>('/payments/summary');
|
|
return response.data.data;
|
|
}
|
|
|
|
// Export service object for convenience
|
|
export const paymentService = {
|
|
// Plans
|
|
getPlans,
|
|
getPlanById,
|
|
getPlanBySlug,
|
|
// Subscriptions
|
|
getCurrentSubscription,
|
|
getSubscriptionHistory,
|
|
createSubscription,
|
|
cancelSubscription,
|
|
reactivateSubscription,
|
|
changeSubscriptionPlan,
|
|
previewSubscriptionChange,
|
|
// Checkout
|
|
createCheckoutSession,
|
|
createPortalSession,
|
|
// Payment Methods
|
|
getPaymentMethods,
|
|
addPaymentMethod,
|
|
setDefaultPaymentMethod,
|
|
removePaymentMethod,
|
|
// Payments & Invoices
|
|
getPaymentHistory,
|
|
getInvoices,
|
|
getInvoiceById,
|
|
downloadInvoice,
|
|
// Billing Info
|
|
getBillingInfo,
|
|
updateBillingInfo,
|
|
// Usage
|
|
getUsageStats,
|
|
// Wallet
|
|
getWallet,
|
|
getWalletTransactions,
|
|
depositToWallet,
|
|
withdrawFromWallet,
|
|
// Coupons
|
|
validateCoupon,
|
|
// Summary
|
|
getBillingSummary,
|
|
};
|
|
|
|
export default paymentService;
|