前提条件
Dodo Paymentsをモバイルアプリに統合する前に、以下を確認してください:- Dodo Paymentsアカウント: APIアクセスを持つアクティブなマーチャントアカウント
- API認証情報: ダッシュボードからのAPIキーとWebhookシークレットキー
- モバイルアプリプロジェクト: Android、iOS、React Native、またはFlutterアプリケーション
- バックエンドサーバー: チェックアウトセッションの作成を安全に処理するため
統合ワークフロー
モバイル統合は、バックエンドがAPI呼び出しを処理し、モバイルアプリがユーザーエクスペリエンスを管理する安全な4ステッププロセスに従います。1
バックエンド: チェックアウトセッションの作成
チェックアウトセッションAPIドキュメント
Node.js、Pythonなどを使用してバックエンドでチェックアウトセッションを作成する方法を学びます。専用のチェックアウトセッションAPIドキュメントで完全な例とパラメータの参照を確認してください。
セキュリティ: チェックアウトセッションはバックエンドサーバーで作成する必要があり、モバイルアプリでは作成しないでください。これにより、APIキーが保護され、適切な検証が保証されます。
2
モバイル: チェックアウトURLの取得
モバイルアプリがバックエンドに呼び出してチェックアウトURLを取得します:
- iOS (Swift)
- Android (Kotlin)
- React Native (JavaScript)
コピー
func getCheckoutURL(productId: String, customerEmail: String, customerName: String) async throws -> String {
let url = URL(string: "https://your-backend.com/api/create-checkout-session")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let requestData: [String: Any] = [
"productId": productId,
"customerEmail": customerEmail,
"customerName": customerName
]
request.httpBody = try JSONSerialization.data(withJSONObject: requestData)
let (data, _) = try await URLSession.shared.data(for: request)
let response = try JSONDecoder().decode(CheckoutResponse.self, from: data)
return response.checkout_url
}
コピー
suspend fun getCheckoutURL(productId: String, customerEmail: String, customerName: String): String {
val client = OkHttpClient()
val requestBody = JSONObject().apply {
put("productId", productId)
put("customerEmail", customerEmail)
put("customerName", customerName)
}.toString().toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("https://your-backend.com/api/create-checkout-session")
.post(requestBody)
.build()
val response = client.newCall(request).execute()
val responseBody = response.body?.string()
val jsonResponse = JSONObject(responseBody ?: "")
return jsonResponse.getString("checkout_url")
}
コピー
const getCheckoutURL = async (productId, customerEmail, customerName) => {
try {
const response = await fetch('https://your-backend.com/api/create-checkout-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
productId,
customerEmail,
customerName
})
});
const data = await response.json();
return data.checkout_url;
} catch (error) {
console.error('Failed to get checkout URL:', error);
throw error;
}
};
セキュリティ: モバイルアプリはバックエンドとのみ通信し、Dodo Payments APIとは直接通信しません。
3
モバイル: ブラウザでチェックアウトを開く
支払い処理のために、安全なアプリ内ブラウザでチェックアウトURLを開きます。
プラットフォーム固有の統合例を見る
Android、iOS、Flutterモバイル決済の完全なコードとセットアップ手順を表示します。
4
バックエンド: 支払い完了の処理
WebhookとリダイレクトURLを介して支払い完了を処理し、支払い状況を確認します。
プラットフォーム固有の統合
以下からモバイルプラットフォームを選択して、完全な実装例を確認してください:- Android
- iOS
- React Native
- Flutter
Android統合
Chromeカスタムタブの実装
コピー
// Add Chrome Custom Tabs dependency to build.gradle
implementation 'androidx.browser:browser:1.5.0'
// In your Activity
class PaymentActivity : AppCompatActivity() {
private var customTabsSession: CustomTabsSession? = null
private var customTabsClient: CustomTabsClient? = null
private var customTabsServiceConnection: CustomTabsServiceConnection? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize Custom Tabs
customTabsServiceConnection = object : CustomTabsServiceConnection() {
override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
customTabsClient = client
customTabsClient?.warmup(0L)
customTabsSession = customTabsClient?.newSession(object : CustomTabsCallback() {
override fun onNavigationEvent(navigationEvent: Int, extras: Bundle?) {
// Handle navigation events
}
})
}
override fun onServiceDisconnected(name: ComponentName) {
customTabsClient = null
}
}
CustomTabsClient.bindCustomTabsService(
this,
"com.android.chrome",
customTabsServiceConnection!!
)
// Get checkout URL from backend and launch
lifecycleScope.launch {
try {
val checkoutURL = getCheckoutURL("prod_123", "[email protected]", "Customer Name")
val customTabsIntent = CustomTabsIntent.Builder(customTabsSession)
.build()
customTabsIntent.launchUrl(this@PaymentActivity, Uri.parse(checkoutURL))
} catch (e: Exception) {
// Handle error
Log.e("PaymentActivity", "Failed to get checkout URL", e)
}
}
}
}
iOS統合
SFSafariViewControllerの実装
コピー
import SafariServices
class PaymentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Task {
do {
let checkoutURL = try await getCheckoutURL(
productId: "prod_123",
customerEmail: "[email protected]",
customerName: "Customer Name"
)
if let url = URL(string: checkoutURL) {
let safariVC = SFSafariViewController(url: url)
present(safariVC, animated: true, completion: nil)
}
} catch {
// Handle error
print("Failed to get checkout URL: \(error)")
}
}
}
}
ディープリンク処理
支払い完了リダイレクトを処理するためにURLスキームを設定します:1. Info.plistにURLスキームを設定する:コピー
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>myapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
コピー
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if url.scheme == "myapp" && url.host == "payment-status" {
NotificationCenter.default.post(name: .paymentCompleted, object: url)
return true
}
return false
}
コピー
NotificationCenter.default.addObserver(self, selector: #selector(handlePaymentCompletion(_:)), name: .paymentCompleted, object: nil)
@objc func handlePaymentCompletion(_ notification: Notification) {
if let url = notification.object as? URL {
safariVC?.dismiss(animated: true)
// Parse payment status from URL parameters
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
let paymentId = components?.queryItems?.first(where: { $0.name == "payment_id" })?.value
let status = components?.queryItems?.first(where: { $0.name == "status" })?.value
// Handle payment completion
}
}
React Native統合
チェックアウトリンクの使用 (InAppBrowser)
Dodoのホストされたチェックアウトページをアプリ内ブラウザで開きます。InAppBrowserの実装
コピー
import React from 'react';
import { View, TouchableOpacity, Text, Alert } from 'react-native';
import { InAppBrowser } from 'react-native-inappbrowser-reborn';
const PaymentButton = ({ productId, onPaymentComplete }) => {
const openPaymentLink = async () => {
try {
// Get checkout URL from backend
const checkoutURL = await getCheckoutURL(
productId,
'[email protected]',
'Customer Name'
);
const result = await InAppBrowser.open(checkoutURL, {
// iOS options
dismissButtonStyle: 'cancel',
preferredBarTintColor: '#453AA4',
preferredControlTintColor: 'white',
readerMode: false,
animated: true,
modalPresentationStyle: 'fullScreen',
modalTransitionStyle: 'coverVertical',
modalEnabled: true,
enableBarCollapsing: false,
// Android options
showTitle: true,
toolbarColor: '#453AA4',
secondaryToolbarColor: 'black',
navigationBarColor: 'black',
navigationBarDividerColor: 'white',
enableUrlBarHiding: true,
enableDefaultShare: true,
forceCloseOnRedirection: false,
animations: {
startEnter: 'slide_in_right',
startExit: 'slide_out_left',
endEnter: 'slide_in_left',
endExit: 'slide_out_right'
},
headers: {
'my-custom-header': 'my custom header value'
}
});
// Handle payment completion based on result
if (result.type === 'dismiss') {
// User dismissed the browser
console.log('Payment browser dismissed');
}
} catch (error) {
Alert.alert('Error', 'Failed to open payment page');
console.error('InAppBrowser error:', error);
}
};
return (
<View style={{ padding: 20 }}>
<TouchableOpacity
style={{
backgroundColor: '#007AFF',
padding: 15,
borderRadius: 8,
alignItems: 'center'
}}
onPress={openPaymentLink}
>
<Text style={{ color: 'white', fontSize: 16, fontWeight: 'bold' }}>
Pay Now
</Text>
</TouchableOpacity>
</View>
);
};
export default PaymentButton;
支払いコールバックのためのディープリンク処理
コピー
// App.js or your main component
import React, { useEffect } from 'react';
import { Linking } from 'react-native';
const App = () => {
useEffect(() => {
// Handle deep links when app is already running
const handleDeepLink = (url) => {
if (url.includes('payment-status')) {
const urlParams = new URLSearchParams(url.split('?')[1]);
const paymentId = urlParams.get('payment_id');
const status = urlParams.get('status');
// Handle payment completion
handlePaymentCallback(paymentId, status);
}
};
// Listen for deep links
const subscription = Linking.addEventListener('url', ({ url }) => {
handleDeepLink(url);
});
// Handle deep link if app was opened from a link
Linking.getInitialURL().then((url) => {
if (url) {
handleDeepLink(url);
}
});
return () => subscription?.remove();
}, []);
const handlePaymentCallback = (paymentId, status) => {
if (status === 'succeeded') {
// Navigate to success screen or show success message
console.log('Payment successful:', paymentId);
} else {
// Handle payment failure
console.log('Payment failed:', paymentId);
}
};
// Your app components...
};
export default App;
パッケージ依存関係
この依存関係をpackage.jsonに追加します:コピー
{
"dependencies": {
"react-native-inappbrowser-reborn": "^6.4.0"
}
}
インストールコマンド
コピー
# Install InAppBrowser
npm install react-native-inappbrowser-reborn
cd ios && pod install
Android設定
android/app/src/main/AndroidManifest.xmlに追加します:コピー
<activity
android:name="com.reactnativeinappbrowserreborn.InAppBrowserActivity"
android:theme="@style/Theme.AppCompat.Dialog"
android:exported="false" />
iOS設定
ios/YourApp/Info.plistに追加します:コピー
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>myapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
ネイティブブラウザ体験のためにInAppBrowserを使用してください。支払いコールバックのためにディープリンクを処理します。
代替: ネイティブ決済SDK
ネイティブUIと完全なカスタマイズを備えたより統合された決済体験のために、Dodo PaymentsはReact Native SDKを提供しています。地域のコンプライアンス: アプリ内決済SDKには地域制限があります。iOSでは、ネイティブ決済SDKはEU(DMA条件の下)でのみ合法です。Androidでは、ユーザー選択課金(UCB)市場で合法です。他の地域では、App StoreおよびPlay Storeのコンプライアンスを確保するために、上記のチェックアウトリンクアプローチを使用してください。
React Native SDK統合ガイド
DodoのReact Native SDKをネイティブ決済シートと統合するための完全なガイド。セットアップ、実装、テスト手順を含みます。
アプリ内決済統合の地域コンプライアンス要件を理解するために、App Store手数料回避ガイドを確認してください。
Flutter統合
コピー
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<String> getCheckoutURL(String productId, String customerEmail, String customerName) async {
final response = await http.post(
Uri.parse('https://your-backend.com/api/create-checkout-session'),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'productId': productId,
'customerEmail': customerEmail,
'customerName': customerName,
}),
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
return data['checkout_url'];
} else {
throw Exception('Failed to get checkout URL: ${response.statusCode}');
}
}
class PaymentScreen extends StatefulWidget {
@override
_PaymentScreenState createState() => _PaymentScreenState();
}
class _PaymentScreenState extends State<PaymentScreen> {
late WebViewController _controller;
String? checkoutURL;
@override
void initState() {
super.initState();
_getCheckoutURL();
}
Future<void> _getCheckoutURL() async {
try {
final url = await getCheckoutURL('prod_123', '[email protected]', 'Customer Name');
setState(() {
checkoutURL = url;
});
} catch (e) {
// Handle error
print('Failed to get checkout URL: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Payment')),
body: checkoutURL == null
? Center(child: CircularProgressIndicator())
: WebView(
initialUrl: checkoutURL,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller = webViewController;
},
navigationDelegate: (NavigationRequest request) {
if (request.url.contains('payment_id') && request.url.contains('status')) {
// Handle payment completion
final uri = Uri.parse(request.url);
final paymentId = uri.queryParameters['payment_id'];
final status = uri.queryParameters['status'];
handlePaymentCompletion(paymentId, status);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
);
}
void handlePaymentCompletion(String? paymentId, String? status) {
if (status == 'succeeded') {
// Payment successful
} else {
// Payment failed
}
}
}
支払いリンクにはWebViewを使用してください。支払い完了を検出するためにリダイレクトURLを処理します。
ベストプラクティス
- セキュリティ: アプリコードにAPIキーを保存しないでください。安全なストレージとSSLピンニングを使用します。
- ユーザーエクスペリエンス: ローディングインジケーターを表示し、エラーを適切に処理し、明確なメッセージを提供します。
- テスト: テストカードを使用し、ネットワークエラーをシミュレートし、さまざまなデバイスでテストします。
トラブルシューティング
一般的な問題
- WebViewが支払いリンクを開かない: 支払いリンクが有効であり、HTTPSを使用していることを確認してください。
- コールバックが受信されない: 戻りURLとWebhook設定を確認してください。
- APIキーエラー: APIキーが正しく、必要な権限を持っていることを確認してください。
追加リソース
質問やサポートが必要な場合は、[email protected]までご連絡ください。