@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
JavaScript
;
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)))] }));
}