# カスタム決済フローを使用して iOS でデジタル商品の支払いを受け付ける Payment Element を使用して、独自のカスタム決済フローを開き、アプリ内のデジタル商品とサブスクリプションを販売します。 米国または欧州経済領域 (EEA) で販売されるデジタル製品、コンテンツ、サブスクリプションの場合、iOS アプリで [Elements](https://docs.stripe.com/payments/elements.md) を使用して Apple Pay を受け付けることができます。 商品の数と価格に限りがある場合は、代わりに [Payment Links](https://docs.stripe.com/mobile/digital-goods/payment-links.md) を使用できます。 その他の地域では、デジタル製品、コンテンツ、またはサブスクリプションに対して App で Apple Pay を使用することはできません。 このガイドでは、Elements を使用してアプリでサブスクリプションを販売し、顧客を独自の決済ページにリダイレクトする方法について説明します。 Elements を使用する独自の決済ページを既にお持ちの場合は、[ユニバーサルリンクの設定](https://docs.stripe.com/mobile/digital-goods/custom-checkout.md#universal-links) 手順に進んでください。 > Stripe を初めて使用し、大量の決済を処理しており、高度な導入ニーズがおありの場合は、[営業チームにお問い合わせ](https://stripe.com/contact/sales) ください。 ## 作成する内容 導入で [customer-configured Accounts](https://docs.stripe.com/api/v2/core/accounts/create.md#v2_create_accounts-configuration-customer) を使用している場合は、コード例内の `Customer` とイベント参照を、対応する Accounts v2 API リファレンスに置き換えてください。詳細については、[Account オブジェクトで顧客を表す](https://docs.stripe.com/connect/use-accounts-as-customers.md)をご覧ください。 このガイドでは以下の方法を説明します。 - [Elements](https://docs.stripe.com/payments/elements.md) を使用して、自社の決済ページで支払い情報を収集します。 - *商品* (Products represent what your business sells—whether that's a good or a service)、*価格* (Prices define how much and how often to charge for products. This includes how much the product costs, what currency to use, and the interval if the price is for subscriptions)、*顧客* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments)を使用してサブスクリプションをモデル化します。 - *ユニバーサルリンク* (Use Universal links on iOS and macOS to link directly to in-app content. They're standard HTTPS links, so the same URL works for your website and your app)を使用して Checkout からアプリへと直接リダイレクトします。 - *Webhook* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests) を監視して顧客のアプリ内サブスクリプションを更新します。 このガイドでは、アプリ内デジタル商品を販売するプロセスのみを説明します。次のいずれかを販売する場合は、代わりに [ネイティブ iOS 支払いガイド](https://docs.stripe.com/payments/mobile.md) を使用してください。 - 物理的品目 - アプリ外での使用を目的とする商品やサービス - リアルタイムの個人間サービス ## Stripe を設定する [サーバー側] まず、Stripeアカウントに[登録](https://dashboard.stripe.com/register)します。 次に、バックエンドに Stripe API ライブラリーを追加します。 #### Ruby ```bash # Available as a gem sudo gem install stripe ``` ```ruby # If you use bundler, you can add this line to your Gemfile gem 'stripe' ``` 次に、Stripe CLI をインストールします。CLI には [Webhook](https://docs.stripe.com/webhooks.md#test-webhook) のテストが用意されているため、これを実行して商品と価格を作成できます。 その他のインストールオプションについては、[Stripe CLI を使ってみる](https://docs.stripe.com/stripe-cli.md)をご覧ください。 ## 商品および価格を作成する 商品とその価格は、ダッシュボードまたは Stripe CLI で作成します。この例では、製品と価格を使用して、月額料金が 9.99 米ドルのサブスクリプション製品を表します。 1. [商品を追加](https://dashboard.stripe.com/test/products/create)ページに移動し、月額 9.99 USD のサブスクリプション商品を作成します。 1. 価格を作成したら、価格 ID を記録しておき、後続のステップで使用できるようにします。料金 ID は、`price_G0FvDp6vZvdwRZ` のように表示されます。 1. 次に、**本番環境にコピー** をクリックして、[テスト環境から本番環境](https://docs.stripe.com/keys.md#test-live-modes)に商品を複製します。 ## 顧客を作成する [サーバー側] 顧客が決済ページにアクセスするたびに、まだ存在しない場合にはその顧客の Customer オブジェクトが作成されます。 サーバーでは以下を処理する必要があります。 - 顧客の作成 (一致する Stripe 顧客がまだ存在しない場合)。 - `incomplete` 状態のサブスクリプション作成。 - PaymentIntent の client secret をフロントエンドに返します。 - 自身のデータベースで顧客のサブスクリプションステータスを更新可能にするための Webhook 処理。 #### Node.js ```javascript // Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. // Find your keys at https://dashboard.stripe.com/apikeys. const stripe = require('stripe')('<>'); // This assumes your app has an existing user database, which we'll call `myUserDB`. const user = myUserDB.getUser("jennyrosen"); if (!user.stripeCustomerID) { const customer = await stripe.customers.create({ name: user.name, email: user.email, }); // Set the user's Stripe Customer ID for later retrieval. user.stripeCustomerID = customer.id; } ``` > ユーザーアカウントと Stripe 顧客 ID との関連付けをサーバーに保存します。顧客を購入に関連付ける方法がなければ、顧客は購入を回復できなくなります。 > > 顧客が決済ページでメールアドレスを変更すると、Customer オブジェクトが新しいメールアドレスで更新されます。 ## サブスクリプションを作成する [サーバー側] Payment Element を使用するサブスクリプションを作成する際には、通常は `payment_behavior: 'default_incomplete'` を渡します。これにより、Stripe に対して、`incomplete` ステータスのサブスクリプションを作成し、初回の支払いの Payment Intent を生成するように指示します。 > データベースに `subscription.id` を保存して、キャンセル、アップグレード、ダウングレードなどの将来のサブスクリプションイベントを管理します。 #### Node.js ```javascript // This example sets up an endpoint using the Express framework. const express = require('express'); const app = express(); // Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. const stripe = require('stripe')('<>'); app.post('/create-subscription', async (req, res) => { const { priceId, customerId } = req.body; // Create the subscription // setting payment_behavior to "default_incomplete" ensures we get a Payment Intent // that we can confirm on the client using the Payment Element const subscription = await stripe.subscriptions.create({ customer: customerId, items: [{ price: priceId }], payment_behavior: 'default_incomplete', expand: ['latest_invoice.payment_intent'], }); // Make sure you associate the subscription ID with the user in your database! myUserDB.addUserSubscription("jennyrosen", subscription.id); // Get the Payment Intent client secret const paymentIntent = subscription.latest_invoice.payment_intent; const clientSecret = paymentIntent.client_secret; return res.json({ subscriptionId: subscription.id, clientSecret: clientSecret, }); }); app.post('/login', async (req, res) => { // This assumes your app has an existing user database, which we'll call `myUserDB`. const token = myUserDB.login(req.body.login_details) res.json({token: token}) }); app.listen(4242, () => console.log(`Listening on port ${4242}!`)); ``` > Apple Pay は[デフォルトで有効](https://dashboard.stripe.com/settings/payment_methods)であり、顧客がサポート対象のデバイスを使用し Wallet アプリに最低でもカードを 1 枚保存している場合は、自動的に Payment Element に表示されます。`payment_method_types` プロパティを使用して、他の支払い方法を受け付けることもできます。詳細については、[支払い方法の概要](https://docs.stripe.com/payments/payment-methods/overview.md)をご覧ください。 ## ユニバーサルリンクを設定する ユニバーサルリンクを使用すると、決済ページをアプリにディープリンクできます。ユニバーサルリンクを設定するには、次の手順を行います。 - ドメインに `apple-app-site-association` ファイルを追加します。 - アプリに Associated Domains エンタイトルメントを追加します。 - Checkout リダイレクト URL のフォールバックページを追加します。 #### 関連ドメインを定義する `.well-known/apple-app-site-association` でドメインにファイルを追加し、アプリで処理する URL を定義します。アプリ ID の前にチーム ID を付加します。チーム IDは、[Apple Developer Portal のメンバーシップページ](https://developer.apple.com/account)にあります。 ```json { "applinks": { "apps": [], "details": [ { "appIDs": [ "A28BC3DEF9.com.example.MyApp1", "A28BC3DEF9.com.example.MyApp1-Debug" ], "components": [ { "/": "/checkout_redirect*", "comment": "Matches any URL whose path starts with /checkout_redirect" } ] } ] } } ``` このファイルは、MIME タイプ `application/json` で提供する必要があります。`curl -I` を使用してコンテンツタイプを確認します。 ```bash curl -I https://example.com/.well-known/apple-app-site-association ``` 詳細については、[関連ドメインのサポート](https://developer.apple.com/documentation/xcode/supporting-associated-domains)に関する Apple のページを参照してください。 #### アプリに Associated Domains エンタイトルメントを追加する 1. アプリのターゲットの、**Signing & Capabilities (署名とケイパビリティ)** ウィンドウを開きます。 1. **+ ケイパビリティ** をクリックし、**関連ドメイン** を選択します。 1. `applinks:example.com` のエントリーを Associated Domains リストに追加します。 ユニバーサルリンクの詳細については、Apple の[ユニバーサルリンク](https://developer.apple.com/ios/universal-links/)のドキュメントを参照してください。 iOS は、`apple-app-site-association` ファイルで定義された URL へのリンクをインターセプトしますが、リダイレクトでアプリを開くことができないケースが発生することがあります。 `success` URL と `cancel` URL に[フォールバックページ](https://docs.stripe.com/payments/checkout/custom-success-page.md)を作成します。たとえば、`/checkout_redirect/success` ページと `/checkout_redirect/cancel` ページを設定できます。 ## Safari で Checkout を開く [クライアント側] アプリに決済ボタンを追加します。このボタンをクリックすると、Safari でカスタム決済ページが開きます。 ```swift import Foundation import SwiftUI import StoreKit struct BuySubscriptionsView: View { @EnvironmentObject var myBackend: MyBackend @State var paymentComplete = false var body: some View { // Check if payments are blocked by Parental Controls on this device. if !SKPaymentQueue.canMakePayments() { Text("Payments are disabled on this device.") } else { if paymentComplete { Text("Payment complete!") } else { Button { UIApplication.shared.open("https://example.com/checkout", options: [:], completionHandler: nil) } label: { Text("Subscribe") }.onOpenURL { url in // Handle the universal link from Checkout. if url.absoluteString.contains("success") { // The payment was completed. Show a success // page and fetch the latest customer entitlements // from your server. paymentComplete = true } } } } } } ``` ## アプリにリダイレクトする [サーバー側] Elements により、支払いを正常に確定したら、ユーザーを (登録済みのユニバーサルリンクを使用して) アプリに[リダイレクト](https://docs.stripe.com/js/payment_intents/confirm_payment#confirm_payment_intent-options-confirmParams-return_url)してください。 ```javascript stripe.confirmPayment({ elements, confirmParams: { // Return URL where the customer should be redirected after the PaymentIntent is confirmed. return_url: 'https://example.com/checkout_redirect/success', }, }) .then(function(result) { if (result.error) { // Inform the customer that there was an error. } }); ``` ## 注文のフルフィルメントを処理する [サーバー側] ユーザーが初回の支払いを完了したとき、または後続の継続支払いが発生したときに、Stripe は次のようなイベントを送信します。 - `invoice.payment_succeeded` - `customer.subscription.updated` - `invoice.payment_failed` これらのイベントを Webhook エンドポイントでリッスンします。以下に例を挙げます。 #### Node.js ```javascript app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.headers['stripe-signature']; let event; try { event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET); } catch (err) { console.error('Webhook signature verification failed.', err.message); return res.sendStatus(400); } switch (event.type) { case 'invoice.payment_succeeded': { const invoice = event.data.object; // Mark subscription as active in your database // For example, invoice.subscription -> "sub_abc123" console.log('Payment succeeded'); break; } case 'invoice.payment_failed': { const invoice = event.data.object; console.log('Payment failed - notify the user to update their payment methods'); break; } case 'customer.subscription.updated': { const subscription = event.data.object; // For example, handle pause, cancellation, or other changes console.log(`Subscription updated: ${subscription.id}`); break; } default: console.log(`Unhandled event type ${event.type}`); } res.json({ received: true }); }); ``` 実装内容をテストするには、[ダッシュボード](https://dashboard.stripe.com/events)または [Stripe CLI](https://docs.stripe.com/webhooks.md#test-webhook) を使用してイベントをモニタリングします。本番環境で開発する場合は、Webhook エンドポイントを設定して適切なイベントタイプに登録します。`STRIPE_WEBHOOK_SECRET` キーが不明な場合は、ダッシュボードで [Webhook](https://dashboard.stripe.com/webhooks) をクリックして表示します。 ### テスト 決済ボタンが機能することをテストするには、次の手順を実行します。 1. 購入ボタンをクリックすると、Stripe の Payment Element を使用する決済フローにリダイレクトされます。 1. 4242 4242 4242 4242 のテストカード番号、3 桁のセキュリティコード、将来の有効期限日、および任意の有効な郵便番号を入力します。 1. **支払う** をタップします。 1. `invoice.payment_succeeded` Webhook が起動し、Stripe が取引についてサーバーに通知します。 1. リダイレクトによってアプリへと戻されます。 実装が機能していない場合には、[その他のテスト用リソース](https://docs.stripe.com/mobile/digital-goods/custom-checkout.md#additional-testing-resources)をご覧ください。 ## Optional: その他のテスト用リソース 実装を本番環境に移行する準備ができたかの確認に使用できる、テスト用の番号がいくつかあります。任意のセキュリティコード、郵便番号、および今後の有効期限を指定して使用します。 | 数字 | 説明 | | ------------------- | --------------------------------------- | | 4242 4242 4242 4242 | 支払いが成功し、すぐに処理されます。 | | 4000 0000 0000 3220 | 支払いを正常に完了させるために、3D セキュア 2 認証を実行します。 | | 4000 0000 0000 9995 | 常に支払い拒否コード `insufficient_funds` で失敗します。 | 全テストカードの一覧については、[テスト](https://docs.stripe.com/testing.md)に関するガイドをご覧ください。 ### ユニバーサルリンクをテストする ユニバーサルリンクで決済ページからアプリにリダイレクトされない場合は、`SharedWebCredentials` ログでエラーがないかを確認してください。 1. Associated Domains エンタイトルメントにデバッグパラメーターを追加する 1. アプリのターゲットの、**Signing & Capabilities (署名とケイパビリティ)** ウィンドウを開きます。 1. 関連ドメインのエントリーに `?mode=developer` フラグを追加します。「(例: `applinks:example.com?mode=developer`)」 1. デバイスを開発者モードに設定します。 1. デバイスで Xcode からアプリを実行し、開発者メニューを有効にします。 1. iPhone で、**Settings (設定)** を開き、**Developer (開発者)** をタップし、**Associated Domains Development (関連ドメイン開発)** を有効にします。 1. アプリを削除して、再インストールします。これにより、iOS は apple-app-site-association ファイルを再取得します。 1. アプリで決済フローを完了します。 1. Checkout によってアプリにリダイレクトされます。そうでない場合は、sysdiagnose を取得します。 1. 音量アップ、音量ダウン、および電源ボタンを同時に 1 秒間押してから離します。短い振動を感じますが、視覚的なフィードバックは表示されません。 1. 5 分間待ってから、**Settings (設定) > Privacy (プライバシー) > Analytics & Improvement (分析と改善) > Analytics Data (分析データ)** に移動し、リストの最後の sysdiagnose ファイルまでスクロールします。 1. 共有ボタンをタップして、お使いのコンピューターにファイルを AirDrop (エアドロップ) します。 1. sysdiagnose アーカイブを開き、次に `swcutil_show.txt` を開きます 1. このファイルでアプリ ID を検索します。アプリのデバッグ情報のセクションが表示されます。エラーがある場合には、それも含まれます。 ``` Service: applinks App ID: Y28TH9SHX7.com.stripe.FruitStore App Version: 1.0 App PI: { v = 0, t = 0x8, u = 0xc98, db = E335D78F-D49E-4F19-A150-F657E50DEDAE, {length = 8, bytes = 0x980c000000000000} } Domain: example.com?mode=developer User Approval: unspecified Site/Fmwk Approval: unspecified Flags: developer Last Checked: 2021-09-23 18:16:58 +0000 Next Check: 2021-09-23 21:21:34 +0000 Error: Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set. around line 1, column 0." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set. around line 1, column 0., NSJSONSerializationErrorIndex=0} Retries: 1 ``` ## See also - [割引を追加する](https://docs.stripe.com/payments/checkout/discounts.md) - [税金を徴収する](https://docs.stripe.com/payments/checkout/taxes.md) - [納税者番号を収集する](https://docs.stripe.com/tax/checkout/tax-ids.md) - [ブランディングをカスタマイズする](https://docs.stripe.com/payments/checkout/customization.md) - [成功ページをカスタマイズする](https://docs.stripe.com/payments/checkout/custom-success-page.md)