trading-platform-frontend-v2/src/services/payment.service.ts
rckrdmrd 5b53c2539a feat: Initial commit - Trading Platform Frontend
React frontend with:
- Authentication UI
- Trading dashboard
- ML signals display
- Portfolio management

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 04:30:39 -06:00

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;