UNPKG

@vegaci_shared/vsplit-payment-gateway

Version:

A B2B payment gateway integration package for Stripe with split payment capabilities

368 lines (367 loc) 15.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VSplitProvider = VSplitProvider; exports.useVSplit = useVSplit; exports.PaymentForm = PaymentForm; exports.SplitPayment = SplitPayment; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = require("react"); const core_1 = require("./core"); const VSplitContext = (0, react_1.createContext)(null); function VSplitProvider({ config, children }) { const [gateway, setGateway] = (0, react_1.useState)(null); const [initialized, setInitialized] = (0, react_1.useState)(false); (0, react_1.useEffect)(() => { const initGateway = async () => { try { const gatewayInstance = new core_1.VSplitPaymentGateway(config); setGateway(gatewayInstance); setInitialized(true); } catch (error) { console.error('Failed to initialize VSplit gateway:', error); } }; initGateway(); return () => { if (gateway) { gateway.destroy(); } }; }, [config]); return ((0, jsx_runtime_1.jsx)(VSplitContext.Provider, { value: { gateway, initialized }, children: children })); } /** * VSplit Hook */ function useVSplit(options = {}) { const context = (0, react_1.useContext)(VSplitContext); if (!context) { throw new Error('useVSplit must be used within a VSplitProvider'); } const { gateway } = context; const [status, setStatus] = (0, react_1.useState)('pending'); const [loading, setLoading] = (0, react_1.useState)(false); const [error, setError] = (0, react_1.useState)(null); const [currentSession, setCurrentSession] = (0, react_1.useState)(null); const [stripe, setStripe] = (0, react_1.useState)(null); const [elements, setElements] = (0, react_1.useState)(null); // Get Stripe instance (0, react_1.useEffect)(() => { if (gateway) { setStripe(gateway.getStripe()); } }, [gateway]); // Event listeners (0, react_1.useEffect)(() => { if (!gateway) return; const handleSuccess = (result) => { setStatus('succeeded'); setLoading(false); setError(null); }; const handleFailed = (result) => { setStatus('failed'); setLoading(false); setError(result.error || 'Payment failed'); }; const handleRequiresAction = (result) => { setStatus('requires_action'); setLoading(false); }; const handleError = (err) => { setError(err.message); setLoading(false); }; gateway.on('payment:success', handleSuccess); gateway.on('payment:failed', handleFailed); gateway.on('payment:requires_action', handleRequiresAction); gateway.on('error', handleError); return () => { gateway.off('payment:success', handleSuccess); gateway.off('payment:failed', handleFailed); gateway.off('payment:requires_action', handleRequiresAction); gateway.off('error', handleError); }; }, [gateway]); // Initialize payment const initializePayment = (0, react_1.useCallback)(async (config) => { if (!gateway) { throw new Error('Gateway not initialized'); } setLoading(true); setError(null); setStatus('pending'); try { const result = await gateway.initializePayment(config); if (result.success && result.data) { setCurrentSession(result.data); setStatus(result.status); // Create elements if client secret is available if (result.data.clientSecret && stripe) { const elementsInstance = gateway.createElements(result.data.clientSecret); setElements(elementsInstance); } } return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; setError(errorMessage); setStatus('failed'); return { success: false, paymentId: '', status: 'failed', error: errorMessage, }; } finally { setLoading(false); } }, [gateway, stripe]); // Process payment const processPayment = (0, react_1.useCallback)(async (paymentMethod) => { if (!gateway || !currentSession || !('id' in currentSession)) { throw new Error('Invalid payment session'); } setLoading(true); setError(null); try { const result = await gateway.processPayment(currentSession.id, paymentMethod); setStatus(result.status); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; setError(errorMessage); setStatus('failed'); return { success: false, paymentId: currentSession.id, status: 'failed', error: errorMessage, }; } finally { setLoading(false); } }, [gateway, currentSession]); // Initialize split payment const initializeSplitPayment = (0, react_1.useCallback)(async (config) => { if (!gateway) { throw new Error('Gateway not initialized'); } setLoading(true); setError(null); setStatus('pending'); try { const session = await gateway.initializeSplitPayment(config); setCurrentSession(session); setStatus(session.status); return session; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; setError(errorMessage); setStatus('failed'); throw error; } finally { setLoading(false); } }, [gateway]); // Process split payment const processSplitPayment = (0, react_1.useCallback)(async (splitIndex, paymentMethod) => { if (!gateway || !currentSession || !('sessionId' in currentSession)) { throw new Error('Invalid split payment session'); } setLoading(true); setError(null); try { const result = await gateway.processSplitPayment(currentSession.sessionId, splitIndex, paymentMethod); // Update session status const updatedSession = gateway.getCurrentSession(); if (updatedSession) { setCurrentSession(updatedSession); setStatus(updatedSession.status); } return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; setError(errorMessage); return { success: false, paymentId: currentSession.sessionId, status: 'failed', error: errorMessage, }; } finally { setLoading(false); } }, [gateway, currentSession]); // Cancel payment const cancelPayment = (0, react_1.useCallback)(async () => { if (!gateway) { throw new Error('Gateway not initialized'); } setLoading(true); try { await gateway.cancelPayment(); setCurrentSession(null); setStatus('canceled'); setElements(null); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; setError(errorMessage); throw error; } finally { setLoading(false); } }, [gateway]); return { initializePayment, processPayment, initializeSplitPayment, processSplitPayment, cancelPayment, status, loading, error, currentSession, stripe, elements, }; } function PaymentForm({ onSuccess, onError, className, disabled, }) { const { processPayment, elements, stripe, loading, error } = useVSplit(); const [localLoading, setLocalLoading] = (0, react_1.useState)(false); const handleSubmit = async (event) => { event.preventDefault(); if (!stripe || !elements) { return; } setLocalLoading(true); try { const { error: submitError } = await elements.submit(); if (submitError) { throw new Error(submitError.message); } const result = await processPayment({ id: 'payment-method', type: 'card', }); if (result.success) { onSuccess?.(result); } else { throw new Error(result.error || 'Payment failed'); } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Unknown error'; onError?.(errorMessage); } finally { setLocalLoading(false); } }; if (!elements) { return (0, jsx_runtime_1.jsx)("div", { children: "Loading payment form..." }); } return ((0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, className: className, children: [(0, jsx_runtime_1.jsx)("div", { id: "payment-element" }), error && ((0, jsx_runtime_1.jsx)("div", { className: "error-message", style: { color: 'red', marginTop: '10px' }, children: error })), (0, jsx_runtime_1.jsx)("button", { type: "submit", disabled: disabled || loading || localLoading || !stripe, style: { marginTop: '20px', padding: '12px 24px', backgroundColor: disabled || loading || localLoading ? '#ccc' : '#007bff', color: 'white', border: 'none', borderRadius: '4px', cursor: disabled || loading || localLoading ? 'not-allowed' : 'pointer', }, children: localLoading || loading ? 'Processing...' : 'Pay Now' })] })); } function SplitPayment({ splits, onStepComplete, onAllComplete, onError, className, }) { const { initializeSplitPayment, processSplitPayment, currentSession, loading, error, } = useVSplit(); const [currentStep, setCurrentStep] = (0, react_1.useState)(0); const [completedSteps, setCompletedSteps] = (0, react_1.useState)(new Array(splits.length).fill(false)); const [initialized, setInitialized] = (0, react_1.useState)(false); // Initialize split payment on mount (0, react_1.useEffect)(() => { const initialize = async () => { try { const totalAmount = splits.reduce((sum, split) => sum + split.amount, 0); const config = { totalAmount, numberOfCards: splits.length, cardAmounts: splits.map((split) => split.amount), currency: 'usd', orderId: `split_${Date.now()}`, timeout: 600, }; await initializeSplitPayment(config); setInitialized(true); } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to initialize split payment'; onError?.(errorMessage); } }; if (!initialized) { initialize(); } }, [initializeSplitPayment, splits, initialized, onError]); const handleStepPayment = async (stepIndex, paymentMethod) => { try { const result = await processSplitPayment(stepIndex, paymentMethod); if (result.success) { const newCompletedSteps = [...completedSteps]; newCompletedSteps[stepIndex] = true; setCompletedSteps(newCompletedSteps); onStepComplete?.(stepIndex, result); // Check if all steps are completed if (newCompletedSteps.every((completed) => completed)) { const session = currentSession; onAllComplete?.(session); } else { // Move to next step const nextStep = newCompletedSteps.findIndex((completed) => !completed); if (nextStep !== -1) { setCurrentStep(nextStep); } } } else { onError?.(result.error || 'Payment failed'); } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Payment processing failed'; onError?.(errorMessage); } }; if (!initialized || loading) { return (0, jsx_runtime_1.jsx)("div", { children: "Initializing split payment..." }); } if (error) { return ((0, jsx_runtime_1.jsx)("div", { className: "error-message", style: { color: 'red' }, children: error })); } return ((0, jsx_runtime_1.jsxs)("div", { className: className, children: [(0, jsx_runtime_1.jsxs)("div", { className: "split-payment-progress", children: [(0, jsx_runtime_1.jsxs)("h3", { children: ["Payment Progress: ", completedSteps.filter(Boolean).length, " of", ' ', splits.length, " completed"] }), (0, jsx_runtime_1.jsx)("div", { className: "progress-bar", style: { display: 'flex', gap: '5px', marginBottom: '20px' }, children: splits.map((_, index) => ((0, jsx_runtime_1.jsx)("div", { style: { flex: 1, height: '10px', backgroundColor: completedSteps[index] ? '#28a745' : '#e9ecef', borderRadius: '5px', } }, index))) })] }), splits.map((split, index) => ((0, jsx_runtime_1.jsxs)("div", { className: `split-step ${index === currentStep ? 'active' : ''} ${completedSteps[index] ? 'completed' : ''}`, style: { padding: '20px', border: '1px solid #ddd', borderRadius: '8px', marginBottom: '10px', opacity: index === currentStep ? 1 : 0.6, }, children: [(0, jsx_runtime_1.jsxs)("h4", { children: ["Step ", index + 1, ": ", split.label || `Payment ${index + 1}`, completedSteps[index] && ' ✓'] }), (0, jsx_runtime_1.jsxs)("p", { children: ["Amount: $", (split.amount / 100).toFixed(2)] }), index === currentStep && !completedSteps[index] && ((0, jsx_runtime_1.jsx)(PaymentForm, { onSuccess: (result) => handleStepPayment(index, result), onError: onError }))] }, index)))] })); }