React Native - การรับชำระเงิน
เรียนรู้วิธีการรับชำระเงินในแอปพลิเคชัน React Native โดยใช้ Omise คำแนะนำนี้ให้กลยุทธ์การใช้งานที่สมบูรณ์สำหรับการรวมการชำระเงินข้ามแพลตฟอร์มที่สนับสนุนทั้ง iOS และ Android
ภาพรวม
การรวม Omise ในแอปพลิเคชัน React Native ต้องรวมความสามารถของ SDK ในตัวกับรหัส JavaScript/TypeScript วิธีนี้ให้กลยุทธ์การใช้งานที่สมบูรณ์สำหรับการรับชำระเงินข้ามแพลตฟอร์ม
คุณสมบัติหลัก
- การสนับสนุนข้ามแพลตฟอร์ม - รหัสฐานเดียวสำหรับ iOS/Android
- ประสิทธิภาพในตัว - ใช้ประโยชน์จาก SDK ในตัวภายใต้ประทุน
- การสนับสนุน TypeScript - รวมนิยามประเภทแบบเต็ม
- React Hooks - รูปแบบและ Hooks ของ React สมัยใหม่
- การจัดการ 3D Secure - การไหลการรับรองอย่างราบรื่น
- เข้ากันได้ Expo - ทำงานกับเวิร์กโฟลว์ Expo ที่จัดการ
ข้อกำหนดเบื้องต้น
ก่อนที่จะใช้งานการรับชำระเงิน:
- ตั้งค่าสภาพแวดล้อม React Native (0.64+)
- ตั้งค่าโมดูลเนทีฟสำหรับ iOS และ Android
- API ด้านหลังสำหรับการสร้างใบเรียกเก็บเงิน
- บัญชี Omise พร้อมกับคีย์ API
การติดตั้ง
ติดตั้งการพึ่งพาเนทีฟ
# ใช้ npm
npm install omise-react-native
# ใช้ yarn
yarn add omise-react-native
# ติดตั้งการพึ่งพา iOS
cd ios && pod install && cd ..
ตั้งค่า Android
เพิ่มสิ่งต่อไปนี้ไปยัง android/app/build.gradle:
dependencies {
implementation 'co.omise:omise-android:3.1.0'
}
ตั้งค่า iOS
การติดตั้ง CocoaPods จัดการการตั้งค่า iOS โดยอัตโนมัติ ให้แน่ใจว่าเวอร์ชัน iOS ขั้นต่ำใน ios/Podfile:
platform :ios, '12.0'
เชื่อมโมดูลเนทีฟ (React Native < 0.60)
react-native link omise-react-native
ขั้นตอนการชำระเงินพื้นฐาน
ตั้งค่า Omise Provider
import React from 'react';
import { OmiseProvider } from 'omise-react-native';
export default function App() {
return (
<OmiseProvider publicKey="pkey_test_123">
<AppNavigator />
</OmiseProvider>
);
}
สร้าง Payment Hook
import { useState, useCallback } from 'react';
import { createToken } from 'omise-react-native';
import { Alert } from 'react-native';
interface CardData {
number: string;
name: string;
expiryMonth: number;
expiryYear: number;
securityCode: string;
}
export function usePayment() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const processPayment = useCallback(
async (cardData: CardData, amount: number, currency: string) => {
setLoading(true);
setError(null);
try {
// ขั้นตอนที่ 1: สร้างโทเค็น
const token = await createToken({
card: {
name: cardData.name,
number: cardData.number,
expiration_month: cardData.expiryMonth,
expiration_year: cardData.expiryYear,
security_code: cardData.securityCode,
},
});
// ขั้นตอนที่ 2: สร้างใบเรียกเก็บเงินบนเซิร์ฟเวอร์ด้านหลัง
const charge = await createCharge({
token: token.id,
amount,
currency,
return_uri: 'myapp://payment/complete',
});
// ขั้นตอนที่ 3: จัดการ 3D Secure หากจำเป็น
if (charge.authorize_uri) {
await handle3DSecure(charge.authorize_uri);
}
return charge;
} catch (err: any) {
setError(err.message);
Alert.alert('Payment Error', err.message);
throw err;
} finally {
setLoading(false);
}
},
[]
);
return { processPayment, loading, error };
}
ส่วนประกอบแบบฟอร์มการชำระเงิน
import React, { useState } from 'react';
import {
View,
TextInput,
TouchableOpacity,
Text,
StyleSheet,
ActivityIndicator,
} from 'react-native';
import { usePayment } from './usePayment';
interface PaymentFormProps {
amount: number;
currency: string;
onSuccess: () => void;
}
export default function PaymentForm({
amount,
currency,
onSuccess,
}: PaymentFormProps) {
const [cardNumber, setCardNumber] = useState('');
const [cardholderName, setCardholderName] = useState('');
const [expiry, setExpiry] = useState('');
const [cvv, setCvv] = useState('');
const { processPayment, loading } = usePayment();
const handleSubmit = async () => {
// วิเคราะห์วันหมดอายุ
const [month, year] = expiry.split('/').map(s => parseInt(s.trim()));
// ตรวจสอบ
if (!validateForm()) {
return;
}
try {
await processPayment(
{
number: cardNumber.replace(/\s/g, ''),
name: cardholderName,
expiryMonth: month,
expiryYear: 2000 + year,
securityCode: cvv,
},
amount,
currency
);
onSuccess();
} catch (error) {
// ข้อผิดพลาดจัดการแล้วในฮุก
}
};
const validateForm = (): boolean => {
if (cardNumber.replace(/\s/g, '').length < 13) {
Alert.alert('Error', 'Invalid card number');
return false;
}
if (!cardholderName.trim()) {
Alert.alert('Error', 'Cardholder name required');
return false;
}
if (!expiry.match(/^\d{2}\/\d{2}$/)) {
Alert.alert('Error', 'Invalid expiry date (MM/YY)');
return false;
}
if (cvv.length < 3) {
Alert.alert('Error', 'Invalid CVV');
return false;
}
return true;
};
const formatCardNumber = (text: string) => {
const cleaned = text.replace(/\s/g, '');
const formatted = cleaned.match(/.{1,4}/g)?.join(' ') || cleaned;
setCardNumber(formatted);
};
const formatExpiry = (text: string) => {
const cleaned = text.replace(/\D/g, '');
if (cleaned.length >= 2) {
setExpiry(`${cleaned.slice(0, 2)}/${cleaned.slice(2, 4)}`);
} else {
setExpiry(cleaned);
}
};
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="Card Number"
value={cardNumber}
onChangeText={formatCardNumber}
keyboardType="number-pad"
maxLength={19}
editable={!loading}
/>
<TextInput
style={styles.input}
placeholder="Cardholder Name"
value={cardholderName}
onChangeText={setCardholderName}
autoCapitalize="words"
editable={!loading}
/>
<View style={styles.row}>
<TextInput
style={[styles.input, styles.halfInput]}
placeholder="MM/YY"
value={expiry}
onChangeText={formatExpiry}
keyboardType="number-pad"
maxLength={5}
editable={!loading}
/>
<TextInput
style={[styles.input, styles.halfInput]}
placeholder="CVV"
value={cvv}
onChangeText={setCvv}
keyboardType="number-pad"
maxLength={4}
secureTextEntry
editable={!loading}
/>
</View>
<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleSubmit}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.buttonText}>
Pay {currency} {(amount / 100).toFixed(2)}
</Text>
)}
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 16,
},
input: {
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 12,
marginBottom: 12,
fontSize: 16,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
},
halfInput: {
width: '48%',
},
button: {
backgroundColor: '#4CAF50',
padding: 16,
borderRadius: 8,
alignItems: 'center',
marginTop: 8,
},
buttonDisabled: {
backgroundColor: '#ccc',
},
buttonText: {
color: '#fff',
fontSize: 18,
fontWeight: 'bold',
},
});
การจัดการ 3D Secure
ไหลการ 3D Secure พร้อมกับ WebView
import React, { useRef } from 'react';
import { Modal, View, StyleSheet, TouchableOpacity, Text } from 'react-native';
import { WebView } from 'react-native-webview';
interface ThreeDSecureModalProps {
visible: boolean;
authorizeUri: string;
onComplete: () => void;
onCancel: () => void;
}
export default function ThreeDSecureModal({
visible,
authorizeUri,
onComplete,
onCancel,
}: ThreeDSecureModalProps) {
const webViewRef = useRef<WebView>(null);
const handleNavigationStateChange = (navState: any) => {
const { url } = navState;
// ตรวจสอบว่าผู้ใช้กลับมาจาก 3DS
if (url.startsWith('myapp://payment/complete')) {
onComplete();
}
};
return (
<Modal visible={visible} animationType="slide">
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>Secure Authentication</Text>
<TouchableOpacity onPress={onCancel} style={styles.closeButton}>
<Text style={styles.closeText}>Cancel</Text>
</TouchableOpacity>
</View>
<WebView
ref={webViewRef}
source={{ uri: authorizeUri }}
onNavigationStateChange={handleNavigationStateChange}
style={styles.webview}
startInLoadingState
/>
</View>
</Modal>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#ddd',
},
title: {
fontSize: 18,
fontWeight: 'bold',
},
closeButton: {
padding: 8,
},
closeText: {
color: '#007AFF',
fontSize: 16,
},
webview: {
flex: 1,
},
});
ใช้โมดัล 3D Secure
import React, { useState } from 'react';
import { View } from 'react-native';
import PaymentForm from './PaymentForm';
import ThreeDSecureModal from './ThreeDSecureModal';
export default function CheckoutScreen() {
const [show3DS, setShow3DS] = useState(false);
const [authorizeUri, setAuthorizeUri] = useState('');
const [chargeId, setChargeId] = useState('');
const handlePaymentInitiated = async (charge: any) => {
if (charge.authorize_uri) {
setAuthorizeUri(charge.authorize_uri);
setChargeId(charge.id);
setShow3DS(true);
} else {
handlePaymentSuccess();
}
};
const handle3DSComplete = async () => {
setShow3DS(false);
// ตรวจสอบสถานะใบเรียกเก็บเงิน
try {
const charge = await verifyCharge(chargeId);
if (charge.status === 'successful') {
handlePaymentSuccess();
} else {
handlePaymentFailure('Payment verification failed');
}
} catch (error) {
handlePaymentFailure(error.message);
}
};
const handle3DSCancel = () => {
setShow3DS(false);
handlePaymentFailure('Authentication cancelled');
};
return (
<View style={{ flex: 1 }}>
<PaymentForm
amount={100000}
currency="THB"
onSuccess={handlePaymentInitiated}
/>
<ThreeDSecureModal
visible={show3DS}
authorizeUri={authorizeUri}
onComplete={handle3DSComplete}
onCancel={handle3DSCancel}
/>
</View>
);
}