@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
1,589 lines (1,556 loc) • 45.3 kB
JavaScript
"use strict";
import { useState, useRef, useMemo, useCallback } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Platform, ScrollView, Animated, Linking, Clipboard, useWindowDimensions } from 'react-native';
import { fontFamilies, useThemeColors, createCommonStyles } from '../styles';
import GroupedPillButtons from '../components/internal/GroupedPillButtons';
import TextField from '../components/internal/TextField';
import { Ionicons } from '@expo/vector-icons';
import { FAIRWalletIcon } from '../components/icon';
import { toast } from 'sonner';
import QRCode from 'react-native-qrcode-svg';
import { GroupedSection } from '../components';
// Restrict payment methods to Card, Oxy Pay, and FairCoin (QR)
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
const PAYMENT_METHODS = [{
key: 'card',
label: 'Credit/Debit Card',
icon: 'card-outline',
description: 'Pay securely with your credit or debit card.'
}, {
key: 'oxy',
label: 'Oxy Pay',
icon: 'wallet-outline',
description: 'Use your Oxy Pay in-app balance.'
}, {
key: 'faircoin',
label: 'FAIRWallet',
icon: 'qr-code-outline',
description: 'Pay with FairCoin by scanning a QR code.'
}];
// Add PaymentItem type
// Extend props to accept onPaymentResult, amount, and currency
// Currency symbol map
const CURRENCY_SYMBOLS = {
FAIR: '⊜',
INR: '₹',
USD: '$',
EUR: '€',
GBP: '£',
JPY: 'Â¥',
CNY: 'Â¥',
AUD: 'A$',
CAD: 'C$'
// Add more as needed
};
const CURRENCY_NAMES = {
FAIR: 'FairCoin',
INR: 'Indian Rupee',
USD: 'US Dollar',
EUR: 'Euro',
GBP: 'British Pound',
JPY: 'Japanese Yen',
CNY: 'Chinese Yuan',
AUD: 'Australian Dollar',
CAD: 'Canadian Dollar'
// Add more as needed
};
// Helper: icon for item type (Ionicons only)
const getItemTypeIcon = (type, color) => {
switch (type) {
case 'product':
return /*#__PURE__*/_jsx(Ionicons, {
name: "cart-outline",
size: 22,
color: color,
style: {
marginRight: 8
}
});
case 'subscription':
return /*#__PURE__*/_jsx(Ionicons, {
name: "repeat-outline",
size: 22,
color: color,
style: {
marginRight: 8
}
});
case 'service':
return /*#__PURE__*/_jsx(Ionicons, {
name: "construct-outline",
size: 22,
color: color,
style: {
marginRight: 8
}
});
case 'fee':
return /*#__PURE__*/_jsx(Ionicons, {
name: "cash-outline",
size: 22,
color: color,
style: {
marginRight: 8
}
});
default:
return /*#__PURE__*/_jsx(Ionicons, {
name: "pricetag-outline",
size: 22,
color: color,
style: {
marginRight: 8
}
});
}
};
// Helper to get unique item types (move to top-level, before component)
const getUniqueItemTypes = items => {
const types = items.map(item => item.type);
return Array.from(new Set(types));
};
const PaymentGatewayScreen = props => {
const {
navigate,
goBack,
theme,
onPaymentResult,
amount,
currency = 'FAIR',
onClose,
paymentItems = [],
// NEW
description = '' // NEW
} = props;
// DEV ENFORCEMENT: Only allow one type of payment item
if (process.env.NODE_ENV !== 'production' && paymentItems.length > 0) {
const uniqueTypes = getUniqueItemTypes(paymentItems);
if (uniqueTypes.length > 1) {
throw new Error(`PaymentGatewayScreen: paymentItems contains mixed types (${uniqueTypes.join(', ')}). Only one type is allowed per payment.`);
}
}
// Step states
const [currentStep, setCurrentStep] = useState(0);
const [paymentMethod, setPaymentMethod] = useState('card');
const [cardDetails, setCardDetails] = useState({
number: '',
expiry: '',
cvv: ''
});
const [upiId, setUpiId] = useState('');
const [isPaying, setIsPaying] = useState(false);
const [success, setSuccess] = useState(false);
// Animations
const fadeAnim = useRef(new Animated.Value(1)).current;
const slideAnim = useRef(new Animated.Value(0)).current;
const scaleAnim = useRef(new Animated.Value(1)).current;
const progressAnim = useRef(new Animated.Value(0.2)).current;
const colors = useThemeColors(theme);
const commonStyles = createCommonStyles(theme);
const styles = useMemo(() => createStyles(colors, theme), [colors, theme]);
// Get symbol and name for currency
const currencySymbol = CURRENCY_SYMBOLS[currency.toUpperCase()] || currency;
const currencyName = CURRENCY_NAMES[currency.toUpperCase()] || currency;
// Calculate total from items if provided, else use amount
const computedTotal = useMemo(() => {
if (paymentItems && paymentItems.length > 0) {
return paymentItems.reduce((sum, item) => {
const qty = item.quantity ?? 1;
return sum + item.price * qty;
}, 0);
}
return Number(amount) || 0;
}, [paymentItems, amount]);
// Determine if the payment is for a recurring item (subscription)
const isRecurring = paymentItems.length > 0 && paymentItems[0].type === 'subscription';
// Filter payment methods: remove 'faircoin' if recurring
const availablePaymentMethods = useMemo(() => {
if (isRecurring) {
return PAYMENT_METHODS.filter(m => m.key !== 'faircoin');
}
return PAYMENT_METHODS;
}, [isRecurring]);
// Add after useState declarations
// Remove itemTypeError state, useEffect, and user-facing error in renderSummaryStep
// Helper to get unique item types
// Remove itemTypeError state, useEffect, and user-facing error in renderSummaryStep
// Validate item types on paymentItems change
// Remove itemTypeError state, useEffect, and user-facing error in renderSummaryStep
// Animation transitions
const animateTransition = useCallback(nextStep => {
Animated.timing(scaleAnim, {
toValue: 0.95,
duration: 150,
useNativeDriver: Platform.OS !== 'web'
}).start();
Animated.timing(fadeAnim, {
toValue: 0,
duration: 200,
useNativeDriver: Platform.OS !== 'web'
}).start(() => {
setCurrentStep(nextStep);
slideAnim.setValue(-50);
scaleAnim.setValue(0.95);
Animated.parallel([Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: Platform.OS !== 'web'
}), Animated.spring(slideAnim, {
toValue: 0,
tension: 80,
friction: 8,
useNativeDriver: Platform.OS !== 'web'
}), Animated.spring(scaleAnim, {
toValue: 1,
tension: 80,
friction: 8,
useNativeDriver: Platform.OS !== 'web'
})]).start();
});
}, []);
const nextStep = useCallback(() => {
if (currentStep < 4) {
Animated.timing(progressAnim, {
toValue: (currentStep + 2) / 5,
duration: 300,
useNativeDriver: false
}).start();
animateTransition(currentStep + 1);
}
}, [currentStep, progressAnim, animateTransition]);
const prevStep = useCallback(() => {
if (currentStep > 0) {
Animated.timing(progressAnim, {
toValue: currentStep / 5,
duration: 300,
useNativeDriver: false
}).start();
animateTransition(currentStep - 1);
}
}, [currentStep, progressAnim, animateTransition]);
// Dummy pay handler
const handlePay = useCallback(() => {
setIsPaying(true);
setTimeout(() => {
setIsPaying(false);
setSuccess(true);
nextStep();
}, 1500);
}, [nextStep]);
// Success handler for Done button
const handleDone = useCallback(() => {
if (onPaymentResult) {
onPaymentResult({
success: true
});
}
navigate('AccountOverview');
}, [onPaymentResult, navigate]);
// Handle close/cancel: return failure result if payment is not completed
const handleClose = useCallback(() => {
if (onPaymentResult) {
onPaymentResult({
success: false,
error: 'cancelled'
});
}
if (onClose) {
onClose();
} else if (goBack) {
goBack();
}
}, [onPaymentResult, onClose, goBack]);
// Optionally, intercept goBack/onClose if user tries to close the screen
// (You may want to use useEffect to listen for unmount or navigation away)
// If amount is missing or invalid, show error
if (!amount || isNaN(Number(amount)) || Number(amount) <= 0) {
return /*#__PURE__*/_jsxs(View, {
style: styles.errorContainer,
children: [/*#__PURE__*/_jsx(Text, {
style: styles.errorText,
children: "Invalid or missing payment amount."
}), /*#__PURE__*/_jsx(GroupedPillButtons, {
buttons: [{
text: 'Close',
onPress: handleClose,
icon: 'close',
variant: 'primary'
}],
colors: useThemeColors(theme)
})]
});
}
// Example FairCoin address (replace with real one)
const faircoinAddress = 'f1abc1234FAIRCOINADDRESS';
const {
width: windowWidth
} = useWindowDimensions();
const isMobile = windowWidth < 600;
const qrSize = !isMobile ? Math.min(windowWidth * 0.3, 220) : Math.min(windowWidth * 0.8, 300);
const handleCopyAddress = () => {
Clipboard.setString(faircoinAddress);
toast('Address copied to clipboard!');
};
const handleOpenFairWallet = () => {
const url = `fairwallet://pay?address=${faircoinAddress}`;
Linking.openURL(url);
};
// Helper for dynamic styles
const getStepIndicatorStyle = active => [styles.stepIndicator, active ? styles.stepIndicatorActive : styles.stepIndicatorInactive];
const getPaymentMethodButtonStyle = active => [styles.paymentMethodButton, active ? styles.paymentMethodButtonActive : styles.paymentMethodButtonInactive];
const getPaymentMethodIconColor = active => active ? colors.primary : colors.text;
// Step indicator
const renderStepIndicator = () => {
const totalSteps = 5;
const activeStep = currentStep + 1;
return /*#__PURE__*/_jsx(View, {
style: styles.stepIndicatorContainer,
children: Array.from({
length: totalSteps
}).map((_, idx) => /*#__PURE__*/_jsx(View, {
style: getStepIndicatorStyle(activeStep === idx + 1)
}, idx))
});
};
// PaymentGatewayHeader component
const stepTitles = ['Complete Your Payment', 'Select Payment Method', 'Enter Payment Details', 'Review & Pay', 'Success'];
// Step 1: Summary step (new first step, no header/dots here)
const renderSummaryStep = () => /*#__PURE__*/_jsxs(Animated.View, {
style: [styles.stepContainer, {
opacity: fadeAnim,
transform: [{
translateY: slideAnim
}, {
scale: scaleAnim
}]
}],
children: [/*#__PURE__*/_jsxs(View, {
style: styles.section,
children: [/*#__PURE__*/_jsx(Text, {
style: styles.sectionTitle,
children: "Payment Summary"
}), /*#__PURE__*/_jsx(View, {
style: styles.summaryCard,
children: /*#__PURE__*/_jsxs(View, {
style: styles.summaryCardContent,
children: [/*#__PURE__*/_jsx(Ionicons, {
name: "receipt-outline",
size: 64,
color: colors.primary,
style: styles.summaryCardIcon
}), /*#__PURE__*/_jsx(Text, {
style: styles.summaryCardMainTitle,
children: paymentItems && paymentItems.length > 0 ? 'Order Summary' : 'Payment'
}), /*#__PURE__*/_jsx(Text, {
style: styles.summaryCardSubtitle,
children: paymentItems && paymentItems.length > 0 ? 'Review your payment details' : 'Complete your payment'
}), paymentItems && paymentItems.length > 0 ? /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(View, {
style: styles.summaryCardItems,
children: /*#__PURE__*/_jsx(GroupedSection, {
items: paymentItems.map((item, idx) => ({
id: `item-${idx}`,
icon: getItemTypeIcon(item.type, colors.primary).props.name,
iconColor: colors.primary,
title: `${item.type === 'product' && item.quantity ? `${item.quantity} × ` : ''}${item.name}${item.type === 'subscription' && item.period ? ` (${item.period})` : ''}`,
subtitle: item.description || `${item.currency ? CURRENCY_SYMBOLS[item.currency.toUpperCase()] || item.currency : currencySymbol} ${item.price * (item.quantity ?? 1)}`,
customContent: /*#__PURE__*/_jsxs(Text, {
style: styles.summaryItemPrice,
children: [item.currency ? CURRENCY_SYMBOLS[item.currency.toUpperCase()] || item.currency : currencySymbol, " ", item.price * (item.quantity ?? 1)]
})
})),
theme: theme
})
}), /*#__PURE__*/_jsx(View, {
style: styles.summaryCardDivider
}), /*#__PURE__*/_jsxs(View, {
style: styles.summaryCardTotalSection,
children: [/*#__PURE__*/_jsxs(View, {
style: styles.summaryCardTotalRow,
children: [/*#__PURE__*/_jsx(Text, {
style: styles.summaryCardTotalLabel,
children: "Subtotal"
}), /*#__PURE__*/_jsxs(Text, {
style: styles.summaryCardTotalValue,
children: [currencySymbol, " ", amount]
})]
}), /*#__PURE__*/_jsxs(View, {
style: styles.summaryCardTotalRow,
children: [/*#__PURE__*/_jsx(Text, {
style: styles.summaryCardTotalLabel,
children: "Tax"
}), /*#__PURE__*/_jsxs(Text, {
style: styles.summaryCardTotalValue,
children: [currencySymbol, " 0.00"]
})]
}), /*#__PURE__*/_jsxs(View, {
style: styles.summaryCardTotalRow,
children: [/*#__PURE__*/_jsx(Text, {
style: styles.summaryCardTotalLabel,
children: "Total"
}), /*#__PURE__*/_jsxs(Text, {
style: styles.summaryCardTotalValue,
children: [currencySymbol, " ", amount]
})]
})]
})]
}) : /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsxs(View, {
style: styles.summaryCardAmount,
children: [/*#__PURE__*/_jsx(Text, {
style: styles.summaryCardAmountLabel,
children: "Amount to Pay"
}), /*#__PURE__*/_jsxs(Text, {
style: styles.summaryCardAmountValue,
children: [currencySymbol, " ", amount]
}), description && /*#__PURE__*/_jsx(Text, {
style: styles.summaryCardAmountDescription,
children: description
})]
}), /*#__PURE__*/_jsx(View, {
style: styles.summaryCardDivider
}), /*#__PURE__*/_jsx(View, {
style: styles.summaryCardTotalSection,
children: /*#__PURE__*/_jsxs(View, {
style: styles.summaryCardTotalRow,
children: [/*#__PURE__*/_jsx(Text, {
style: styles.summaryCardTotalLabel,
children: "Total"
}), /*#__PURE__*/_jsxs(Text, {
style: styles.summaryCardTotalValue,
children: [currencySymbol, " ", amount]
})]
})
})]
})]
})
})]
}), /*#__PURE__*/_jsx(GroupedPillButtons, {
buttons: [{
text: 'Close',
onPress: handleClose,
icon: 'close',
variant: 'transparent'
}, {
text: 'Continue',
onPress: nextStep,
icon: 'arrow-forward',
variant: 'primary'
}],
colors: colors
})]
});
// Step 2: Choose Payment Method (now the second step, no header/dots here)
const renderMethodStep = () => /*#__PURE__*/_jsxs(Animated.View, {
style: [styles.stepContainer, {
opacity: fadeAnim,
transform: [{
translateY: slideAnim
}, {
scale: scaleAnim
}]
}],
children: [/*#__PURE__*/_jsxs(View, {
style: styles.section,
children: [/*#__PURE__*/_jsx(Text, {
style: styles.sectionTitle,
children: "Choose Payment Method"
}), /*#__PURE__*/_jsx(GroupedSection, {
items: availablePaymentMethods.map(method => ({
id: method.key,
icon: method.key === 'faircoin' ? undefined : method.icon,
iconColor: method.key === 'card' ? '#007AFF' : method.key === 'oxy' ? '#32D74B' : method.key === 'faircoin' ? '#9ffb50' : colors.primary,
title: method.label,
subtitle: method.description,
onPress: () => setPaymentMethod(method.key),
selected: paymentMethod === method.key,
showChevron: false,
customIcon: method.key === 'faircoin' ? /*#__PURE__*/_jsx(FAIRWalletIcon, {
size: 20
}) : undefined
})),
theme: theme
})]
}), /*#__PURE__*/_jsx(GroupedPillButtons, {
buttons: [{
text: 'Back',
onPress: prevStep,
icon: 'arrow-back',
variant: 'transparent'
}, {
text: 'Continue',
onPress: nextStep,
icon: 'arrow-forward',
variant: 'primary'
}],
colors: colors
})]
});
// Step 2: Enter Payment Details
const renderDetailsStep = () => /*#__PURE__*/_jsxs(Animated.View, {
style: [styles.stepContainer, {
opacity: fadeAnim,
transform: [{
translateY: slideAnim
}, {
scale: scaleAnim
}]
}],
children: [/*#__PURE__*/_jsxs(View, {
style: styles.section,
children: [/*#__PURE__*/_jsx(Text, {
style: styles.sectionTitle,
children: paymentMethod === 'card' ? 'Card Details' : paymentMethod === 'oxy' ? 'Oxy Pay' : paymentMethod === 'faircoin' ? 'FairCoin Payment' : 'Payment Details'
}), paymentMethod === 'card' && /*#__PURE__*/_jsx(View, {
style: styles.cardPaymentCard,
children: /*#__PURE__*/_jsxs(View, {
style: styles.cardPaymentContent,
children: [/*#__PURE__*/_jsx(Ionicons, {
name: "card-outline",
size: 64,
color: colors.primary,
style: styles.cardPaymentIcon
}), /*#__PURE__*/_jsx(Text, {
style: styles.cardPaymentMainTitle,
children: "Credit Card"
}), /*#__PURE__*/_jsx(Text, {
style: styles.cardPaymentSubtitle,
children: "Enter your card details securely"
}), /*#__PURE__*/_jsxs(View, {
style: styles.cardPaymentFields,
children: [/*#__PURE__*/_jsxs(View, {
style: styles.cardRowInfo,
children: [/*#__PURE__*/_jsx(Ionicons, {
name: "card-outline",
size: 24,
color: colors.primary,
style: styles.cardRowIcon
}), /*#__PURE__*/_jsx(Text, {
style: styles.cardRowText,
children: "We accept Visa, Mastercard, and more"
})]
}), /*#__PURE__*/_jsx(TextField, {
value: cardDetails.number,
onChangeText: text => {
// Format card number with spaces
const formatted = text.replace(/\s/g, '').replace(/(\d{4})/g, '$1 ').trim();
setCardDetails({
...cardDetails,
number: formatted
});
},
placeholder: "1234 5678 9012 3456",
keyboardType: "numeric",
maxLength: 19,
style: styles.cardFieldContainer,
leading: /*#__PURE__*/_jsx(Ionicons, {
name: "card-outline",
size: 18,
color: colors.primary
})
}), /*#__PURE__*/_jsxs(View, {
style: styles.cardFieldRow,
children: [/*#__PURE__*/_jsx(TextField, {
value: cardDetails.expiry,
onChangeText: text => {
// Format expiry date
const formatted = text.replace(/\D/g, '').replace(/(\d{2})(\d)/, '$1/$2');
setCardDetails({
...cardDetails,
expiry: formatted
});
},
placeholder: "MM/YY",
maxLength: 5,
style: styles.cardFieldHalfLeft,
leading: /*#__PURE__*/_jsx(Ionicons, {
name: "calendar-outline",
size: 16,
color: colors.primary
})
}), /*#__PURE__*/_jsx(TextField, {
value: cardDetails.cvv,
onChangeText: text => {
// Only allow numbers
const formatted = text.replace(/\D/g, '');
setCardDetails({
...cardDetails,
cvv: formatted
});
},
placeholder: "123",
keyboardType: "numeric",
maxLength: 4,
style: styles.cardFieldHalfRight,
leading: /*#__PURE__*/_jsx(Ionicons, {
name: "lock-closed-outline",
size: 16,
color: colors.primary
})
})]
})]
}), /*#__PURE__*/_jsx(View, {
style: {
height: 18
}
}), /*#__PURE__*/_jsx(Text, {
style: styles.cardPaymentWaiting,
children: "Ready to process payment..."
})]
})
}), paymentMethod === 'oxy' && /*#__PURE__*/_jsx(View, {
style: styles.oxyPayCard,
children: /*#__PURE__*/_jsxs(View, {
style: styles.oxyPayContent,
children: [/*#__PURE__*/_jsx(Ionicons, {
name: "wallet-outline",
size: 64,
color: colors.primary,
style: styles.oxyPayIcon
}), /*#__PURE__*/_jsx(Text, {
style: styles.oxyPayMainTitle,
children: "Oxy Pay"
}), /*#__PURE__*/_jsx(Text, {
style: styles.oxyPaySubtitle,
children: "Pay with your in-app wallet"
}), /*#__PURE__*/_jsx(View, {
style: styles.oxyPayBalanceBox,
children: /*#__PURE__*/_jsx(Text, {
style: styles.oxyPayBalanceText,
children: "Balance: \u229C 123.45"
})
}), /*#__PURE__*/_jsx(View, {
style: {
height: 18
}
}), /*#__PURE__*/_jsx(Text, {
style: styles.oxyPayWaiting,
children: "Ready to process payment..."
})]
})
}), paymentMethod === 'faircoin' && /*#__PURE__*/_jsx(View, {
style: styles.faircoinCard,
children: /*#__PURE__*/_jsxs(View, {
style: styles.faircoinContent,
children: [/*#__PURE__*/_jsx(FAIRWalletIcon, {
size: 64,
style: styles.faircoinIcon
}), /*#__PURE__*/_jsx(Text, {
style: styles.faircoinMainTitle,
children: "FAIRWallet"
}), /*#__PURE__*/_jsx(Text, {
style: styles.faircoinSubtitle,
children: "Pay with FairCoin"
}), !isMobile ? /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(Text, {
style: styles.faircoinScanText,
children: "Scan to Pay"
}), /*#__PURE__*/_jsxs(View, {
style: styles.faircoinQRCard,
children: [/*#__PURE__*/_jsx(QRCode, {
value: faircoinAddress,
size: qrSize - 32
}), /*#__PURE__*/_jsx(View, {
style: styles.faircoinQRBadge,
children: /*#__PURE__*/_jsx(FAIRWalletIcon, {
size: 28
})
})]
})]
}) : /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(Text, {
style: styles.faircoinTitle,
children: "Use the options below to pay with FAIRWallet"
}), /*#__PURE__*/_jsx(Text, {
style: styles.faircoinAddress,
children: faircoinAddress
}), /*#__PURE__*/_jsxs(TouchableOpacity, {
style: [styles.faircoinButton, {
backgroundColor: '#9ffb50',
borderRadius: 18,
marginTop: 12,
width: '90%',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center'
}],
onPress: handleOpenFairWallet,
children: [/*#__PURE__*/_jsx(FAIRWalletIcon, {
size: 20,
style: {
marginRight: 8
}
}), /*#__PURE__*/_jsx(Text, {
style: [styles.faircoinButtonText, {
color: '#1b1f0a',
fontWeight: 'bold',
fontSize: 16
}],
children: "Open in FAIRWallet"
})]
}), /*#__PURE__*/_jsxs(TouchableOpacity, {
style: [styles.faircoinButton, {
backgroundColor: '#9ffb50',
borderRadius: 18,
marginTop: 10,
width: '90%',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center'
}],
onPress: handleCopyAddress,
children: [/*#__PURE__*/_jsx(FAIRWalletIcon, {
size: 20,
style: {
marginRight: 8
}
}), /*#__PURE__*/_jsx(Text, {
style: [styles.faircoinButtonText, {
color: '#1b1f0a',
fontWeight: 'bold',
fontSize: 16
}],
children: "Copy Address"
})]
})]
}), /*#__PURE__*/_jsx(View, {
style: {
height: 18
}
}), /*#__PURE__*/_jsx(Text, {
style: styles.faircoinWaiting,
children: "Waiting for payment..."
}), /*#__PURE__*/_jsx(Text, {
style: styles.faircoinPlaceholder,
children: "(This is a placeholder. Integrate with a QR code generator for production.)"
})]
})
})]
}), /*#__PURE__*/_jsx(GroupedPillButtons, {
buttons: [{
text: 'Back',
onPress: prevStep,
icon: 'arrow-back',
variant: 'transparent'
}, {
text: 'Continue',
onPress: nextStep,
icon: 'arrow-forward',
variant: 'primary',
disabled: paymentMethod === 'card' && (!cardDetails.number || !cardDetails.expiry || !cardDetails.cvv)
}],
colors: colors
})]
});
// Step 4: Review & Pay
const renderReviewStep = () => /*#__PURE__*/_jsxs(Animated.View, {
style: [styles.stepContainer, {
opacity: fadeAnim,
transform: [{
translateY: slideAnim
}, {
scale: scaleAnim
}]
}],
children: [/*#__PURE__*/_jsxs(View, {
style: styles.section,
children: [/*#__PURE__*/_jsx(Text, {
style: styles.sectionTitle,
children: "Review Payment"
}), /*#__PURE__*/_jsx(GroupedSection, {
items: [{
id: 'secure-payment',
icon: 'shield-checkmark',
iconColor: colors.success || '#4BB543',
title: 'Secure payment',
subtitle: 'Your payment is protected by industry-standard encryption'
}, {
id: 'amount',
icon: 'cash',
iconColor: colors.primary,
title: 'Amount',
subtitle: `${currencySymbol} ${amount}`
}, {
id: 'payment-method',
icon: PAYMENT_METHODS.find(m => m.key === paymentMethod)?.icon,
iconColor: colors.primary,
title: 'Payment Method',
subtitle: PAYMENT_METHODS.find(m => m.key === paymentMethod)?.label
}, ...(paymentMethod === 'card' ? [{
id: 'card-details',
icon: 'card',
iconColor: colors.primary,
title: 'Card',
subtitle: cardDetails.number.replace(/.(?=.{4})/g, '*')
}] : []), ...(paymentMethod === 'oxy' ? [{
id: 'oxy-balance',
icon: 'wallet',
iconColor: colors.primary,
title: 'Oxy Pay Account',
subtitle: 'Balance: ⊜ 123.45'
}] : []), ...(paymentMethod === 'faircoin' ? [{
id: 'faircoin-wallet',
icon: 'qr-code',
iconColor: colors.primary,
title: 'FairCoin Wallet',
subtitle: 'Paid via QR'
}] : [])],
theme: theme
})]
}), /*#__PURE__*/_jsx(GroupedPillButtons, {
buttons: [{
text: 'Back',
onPress: prevStep,
icon: 'arrow-back',
variant: 'transparent'
}, {
text: isPaying ? 'Processing...' : 'Pay Now',
onPress: handlePay,
icon: 'checkmark',
variant: 'primary',
loading: isPaying
}],
colors: colors
})]
});
// Step 5: Success
const renderSuccessStep = () => /*#__PURE__*/_jsxs(Animated.View, {
style: [styles.stepContainer, {
opacity: fadeAnim,
transform: [{
translateY: slideAnim
}, {
scale: scaleAnim
}]
}],
children: [/*#__PURE__*/_jsxs(View, {
style: styles.section,
children: [/*#__PURE__*/_jsx(Text, {
style: styles.sectionTitle,
children: "Payment Complete"
}), /*#__PURE__*/_jsx(View, {
style: styles.successCard,
children: /*#__PURE__*/_jsxs(View, {
style: styles.successContent,
children: [/*#__PURE__*/_jsx(Ionicons, {
name: "checkmark-circle",
size: 64,
color: colors.success || '#4BB543',
style: styles.successIcon
}), /*#__PURE__*/_jsx(Text, {
style: styles.successMainTitle,
children: "Payment Successful!"
}), /*#__PURE__*/_jsx(Text, {
style: styles.successSubtitle,
children: "Thank you for your payment."
}), /*#__PURE__*/_jsx(View, {
style: {
height: 18
}
}), /*#__PURE__*/_jsx(Text, {
style: styles.successMessage,
children: "Your transaction has been processed successfully."
})]
})
})]
}), /*#__PURE__*/_jsx(GroupedPillButtons, {
buttons: [{
text: 'Done',
onPress: handleDone,
icon: 'checkmark',
variant: 'primary'
}],
colors: colors
})]
});
const renderCurrentStep = () => {
switch (currentStep) {
case 0:
return renderSummaryStep();
case 1:
return renderMethodStep();
case 2:
return renderDetailsStep();
case 3:
return renderReviewStep();
case 4:
return renderSuccessStep();
default:
return renderSummaryStep();
}
};
// Memoize theme-related calculations to prevent unnecessary recalculations
const themeStyles = useMemo(() => {
const isDarkTheme = theme === 'dark';
return {
isDarkTheme,
backgroundColor: isDarkTheme ? '#121212' : '#f2f2f2',
primaryColor: '#007AFF'
};
}, [theme]);
return /*#__PURE__*/_jsx(View, {
style: [styles.container, {
backgroundColor: themeStyles.backgroundColor
}],
children: /*#__PURE__*/_jsx(ScrollView, {
style: styles.content,
showsVerticalScrollIndicator: false,
children: renderCurrentStep()
})
});
};
const createStyles = (colors, theme) => StyleSheet.create({
container: {
flex: 1
},
content: {
flex: 1,
padding: 16
},
stepContainer: {
justifyContent: 'flex-start',
alignItems: 'flex-start',
width: '100%'
},
section: {
marginBottom: 24,
width: '100%'
},
sectionTitle: {
fontSize: 16,
fontWeight: '600',
color: colors.text,
marginBottom: 12,
fontFamily: fontFamilies.phuduSemiBold
},
stepIndicatorContainer: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
marginVertical: 16
},
stepIndicator: {
height: 10,
borderRadius: 5,
marginHorizontal: 4
},
stepIndicatorActive: {
width: 28,
backgroundColor: colors.primary
},
stepIndicatorInactive: {
width: 10,
backgroundColor: colors.border
},
logo: {
width: 40,
height: 20,
alignSelf: 'center',
resizeMode: 'contain'
},
headerTitle: {
fontFamily: fontFamilies.phuduBold,
fontSize: 22,
fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
color: colors.text,
letterSpacing: -0.5
},
paymentMethodButton: {
flexDirection: 'row',
alignItems: 'center',
borderRadius: 16,
padding: 14,
marginBottom: 10,
width: '90%',
alignSelf: 'center',
borderWidth: 1
},
paymentMethodButtonActive: {
backgroundColor: colors.primary + '22',
borderColor: colors.primary,
borderWidth: 2
},
paymentMethodButtonInactive: {
backgroundColor: 'transparent',
borderColor: colors.border,
borderWidth: 1
},
paymentMethodLabel: {
fontFamily: fontFamilies.phudu,
fontSize: 18,
color: colors.text,
fontWeight: '600'
},
paymentMethodDescription: {
fontFamily: fontFamilies.phudu,
fontSize: 15,
color: colors.secondaryText,
marginTop: 8,
minHeight: 36,
textAlign: 'center'
},
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 32
},
errorText: {
fontSize: 18,
color: 'red',
marginBottom: 24
},
methodListContainer: {
width: '100%',
alignItems: 'center'
},
methodIcon: {
marginRight: 12
},
methodCheckIcon: {
marginLeft: 'auto'
},
cardRowInfo: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 16
},
cardRowIcon: {
marginRight: 8
},
cardRowText: {
fontSize: 15,
color: colors.secondaryText
},
cardFieldContainer: {
marginBottom: 16
},
cardFieldRow: {
flexDirection: 'row',
gap: 12
},
cardFieldHalfLeft: {
flex: 1,
marginRight: 6
},
cardFieldHalfRight: {
flex: 1,
marginLeft: 6
},
oxyPayContainer: {
alignItems: 'center'
},
oxyPayIcon: {
marginBottom: 8
},
oxyPayTitle: {
fontSize: 16,
marginBottom: 8,
color: colors.text,
fontWeight: '600',
textAlign: 'center'
},
oxyPaySubtitle: {
fontSize: 14,
color: colors.secondaryText,
marginBottom: 8,
textAlign: 'center'
},
oxyPayBalanceBox: {
backgroundColor: colors.primary + '22',
borderRadius: 12,
padding: 8,
marginTop: 8
},
oxyPayBalanceText: {
color: colors.primary,
fontWeight: '600'
},
oxyPayCard: {
backgroundColor: '#fff',
borderRadius: 16,
padding: 24,
marginBottom: 24,
alignItems: 'center',
width: '100%'
},
oxyPayContent: {
alignItems: 'center',
width: '100%'
},
oxyPayMainTitle: {
fontFamily: fontFamilies.phuduBold,
fontWeight: 'bold',
fontSize: 28,
color: colors.text,
marginBottom: 2,
textAlign: 'center',
letterSpacing: 0.5
},
oxyPayWaiting: {
fontSize: 14,
color: colors.secondaryText,
textAlign: 'center',
marginBottom: 8
},
faircoinContainer: {
alignItems: 'center',
marginBottom: 24,
width: '100%'
},
faircoinIcon: {
marginBottom: 8
},
faircoinMainTitle: {
fontFamily: fontFamilies.phuduBold,
fontWeight: 'bold',
fontSize: 28,
color: '#1b1f0a',
marginBottom: 2,
textAlign: 'center',
letterSpacing: 0.5
},
faircoinSubtitle: {
color: '#1b1f0a',
fontWeight: '700',
fontSize: 17,
marginBottom: 18,
textAlign: 'center',
letterSpacing: 0.2
},
faircoinScanText: {
color: '#1b1f0a',
fontWeight: '600',
fontSize: 15,
marginBottom: 8
},
faircoinQRCard: {
width: 200,
height: 200,
backgroundColor: '#fff',
borderRadius: 32,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 16,
padding: 16,
borderWidth: 3,
borderColor: '#9ffb50',
shadowColor: '#9ffb50',
shadowOffset: {
width: 0,
height: 4
},
shadowOpacity: 0.25,
shadowRadius: 12,
elevation: 8,
position: 'relative'
},
faircoinQRBadge: {
position: 'absolute',
bottom: 12,
right: 12,
backgroundColor: '#fff',
borderRadius: 16,
padding: 4,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2
},
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 2
},
faircoinTitle: {
fontSize: 16,
marginBottom: 8,
color: colors.text,
fontWeight: '600',
textAlign: 'center'
},
faircoinQRBox: {
width: 160,
height: 160,
backgroundColor: '#eee',
borderRadius: 16,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 12,
borderWidth: 2,
borderColor: colors.primary
},
faircoinQRText: {
color: '#aaa'
},
faircoinWaiting: {
fontSize: 14,
color: colors.secondaryText,
textAlign: 'center',
marginBottom: 8
},
faircoinPlaceholder: {
fontSize: 13,
color: colors.secondaryText,
textAlign: 'center'
},
faircoinCard: {
backgroundColor: '#fff',
borderRadius: 16,
padding: 24,
marginBottom: 24,
alignItems: 'center',
width: '100%'
},
faircoinContent: {
alignItems: 'center',
width: '100%'
},
successCard: {
backgroundColor: '#fff',
borderRadius: 16,
padding: 24,
marginBottom: 24,
alignItems: 'center',
width: '100%'
},
successContent: {
alignItems: 'center',
width: '100%'
},
successIcon: {
marginBottom: 8
},
successMainTitle: {
fontFamily: fontFamilies.phuduBold,
fontWeight: 'bold',
fontSize: 28,
color: colors.success || '#4BB543',
marginBottom: 2,
textAlign: 'center',
letterSpacing: 0.5
},
successSubtitle: {
fontSize: 16,
color: colors.text,
textAlign: 'center',
marginBottom: 8,
width: '100%'
},
successMessage: {
fontSize: 14,
color: colors.secondaryText,
textAlign: 'center',
marginBottom: 8
},
methodCard: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
borderRadius: 18,
paddingVertical: 18,
paddingHorizontal: 18,
marginBottom: 14,
borderWidth: 1.5,
borderColor: colors.border,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2
},
shadowOpacity: 0.06,
shadowRadius: 6,
elevation: 2,
minHeight: 72
},
methodCardSelected: {
backgroundColor: colors.primary + '11',
borderColor: colors.primary,
shadowOpacity: 0.13,
shadowRadius: 10,
elevation: 4
},
methodCardContent: {
flexDirection: 'row',
alignItems: 'center',
flex: 1
},
methodCardIcon: {
marginRight: 18,
marginLeft: 2
},
methodCardTextContainer: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center'
},
methodCardDescription: {
fontSize: 14,
color: colors.secondaryText,
marginTop: 2,
opacity: 0.85
},
methodCardCheckIcon: {
marginLeft: 12
},
paymentMethodLabelSelected: {
color: colors.primary
},
circleListContainer: {
flexDirection: 'row',
flexWrap: 'nowrap',
justifyContent: 'center',
alignItems: 'flex-start',
paddingHorizontal: 4,
width: '100%',
marginBottom: 0
},
circleMethod: {
alignItems: 'center',
marginHorizontal: 0,
flex: 1,
minWidth: 62,
paddingVertical: 2,
paddingHorizontal: 2
},
circleMethodSelected: {
// No extra margin, but highlight below
},
circleIconWrapper: {
width: 48,
// restored padding
height: 48,
// restored padding
borderRadius: 24,
// half of width/height
backgroundColor: '#fff',
borderWidth: 2,
borderColor: colors.border,
alignItems: 'center',
justifyContent: 'center',
marginBottom: 8,
// spacing below icon
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2
},
shadowOpacity: 0.07,
shadowRadius: 6,
elevation: 2
},
circleLabel: {
fontFamily: fontFamilies.phudu,
fontSize: 16,
color: colors.text,
fontWeight: '600',
textAlign: 'center',
marginBottom: 4,
marginTop: 2
},
circleLabelSelected: {
color: colors.primary
},
circleDescription: {
fontSize: 13,
color: colors.secondaryText,
textAlign: 'center',
opacity: 0.85,
minHeight: 36,
marginBottom: 2
},
headerStepIndicatorContainer: {
marginVertical: 2,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center'
},
faircoinButton: {
flexDirection: 'row',
alignItems: 'center',
alignSelf: 'center',
backgroundColor: colors.primary + '11',
borderRadius: 16,
paddingHorizontal: 16,
paddingVertical: 8,
marginTop: 6,
marginBottom: 2
},
faircoinButtonText: {
color: colors.primary,
fontWeight: '600',
fontSize: 15
},
faircoinAddress: {
color: colors.secondaryText,
fontSize: 13,
textAlign: 'center',
marginTop: 6,
marginBottom: 2
},
// Summary step styles
summaryDescriptionContainer: {
marginBottom: 16
},
summaryDescriptionText: {
color: colors.secondaryText,
fontSize: 15,
lineHeight: 20
},
summaryItemPrice: {
color: colors.text,
fontWeight: '600',
fontSize: 16
},
summaryFallbackContainer: {
padding: 16,
backgroundColor: '#fff',
borderRadius: 12,
borderWidth: 1,
borderColor: colors.border
},
summaryFallbackText: {
color: colors.text,
fontSize: 16,
textAlign: 'center'
},
// Card payment styles
cardPaymentCard: {
backgroundColor: '#fff',
borderRadius: 16,
padding: 24,
marginBottom: 24,
alignItems: 'center',
width: '100%'
},
cardPaymentContent: {
alignItems: 'center',
width: '100%'
},
cardPaymentIcon: {
marginBottom: 8
},
cardPaymentMainTitle: {
fontFamily: fontFamilies.phuduBold,
fontWeight: 'bold',
fontSize: 28,
color: colors.text,
marginBottom: 2,
textAlign: 'center',
letterSpacing: 0.5
},
cardPaymentSubtitle: {
fontSize: 16,
color: colors.secondaryText,
textAlign: 'center',
marginBottom: 24,
width: '100%'
},
cardPaymentFields: {
width: '100%',
marginBottom: 16
},
cardPaymentWaiting: {
fontSize: 14,
color: colors.secondaryText,
textAlign: 'center',
marginBottom: 8
},
// Summary card styles
summaryCard: {
backgroundColor: '#fff',
borderRadius: 16,
padding: 24,
marginBottom: 24,
alignItems: 'center',
width: '100%'
},
summaryCardContent: {
alignItems: 'center',
width: '100%'
},
summaryCardIcon: {
marginBottom: 8
},
summaryCardMainTitle: {
fontFamily: fontFamilies.phuduBold,
fontWeight: 'bold',
fontSize: 28,
color: colors.text,
marginBottom: 2,
textAlign: 'center',
letterSpacing: 0.5
},
summaryCardSubtitle: {
fontSize: 16,
color: colors.secondaryText,
textAlign: 'center',
marginBottom: 24,
width: '100%'
},
summaryCardItems: {
width: '100%',
marginBottom: 16
},
summaryCardTotal: {
fontSize: 18,
fontWeight: 'bold',
color: colors.text,
textAlign: 'center',
marginBottom: 8
},
// Simple amount styles
summaryCardAmount: {
alignItems: 'center',
width: '100%',
marginBottom: 16
},
summaryCardAmountLabel: {
fontSize: 16,
color: colors.secondaryText,
textAlign: 'center',
marginBottom: 8
},
summaryCardAmountValue: {
fontSize: 32,
fontWeight: 'bold',
color: colors.text,
textAlign: 'center',
marginBottom: 8,
fontFamily: fontFamilies.phuduBold
},
summaryCardAmountDescription: {
fontSize: 14,
color: colors.secondaryText,
textAlign: 'center',
lineHeight: 20
},
// Enhanced summary styles
summaryCardDivider: {
height: 1,
backgroundColor: colors.border,
marginVertical: 16,
width: '100%'
},
summaryCardTotalSection: {
width: '100%',
marginBottom: 8
},
summaryCardTotalRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 4
},
summaryCardTotalLabel: {
fontSize: 16,
color: colors.secondaryText,
fontWeight: '500'
},
summaryCardTotalValue: {
fontSize: 16,
color: colors.text,
fontWeight: '600'
}
});
export default PaymentGatewayScreen;
//# sourceMappingURL=PaymentGatewayScreen.js.map