Skip to main content

Payment Form Example

A complete payment form with card elements, customer input, and checkout processing.

Complete Example

import React, { useRef, useState } from "react";
import {
View,
Text,
TextInput,
Button,
StyleSheet,
ScrollView,
KeyboardAvoidingView,
Platform,
Alert,
} from "react-native";
import {
EasyProvider,
useEasy,
CardNumberElement,
CardExpirationDateElement,
CardVerificationCodeElement,
} from "@easylabs/react-native";
import Constants from "expo-constants";

function PaymentForm() {
const { checkout } = useEasy();

// Form refs
const cardNumberRef = useRef(null);
const expiryRef = useRef(null);
const cvcRef = useRef(null);

// Form state
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false);

const handleSubmit = async () => {
// Validate form
if (!firstName || !lastName || !email) {
Alert.alert("Error", "Please fill in all fields");
return;
}

if (!cardNumberRef.current || !expiryRef.current || !cvcRef.current) {
Alert.alert("Error", "Please enter your card details");
return;
}

setLoading(true);

try {
const result = await checkout({
customer_creation: true,
customer_details: {
first_name: firstName,
last_name: lastName,
email: email,
},
source: {
type: "PAYMENT_CARD",
cardNumberElement: cardNumberRef,
cardExpirationDateElement: expiryRef,
cardVerificationCodeElement: cvcRef,
name: `${firstName} ${lastName}`,
},
line_items: [{ price_id: "price_123", quantity: 1 }],
});

Alert.alert("Success", "Payment processed successfully!");
console.log("Checkout result:", result);

// Reset form
setFirstName("");
setLastName("");
setEmail("");
} catch (error) {
Alert.alert(
"Error",
error.message || "Payment failed. Please try again.",
);
console.error("Checkout error:", error);
} finally {
setLoading(false);
}
};

return (
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.container}
>
<ScrollView style={styles.scrollView}>
<Text style={styles.title}>Payment Information</Text>

<Text style={styles.sectionTitle}>Customer Details</Text>

<Text style={styles.label}>First Name</Text>
<TextInput
style={styles.textInput}
value={firstName}
onChangeText={setFirstName}
placeholder="John"
autoCapitalize="words"
/>

<Text style={styles.label}>Last Name</Text>
<TextInput
style={styles.textInput}
value={lastName}
onChangeText={setLastName}
placeholder="Doe"
autoCapitalize="words"
/>

<Text style={styles.label}>Email</Text>
<TextInput
style={styles.textInput}
value={email}
onChangeText={setEmail}
placeholder="[email protected]"
keyboardType="email-address"
autoCapitalize="none"
/>

<Text style={styles.sectionTitle}>Card Details</Text>

<Text style={styles.label}>Card Number</Text>
<CardNumberElement
btRef={cardNumberRef}
placeholder="4242 4242 4242 4242"
style={styles.cardInput}
/>

<View style={styles.row}>
<View style={styles.column}>
<Text style={styles.label}>Expiration Date</Text>
<CardExpirationDateElement
btRef={expiryRef}
placeholder="MM/YY"
style={styles.cardInput}
/>
</View>

<View style={styles.column}>
<Text style={styles.label}>CVC</Text>
<CardVerificationCodeElement
btRef={cvcRef}
placeholder="123"
style={styles.cardInput}
/>
</View>
</View>

<View style={styles.buttonContainer}>
<Button
title={loading ? "Processing..." : "Pay $25.00"}
onPress={handleSubmit}
disabled={loading}
/>
</View>

<Text style={styles.disclaimer}>
Your payment information is encrypted and secure. We never store your
card details.
</Text>
</ScrollView>
</KeyboardAvoidingView>
);
}

function App() {
return (
<EasyProvider apiKey={Constants.expoConfig?.extra?.easyApiKey}>
<PaymentForm />
</EasyProvider>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f5f5f5",
},
scrollView: {
flex: 1,
padding: 20,
},
title: {
fontSize: 28,
fontWeight: "bold",
marginBottom: 24,
color: "#333",
},
sectionTitle: {
fontSize: 20,
fontWeight: "600",
marginTop: 24,
marginBottom: 16,
color: "#333",
},
label: {
fontSize: 16,
fontWeight: "600",
marginBottom: 8,
marginTop: 16,
color: "#555",
},
textInput: {
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 8,
padding: 12,
fontSize: 16,
backgroundColor: "#fff",
},
cardInput: {
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 8,
padding: 12,
fontSize: 16,
backgroundColor: "#fff",
},
row: {
flexDirection: "row",
gap: 12,
},
column: {
flex: 1,
},
buttonContainer: {
marginTop: 32,
marginBottom: 16,
},
disclaimer: {
fontSize: 12,
color: "#999",
textAlign: "center",
marginTop: 16,
marginBottom: 32,
},
});

export default App;

Key Features

Form Validation

const validateForm = () => {
if (!firstName.trim() || !lastName.trim()) {
Alert.alert("Error", "Please enter your full name");
return false;
}

if (!email.includes("@")) {
Alert.alert("Error", "Please enter a valid email");
return false;
}

if (!cardNumberRef.current) {
Alert.alert("Error", "Please enter your card number");
return false;
}

return true;
};

Loading State

const [loading, setLoading] = useState(false);

const handleSubmit = async () => {
setLoading(true);
try {
await checkout({...});
} finally {
setLoading(false);
}
};

Error Handling

try {
const result = await checkout({...});
Alert.alert("Success", "Payment processed!");
} catch (error) {
Alert.alert("Error", error.message || "Payment failed");
console.error(error);
}

Keyboard Management

import { KeyboardAvoidingView, Platform } from "react-native";

<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.container}
>
{/* Form content */}
</KeyboardAvoidingView>;

Customization

Custom Button

import { TouchableOpacity } from "react-native";

<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleSubmit}
disabled={loading}
>
<Text style={styles.buttonText}>
{loading ? "Processing..." : "Pay $25.00"}
</Text>
</TouchableOpacity>;

const styles = StyleSheet.create({
button: {
backgroundColor: "#007AFF",
padding: 16,
borderRadius: 8,
alignItems: "center",
marginTop: 24,
},
buttonDisabled: {
backgroundColor: "#ccc",
},
buttonText: {
color: "#fff",
fontSize: 18,
fontWeight: "600",
},
});

Dark Mode Support

import { useColorScheme } from "react-native";

function PaymentForm() {
const colorScheme = useColorScheme();
const isDark = colorScheme === "dark";

return (
<View style={[styles.container, isDark && styles.containerDark]}>
{/* Form content */}
</View>
);
}

const styles = StyleSheet.create({
container: {
backgroundColor: "#f5f5f5",
},
containerDark: {
backgroundColor: "#1c1c1e",
},
});

Custom Styling

const styles = StyleSheet.create({
cardInput: {
borderWidth: 2,
borderColor: "#e0e0e0",
borderRadius: 12,
padding: 16,
fontSize: 18,
backgroundColor: "#fff",
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
});

Testing

Use test card numbers in development:

// In your app
<EasyProvider apiKey={apiKey} __dev={true}>
<PaymentForm />
</EasyProvider>

// Test with these card numbers:
// Success: 4242 4242 4242 4242
// Declined: 4000 0000 0000 0002
// Any future expiry date (e.g., 12/25)
// Any 3-digit CVC (e.g., 123)

Next Steps