Flutter SDK
Omise Flutter SDK ช่วยให้คุณสร้างระบบการชำระเงินที่ปลอดภัยสำหรับทั้งแพลตฟอร์ม iOS และ Android ด้วยโค้ดเบสเดียว สร้างด้วย Dart และมี API แบบรวมสำหรับการสร้างโทเค็นบัตร การสร้างแหล่งที่มาของการชำระเงิน และรองรับทุกวิธีการชำระเงินหลักในเอเชียตะวันออกเฉียงใต้
ภาพรวม
Flutter SDK ช่วยให้คุณสามารถ:
- เขียนครั้งเดียว ใช้ได้ทุกที่ - โค้ดเบสเดียวสำหรับ iOS และ Android
- สร้างโทเค็นบัตรเครดิต อย่างปลอดภัยโดยไม่มีข้อมูลสำคัญผ่านเซิร์ฟเวอร์ของคุณ
- สร้างแหล่งที่มาของการชำระเงิน สำหรับวิธีการชำระเงินทางเลือก
- ใช้งาน native UI ด้วย Flutter widgets
- รองรับการทำงานแบบ async ด้วย Future และ Stream APIs ของ Dart
- จัดการข้อผิดพลาดอย่างสง่างาม ด้วยระบบจัดการ exception ที่ครอบคลุม
คุณสมบัติหลัก
- รองรับข้ามแพลตฟอร์ม (iOS 12+, Android 5.0+)
- Dart API ที่ปลอดภัยจาก null
- การทำงานแบบ async ด้วย Future
- รองรับ Stream สำหรับการอัปเดตแบบเรียลไทม์
- ตัวตรวจสอบข้อมูลที่ป้อนภายใน
- โมเดล request/response ที่ปลอดภัยต่อประเภท
- จัดการคุณสมบัติเฉพาะแพลตฟอร์มโดยอัตโนมัติ
- รองรับ hot reload สำหรับการพัฒนาที่รวดเร็ว
ข้อกำหนด
- Flutter 2.0 หรือใหม่กว่า
- Dart 2.12 หรือใหม่กว่า (null safety)
- iOS 12.0+ หรือ Android API Level 21+
- Xcode 13.0+ (สำหรับ iOS)
- Android Studio Arctic Fox+ (สำหรับ Android)
การติดตั้ง
เพิ่มใน pubspec.yaml
dependencies:
omise_flutter: ^4.0.0
ติดตั้งแพ็กเกจ
flutter pub add omise_flutter
หรือแบบแมน นวล:
flutter pub get
การตั้งค่าแพลตฟอร์ม
การตั้งค่า iOS
เพิ่มใน ios/Podfile:
platform :ios, '12.0'
การตั้งค่า Android
อัปเดต android/app/build.gradle:
android {
defaultConfig {
minSdkVersion 21
}
}
เพิ่มสิทธิ์อินเทอร์เน็ตใน android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
เริ่มต้นอย่างรวดเร็ว
1. นำเข้าแพ็กเกจ
import 'package:omise_flutter/omise_flutter.dart';
2. กำหนดค่าเริ่มต้นของ Client
class Paymentบริการ {
final Omise omise;
Paymentบริการ()
: omise = Omise(
publicKey: 'pkey_test_5xyzyx5xyzyx5xyzyx5',
);
}
3. สร้างโทเค็น
Future<Token> createToken({
required String name,
required String number,
required int expirationMonth,
required int expirationYear,
required String securityCode,
}) async {
try {
final token = await omise.createToken(
name: name,
number: number,
expirationMonth: expirationMonth,
expirationYear: expirationYear,
securityCode: securityCode,
);
print('Token created: ${token.id}');
return token;
} catch (error) {
print('Error creating token: $error');
rethrow;
}
}
// การใช้งาน
void processPayment() async {
try {
final token = await createToken(
name: 'John Doe',
number: '4242424242424242',
expirationMonth: 12,
expirationYear: 2025,
securityCode: '123',
);
// ส่งโทเค็นไปยังเซิร์ฟเวอร์ของคุณ
await sendTokenToServer(token.id);
} catch (error) {
showError(error.toString());
}
}
การกำหนดค่า
การกำหนดค่า Client
// การกำหนดค่าพื้นฐาน
final omise = Omise(
publicKey: 'pkey_test_5xyzyx5xyzyx5xyzyx5',
);
// การกำหนดค่าขั้นสูง
final omise = Omise(
publicKey: 'pkey_test_5xyzyx5xyzyx5xyzyx5',
apiVersion: '2019-05-29',
timeout: Duration(seconds: 60),
debugMode: kDebugMode,
);
การกำหนดค่าตาม Environment
class Config {
static String get publicKey {
const environment = String.fromEnvironment(
'ENVIRONMENT',
defaultValue: 'development',
);
switch (environment) {
case 'production':
return 'pkey_5xyzyx5xyzyx5xyzyx5';
case 'staging':
return 'pkey_test_staging_key';
default:
return 'pkey_test_5xyzyx5xyzyx5xyzyx5';
}
}
}
final omise = Omise(publicKey: Config.publicKey);
การสร้างโทเค็น
การสร้างโทเค็นบัตรพื้นฐาน
Future<Token> tokenizeCard({
required String name,
required String number,
required int expirationMonth,
required int expirationYear,
required String securityCode,
}) async {
return await omise.createToken(
name: name,
number: number,
expirationMonth: expirationMonth,
expirationYear: expirationYear,
securityCode: securityCode,
);
}
พร้อมที่อยู่การเรียกเก็บเงิน
Future<Token> tokenizeCardWithAddress() async {
final address = Address(
street1: '123 Wireless Road',
street2: 'Lumpini',
city: 'Pathum Wan',
state: 'Bangkok',
postalCode: '10330',
country: 'TH',
);
return await omise.createToken(
name: 'John Doe',
number: '4242424242424242',
expirationMonth: 12,
expirationYear: 2025,
securityCode: '123',
billingAddress: address,
);
}
การตรวจสอบก่อนสร้างโทเค็น
import 'package:omise_flutter/omise_flutter.dart';
Future<Token> validateAndTokenize({
required String number,
required int expirationMonth,
required int expirationYear,
required String securityCode,
required String name,
}) async {
// ตรวจสอบหมายเลขบัตร
if (!CardValidator.isValidNumber(number)) {
throw ValidationException('Invalid card number');
}
// ตรวจสอบวันหมดอายุ
if (!CardValidator.isValidExpiry(expirationMonth, expirationYear)) {
throw ValidationException('Invalid or expired card');
}
// ตรวจ สอบ CVV
final brand = CardValidator.detectBrand(number);
if (!CardValidator.isValidCVV(securityCode, brand)) {
throw ValidationException('Invalid security code');
}
// ตรวจสอบชื่อ
if (name.trim().isEmpty) {
throw ValidationException('Cardholder name is required');
}
return await omise.createToken(
name: name,
number: number,
expirationMonth: expirationMonth,
expirationYear: expirationYear,
securityCode: securityCode,
);
}
class ValidationException implements Exception {
final String message;
ValidationException(this.message);
@override
String toString() => message;
}
การสร้างแหล่งที่มาของการชำระเงิน
ธนาคารอินเทอร์เน็ต
Future<Source> createInternetBankingSource({
required int amount,
required InternetBanking bank,
}) async {
final source = await omise.createSource(
amount: amount,
currency: 'thb',
type: SourceType.internetBanking,
internetBanking: bank,
);
// เปลี่ยนเส้นทางผู้ใช้เพื่ออนุมัติการชำระเงิน
if (source.authorizeUri != null) {
await openAuthorizeUrl(source.authorizeUri!);
}
return source;
}
// การใช้งาน
final source = await createInternetBankingSource(
amount: 100000, // 1,000.00 บาท
bank: InternetBanking.bay,
);
โมบายแบงก์กิ้ง
Future<Source> createMobileBankingSource({
required int amount,
required MobileBanking bank,
}) async {
return await omise.createSource(
amount: amount,
currency: 'thb',
type: SourceType.mobileBanking,
mobileBanking: bank,
);
}
// การใช้งาน
final source = await createMobileBankingSource(
amount: 50000, // 500.00 บาท
bank: MobileBanking.scb,
);
พร้อมเพย ์
Future<Source> createPromptPaySource({
required int amount,
}) async {
final source = await omise.createSource(
amount: amount,
currency: 'thb',
type: SourceType.promptPay,
);
// แสดง QR code ให้ผู้ใช้
if (source.scanQRCodeUrl != null) {
displayQRCode(source.scanQRCodeUrl!);
}
return source;
}
TrueMoney Wallet
Future<Source> createTrueMoneySource({
required int amount,
required String phoneNumber,
}) async {
return await omise.createSource(
amount: amount,
currency: 'thb',
type: SourceType.trueMoney,
phoneNumber: phoneNumber,
);
}
// การใช้งาน
final source = await createTrueMoneySource(
amount: 100000,
phoneNumber: '0812345678',
);
อาลีเพย์
Future<Source> createAlipaySource({
required int amount,
}) async {
return await omise.createSource(
amount: amount,
currency: 'thb',
type: SourceType.alipay,
);
}
คอมโพเนนต์ UI
วิดเจ็ตฟอร์มบัตรเครดิต
import 'package:flutter/material.dart';
import 'package:omise_flutter/omise_flutter.dart';
class CreditCardFormWidget extends StatefulWidget {
final Function(Token) onTokenCreated;
final Function(String) onError;
const CreditCardFormWidget({
Key? key,
required this.onTokenCreated,
required this.onError,
}) : super(key: key);
@override
State<CreditCardFormWidget> createState() => _CreditCardFormWidgetState();
}
class _CreditCardFormWidgetState extends State<CreditCardFormWidget> {
final _formKey = GlobalKey<FormState>();
final _cardNumberตัวควบคุม = TextEditingตัวควบคุม();
final _expiryตัวควบคุม = TextEditingตัวควบคุม();
final _cvvตัวควบคุม = TextEditingตัวควบคุม();
final _nameตัวควบคุม = TextEditingตัวควบคุม();
bool _isLoading = false;
final omise = Omise(publicKey: 'pkey_test_5xyzyx5xyzyx5xyzyx5');
@override
void dispose() {
_cardNumberตัวควบคุม.dispose();
_expiryตัวควบคุม.dispose();
_cvvตัวควบคุม.dispose();
_nameตัวควบคุม.dispose();
super.dispose();
}
Future<void> _submitForm() async {
if (!_formKey.currentState!.validate()) return;
setState(() => _isLoading = true);
try {
// แยกวันที่หมดอายุ
final expiry = _expiryตัวควบคุม.text.split('/');
final month = int.parse(expiry[0].trim());
final year = 2000 + int.parse(expiry[1].trim());
final token = await omise.createToken(
name: _nameตัวควบคุม.text,
number: _cardNumberตัวควบคุม.text.replaceAll(' ', ''),
expirationMonth: month,
expirationYear: year,
securityCode: _cvvตัวควบคุม.text,
);
widget.onTokenCreated(token);
} catch (error) {
widget.onError(error.toString());
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
controller: _cardNumberตัวควบคุม,
decoration: const InputDecoration(
labelText: 'Card Number',
hintText: '4242 4242 4242 4242',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Card number is required';
}
final cleaned = value.replaceAll(' ', '');
if (!CardValidator.isValidNumber(cleaned)) {
return 'Invalid card number';
}
return null;
},
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: TextFormField(
controller: _expiryตัวควบคุม,
decoration: const InputDecoration(
labelText: 'Expiry',
hintText: 'MM/YY',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Expiry is required';
}
// Add expiry validation
return null;
},
),
),
const SizedBox(width: 16),
Expanded(
child: TextFormField(
controller: _cvvตัวควบคุม,
decoration: const InputDecoration(
labelText: 'CVV',
hintText: '123',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'CVV is required';
}
if (value.length < 3) {
return 'Invalid CVV';
}
return null;
},
),
),
],
),
const SizedBox(height: 16),
TextFormField(
controller: _nameตัวควบคุม,
decoration: const InputDecoration(
labelText: 'Cardholder Name',
hintText: 'John Doe',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Name is required';
}
return null;
},
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _isLoading ? null : _submitForm,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(16),
),
child: _isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Pay Now'),
),
],
),
);
}
}
ตัวเลือกวิธีการชำระเงิน
class PaymentMethodSelector extends StatelessWidget {
final Function(PaymentMethod) onMethodSelected;
const PaymentMethodSelector({
Key? key,
required this.onMethodSelected,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: [
PaymentMethodTile(
icon: Icons.credit_card,
title: 'Credit/Debit Card',
subtitle: 'Visa, Mastercard, JCB',
onTap: () => onMethodSelected(PaymentMethod.card),
),
PaymentMethodTile(
icon: Icons.account_balance,
title: 'ธนาคารอินเทอร์เน็ต',
subtitle: 'Bangkok Bank, SCB, Kasikorn',
onTap: () => onMethodSelected(PaymentMethod.internetBanking),
),
PaymentMethodTile(
icon: Icons.qr_code,
title: 'PromptPay',
subtitle: 'Scan QR code to pay',
onTap: () => onMethodSelected(PaymentMethod.promptPay),
),
PaymentMethodTile(
icon: Icons.wallet,
title: 'TrueMoney Wallet',
subtitle: 'Pay with TrueMoney',
onTap: () => onMethodSelected(PaymentMethod.trueMoney),
),
],
);
}
}
class PaymentMethodTile extends StatelessWidget {
final IconData icon;
final String title;
final String subtitle;
final VoidCallback onTap;
const PaymentMethodTile({
Key? key,
required this.icon,
required this.title,
required this.subtitle,
required this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(icon, size: 32),
title: Text(title),
subtitle: Text(subtitle),
trailing: const Icon(Icons.chevron_right),
onTap: onTap,
);
}
}
enum PaymentMethod {
card,
internetBanking,
promptPay,
trueMoney,
}
วิดเจ็ตแสดง QR Code
import 'package:qr_flutter/qr_flutter.dart';
class QRCodeWidget extends StatelessWidget {
final String qrCodeUrl;
final String amount;
const QRCodeWidget({
Key? key,
required this.qrCodeUrl,
required this.amount,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'สแกนเพื่อชำระเงิน',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Text(
'จำนวนเงิน: $amount บาท',
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 24),
QrImageView(
data: qrCodeUrl,
version: QrVersions.auto,
size: 250.0,
),
const SizedBox(height: 24),
const Text(
'เปิดแอปโมบายแบงก์กิ้งของคุณ\nและสแกน QR code นี้',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
),
],
);
}
}
การตรวจสอบข้อมูลที่ป้อน
การตรวจสอบหมายเลขบัตร
import 'package:omise_flutter/omise_flutter.dart';
class CardNumberValidator {
static bool validate(String number) {
return CardValidator.isValidNumber(number);
}
static CardBrand detectBrand(String number) {
return CardValidator.detectBrand(number);
}
static String format(String number) {
// ลบช่องว่างที่มีอยู่
final cleaned = number.replaceAll(' ', '');
// เพิ่มช่องว่างทุก 4 หลัก
final buffer = StringBuffer();
for (var i = 0; i < cleaned.length; i++) {
if (i > 0 && i % 4 == 0) {
buffer.write(' ');
}
buffer.write(cleaned[i]);
}
return buffer.toString();
}
}
// การใช้งานใน TextField
class CardNumberField extends StatelessWidget {
final TextEditingตัวควบคุม controller;
const CardNumberField({Key? key, required this.controller}) : super(key: key);
@override
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
decoration: const InputDecoration(
labelText: 'Card Number',
hintText: '4242 4242 4242 4242',
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(16),
CardNumberInputFormatter(),
],
validator: (value) {
if (value == null || value.isEmpty) {
return 'Card number is required';
}
if (!CardNumberValidator.validate(value.replaceAll(' ', ''))) {
return 'Invalid card number';
}
return null;
},
);
}
}
class CardNumberInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final text = newValue.text.replaceAll(' ', '');
final formatted = CardNumberValidator.format(text);
return TextEditingValue(
text: formatted,
selection: TextSelection.collapsed(offset: formatted.length),
);
}
}
การตรวจสอบวันหมดอายุ
class ExpiryValidator {
static bool isValid(int month, int year) {
return CardValidator.isValidExpiry(month, year);
}
static bool isExpired(int month, int year) {
final now = DateTime.now();
final expiry = DateTime(year, month);
return expiry.isBefore(now);
}
}
class ExpiryInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final text = newValue.text.replaceAll('/', '');
if (text.length >= 2) {
return TextEditingValue(
text: '${text.substring(0, 2)}/${text.substring(2)}',
selection: TextSelection.collapsed(
offset: text.length <= 2 ? text.length : text.length + 1,
),
);
}
return newValue;
}
}
การตรวจสอบ CVV
class CVVValidator {
static bool isValid(String cvv, CardBrand brand) {
return CardValidator.isValidCVV(cvv, brand);
}
static int expectedLength(CardBrand brand) {
return brand == CardBrand.amex ? 4 : 3;
}
}
การจัดการ State
การใช้งาน Provider
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class PaymentProvider with ChangeNotifier {
final Omise omise;
PaymentState _state = PaymentState.idle;
String? _errorMessage;
Token? _token;
Source? _source;
PaymentProvider(this.omise);
PaymentState get state => _state;
String? get errorMessage => _errorMessage;
Token? get token => _token;
Source? get source => _source;
Future<void> createToken({
required String name,
required String number,
required int expirationMonth,
required int expirationYear,
required String securityCode,
}) async {
_state = PaymentState.loading;
_errorMessage = null;
notifyListeners();
try {
_token = await omise.createToken(
name: name,
number: number,
expirationMonth: expirationMonth,
expirationYear: expirationYear,
securityCode: securityCode,
);
_state = PaymentState.success;
notifyListeners();
} catch (error) {
_state = PaymentState.error;
_errorMessage = error.toString();
notifyListeners();
}
}
Future<void> createSource({
required int amount,
required String currency,
required SourceType type,
}) async {
_state = PaymentState.loading;
_errorMessage = null;
notifyListeners();
try {
_source = await omise.createSource(
amount: amount,
currency: currency,
type: type,
);
_state = PaymentState.success;
notifyListeners();
} catch (error) {
_state = PaymentState.error;
_errorMessage = error.toString();
notifyListeners();
}
}
void reset() {
_state = PaymentState.idle;
_errorMessage = null;
_token = null;
_source = null;
notifyListeners();
}
}
enum PaymentState {
idle,
loading,
success,
error,
}
// การใช้งาน
class PaymentScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => PaymentProvider(
Omise(publicKey: 'pkey_test_5xyzyx5xyzyx5xyzyx5'),
),
child: Consumer<PaymentProvider>(
builder: (context, provider, child) {
switch (provider.state) {
case PaymentState.loading:
return const Center(child: CircularProgressIndicator());
case PaymentState.error:
return ErrorWidget(message: provider.errorMessage!);
case PaymentState.success:
return SuccessWidget(token: provider.token!);
default:
return CreditCardFormWidget(
onTokenCreated: (token) {
// จัดการโทเค็น
},
onError: (error) {
// จัดการข้อผิดพลาด
},
);
}
},
),
);
}
}
การใช้งาน Riverpod
import 'package:flutter_riverpod/flutter_riverpod.dart';
final omiseProvider = Provider((ref) {
return Omise(publicKey: 'pkey_test_5xyzyx5xyzyx5xyzyx5');
});
final tokenProvider = FutureProvider.family<Token, TokenRequest>((ref, request) async {
final omise = ref.watch(omiseProvider);
return await omise.createToken(
name: request.name,
number: request.number,
expirationMonth: request.expirationMonth,
expirationYear: request.expirationYear,
securityCode: request.securityCode,
);
});
class TokenRequest {
final String name;
final String number;
final int expirationMonth;
final int expirationYear;
final String securityCode;
TokenRequest({
required this.name,
required this.number,
required this.expirationMonth,
required this.expirationYear,
required this.securityCode,
});
}
// การใช้
class PaymentScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final tokenAsync = ref.watch(tokenProvider(TokenRequest(
name: 'John Doe',
number: '4242424242424242',
expirationMonth: 12,
expirationYear: 2025,
securityCode: '123',
)));
return tokenAsync.when(
data: (token) => SuccessWidget(token: token),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => ErrorWidget(message: error.toString()),
);
}
}
การยืนยันตัวตน 3D Secure
การจัดการ 3D Secure ด้วย WebView
import 'package:webview_flutter/webview_flutter.dart';
class ThreeDSecureWebView extends StatefulWidget {
final String authorizeUrl;
final String returnUrl;
final Function(bool) onComplete;
const ThreeDSecureWebView({
Key? key,
required this.authorizeUrl,
required this.returnUrl,
required this.onComplete,
}) : super(key: key);
@override
State<ThreeDSecureWebView> createState() => _ThreeDSecureWebViewState();
}
class _ThreeDSecureWebViewState extends State<ThreeDSecureWebView> {
late final WebViewตัวควบคุม _controller;
bool _isLoading = true;
@override
void initState() {
super.initState();
_controller = WebViewตัวควบคุม()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (url) {
setState(() => _isLoading = true);
},
onPageFinished: (url) {
setState(() => _isLoading = false);
// ตรวจสอบว่ากลับมาจาก 3DS หรือไม่
if (url.startsWith(widget.returnUrl)) {
widget.onComplete(true);
}
},
onNavigationRequest: (request) {
if (request.url.startsWith(widget.returnUrl)) {
widget.onComplete(true);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse(widget.authorizeUrl));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Secure Payment'),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => widget.onComplete(false),
),
),
body: Stack(
children: [
WebViewWidget(controller: _controller),
if (_isLoading)
const Center(child: CircularProgressIndicator()),
],
),
);
}
}
// การใช้งาน
Future<void> processPaymentWithToken(String tokenId) async {
// สร้างการชำระเงินบนเซิร์ฟเวอร์ของคุณ
final charge = await createChargeOnServer(tokenId);
if (charge.authorizeUri != null) {
final success = await Navigator.push<bool>(
context,
MaterialPageRoute(
builder: (_) => ThreeDSecureWebView(
authorizeUrl: charge.authorizeUri!,
returnUrl: 'myapp://payment/callback',
onComplete: (success) {
Navigator.pop(context, success);
},
),
),
);
if (success == true) {
await verifyChargeStatus();
}
}
}
การจัดการข้อผิดพลาด
ประเภทของข้อผิดพลาด
Future<void> handlePayment() async {
try {
final token = await omise.createToken(
name: 'John Doe',
number: '4242424242424242',
expirationMonth: 12,
expirationYear: 2025,
securityCode: '123',
);
handleSuccess(token);
} on OmiseException catch (e) {
// ข้อผิดพลาด API จาก Omise
handleOmiseError(e);
} on NetworkException catch (e) {
// ข้อผิดพลาดการเชื่อมต่อเครือข่าย
showNetworkError();
} on ValidationException catch (e) {
// ข้อผิดพลาดการตรวจสอบข้อมูล
showValidationError(e.message);
} catch (e) {
// ข้อผิดพลาดที่ไม่ทราบสาเหตุ
showGenericError(e.toString());
}
}
การจัดการข้อผิดพลาดแบบกำหน ดเอง
void handleOmiseError(OmiseException error) {
switch (error.code) {
case 'invalid_card':
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Invalid Card'),
content: const Text('Please check your card details'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
break;
case 'insufficient_fund':
showSnackBar('Your card has insufficient funds');
break;
case 'failed_processing':
showSnackBar('Unable to process your payment');
break;
default:
showSnackBar(error.message);
}
}
void showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
ตรรกะการลองใหม่
Future<Token> createTokenWithRetry({
required int maxAttempts,
required Duration delay,
}) async {
int attempts = 0;
while (attempts < maxAttempts) {
try {
return await omise.createToken(
name: 'John Doe',
number: '4242424242424242',
expirationMonth: 12,
expirationYear: 2025,
securityCode: '123',
);
} catch (error) {
attempts++;
// ไม่ลองใหม่สำหรับข้อผิดพลาดการตรวจสอบ
if (error is ValidationException) {
rethrow;
}
if (attempts >= maxAttempts) {
rethrow;
}
// รอก่อนลองใหม่
await Future.delayed(delay * attempts);
}
}
throw Exception('Max retry attempts reached');
}
แนวทางปฏิบัติที่ดีที่สุด
ความปลอดภัย
// ✅ ควรทำ: ใช้ตัวแปรสิ่งแวดล้อม
class Config {
static const publicKey = String.fromEnvironment(
'OMISE_PUBLIC_KEY',
defaultValue: 'pkey_test_5xyzyx5xyzyx5xyzyx5',
);
}
// ❌ ไม่ควรทำ: ฮาร์ดโค้ดคีย์สำหรับใช้งานจริง
// const publicKey = 'pkey_5xyzyx5xyzyx5xyzyx5';
// ✅ ควรทำ: ใช้ kDebugMode สำหรับการทดสอบ
final omise = Omise(
publicKey: kDebugMode ? Config.testKey : Config.publicKey,
);
// ❌ ไม่ควรทำ: บันทึกข้อมูลสำคัญ
// print('Card number: $cardNumber');
// ✅ ควรทำ: ใช้การบันทึกที่ปลอดภัย
debugPrint('Token created: ${token.id}');
ประสิทธิภาพ
// ✅ ควรทำ: ใช้ const constructors
const CreditCardFormWidget(
onTokenCreated: handleToken,
onError: handleError,
);
// ✅ ควรทำ: ปิดตัวควบคุม
@override
void dispose() {
_cardNumberตัวควบคุม.dispose();
_expiryตัวควบคุม.dispose();
_cvvตัวควบคุม.dispose();
super.dispose();
}
// ✅ ควรทำ: ใช้ keys สำหรับวิดเจ็ตที่ใช้ทรัพยากรมาก
ListView.builder(
itemBuilder: (context, index) {
return PaymentMethodTile(
key: ValueKey(methods[index].id),
method: methods[index],
);
},
);
ประสบการณ์ผู้ใช้
// ✅ ควรทำ: แสดงตัวบ่งชี้การโหลด
if (_isLoading)
const CircularProgressIndicator()
else
const Text('ชำระเงินเดี๋ยวนี้');
// ✅ ควรทำ: ให้ข้อเสนอแนะ
void handleSuccess(Token token) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('ชำระเงินสำเร็จ!'),
backgroundColor: Colors.green,
),
);
}
// ✅ ควรทำ: จัดการข้อผิดพลาดอย่างสง่างาม
void handleError(String error) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('ข้อผิดพลาด'),
content: Text(error),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('ตกลง'),
),
],
),
);
}
การทดสอบ
การทดสอบหน่วย
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
class MockOmise extends Mock implements Omise {}
void main() {
group('Payment Tests', () {
late MockOmise mockOmise;
setUp(() {
mockOmise = MockOmise();
});
test('createToken คืนโทเค็นเมื่อสำเร็จ', () async {
// จัดเตรียม
final expectedToken = Token(id: 'tokn_test_123');
when(mockOmise.createToken(
name: anyNamed('name'),
number: anyNamed('number'),
expirationMonth: anyNamed('expirationMonth'),
expirationYear: anyNamed('expirationYear'),
securityCode: anyNamed('securityCode'),
)).thenAnswer((_) async => expectedToken);
// ทำการทดสอบ
final token = await mockOmise.createToken(
name: 'John Doe',
number: '4242424242424242',
expirationMonth: 12,
expirationYear: 2025,
securityCode: '123',
);
// ตรวจสอบ
expect(token.id, equals('tokn_test_123'));
});
test('createToken แสดงข้อผิดพลาดเมื่อบัตรไม่ถูกต้อง', () async {
// จัดเตรียม
when(mockOmise.createToken(
name: anyNamed('name'),
number: anyNamed('number'),
expirationMonth: anyNamed('expirationMonth'),
expirationYear: anyNamed('expirationYear'),
securityCode: anyNamed('securityCode'),
)).thenThrow(OmiseException('invalid_card', 'Invalid card number'));
// ทำการทดสอบและตรวจสอบ
expect(
() => mockOmise.createToken(
name: 'John Doe',
number: '1234567890123456',
expirationMonth: 12,
expirationYear: 2025,
securityCode: '123',
),
throwsA(isA<OmiseException>()),
);
});
});
}
การทดสอบวิดเจ็ต
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('CreditCardForm ตรวจสอบข้อมูลที่ป้อน', (tester) async {
// สร้างวิดเจ็ต
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: CreditCardFormWidget(
onTokenCreated: (_) {},
onError: (_) {},
),
),
),
);
// ป้อนหมายเลขบัตรที่ไม่ถูกต้อง
await tester.enterText(
find.byType(TextFormField).first,
'1234',
);
// แตะปุ่มส่ง
await tester.tap(find.text('ชำระเงินเดี๋ยวนี้'));
await tester.pump();
// ตรวจสอบว่าแสดงข้อความข้อผิดพลาด
expect(find.text('Invalid card number'), findsOneWidget);
});
}
การทดสอบการรวมระบบ
import 'package:integration_test/integration_test.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('กระบวนการชำระเงินแบบเต็ม', (tester) async {
// เปิดแอป
await tester.pumpWidget(MyApp());
// ไปยังหน้าจอการชำระเงิน
await tester.tap(find.text('ชำระเงินเดี๋ยวนี้'));
await tester.pumpAndSettle();
// ป้อนรายละเอียดบัตร
await tester.enterText(
find.byKey(const Key('cardNumber')),
'4242424242424242',
);
await tester.enterText(
find.byKey(const Key('expiry')),
'12/25',
);
await tester.enterText(
find.byKey(const Key('cvv')),
'123',
);
await tester.enterText(
find.byKey(const Key('name')),
'John Doe',
);
// ส่ง
await tester.tap(find.text('ส่ง'));
await tester.pumpAndSettle();
// ตรวจสอบความสำเร็จ
expect(find.text('ชำระเงินสำเร็จ'), findsOneWidget);
});
}
การแก้ไขปัญหา
ปัญหาทั่วไป
ปัญหา: "MissingPluginException"
# วิธีแก้: สร้างแอปใหม่
flutter clean
flutter pub get
flutter run
ปัญหา: การสร้าง iOS ล้มเหลว
# วิธีแก้: อัปเดต CocoaPods
cd ios
pod install --repo-update
cd ..
flutter run
ปัญหา: การสร้าง Android ล้มเหลวด้วย "Minimum SDK version"
// วิธีแก้: อัปเดต android/app/build.gradle
android {
defaultConfig {
minSdkVersion 21 // เปลี่ยนจากเวอร์ชันที่ต่ำกว่า
}
}
ปัญหา: การสร้างโทเค็นคืนค่า null
// วิธีแก้: ตรวจสอบ public key และการเชื่อมต่อเครือข่าย
final omise = Omise(
publicKey: 'pkey_test_5xyzyx5xyzyx5xyzyx5',
debugMode: true, // เปิดใช้งานการบันทึกแบบดีบัก
);
ปัญหา: Hot reload ไม่ทำงานกับฟอร์ม
// วิธีแก้: ใช้ keys ที่ไม่ซ้ำกันสำหรับ StatefulWidgets
class CreditCardForm extends StatefulWidget {
const CreditCardForm({Key? key}) : super(key: key);
// ...
}
คำถามที่พบบ่อย
ฉันสามารถใช้ SDK นี้กับ GetX/Bloc/MobX ได้หรือไม่?
ได้ครับ SDK นี้ไม่ขึ้นกับระบบจัดการ state ใด ๆ คุณสามารถใช้กับโซลูชันการจัดการ state ใดก็ได้โดยการห่อการเรียก API ในแพทเทิร์นที่คุณเลือก
SDK รองรับเว็บและแพลตฟอร์มเดสก์ท็อปหรือไม่?
ปัจจุบัน SDK มุ่งเน้นไปที่แพลตฟอร์มมือถือ (iOS และ Android) สำหรับเว็บ ให้พิจารณาใช้ Omise.js สำหรับเดสก์ท็อป คุณสามารถใช้ Dart SDK ได้
ฉันสามารถปรับแต่งข้อความข้อผิดพลาดได้หรือไม่?
ได้ครับ คุณสามารถจับ exceptions และแสดงข้อความแบบกำหนดเองให้ผู้ใช้ของคุณ:
try {
await createToken();
} on OmiseException catch (e) {
showCustomError(e.code);
}
ฉันจะจัดการเหตุการณ ์ lifecycle ของแอปได้อย่างไร?
ใช้ WidgetsBindingObserver เพื่อจัดการ lifecycle ของแอป:
class _PaymentScreenState extends State<PaymentScreen>
with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// ตรวจสอบสถานะการชำระเงิน
verifyPaymentStatus();
}
}
}
ฉันสามารถบันทึกรายละเอียดบัตรเพื่อใช้ในอนาคตได้หรือไม่?
SDK สร้างโทเค็นที่คุณส่งไปยังเซิร์ฟเวอร์ของคุณ ใช้ Customers API บนแบ็กเอนด์ของคุณเพื่อแนบโทเค็นกับลูกค้าสำหรับการเรียกเก็บเงินในอนาคต
SDK ทำงานแบบออฟไลน์ได้หรือไม่?
ไม่ได้ครับ SDK ต้องการการเชื่อมต่อเครือข่ายเพื่อสร้างโทเค็นและแหล่งที่มา พิจารณาการใช้กลไกคิวสำหรับสถานการณ์แบบออฟไลน์
ฉันจะย้ายจาก native iOS/Android SDKs ได้อย่างไร?
Flutter SDK มี APIs ที่คล้ายกัน แทนที่โค้ดเฉพาะแพลตฟอร์มด้วย Flutter widgets และใช้ Dart API แบบรวม
ฉันสามารถใช้กับ Flutter Web ได้หรือไม่?
SDK ออกแบบมาสำหรับแพลตฟอร์มมือถือ สำหรับเว็บแอปพลิเคชัน ให้ใช้ Omise.js แทน
ผลกระทบต่อขนาด bundle เท่าไหร่?
SDK เพิ่มขนาดแอปประมาณ 500KB (บีบอัด) ซึ่งอาจแตกต่างกันตามแพลตฟอร์มเป้าหมายและการตั้งค่าการเพิ่มประสิทธิภาพของคุณ
ฉันจะเปิดใช้งานการบันทึกแบบดีบักได้อย่างไร?
final omise = Omise(
publicKey: 'pkey_test_...',
debugMode: kDebugMode,
);
ทรัพยากรที่เกี่ยวข้อง
- ภาพรวม Mobile SDKs - เปรียบเทียบ mobile SDKs ทั้งหมด
- Dart SDK - การรวมระบบ Dart ฝั่งเซิร์ฟเวอร์
- iOS SDK - การพัฒนา iOS แบบ Native
- Android SDK - การพัฒนา Android แบบ Native
- Charges API - สร้างการเรียกเก็บเงินจากโทเค็น
- เอกสาร Flutter - เอกสารอย่างเป็นทางการของ Flutter
- แพ็กเกจ pub.dev - ที่เก็บแพ็กเกจ