# Stripe をマーチャントオブレコードとして、iOS でデジタル製品の決済を受け付けます Stripe Checkout と Managed Payments をブラウザで開くと、アプリ内でデジタル製品やサブスクリプションを販売できます。 > #### 利用規約が必要 > > Managed Payments を利用する前に、[ダッシュボード](https://dashboard.stripe.com/settings/managed-payments)にある [Managed Payments 利用規約](https://stripe.com/legal/managed-payments)に同意する必要があります。 [一部の国](https://support.stripe.com/questions/changes-to-ios-app-store-guidelines) では、iOS の Managed Payments を使用して、外部のウェブサイトにリンクして決済を受け付けることができます。[Stripe Checkout](https://docs.stripe.com/payments/checkout.md) を使用して、顧客を Stripe がホストする支払いページにリダイレクトします。例として、このガイドではアプリで使用するデジタルクレジットを販売する方法を説明します。1 回限りまたはサブスクリプションの両方の支払いを受け付けることができます。 ![一括払い](https://b.stripecdn.com/docs-statics-srv/assets/one-time-hero.36361064aef1a09b8192ef17b0b557de.png) Managed Payments を利用した一括払いの顧客に表示される UI ![継続支払い](https://b.stripecdn.com/docs-statics-srv/assets/recurring-hero.00968928fbe5564c9964922965c8b3c1.png) Managed Payments を利用したサブスクリプション支払いで顧客に表示される UI ## 仕組み 次の図は、アプリからウェブへの決済フロー全体の概要を示しています。 アプリ内購入向けの、アプリからウェブへの決済フローの概要 (See full diagram at https://docs.stripe.com/payments/managed-payments/set-up-mobile) ## 制限事項 このガイドではカバーしていません: - ユーザー認証。既存の認証プロバイダーがない場合には、[Sign in with Apple (Apple でサインイン)](https://developer.apple.com/sign-in-with-apple/) や [Firebase Authentication](https://firebase.google.com/docs/auth) などのサードパーティプロバイダーを使用できます。 - ネイティブのアプリ内購入。StoreKit を使用してアプリ内購入を実装するには、[Apple のアプリ内課金](https://developer.apple.com/in-app-purchase/) ガイドをご覧ください。 このガイドでは、この[資格基準](https://docs.stripe.com/payments/managed-payments/eligibility.md)に従ったアプリ内デジタル商品の販売プロセスのみを説明します。デジタル商品がこの基準に適合しない場合は、[iOS でデジタル商品の決済を受け付ける](https://docs.stripe.com/mobile/digital-goods/checkout.md)を参照してください。物理的な商品を販売する場合は、[Stripe アプリ内決済](https://docs.stripe.com/payments/mobile.md)を参照してください。 ## Before you begin - 商品が Managed Payments の[対象要件](https://docs.stripe.com/payments/managed-payments/eligibility.md)を満たしていることを確認してください。Managed Payments で決済を処理するには、顧客が購入するすべての商品が対象である必要があります。 - [ダッシュボード](https://dashboard.stripe.com/settings/managed-payments)で Managed Payments を有効にします。 - [開発環境を設定](https://docs.stripe.com/get-started/development-environment.md)します。 - `2025-03-31.basil` [以降](https://docs.stripe.com/changelog.md)の API バージョンを使用してください。 ## 商品および価格を作成する ダッシュボードまたは Stripe CLI で、*商品* (Products represent items your customer can subscribe to with a Subscription. An associated Price object describes the pricing and other terms of the subscription)とその *価格* (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)を作成します。1 回限りの価格のデジタル商品や、継続価格を使用するサブスクリプションを追加できます。また、**顧客が支払う金額を決定する** を選択すると、顧客が支払う金額を自由に選べるようにすることもできます (たとえば、購入するクレジット数を決める場合)。商品を作成する際は、選択する税コードが [Managed Payments の対象](https://docs.stripe.com/payments/managed-payments/eligibility.md#eligible-tax-codes)である必要があります。対象の税コードには、`Managed Payments の対象`というラベルが表示されます。 この例では、単一の *商品* と *価格* を使用して、10 USD の 100 コイン束を表しています。 #### ダッシュボード [Add a product](https://dashboard.stripe.com/test/products/create) ページに移動し、コインバンドルを作成します。1 回限りの価格として 10 USD を追加します。`txcd_10201000` を選択して、`Video Games - downloaded - non subscription - with permanent rights tax code` を取得します。 - 100 コイン: アプリ内の 100 コインのバンドル - 価格: Standard モデル|10 USD|1 回|txcd_10201000 価格を作成したら、価格 ID を記録しておき、後続のステップで使用できるようにします。料金 ID は、`price_G0FvDp6vZvdwRZ` のように表示されます。 準備ができたら、**Copy to live mode** をクリックして、[テスト環境から本番環境](https://docs.stripe.com/keys.md#test-live-modes) に製品をクローンします。 #### Stripe CLI 次に、Product オブジェクトを作成します: ```bash stripe products create \ --name="100 coins" \ --description="Bundle of 100 in-app coins" \ --tax-code="txcd_10201000" ``` Stripe CLI は、商品 ID を含む商品詳細を返します。 ```json { "id": "prod_H94k5odtwJXMtQ", "object": "product", "active": true, "attributes": [ ], "created": 1587577341, "description": "Bundle of 100 in-app coins", "images": [ ], "livemode": false, "metadata": { }, "name": "100 coins", "statement_descriptor": null, "tax_code": "txcd_10201000", "type": "service", "unit_label": null, "updated": 1587577341 } ``` 商品 ID を使用して価格を作成します。`unit_amount` はセント単位なので、例えば`1000` は 10 USD に相当します。 ```bash stripe prices create \ -d product=prod_H94k5odtwJXMtQ \ -d unit_amount=1000 \ -d currency=usd ``` 各価格の価格 ID を保存しておくと、以降のステップで使用できます。以下のようになります: ```json { "id": "price_1Jh0tjEmNk5jCjFGCkLnNYGO", "object": "price", "active": true, "billing_scheme": "per_unit", "created": 1633391323, "currency": "usd", "livemode": false, "lookup_key": null, "metadata": { }, "nickname": null, "product": "prod_H94k5odtwJXMtQ", "recurring": null, "tax_behavior": "unspecified", "tiers_mode": null, "transform_quantity": null, "type": "one_time", "unit_amount": 1000, "unit_amount_decimal": "1000" } ``` ## 顧客を作成する [サーバー側] Checkout セッションを作成するたびに、そのユーザーにまだ設定されていなければ *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments) オブジェクトを作成します。 #### 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 との関連付けを、サーバーに必ず保存してください。顧客を購入に関連付ける方法がなければ、顧客は購入を回復できなくなります。 > > アプリが既存の認証プロバイダーを持たない場合は、代わりに [Apple でサインイン](https://developer.apple.com/sign-in-with-apple/)を利用できます。 [customer](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-customer) 引数を使用して、Checkout Session の作成時に顧客 ID を渡します。これにより、セッション中に作成されるすべてのオブジェクトが正しい `Customer` オブジェクトに関連付けられます。 ## ユニバーサルリンクを設定する [クライアント側] [サーバー側] *ユニバーサルリンク* (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 をお使いのアプリにディープリンクできます。ユニバーサルリンクを設定するには、次の手順を行います。 1. ドメインに `apple-app-site-association` ファイルを追加します。 1. アプリに Associated Domains Entitlement を追加します。 1. 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 の [Universal Links for Developers (開発者のためのユニバーサルリンク) (英語)](https://developer.apple.com/ios/universal-links/) ページをご覧ください。 iOS は、`apple-app-site-association` ファイルで定義された URL へのリンクをインターセプトしますが、リダイレクトでアプリを開くことができないケースが発生することがあります。 必ず `success_url` に[フォールバックページ](https://docs.stripe.com/payments/checkout/custom-success-page.md?payment-ui=stripe-hosted)を作成してください。たとえば、[アプリにカスタム URL スキームを定義](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app)し、ユニバーサルリンクが失敗した場合にリンクバックに使用できます。 ## Checkout セッションを作成する [サーバー側] [Checkout Session](https://docs.stripe.com/api/checkout/Sessions/create.md)は、支払いフォームにリダイレクトされた顧客に表示される内容をプログラムで表現したものです。Checkout Session は作成後 24 時間で有効期限が切れます。以下のように設定してください。 - 顧客 ID - プロダクト ID (一括払いまたはサブスクリプションのいずれか) - アプリからウェブへの購入に最適化された UI を選択するために、`mobile_app` に設定された `origin_context`。 - `managed_payments[enabled]` パラメータを`true` に設定します。 - `success_url`。支払いが完了した後に、顧客をお客様のアプリにリダイレクトする*ユニバーサルリンク* (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 セッションを作成したら、レスポンスからの [URL](https://docs.stripe.com/api/checkout/sessions/object.md#checkout_session_object-url) をお客様のアプリに返します。 #### 一括払い #### 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-checkout-session', async (req, res) => { // Fetch the Stripe customer ID for the customer associated with this request. // This assumes your app has an existing user database, which we'll call `myUserDB`. const user = myUserDB.getUserFromToken(req.query.token); const customerId = user.stripeCustomerID; // The price ID from the previous step const priceId = '{{PRICE_ID}}'; const session = await stripe.checkout.sessions.create({ line_items: [ { price: priceId, quantity: 1, }, ], mode: 'payment', managed_payments: {enabled: true}, origin_context: 'mobile_app', customer: customerId, success_url: 'https://example.com/checkout_redirect/success', }); res.json({url: session.url}); }); 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}!`)); ``` #### サブスクリプション #### 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-checkout-session', async (req, res) => {// Fetch the Stripe customer ID for the customer associated with this request. // This assumes your app has an existing user database, which we'll call `myUserDB`. const user = myUserDB.getUserFromToken(req.query.token); const customerId = user.stripeCustomerID; // The price ID from the previous step const priceId = '{{SUBSCRIPTION_PRICE_ID}}'; const session = await stripe.checkout.sessions.create({ line_items: [ { price: priceId, quantity: 1, }, ], mode: 'subscription', managed_payments: {enabled: true}, origin_context: 'mobile_app', customer: customerId, success_url: 'https://example.com/checkout_redirect/success', }); res.json({url: session.url}); }); 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}!`)); ``` ## Safari で Checkout を開く [クライアント側] アプリに決済ボタンを追加します。このボタンは以下を行います。 1. サーバー側のエンドポイントを呼び出して、Checkout セッションを作成します。 1. クライアントに Checkout セッションを返します。 1. Safari でセッション URL を開きます。 ```swift import Foundation import SwiftUI import StoreKit struct BuyCoinsView: View { @EnvironmentObject var myBackend: MyServer @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 { myBackend.createCheckoutSession { url in UIApplication.shared.open(url, options: [:], completionHandler: nil) } } label: { Text("Buy 100 coins") }.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 } } } } } } ``` ### クライアントで Checkout URL を取得する サーバーエンドポイントを使用して、Checkout セッションを取得します。 ```swift class MyServer: ObservableObject { // The cached login token var token: String? func createCheckoutSession(completion: @escaping (URL) -> Void) { // Send the login token to the `/create_checkout_session` endpoint let request = URLRequest(url: URL(string: "https://example.com/create-checkout-session?token=\(self.token)")!) let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in guard let unwrappedData = data, let json = try? JSONSerialization.jsonObject(with: unwrappedData, options: []) as? [String : Any], let urlString = json["url"] as? String, let url = URL(string: urlString) else { // Handle error return } DispatchQueue.main.async { // Call the completion block with the Checkout session URL returned from the backend completion(url) } }) task.resume() } func login() { // Login using the server and set the login token. let request = URLRequest(url: URL(string: "https://example.com/login")!) let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in guard let unwrappedData = data, let json = try? JSONSerialization.jsonObject(with: unwrappedData, options: []) as? [String : Any], let token = json["token"] as? String else { // Handle error return } self.token = token }) task.resume() } } ``` ## 注文のフルフィルメントを処理する [サーバー側] 購入が成功すると、Stripe は `checkout.session.completed` *Webhook* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests) を送信します。このイベントを受け取ったら、サーバー上の顧客にコインを追加できます。 [イベントを受信したことを確認](https://docs.stripe.com/webhooks.md#acknowledge-events-immediately)すると、Checkout は購入者を `success_url` にリダイレクトします。エンドポイントが停止していたり、イベントが正しく確認されない場合、Checkout は支払い成功の 10 秒後に購入者を `success_url` にリダイレクトします。 テスト目的の場合は、[ダッシュボード](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) をクリックして表示します。 #### 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')('<>'); app.post("/webhook", async (req, res) => { let data; let eventType; // Check if webhook signing is configured. const webhookSecret = "{{STRIPE_WEBHOOK_SECRET}}" // 例: whsec_c7681Dm if (webhookSecret) { // Retrieve the event by verifying the signature using the raw body and secret. let event; let signature = req.headers["stripe-signature"]; try { event = stripe.webhooks.constructEvent( req.body, signature, webhookSecret ); } catch (err) { console.log(`⚠️ Webhook signature verification failed.`); return res.sendStatus(400); } // Extract the object from the event. data = event.data; eventType = event.type; } else { // Webhook signing is recommended, but if the secret is not configured in `config.js`, // retrieve the event data directly from the request body. data = req.body.data; eventType = req.body.type; } switch (eventType) { case 'checkout.session.completed': const session = event.data.object; // Payment is successful. // Update the customer in your database to reflect this purchase. const user = myUserDB.userForStripeCustomerID(session.customer); user.addCoinsTransaction(100, session.id); break; default: // Unhandled event type } res.sendStatus(200); }); ``` ### テスト 顧客をStripe Checkout にリダイレクトする決済ボタンをテストします。 1. 決済ボタンをクリックすると、Stripe Checkout 支払いフォームにリダイレクトされます。 1. 4242 4242 4242 4242 のテストカード番号、3 桁のセキュリティコード、将来の有効期限日、および任意の有効な郵便番号を入力します。 1. **支払う** をタップします。 1. `checkout.session.completed` Webhook が起動して、Stripe は取引についてお客様のサーバーに通知します。 1. リダイレクトによってアプリへと戻されます。 実装が機能していない場合には、以下の[その他のテスト用リソース](https://docs.stripe.com/payments/managed-payments/set-up-mobile.md#additional-testing-resources)のセクションをご覧ください。 ### 支払いの詳細 #### Item 1 1. テスト決済を確認したら、**ダッシュボード** > [Transactions](https://dashboard.stripe.com/test/payments) に移動します。 1. テスト支払いをクリックして、支払いの詳細を表示します。このページには以下が表示されます: - 購入された商品 - 作成された[サブスクリプション](https://docs.stripe.com/api/subscriptions.md) - 作成された[請求書](https://docs.stripe.com/api/invoices.md) - Managed Payments によって計算および源泉徴収された税額 - 顧客の明細書に表示される明細書表記 > #### 顧客オーソリ > > 顧客が Managed Payments を通じてサブスクリプションを購入した場合、その決済手段は Managed Payments による請求に対してのみオーソリされます。Managed Payments 以外の取引でこの決済手段に請求するには、顧客から適切な同意を得てください。 #### Item 2 1. テスト決済を確認したら、**ダッシュボード** > [Transactions](https://dashboard.stripe.com/test/payments) に移動します。 1. テスト支払いをクリックして、支払いの詳細を表示します。このページには以下が表示されます: - 購入された商品 - 作成された[サブスクリプション](https://docs.stripe.com/api/subscriptions.md) (購入した場合) - 作成された[請求書](https://docs.stripe.com/api/invoices.md) - Managed Payments によって計算および源泉徴収された税額 - 顧客の明細書に表示される明細書表記 #### 領収書のプレビュー 1. **Receipt history** で、**View receipt** をクリックします。 1. **Send receipt** をクリックすると、顧客に送信される領収書メールをプレビューできます。 > サンドボックスモードでは、購入後にメール領収書は自動では届きません。上記の手順に従って手動で送信できます。 ### Link [Link](https://docs.stripe.com/payments/link.md)は決済時にマーチャントオブレコードとして機能し、[Link website](https://link.com)でサブスクリプション管理と取引サポートを提供します。 決済時の Link の動作をテストするには、初回の Checkout Session で Link アカウントを作成します。Link アカウントを作成したら、同じメールアドレスを使用して別のセッションを試行します。認証するには、テストパスコード `000000` を使用します。 テスト購入は Link アプリには表示されません。本番環境の Checkout Session 中に Link アカウントを作成することで、Link アプリで注文管理ツールをテストできます。 ## Optional: その他のテスト用リソース 実装を本番環境に移行する準備ができたかの確認に使用できる、テスト用の番号がいくつかあります。任意のセキュリティコード、郵便番号、および今後の有効期限を指定して使用します。 | 数字 | 説明 | | ------------------- | --------------------------------------- | | 4242 4242 4242 4242 | 支払いが成功し、すぐに処理されます。 | | 4000 0000 0000 3220 | 支払いを正常に完了させるために、3D セキュア 2 認証を実行します。 | | 4000 0000 0000 9995 | 常に支払い拒否コード `insufficient_funds` で失敗します。 | 全テストカードの一覧については、[テスト](https://docs.stripe.com/testing.md)に関するガイドをご覧ください。 ### ユニバーサルリンクをテストする ユニバーサルリンクで Checkout からアプリにリダイレクトされない場合は、`SharedWebCredentials` ログでエラーがないかを確認してください。 1. Associated Domains エンタイトルメントにデバッグパラメーターを追加する - アプリのターゲットの、**Signing & Capabilities (署名とケイパビリティ)** ウィンドウを開きます。 - 関連ドメインのエントリーに `?mode=developer` フラグを追加します。「(例: `applinks:example.com?mode=developer`)」 1. デバイスを開発者モードに設定します。 - デバイスで Xcode からアプリを実行し、開発者メニューを有効にします。 - 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/customization.md) - [成功ページをカスタマイズする](https://docs.stripe.com/payments/checkout/custom-success-page.md)