# 対面支払いを受け付ける # 対面支払いを受け付ける このガイドでは、Stripe Terminal を使用して、自社の POS アプリで対面決済を受け付ける方法を説明します。Stripe の[シミュレーションリーダー](https://docs.stripe.com/terminal/references/testing.md#simulated-reader)を使用すれば、これらのステップを完了するのにハードウェアは必要ありません。 物理リーダーを使用する準備ができたら、[リーダー登録ステップ](https://docs.stripe.com/terminal/quickstart.md#register-reader) (サーバー主導型の実装の場合) または[リーダー検出ステップ](https://docs.stripe.com/terminal/quickstart.md#discover-reader) (SDK の実装の場合) のみを更新する必要があります。 > #### Verifone リーダーサポート > > Verifone リーダーサポートは、アメリカとカナダでパブリックプレビューです。一部の Verifone リーダーは、アイルランドとイギリス (V660p、UX700、P630)、シンガポール (V660p、P630) 向けにプライベートプレビューです。プレビューに参加するには、営業チームに[連絡して該当するリーダーを注文する必要があります](https://stripe.com/contact/sales)。 ### Stripe Node ライブラリーをインストールする パッケージをインストールし、それをコードにインポートします。また、まったくゼロから開始していて package.json ファイルが必要な場合には、コードエディターのダウンロードリンクを使用してプロジェクトファイルをダウンロードします。 #### npm ライブラリーをインストールします。 ```bash npm install --save stripe ``` #### GitHub または、stripe-node ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-node)ダウンロードします。 ### Stripe Ruby ライブラリーをインストールする Stripe ruby gem をインストールし、require を指定してコードに読み込みます。または、まったくゼロから開始していて Gemfile が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Terminal gem をインストールします。 ```bash gem install stripe ``` #### Bundler この行を Gemfile に追加します。 ```bash gem 'stripe' ``` #### GitHub または、stripe-ruby gem のソースコードを直接 [GitHub から](https://github.com/stripe/stripe-ruby)ダウンロードします。 ### Stripe Java ライブラリーをインストールする ビルドに依存関係を追加し、ライブラリーをインポートします。まったくゼロから開始していてサンプルの pom.xml ファイル (Maven 用) が必要な場合は、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Maven POM に以下の依存関係を追加し、{VERSION} を使用するバージョン番号に置き換えます。 ```bash \ncom.stripe\nstripe-java\n{VERSION}\n ``` #### Gradle build.gradle ファイルに依存関係を追加し、{VERSION} を使用するバージョン番号に置き換えます。 ```bash implementation "com.stripe:stripe-java:{VERSION}" ``` #### GitHub JAR を直接 [GitHub から](https://github.com/stripe/stripe-java/releases/latest)ダウンロードします。 ### Stripe Python パッケージをインストールする Stripe パッケージをインストールし、コードにインポートします。まったくゼロから開始していて requirements.txt が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### pip pip を使用してパッケージをインストールします。 ```bash pip3 install stripe ``` #### GitHub stripe-python ライブラリのソースコードを [GitHub から](https://github.com/stripe/stripe-python)直接ダウンロードします。 ### Stripe PHP ライブラリーをインストールする Composer を使用してライブラリーをインストールし、シークレット API キーで初期化します。まったくゼロから開始していて composer.json ファイルが必要な場合には、コードエディターのリンクを使用してファイルをダウンロードします。 #### Composer ライブラリーをインストールします。 ```bash composer require stripe/stripe-php ``` #### GitHub または、stripe-php ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-php)ダウンロードします。 ### サーバーを設定する ビルドに依存関係を追加し、ライブラリーをインポートします。まったくゼロから開始していて go.mod ファイルが必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Go 必ず Go モジュールを使用してを初期化してください。 ```bash go get -u github.com/stripe/stripe-go/v85 ``` #### GitHub または、stripe-go モジュールのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-go)ダウンロードします。 ### Stripe.net ライブラリーをインストールする .NET または NuGet でパッケージをインストールします。まったくゼロから開始する場合には、設定済みの .csproj ファイルが含まれるファイルをダウンロードします。 #### dotnet ライブラリーをインストールします。 ```bash dotnet add package Stripe.net ``` #### NuGet ライブラリーをインストールします。 ```bash Install-Package Stripe.net ``` #### GitHub または、Stripe.net ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-dotnet)ダウンロードします。 ### Stripe ライブラリーをインストールする パッケージをインストールし、コードにインポートします。まったくゼロから開始していて `package.json` が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 ライブラリーをインストールします。 ```bash npm install --save stripe @stripe/stripe-js next ``` ### リーダーの店舗を作成する リーダーを整理するための[店舗を作成](https://docs.stripe.com/terminal/fleet/locations-and-zones.md)します。店舗はリーダーをグループ化し、使用地域に必要なリーダー設定を自動的にダウンロードできるようにします。リーダーを[登録](https://docs.stripe.com/terminal/fleet/register-readers.md)する際に、各リーダーに店舗を割り当てる必要があります。これは、API またはダッシュボードを使用して実行できます。 ### リーダーを登録する シミュレーションされたリーダーを使用すると、物理的なハードウェアがなくても、すぐに開始して、機能する実装を構築することができます。特別な登録コード `simulated-s700` を使用して、シミュレーションされたリーダーを作成します。後で支払いをリーダーで処理するために使用できるように、リーダー ID を記録してください。 ### リーダーを登録する シミュレーションされたリーダーを使用すると、物理的なハードウェアがなくても、素早く開始して、機能する実装を構築することができます。シミュレーターリーダーを作成するには、特別な登録コード `simulated-s710` を使用します。後で決済をリーダーで処理するために使用できるように、リーダー ID を控えておきます。 ### リーダーを登録する シミュレーションされたリーダーを使用すると、物理的なハードウェアがなくても、素早く開始して、機能する実装を構築することができます。シミュレーションされたリーダーを作成するには、特別な登録コード `simulated-v660p` を使用します。後で支払いをリーダーで処理するために使用できるように、リーダー ID を記録してください。 ### リーダーを登録する シミュレーションされたリーダーを使用すると、物理的なハードウェアがなくても、素早く開始して、機能する実装を構築することができます。シミュレーションされたリーダーを作成するには、特別な登録コード `simulated-wpe` を使用します。後で支払いをリーダーで処理するために使用できるように、リーダー ID を記録してください。 ### PaymentIntent を作成する お使いのサーバーで PaymentIntent を作成します。PaymentIntent は、顧客の支払いライフサイクルを追跡して、試行で失敗があった場合にはその記録を残し、顧客への請求が重複しないようにします。後で処理するために PaymentIntent ID を保存します。 ### リーダーで PaymentIntent を処理する 特定の金額を指定して PaymentIntent を作成し、シミュレーションされたリーダーで決済を処理します。リーダーは、オーソリを試行する前にクレジットカードを挿入またはタップして、顧客に提示するように求めます。 ### リーダーでカード提示をシミュレーションする > 実際の取引フローでは、顧客は物理的なリーダーにカードを挿入またはタップします。シミュレートされたリーダーでは、別の API コールを行うことで、カード提示ステップをシミュレートします。 このコールにより、テストカードでの PaymentIntent が正常に確認されます。他のテストカードを試すこともできます。 ### PaymentIntent をキャプチャーする PaymentIntent が正常に確定されると、キャプチャーして売上を移動することができます。 ### エラーを処理する エラーシナリオごとに特定のコードが設定されており、これらのコードをアプリケーションで処理する必要があります。エラーが発生した場合は通常、店舗のレジ係による対応が必要になります。エラーに対処できるように、POS アプリケーションに適切なメッセージが表示されます。詳細については、[エラー処理に関するドキュメント](https://docs.stripe.com/terminal/payments/collect-card-payment.md?terminal-sdk-platform=server-driven#handle-errors) をご覧ください。 ### アプリケーションを実行する サーバーを実行し、http://localhost:4242 に移動します。 ### テストカード番号を使用して実装を試す シミュレートされたリーダーを構成して、さまざまなカードブランドや請求の拒否などのエラーシナリオなど、POS アプリケーション内のさまざまなフローをテストできます。この動作を有効にするには、テストの支払い方法を[現在の支払い方法](https://docs.stripe.com/api/terminal/readers/present_payment_method.md) API 呼び出しに渡します。 | Scenario | Card Number | | ------------------- | ---------------- | | Payment succeeds | 4242424242424242 | | Payment is declined | 4000000000009995 | ### Stripe Node ライブラリーをインストールする パッケージをインストールし、それをコードにインポートします。また、まったくゼロから開始していて package.json ファイルが必要な場合には、コードエディターのダウンロードリンクを使用してプロジェクトファイルをダウンロードします。 #### npm ライブラリーをインストールします。 ```bash npm install --save stripe ``` #### GitHub または、stripe-node ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-node)ダウンロードします。 ### Stripe Ruby ライブラリーをインストールする Stripe ruby gem をインストールし、require を指定してコードに読み込みます。または、まったくゼロから開始していて Gemfile が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Terminal gem をインストールします。 ```bash gem install stripe ``` #### Bundler この行を Gemfile に追加します。 ```bash gem 'stripe' ``` #### GitHub または、stripe-ruby gem のソースコードを直接 [GitHub から](https://github.com/stripe/stripe-ruby)ダウンロードします。 ### Stripe Java ライブラリーをインストールする ビルドに依存関係を追加し、ライブラリーをインポートします。まったくゼロから開始していてサンプルの pom.xml ファイル (Maven 用) が必要な場合は、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Maven POM に以下の依存関係を追加し、{VERSION} を使用するバージョン番号に置き換えます。 ```bash \ncom.stripe\nstripe-java\n{VERSION}\n ``` #### Gradle build.gradle ファイルに依存関係を追加し、{VERSION} を使用するバージョン番号に置き換えます。 ```bash implementation "com.stripe:stripe-java:{VERSION}" ``` #### GitHub JAR を直接 [GitHub から](https://github.com/stripe/stripe-java/releases/latest)ダウンロードします。 ### Stripe Python パッケージをインストールする Stripe パッケージをインストールし、コードにインポートします。まったくゼロから開始していて requirements.txt が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### pip pip を使用してパッケージをインストールします。 ```bash pip3 install stripe ``` #### GitHub stripe-python ライブラリのソースコードを [GitHub から](https://github.com/stripe/stripe-python)直接ダウンロードします。 ### Stripe PHP ライブラリーをインストールする Composer を使用してライブラリーをインストールし、シークレット API キーで初期化します。まったくゼロから開始していて composer.json ファイルが必要な場合には、コードエディターのリンクを使用してファイルをダウンロードします。 #### Composer ライブラリーをインストールします。 ```bash composer require stripe/stripe-php ``` #### GitHub または、stripe-php ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-php)ダウンロードします。 ### サーバーを設定する ビルドに依存関係を追加し、ライブラリーをインポートします。まったくゼロから開始していて go.mod ファイルが必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Go 必ず Go モジュールを使用してを初期化してください。 ```bash go get -u github.com/stripe/stripe-go/v85 ``` #### GitHub または、stripe-go モジュールのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-go)ダウンロードします。 ### Stripe.net ライブラリーをインストールする .NET または NuGet でパッケージをインストールします。まったくゼロから開始する場合には、設定済みの .csproj ファイルが含まれるファイルをダウンロードします。 #### dotnet ライブラリーをインストールします。 ```bash dotnet add package Stripe.net ``` #### NuGet ライブラリーをインストールします。 ```bash Install-Package Stripe.net ``` #### GitHub または、Stripe.net ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-dotnet)ダウンロードします。 ### Stripe ライブラリーをインストールする パッケージをインストールし、コードにインポートします。まったくゼロから開始していて `package.json` が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 ライブラリーをインストールします。 ```bash npm install --save stripe @stripe/stripe-js next ``` > #### macOS で Terminal にアクセスする > > Stripe Terminal SDK には、ローカルネットワークへのアクセスが必要です。macOS を使用する場合は、ブラウザーアプリがローカルネットワークデバイスにアクセスすることを明示的に許可する必要があります。詳細については、[Stripe のサポート記事](https://support.stripe.com/questions/ensuring-stripe-terminal-javascript-sdk-functionality-on-macos-15)をご覧ください。 ### ConnectionToken エンドポイントを作成する リーダーに接続するには、バックエンドから顧客の Stripe アカウントに、リーダーを使用するための SDK 権限を付与する必要があります。それには、[ConnectionToken](https://docs.stripe.com/api/terminal/connection_tokens.md)から Secret を提供します。信頼できるクライアントに対してのみ接続トークンを作成し、リーダーへのアクセスを制御する接続トークンを作成する際に [Location ID を渡し](https://docs.stripe.com/terminal/fleet/locations-and-zones.md#connection-tokens)ます。Connect を使用している場合は、関連する連結アカウントに[接続トークンの範囲を限定](https://docs.stripe.com/terminal/features/connect.md)します。 ### SDK をインストールする 最新のリーダーソフトウェアとの互換性を保つため、このスクリプトは常に https://js.stripe.com から直接読み込む必要があります。スクリプトをバンドルに含めたり、そのコピーを自分で保持したりしないでください。このようにすると、警告なく組み込みが破損することがあります。Terminal JS SDK をモジュールとして読み込み、使用を容易にする npm パッケージも用意されています。詳細については、[GitHub のプロジェクト](https://github.com/stripe/terminal-js)をご覧ください。 ### リーダーの店舗を作成する リーダーを整理するための[店舗を作成](https://docs.stripe.com/terminal/fleet/locations-and-zones.md)します。店舗はリーダーをグループ化し、使用地域に必要なリーダー設定を自動的にダウンロードできるようにします。リーダーを[登録](https://docs.stripe.com/terminal/fleet/register-readers.md)する際に、各リーダーに店舗を割り当てる必要があります。これは、API またはダッシュボードを使用して実行できます。 ### ConnectionToken を取得する SDK にこのエンドポイントへのアクセスを許可するには、バックエンドから ConnectionToken をリクエストし、ConnectionToken オブジェクトからシークレットを返す関数をウェブアプリケーションで作成します。 ### SDK を初期化する JavaScript アプリケーションで `StripeTerminal` インスタンスを初期化するには、`onFetchConnectionToken` 関数を指定します。リーダーから予期せず切断された場合に対処するために、`onUnexpectedReaderDisconnect` 関数も指定する必要があります。 ### リーダーを検出する Stripe Terminal SDK にはシミュレートされた内蔵カードリーダーが付属しているため、物理ハードウェアに接続することなくアプリを開発してテストすることができます。シミュレートされたリーダーを使用するには、シミュレートされたオプションを true に設定して、`discoverReaders` を呼び出してリーダーを検索します。対象のリーダーをより簡単に見つけるには、店舗で絞り込みます。 ### シミュレーションされたリーダーに接続する `discoverReaders` から結果が返されたら、`connectReader` を呼び出してシミュレーションされたリーダーに接続します。 ### PaymentIntent を作成する PaymentIntent を作成するエンドポイントをサーバーに追加します。PaymentIntent は、顧客の支払いライフサイクルを追跡して、支払いの実行で失敗があった場合にはその記録を残し、顧客への請求が重複しないようにします。レスポンスで PaymentIntent の Client Secret を返します。Connect を使用している場合は、プラットフォームの支払いロジックに基づいて[連結アカウント情報](https://docs.stripe.com/terminal/features/connect.md)を指定することもできます。 ### PaymentIntent を取得する サーバーに対して PaymentIntent が支払い処理を開始するようにリクエストします。 ### 支払い方法の詳細を収集する PaymentIntent の Client Secret を使用して `collectPaymentMethod` を呼び出し、決済手段を収集します。シミュレートされたリーダーに接続しているときにこのメソッドを呼び出すと、PaymentIntent オブジェクトが[シミュレートされたテストカード](https://docs.stripe.com/terminal/references/testing.md#simulated-test-cards)で直ちに更新されます。物理リーダーに接続されている場合、接続されたリーダーはカードの提示を待ち受けます。 ### 支払いを処理する 支払い方法データが正常に収集されたら、更新された PaymentIntent を使用して `processPayment` を呼び出して、支払いを処理します。呼び出しが成功すると、PaymentIntent のステータスは手動キャプチャーの場合は `requires_capture`、自動キャプチャーの場合は `succeeded` になります。 ### PaymentIntent をキャプチャーするエンドポイントを作成する エンドポイントをバックエンドに作成して、PaymentIntent ID を受け付け、Stripe API にそれをキャプチャーするようにリクエストを送信します。 ### PaymentIntent をキャプチャーする PaymentIntent 作成時に `capture_method` を `manual` として定義した場合、SDK はオーソリ済みでキャプチャーされていない PaymentIntent をアプリケーションに返します。PaymentIntent のステータスが `requires_capture` の場合は、PaymentIntent をキャプチャーするようにバックエンドに通知します。 連結アカウントの場合、決済を手動でキャプチャーする前に、PaymentIntent の `application_fee_amount` を調べ、必要に応じて変更します。 ### アプリケーションを実行する サーバーを実行し、[localhost:4242](http://localhost:4242) に移動します。 ### テストカード番号を使用して組み込みを試す シミュレートされたリーダーを構成して、さまざまなカードブランドや請求の拒否などのエラーシナリオなど、POS アプリケーション内のさまざまなフローをテストできます。この動作を有効にするには、`collectPaymentMethod` を呼び出す前に、次のコード行を挿入します。 | Scenario | Card Number | | ------------------- | ---------------- | | Payment succeeds | 4242424242424242 | | Payment is declined | 4000000000009995 | ### Stripe Node ライブラリーをインストールする パッケージをインストールし、それをコードにインポートします。また、まったくゼロから開始していて package.json ファイルが必要な場合には、コードエディターのダウンロードリンクを使用してプロジェクトファイルをダウンロードします。 #### npm ライブラリーをインストールします。 ```bash npm install --save stripe ``` #### GitHub または、stripe-node ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-node)ダウンロードします。 ### Stripe Ruby ライブラリーをインストールする Stripe ruby gem をインストールし、require を指定してコードに読み込みます。または、まったくゼロから開始していて Gemfile が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Terminal gem をインストールします。 ```bash gem install stripe ``` #### Bundler この行を Gemfile に追加します。 ```bash gem 'stripe' ``` #### GitHub または、stripe-ruby gem のソースコードを直接 [GitHub から](https://github.com/stripe/stripe-ruby)ダウンロードします。 ### Stripe Java ライブラリーをインストールする ビルドに依存関係を追加し、ライブラリーをインポートします。まったくゼロから開始していてサンプルの pom.xml ファイル (Maven 用) が必要な場合は、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Maven POM に以下の依存関係を追加し、{VERSION} を使用するバージョン番号に置き換えます。 ```bash \ncom.stripe\nstripe-java\n{VERSION}\n ``` #### Gradle build.gradle ファイルに依存関係を追加し、{VERSION} を使用するバージョン番号に置き換えます。 ```bash implementation "com.stripe:stripe-java:{VERSION}" ``` #### GitHub JAR を直接 [GitHub から](https://github.com/stripe/stripe-java/releases/latest)ダウンロードします。 ### Stripe Python パッケージをインストールする Stripe パッケージをインストールし、コードにインポートします。まったくゼロから開始していて requirements.txt が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### pip pip を使用してパッケージをインストールします。 ```bash pip3 install stripe ``` #### GitHub stripe-python ライブラリのソースコードを [GitHub から](https://github.com/stripe/stripe-python)直接ダウンロードします。 ### Stripe PHP ライブラリーをインストールする Composer を使用してライブラリーをインストールし、シークレット API キーで初期化します。まったくゼロから開始していて composer.json ファイルが必要な場合には、コードエディターのリンクを使用してファイルをダウンロードします。 #### Composer ライブラリーをインストールします。 ```bash composer require stripe/stripe-php ``` #### GitHub または、stripe-php ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-php)ダウンロードします。 ### サーバーを設定する ビルドに依存関係を追加し、ライブラリーをインポートします。まったくゼロから開始していて go.mod ファイルが必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Go 必ず Go モジュールを使用してを初期化してください。 ```bash go get -u github.com/stripe/stripe-go/v85 ``` #### GitHub または、stripe-go モジュールのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-go)ダウンロードします。 ### Stripe.net ライブラリーをインストールする .NET または NuGet でパッケージをインストールします。まったくゼロから開始する場合には、設定済みの .csproj ファイルが含まれるファイルをダウンロードします。 #### dotnet ライブラリーをインストールします。 ```bash dotnet add package Stripe.net ``` #### NuGet ライブラリーをインストールします。 ```bash Install-Package Stripe.net ``` #### GitHub または、Stripe.net ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-dotnet)ダウンロードします。 ### Stripe ライブラリーをインストールする パッケージをインストールし、コードにインポートします。まったくゼロから開始していて `package.json` が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 ライブラリーをインストールします。 ```bash npm install --save stripe @stripe/stripe-js next ``` > #### macOS で Terminal にアクセスする > > Stripe Terminal SDK には、ローカルネットワークへのアクセスが必要です。macOS を使用する場合は、ブラウザーアプリがローカルネットワークデバイスにアクセスすることを明示的に許可する必要があります。詳細については、[Stripe のサポート記事](https://support.stripe.com/questions/ensuring-stripe-terminal-javascript-sdk-functionality-on-macos-15)をご覧ください。 ### ConnectionToken エンドポイントを作成する リーダーに接続するには、バックエンドから顧客の Stripe アカウントに、リーダーを使用するための SDK 権限を付与する必要があります。それには、[ConnectionToken](https://docs.stripe.com/api/terminal/connection_tokens.md)から Secret を提供します。信頼できるクライアントに対してのみ接続トークンを作成し、リーダーへのアクセスを制御する接続トークンを作成する際に [Location ID を渡し](https://docs.stripe.com/terminal/fleet/locations-and-zones.md#connection-tokens)ます。Connect を使用している場合は、関連する連結アカウントに[接続トークンの範囲を限定](https://docs.stripe.com/terminal/features/connect.md)します。 ### SDK をインストールする iOS SDK は[オープンソース](https://github.com/stripe/stripe-terminal-ios)であり、詳細なドキュメントが提供され、iOS 10 以降をサポートするアプリと互換性があります。Stripe SDK を決済画面の UIViewController にインポートします。 #### CocoaPods この行を Podfile に追加します。これ以降は、Xcode でプロジェクトを開く際に、.xcodeproj ファイルではなく、.xcworkspace ファイルを使用してください。 ```bash pod 'StripeTerminal', '~> 5.0' ``` #### Carthage この行を Cartfile に追加します。 ```bash github "stripe/stripe-terminal-ios" ``` #### XCFramework 1. GitHub の最新リリースから StripeTerminal.xcframework.zip をダウンロードします。 1. この zip を解凍し、「Copy items if needed」(必要に応じてアイテムをコピー) を必ず選択して、.xcframework をプロジェクトにドラッグします。 1. Xcode でアプリケーションターゲットの「Frameworks, Libraries, and Embedded Content」(フレームワーク、ライブラリー、埋め込みコンテンツ) セクションに xcframework が含まれていることを確認し、「Embed & Sign」(埋め込んで署名) に設定します。 #### Swift Package Manager 1. Xcode で、メニューバーの **File** > **Add Packages…** を選択します。 1. Stripe Terminal iOS SDK の GitHub URL を入力します。 ```bash https://github.com/stripe/stripe-terminal-ios ``` ### リーダーの店舗を作成する リーダーを整理するための[店舗を作成](https://docs.stripe.com/terminal/fleet/locations-and-zones.md)します。店舗はリーダーをグループ化し、使用地域に必要なリーダー設定を自動的にダウンロードできるようにします。リーダーを[登録](https://docs.stripe.com/terminal/fleet/register-readers.md)する際に、各リーダーに店舗を割り当てる必要があります。これは、API またはダッシュボードを使用して実行できます。 ### アプリを設定する アプリが Stripe Terminal SDK と連携して動作するように準備するには、Xcode で Info.plist ファイルにいくつかの変更を加えます。 #### 位置 次のキーと値のペアを使用して位置サービスを有効にします。 ```bash NSLocationWhenInUseUsageDescription\nLocation access is required to accept payments. ``` #### バックグラウンドモード アプリがバックグラウンドで実行され、Bluetooth リーダーに接続されたままであることを確認してください。 ```bash UIBackgroundModes\n\nbluetooth-central\n ``` #### Bluetooth Peripheral App Store に送信するときに、アプリの検証確認を渡します。 ```bash NSBluetoothPeripheralUsageDescription\nBluetooth access is required to connect to supported bluetooth card readers. ``` #### Bluetooth Always アプリで Bluetooth に対する権限のダイアログを表示できるようにします。 ```bash NSBluetoothAlwaysUsageDescription\nThis app uses Bluetooth to connect to supported card readers. ``` ### ConnectionToken を取得する アプリに ConnectionTokenProvider プロトコルを実装します。これはバックエンドから接続トークンをリクエストする 1 つの関数を定義します。 ### SDK を初期化する まず、ConnectionTokenProvider を指定します。`setTokenProvider` をアプリで呼び出すことができるのは 1 回のみであり、`Terminal.shared` にアクセスする前に呼び出す必要があります。 ### リーダーを検出する Stripe Terminal SDK にはシミュレートされた内蔵カードリーダーが付属しているため、物理ハードウェアに接続することなくアプリを開発してテストすることができます。シミュレートされたリーダーを使用するには、シミュレートされたオプションを true に設定して、`discoverReaders` を呼び出してリーダーを検索します。 ### リーダーを検出する Stripe Terminal SDK にはシミュレートされた内蔵カードリーダーが付属しているため、物理ハードウェアに接続することなくアプリを開発してテストすることができます。シミュレートされたリーダーを使用するには、シミュレートされたオプションを true に設定して、`discoverReaders` を呼び出してリーダーを検索します。対象のリーダーをより簡単に見つけるには、店舗で絞り込みます。 ### シミュレーションされたリーダーに接続する `didUpdateDiscoveredReaders` デリゲートメソッドが呼び出されたら、`connectReader` を呼び出してシミュレーションされたリーダーに接続します。 ### PaymentIntent を作成する SDK を使用して、[PaymentIntent (支払いインテント)](https://docs.stripe.com/api/payment_intents.md) オブジェクトを作成します。PaymentIntent は、顧客の支払いライフサイクルを追跡して、支払いの試行失敗を記録し、顧客への請求が重複しないようにします。 ### PaymentIntent を処理する PaymentIntent を使用して `processPaymentIntent` を呼び出し、決済手段を収集して決済を承認します。シミュレーションされたリーダーに接続されている場合、このメソッドを呼び出すと、PaymentIntent オブジェクトが [simulated test card](https://docs.stripe.com/terminal/references/testing.md#simulated-test-cards) で即座に更新されます。物理リーダーに接続されている場合、接続されたリーダーはカードの提示を待ちます。呼び出しが成功すると、PaymentIntent のステータスは、手動キャプチャーの場合は `requires_capture`、自動キャプチャーの場合は `succeeded` になります。 ### PaymentIntent をキャプチャーするエンドポイントを作成する エンドポイントをバックエンドに作成して、PaymentIntent ID を受け付け、Stripe API にそれをキャプチャーするようにリクエストを送信します。 ### PaymentIntent をキャプチャーする PaymentIntent 作成時に `capture_method` を `manual` として定義した場合、SDK はオーソリ済みでキャプチャーされていない PaymentIntent をアプリケーションに返します。PaymentIntent のステータスが `requires_capture` の場合は、PaymentIntent をキャプチャーするようにバックエンドに通知します。 連結アカウントの場合、決済を手動でキャプチャーする前に、PaymentIntent の `application_fee_amount` を調べ、必要に応じて変更します。 ### アプリケーションを実行する サーバーを実行し、[localhost:4242](http://localhost:4242) に移動します。 ### テストカード番号を使用して組み込みを試す シミュレートされたリーダーを構成して、さまざまなカードブランドや請求の拒否などのエラーシナリオなど、POS アプリケーション内のさまざまなフローをテストできます。この動作を有効にするには、`collectPaymentMethod` を呼び出す前に、次のコード行を挿入します。 | Scenario | Card Number | | ------------------- | ---------------- | | Payment succeeds | 4242424242424242 | | Payment is declined | 4000000000009995 | ### Stripe Node ライブラリーをインストールする パッケージをインストールし、それをコードにインポートします。また、まったくゼロから開始していて package.json ファイルが必要な場合には、コードエディターのダウンロードリンクを使用してプロジェクトファイルをダウンロードします。 #### npm ライブラリーをインストールします。 ```bash npm install --save stripe ``` #### GitHub または、stripe-node ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-node)ダウンロードします。 ### Stripe Ruby ライブラリーをインストールする Stripe ruby gem をインストールし、require を指定してコードに読み込みます。または、まったくゼロから開始していて Gemfile が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Terminal gem をインストールします。 ```bash gem install stripe ``` #### Bundler この行を Gemfile に追加します。 ```bash gem 'stripe' ``` #### GitHub または、stripe-ruby gem のソースコードを直接 [GitHub から](https://github.com/stripe/stripe-ruby)ダウンロードします。 ### Stripe Java ライブラリーをインストールする ビルドに依存関係を追加し、ライブラリーをインポートします。まったくゼロから開始していてサンプルの pom.xml ファイル (Maven 用) が必要な場合は、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Maven POM に以下の依存関係を追加し、{VERSION} を使用するバージョン番号に置き換えます。 ```bash \ncom.stripe\nstripe-java\n{VERSION}\n ``` #### Gradle build.gradle ファイルに依存関係を追加し、{VERSION} を使用するバージョン番号に置き換えます。 ```bash implementation "com.stripe:stripe-java:{VERSION}" ``` #### GitHub JAR を直接 [GitHub から](https://github.com/stripe/stripe-java/releases/latest)ダウンロードします。 ### Stripe Python パッケージをインストールする Stripe パッケージをインストールし、コードにインポートします。まったくゼロから開始していて requirements.txt が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### pip pip を使用してパッケージをインストールします。 ```bash pip3 install stripe ``` #### GitHub stripe-python ライブラリのソースコードを [GitHub から](https://github.com/stripe/stripe-python)直接ダウンロードします。 ### Stripe PHP ライブラリーをインストールする Composer を使用してライブラリーをインストールし、シークレット API キーで初期化します。まったくゼロから開始していて composer.json ファイルが必要な場合には、コードエディターのリンクを使用してファイルをダウンロードします。 #### Composer ライブラリーをインストールします。 ```bash composer require stripe/stripe-php ``` #### GitHub または、stripe-php ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-php)ダウンロードします。 ### サーバーを設定する ビルドに依存関係を追加し、ライブラリーをインポートします。まったくゼロから開始していて go.mod ファイルが必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Go 必ず Go モジュールを使用してを初期化してください。 ```bash go get -u github.com/stripe/stripe-go/v85 ``` #### GitHub または、stripe-go モジュールのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-go)ダウンロードします。 ### Stripe.net ライブラリーをインストールする .NET または NuGet でパッケージをインストールします。まったくゼロから開始する場合には、設定済みの .csproj ファイルが含まれるファイルをダウンロードします。 #### dotnet ライブラリーをインストールします。 ```bash dotnet add package Stripe.net ``` #### NuGet ライブラリーをインストールします。 ```bash Install-Package Stripe.net ``` #### GitHub または、Stripe.net ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-dotnet)ダウンロードします。 ### Stripe ライブラリーをインストールする パッケージをインストールし、コードにインポートします。まったくゼロから開始していて `package.json` が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 ライブラリーをインストールします。 ```bash npm install --save stripe @stripe/stripe-js next ``` > #### macOS で Terminal にアクセスする > > Stripe Terminal SDK には、ローカルネットワークへのアクセスが必要です。macOS を使用する場合は、ブラウザーアプリがローカルネットワークデバイスにアクセスすることを明示的に許可する必要があります。詳細については、[Stripe のサポート記事](https://support.stripe.com/questions/ensuring-stripe-terminal-javascript-sdk-functionality-on-macos-15)をご覧ください。 ### ConnectionToken エンドポイントを作成する リーダーに接続するには、バックエンドから顧客の Stripe アカウントに、リーダーを使用するための SDK 権限を付与する必要があります。それには、[ConnectionToken](https://docs.stripe.com/api/terminal/connection_tokens.md)から Secret を提供します。信頼できるクライアントに対してのみ接続トークンを作成し、リーダーへのアクセスを制御する接続トークンを作成する際に [Location ID を渡し](https://docs.stripe.com/terminal/fleet/locations-and-zones.md#connection-tokens)ます。Connect を使用している場合は、関連する連結アカウントに[接続トークンの範囲を限定](https://docs.stripe.com/terminal/features/connect.md)します。 ### SDK をインストールする SDK をインストールするには、build.gradle ファイルの dependencies ブロックに `stripeterminal` を追加します。 #### Gradle build.gradle ファイルに依存関係を追加します。 #### Groovy ```groovy dependencies { // ... // Stripe Terminal SDK implementation 'com.stripe:stripeterminal:5.4.0' } ``` #### GitHub Stripe Android SDK はオープンソースです。[GitHub でご確認ください](https://github.com/stripe/stripe-android)。 ### SDK をインストールする Tap to Pay SDK をインストールするには、`stripeterminal-taptopay` と `stripeterminal-core` を `build.gradle` または `build.gradle.kts` ファイルの dependencies ブロックに追加します。 すでに `stripeterminal` の依存関係がある場合は、`build.gradle` ファイルまたは `build.gradle.kts` ファイルに依存関係を追加して、次のように置き換えます。 #### Groovy ```groovy dependencies { // ... // Stripe Tap to Pay SDK implementation 'com.stripe:stripeterminal-taptopay:5.4.0' implementation 'com.stripe:stripeterminal-core:5.4.0' } ``` ### リーダーの店舗を作成する リーダーを整理するための[店舗を作成](https://docs.stripe.com/terminal/fleet/locations-and-zones.md)します。店舗はリーダーをグループ化し、使用地域に必要なリーダー設定を自動的にダウンロードできるようにします。リーダーを[登録](https://docs.stripe.com/terminal/fleet/register-readers.md)する際に、各リーダーに店舗を割り当てる必要があります。これは、API またはダッシュボードを使用して実行できます。 ### ACCESS_FINE_LOCATION 権限を確認する チェックを追加し、アプリで `ACCESS_FINE_LOCATION` 権限を有効にしていることを確認します。 ### ユーザーの位置情報のアクセス許可を確認する アプリで `onRequestPermissionsResult` メソッドを上書きし、権限の結果を調べてアプリのユーザーが位置情報のアクセス許可を付与したことを確認します。 ### ConnectionToken を取得する アプリに ConnectionTokenProvider インターフェイスを実装します。これはバックエンドからの接続トークンをリクエストする 1 つの関数を定義します。 ### TerminalApplicationDelegate を設定する メモリーリークを防止し、長期にわたる Terminal SDK プロセスの適切なクリーンアップを行うために、アプリケーションに `Application` サブクラスを含めて、`onCreate` メソッドから `TerminalApplicationDelegate` を呼び出す必要があります。 ### SDK を初期化する まずはじめに、現在のアプリケーションのコンテキスト、ConnectionTokenProvider、TerminalListener オブジェクトを指定します。 ### リーダーを検出する Stripe Terminal SDK にはシミュレートされた内蔵カードリーダーが付属しているため、物理ハードウェアに接続することなくアプリを開発してテストすることができます。シミュレートされたリーダーを使用するには、シミュレートされたオプションを true に設定して、`discoverReaders` を呼び出してリーダーを検索します。 ### リーダーを検出する Stripe Terminal SDK にはシミュレートされた内蔵カードリーダーが付属しているため、物理ハードウェアに接続することなくアプリを開発してテストすることができます。シミュレートされたリーダーを使用するには、シミュレートされたオプションを true に設定して、`discoverReaders` を呼び出してリーダーを検索します。対象のリーダーをより簡単に見つけるには、店舗で絞り込みます。 ### シミュレーションされたリーダーに接続する `discoverReaders` から結果が返されたら、`connectReader` を呼び出してシミュレーションされたリーダーに接続します。 ### PaymentIntent を作成する SDK を使用して、[PaymentIntent (支払いインテント)](https://docs.stripe.com/api/payment_intents.md) オブジェクトを作成します。PaymentIntent は、顧客の支払いライフサイクルを追跡して、支払いの試行失敗を記録し、顧客への請求が重複しないようにします。 ### PaymentIntent を処理する PaymentIntent を使用して `processPaymentIntent` を呼び出し、決済手段を収集して決済を承認します。シミュレーションされたリーダーに接続されている場合、このメソッドを呼び出すと、PaymentIntent オブジェクトが [simulated test card](https://docs.stripe.com/terminal/references/testing.md#simulated-test-cards) で即座に更新されます。物理リーダーに接続されている場合、接続されたリーダーはカードの提示を待ちます。呼び出しが成功すると、PaymentIntent のステータスは、手動キャプチャーの場合は `requires_capture`、自動キャプチャーの場合は `succeeded` になります。 ### PaymentIntent をキャプチャーするエンドポイントを作成する エンドポイントをバックエンドに作成して、PaymentIntent ID を受け付け、Stripe API にそれをキャプチャーするようにリクエストを送信します。 ### PaymentIntent をキャプチャーする PaymentIntent 作成時に `capture_method` を `manual` として定義した場合、SDK はオーソリ済みでキャプチャーされていない PaymentIntent をアプリケーションに返します。PaymentIntent のステータスが `requires_capture` の場合は、PaymentIntent をキャプチャーするようにバックエンドに通知します。 連結アカウントの場合、決済を手動でキャプチャーする前に、PaymentIntent の`application_fee_amount` を調べ、必要に応じて変更します。 ### アプリケーションを実行する サーバーを実行し、[localhost:4242](http://localhost:4242) に移動します。 ### テストカード番号を使用して組み込みを試す シミュレートされたリーダーを構成して、さまざまなカードブランドや請求の拒否などのエラーシナリオなど、POS アプリケーション内のさまざまなフローをテストできます。この動作を有効にするには、`collectPaymentMethod` を呼び出す前に、次のコード行を挿入します。 | Scenario | Card Number | | ------------------- | ---------------- | | Payment succeeds | 4242424242424242 | | Payment is declined | 4000000000009995 | ### Stripe Node ライブラリーをインストールする パッケージをインストールし、それをコードにインポートします。また、まったくゼロから開始していて package.json ファイルが必要な場合には、コードエディターのダウンロードリンクを使用してプロジェクトファイルをダウンロードします。 #### npm ライブラリーをインストールします。 ```bash npm install --save stripe ``` #### GitHub または、stripe-node ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-node)ダウンロードします。 ### Stripe Ruby ライブラリーをインストールする Stripe ruby gem をインストールし、require を指定してコードに読み込みます。または、まったくゼロから開始していて Gemfile が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Terminal gem をインストールします。 ```bash gem install stripe ``` #### Bundler この行を Gemfile に追加します。 ```bash gem 'stripe' ``` #### GitHub または、stripe-ruby gem のソースコードを直接 [GitHub から](https://github.com/stripe/stripe-ruby)ダウンロードします。 ### Stripe Java ライブラリーをインストールする ビルドに依存関係を追加し、ライブラリーをインポートします。まったくゼロから開始していてサンプルの pom.xml ファイル (Maven 用) が必要な場合は、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Maven POM に以下の依存関係を追加し、{VERSION} を使用するバージョン番号に置き換えます。 ```bash \ncom.stripe\nstripe-java\n{VERSION}\n ``` #### Gradle build.gradle ファイルに依存関係を追加し、{VERSION} を使用するバージョン番号に置き換えます。 ```bash implementation "com.stripe:stripe-java:{VERSION}" ``` #### GitHub JAR を直接 [GitHub から](https://github.com/stripe/stripe-java/releases/latest)ダウンロードします。 ### Stripe Python パッケージをインストールする Stripe パッケージをインストールし、コードにインポートします。まったくゼロから開始していて requirements.txt が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### pip pip を使用してパッケージをインストールします。 ```bash pip3 install stripe ``` #### GitHub stripe-python ライブラリのソースコードを [GitHub から](https://github.com/stripe/stripe-python)直接ダウンロードします。 ### Stripe PHP ライブラリーをインストールする Composer を使用してライブラリーをインストールし、シークレット API キーで初期化します。まったくゼロから開始していて composer.json ファイルが必要な場合には、コードエディターのリンクを使用してファイルをダウンロードします。 #### Composer ライブラリーをインストールします。 ```bash composer require stripe/stripe-php ``` #### GitHub または、stripe-php ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-php)ダウンロードします。 ### サーバーを設定する ビルドに依存関係を追加し、ライブラリーをインポートします。まったくゼロから開始していて go.mod ファイルが必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 #### Go 必ず Go モジュールを使用してを初期化してください。 ```bash go get -u github.com/stripe/stripe-go/v85 ``` #### GitHub または、stripe-go モジュールのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-go)ダウンロードします。 ### Stripe.net ライブラリーをインストールする .NET または NuGet でパッケージをインストールします。まったくゼロから開始する場合には、設定済みの .csproj ファイルが含まれるファイルをダウンロードします。 #### dotnet ライブラリーをインストールします。 ```bash dotnet add package Stripe.net ``` #### NuGet ライブラリーをインストールします。 ```bash Install-Package Stripe.net ``` #### GitHub または、Stripe.net ライブラリーのソースコードを直接 [GitHub から](https://github.com/stripe/stripe-dotnet)ダウンロードします。 ### Stripe ライブラリーをインストールする パッケージをインストールし、コードにインポートします。まったくゼロから開始していて `package.json` が必要な場合には、コードエディターのリンクを使用してプロジェクトファイルをダウンロードします。 ライブラリーをインストールします。 ```bash npm install --save stripe @stripe/stripe-js next ``` > #### macOS で Terminal にアクセスする > > Stripe Terminal SDK には、ローカルネットワークへのアクセスが必要です。macOS を使用する場合は、ブラウザーアプリがローカルネットワークデバイスにアクセスすることを明示的に許可する必要があります。詳細については、[Stripe のサポート記事](https://support.stripe.com/questions/ensuring-stripe-terminal-javascript-sdk-functionality-on-macos-15)をご覧ください。 ### ConnectionToken エンドポイントを作成する リーダーに接続するには、バックエンドから顧客の Stripe アカウントに、リーダーを使用するための SDK 権限を付与する必要があります。それには、[ConnectionToken](https://docs.stripe.com/api/terminal/connection_tokens.md)から Secret を提供します。信頼できるクライアントに対してのみ接続トークンを作成し、リーダーへのアクセスを制御する接続トークンを作成する際に [Location ID を渡し](https://docs.stripe.com/terminal/fleet/locations-and-zones.md#connection-tokens)ます。Connect を使用している場合は、関連する連結アカウントに[接続トークンの範囲を限定](https://docs.stripe.com/terminal/features/connect.md)します。 ### SDK をインストールする ```bash yarn install @stripe/stripe-terminal-react-native ``` ### リーダーの店舗を作成する リーダーを整理するための[店舗を作成](https://docs.stripe.com/terminal/fleet/locations-and-zones.md)します。店舗はリーダーをグループ化し、使用地域に必要なリーダー設定を自動的にダウンロードできるようにします。リーダーを[登録](https://docs.stripe.com/terminal/fleet/register-readers.md)する際に、各リーダーに店舗を割り当てる必要があります。これは、API またはダッシュボードを使用して実行できます。 ### ConnectionToken を取得する SDK にこのエンドポイントへのアクセスを許可するには、バックエンドから ConnectionToken をリクエストし、ConnectionToken オブジェクトからシークレットを返す関数をウェブアプリケーションで作成します。 ### 権限を設定する #### Android チェックを追加し、アプリで `ACCESS_FINE_LOCATION` 権限を有効にしていることを確認します。 #### iOS アプリが Stripe Terminal SDK と連携して動作するように作成するには、Xcode で Info.plist ファイルにいくつかの変更を加えます。 #### 店舗 次のキーと値のペアを使用して位置サービスを有効にします。 ```bash NSLocationWhenInUseUsageDescription\nLocation access is required to accept payments. ``` #### バックグラウンドモード アプリがバックグラウンドで実行され、Bluetooth リーダーに接続されたままであることを確認してください。 ```bash UIBackgroundModes\n\nbluetooth-central\n ``` #### Bluetooth 周辺機器 App Store に送信するときに、アプリの検証確認を渡します。 ```bash NSBluetoothPeripheralUsageDescription\nBluetooth access is required to connect to supported bluetooth card readers. ``` #### 常に Bluetooth アプリで Bluetooth に対する権限を求めるダイアログを表示できるようにします。 ```bash NSBluetoothAlwaysUsageDescription\nThis app uses Bluetooth to connect to supported card readers. ``` ### コンテキストプロバイダーを設定する `onFetchConnectionToken` 関数をプロパティとして `StripeTerminalProvider` に渡します。 ### SDK を初期化する React Native アプリケーションで `StripeTerminal` インスタンスを初期化するには、`useStripeTerminal` フックから initialize メソッドを呼び出します。`initialize` メソッドは、`StripeTerminalProvider` を含むコンポーネントからではなく、`StripeTerminalProvider` 内にネストされたコンポーネントから呼び出す必要があります。 ### リーダーを検出する Stripe Terminal SDK にはシミュレートされた内蔵カードリーダーが付属しているため、物理ハードウェアに接続することなくアプリを開発してテストすることができます。シミュレートされたリーダーを使用するには、シミュレートされたオプションを true に設定して、`discoverReaders` を呼び出してリーダーを検索します。 ### リーダーを検出する Stripe Terminal SDKには、シミュレーションされたカードリーダーが内蔵されています。シミュレートリーダーを使用するには、`discoverReaders` を呼び出し、simulated オプションを true に設定してリーダーを検索します。 ### リーダーを検出する Stripe Terminal SDK にはシミュレートされた内蔵カードリーダーが付属しているため、物理ハードウェアに接続することなくアプリを開発してテストすることができます。シミュレートされたリーダーを使用するには、シミュレートされたオプションを true に設定して、`discoverReaders` を呼び出してリーダーを検索します。対象のリーダーをより簡単に見つけるには、店舗で絞り込みます。 ### シミュレートされたリーダーに接続する `discoverReaders` から結果が返されたら、`connectReader` を呼び出してシミュレーションされたリーダーに接続します。 ### PaymentIntent を作成する PaymentIntent を作成するエンドポイントをサーバーに追加します。PaymentIntent は、顧客の支払いライフサイクルを追跡して、支払いの実行で失敗があった場合にはその記録を残し、顧客への請求が重複しないようにします。レスポンスで PaymentIntent の Client Secret を返します。Connect を使用している場合は、プラットフォームの支払いロジックに基づいて[連結アカウント情報](https://docs.stripe.com/terminal/features/connect.md)を指定することもできます。 ### PaymentIntent を取得する サーバに対して PaymentIntent が支払い処理を開始するようにリクエストします。 ### 支払い方法の詳細を収集する PaymentIntent の Client Secret を使用して `collectPaymentMethod` を呼び出し、決済手段を収集します。シミュレートされたリーダーに接続しているときにこのメソッドを呼び出すと、PaymentIntent オブジェクトが[シミュレートされたテストカード](https://docs.stripe.com/terminal/references/testing.md#simulated-test-cards)で直ちに更新されます。物理リーダーに接続されている場合、接続されたリーダーはカードの提示を待ち受けます。 ### 支払いを処理する 決済手段データが正常に収集されたら、更新した PaymentIntent を使用して `processPayment` を呼び出し、支払いを処理します。呼び出しが成功すると、PaymentIntent のステータスが、手動キャプチャーの場合は `requires_capture`、自動キャプチャーの場合は `succeeded` になります。 ### PaymentIntent をキャプチャーするエンドポイントを作成する エンドポイントをバックエンドに作成して、PaymentIntent ID を受け付け、Stripe API にそれをキャプチャーするようにリクエストを送信します。 ### PaymentIntent をキャプチャーする PaymentIntent 作成時に `capture_method` を `manual` として定義した場合、SDK はオーソリ済みでキャプチャーされていない PaymentIntent をアプリケーションに返します。PaymentIntent のステータスが `requires_capture` の場合は、PaymentIntent をキャプチャーするようにバックエンドに通知します。 連結アカウントの場合、決済を手動でキャプチャーする前に、PaymentIntent の `application_fee_amount` を調べ、必要に応じて変更します。 ### アプリケーションを実行する サーバーを実行し、[localhost:4242](http://localhost:4242) に移動します。 ### テストカード番号を使用して組み込みを試す シミュレートされたリーダーを構成して、さまざまなカードブランドや請求の拒否などのエラーシナリオなど、POS アプリケーション内のさまざまなフローをテストできます。この動作を有効にするには、`collectPaymentMethod` を呼び出す前に、次のコード行を挿入します。 | Scenario | Card Number | | ------------------- | ---------------- | | Payment succeeds | 4242424242424242 | | Payment is declined | 4000000000009995 | // This is a public sample test API key. // Don’t submit any personally identifiable information in requests made with this key. // Sign in to see your own test API key embedded in code samples. const stripe = require("stripe")("<>"); const createLocation = async () => { const location = await stripe.terminal.locations.create({ display_name: '{{TERMINAL_LOCATION_NAME}}', address: { line1: '{{TERMINAL_LOCATION_LINE1}}', line2: '{{TERMINAL_LOCATION_LINE2}}', city: '{{TERMINAL_LOCATION_CITY}}', state: '{{TERMINAL_LOCATION_STATE}}', country: '{{TERMINAL_LOCATION_COUNTRY}}', postal_code: '{{TERMINAL_LOCATION_POSTAL}}', }, }); return location; }; const createLocation = async () => { const location = await stripe.terminal.locations.create({ display_name: '{{TERMINAL_LOCATION_NAME}}', display_name_kana: '{{TERMINAL_LOCATION_NAMEKANA}}', display_name_kanji: '{{TERMINAL_LOCATION_NAMEKANJI}}', phone: '{{TERMINAL_LOCATION_PHONE}}', address_kana: { line1: '{{TERMINAL_LOCATION_LINE1KANA}}', line2: '{{TERMINAL_LOCATION_LINE2KANA}}', town: '{{TERMINAL_LOCATION_TOWNKANA}}', city: '{{TERMINAL_LOCATION_CITYKANA}}', state: '{{TERMINAL_LOCATION_STATEKANA}}', postal_code: '{{TERMINAL_LOCATION_POSTALCODEKANA}}', }, address_kanji: { line1: '{{TERMINAL_LOCATION_LINE1KANJI}}', line2: '{{TERMINAL_LOCATION_LINE2KANJI}}', town: '{{TERMINAL_LOCATION_TOWNKANJI}}', city: '{{TERMINAL_LOCATION_CITYKANJI}}', state: '{{TERMINAL_LOCATION_STATEKANJI}}', postal_code: '{{TERMINAL_LOCATION_POSTALCODEKANJI}}', }, }); return location; }; app.post("/create_location", async (req, res) => { const location = await stripe.terminal.locations.create({ display_name: req.body.display_name, address: { line1: req.body.address.line1, city: req.body.address.city, state: req.body.address.state, country: req.body.address.country, postal_code: req.body.address.postal_code, }, }); res.json(location); }); app.post("/create_location", async (req, res) => { const location = await stripe.terminal.locations.create({ display_name: '{{TERMINAL_LOCATION_NAME}}', display_name_kana: '{{TERMINAL_LOCATION_NAMEKANA}}', display_name_kanji: '{{TERMINAL_LOCATION_NAMEKANJI}}', phone: '{{TERMINAL_LOCATION_PHONE}}', address_kana: { line1: '{{TERMINAL_LOCATION_LINE1KANA}}', line2: '{{TERMINAL_LOCATION_LINE2KANA}}', town: '{{TERMINAL_LOCATION_TOWNKANA}}', city: '{{TERMINAL_LOCATION_CITYKANA}}', state: '{{TERMINAL_LOCATION_STATEKANA}}', postal_code: '{{TERMINAL_LOCATION_POSTALCODEKANA}}', }, address_kanji: { line1: '{{TERMINAL_LOCATION_LINE1KANJI}}', line2: '{{TERMINAL_LOCATION_LINE2KANJI}}', town: '{{TERMINAL_LOCATION_TOWNKANJI}}', city: '{{TERMINAL_LOCATION_CITYKANJI}}', state: '{{TERMINAL_LOCATION_STATEKANJI}}', postal_code: '{{TERMINAL_LOCATION_POSTALCODEKANJI}}', }, }); res.json(location); }); app.post("/register_reader", async (req, res) => { const reader = await stripe.terminal.readers.create({ location: req.body.location_id, label: 'Quickstart - S700 Simulated Reader', registration_code: 'simulated-s700', label: 'Quickstart - S710 Simulated Reader', registration_code: 'simulated-s710', label: 'Quickstart - V660p Simulated Reader', registration_code: 'simulated-v660p', label: 'Quickstart - WisePOS E Simulated Reader', registration_code: 'simulated-wpe', }); res.json(reader); }); // The ConnectionToken's secret lets you connect to any Stripe Terminal reader // and take payments with your Stripe account. // Be sure to authenticate the endpoint for creating connection tokens. app.post("/connection_token", async (req, res) => { let connectionToken = await stripe.terminal.connectionTokens.create(); res.json({secret: connectionToken.secret}); }); app.post("/create_payment_intent", async (req, res) => { // For Terminal payments, the 'payment_method_types' parameter must include // 'card_present'. // To automatically capture funds when a charge is authorized, // set `capture_method` to `automatic`. const intent = await stripe.paymentIntents.create({ amount: req.body.amount, currency: '{{TERMINAL_CURRENCY}}', payment_method_types: [ '{{TERMINAL_PAYMENT_METHODS}}' ], capture_method: 'automatic', payment_method_options: { card_present: { capture_method: 'manual_preferred' } } }); res.json(intent); }); app.post("/process_payment", async (req, res) => { var attempt = 0; const tries = 3; while (true) { attempt++; try { const reader = await stripe.terminal.readers.processPaymentIntent( req.body.reader_id, { payment_intent: req.body.payment_intent_id, } ); return res.send(reader); } catch (error) { console.log(error); switch (error.code) { case "terminal_reader_timeout": // Temporary networking blip, automatically retry a few times. if (attempt == tries) { return res.send(error); } break; case "terminal_reader_offline": // Reader is offline and won't respond to API requests. Make sure the reader is powered on // and connected to the internet before retrying. return res.send(error); case "terminal_reader_busy": // Reader is currently busy processing another request, installing updates or changing settings. // Remember to disable the pay button in your point-of-sale application while waiting for a // reader to respond to an API request. return res.send(error); case "intent_invalid_state": // Check PaymentIntent status because it's not ready to be processed. It might have been already // successfully processed or canceled. const paymentIntent = await stripe.paymentIntents.retrieve( req.body.payment_intent_id ); console.log( "PaymentIntent is already in " + paymentIntent.status + " state." ); return res.send(error); default: return res.send(error); } } } }); app.post("/simulate_payment", async (req, res) => { const reader = await stripe.testHelpers.terminal.readers.presentPaymentMethod( req.body.reader_id, { card_present: { number: req.body.card_number, }, type: "card_present", } ); res.send(reader); }); app.post("/capture_payment_intent", async (req, res) => { const intent = await stripe.paymentIntents.capture(req.body.payment_intent_id); res.send(intent); }); { "name": "stripe-sample", "version": "1.0.0", "description": "A sample Stripe implementation", "main": "server.js", "scripts": { "start": "node server.js" }, "author": "stripe-samples", "license": "ISC", "dependencies": { "express": "^4.17.1", "stripe": "^21.0.1" } } { "name": "stripe-sample", "version": "0.1.0", "dependencies": { "@stripe/react-stripe-js": "^3.7.0", "@stripe/stripe-js": "^7.3.0", "express": "^4.17.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "^3.4.0", "stripe": "21.0.1" }, "devDependencies": { "concurrently": "4.1.2" }, "homepage": "http://localhost:3000/checkout", "proxy": "http://localhost:4242", "scripts": { "start-client": "react-scripts start", "start-server": "node server.js", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "start": "concurrently \"yarn start-client\" \"yarn start-server\"" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } require 'stripe' \# This is a public sample test API key. # Don’t submit any personally identifiable information in requests made with this key. # Sign in to see your own test API key embedded in code samples. Stripe.api_key = '<>' def create_location Stripe::Terminal::Location.create({ display_name: '{{TERMINAL_LOCATION_NAME}}', phone: '{{TERMINAL_LOCATION_PHONE}}', address: { line1: '{{TERMINAL_LOCATION_LINE1}}', line2: '{{TERMINAL_LOCATION_LINE2}}', city: '{{TERMINAL_LOCATION_CITY}}', state: '{{TERMINAL_LOCATION_STATE}}', country: '{{TERMINAL_LOCATION_COUNTRY}}', postal_code: '{{TERMINAL_LOCATION_POSTAL}}', } }) end def create_location Stripe::Terminal::Location.create({ display_name: '{{TERMINAL_LOCATION_NAME}}', display_name_kana: '{{TERMINAL_LOCATION_NAMEKANA}}', display_name_kanji: '{{TERMINAL_LOCATION_NAMEKANJI}}', phone: '{{TERMINAL_LOCATION_PHONE}}', address_kana: { line1: '{{TERMINAL_LOCATION_LINE1KANA}}', line2: '{{TERMINAL_LOCATION_LINE2KANA}}', town: '{{TERMINAL_LOCATION_TOWNKANA}}', city: '{{TERMINAL_LOCATION_CITYKANA}}', state: '{{TERMINAL_LOCATION_STATEKANA}}', postal_code: '{{TERMINAL_LOCATION_POSTALCODEKANA}}', }, address_kanji: { line1: '{{TERMINAL_LOCATION_LINE1KANJI}}', line2: '{{TERMINAL_LOCATION_LINE2KANJI}}', town: '{{TERMINAL_LOCATION_TOWNKANJI}}', city: '{{TERMINAL_LOCATION_CITYKANJI}}', state: '{{TERMINAL_LOCATION_STATEKANJI}}', postal_code: '{{TERMINAL_LOCATION_POSTALCODEKANJI}}', } }) end post '/create_location' do content_type 'application/json' data = JSON.parse(request.body.read) location = Stripe::Terminal::Location.create({ display_name: data['display_name'], address: { line1: data['address']['line1'], city: data['address']['city'], state: data['address']['state'], country: data['address']['country'], postal_code: data['address']['postal_code'], } }) location.to_json end post '/create_location' do content_type 'application/json' location = Stripe::Terminal::Location.create({ display_name: '{{TERMINAL_LOCATION_NAME}}', display_name_kana: '{{TERMINAL_LOCATION_NAMEKANA}}', display_name_kanji: '{{TERMINAL_LOCATION_NAMEKANJI}}', phone: '{{TERMINAL_LOCATION_PHONE}}', address_kana: { line1: '{{TERMINAL_LOCATION_LINE1KANA}}', line2: '{{TERMINAL_LOCATION_LINE2KANA}}', town: '{{TERMINAL_LOCATION_TOWNKANA}}', city: '{{TERMINAL_LOCATION_CITYKANA}}', state: '{{TERMINAL_LOCATION_STATEKANA}}', postal_code: '{{TERMINAL_LOCATION_POSTALCODEKANA}}', }, address_kanji: { line1: '{{TERMINAL_LOCATION_LINE1KANJI}}', line2: '{{TERMINAL_LOCATION_LINE2KANJI}}', town: '{{TERMINAL_LOCATION_TOWNKANJI}}', city: '{{TERMINAL_LOCATION_CITYKANJI}}', state: '{{TERMINAL_LOCATION_STATEKANJI}}', postal_code: '{{TERMINAL_LOCATION_POSTALCODEKANJI}}', }, }) location.to_json end post '/register_reader' do content_type 'application/json' data = JSON.parse(request.body.read) reader = Stripe::Terminal::Reader.create( location: data['location_id'], label: 'Quickstart - S700 Simulated Reader', registration_code: 'simulated-s700' label: 'Quickstart - S710 Simulated Reader', registration_code: 'simulated-s710' label: 'Quickstart - V660p Simulated Reader', registration_code: 'simulated-v660p' label: 'Quickstart - WisePOS E Simulated Reader', registration_code: 'simulated-wpe' ) reader.to_json end \# The ConnectionToken's secret lets you connect to any Stripe Terminal reader # and take payments with your Stripe account. # Be sure to authenticate the endpoint for creating connection tokens. post '/connection_token' do content_type 'application/json' connection_token = Stripe::Terminal::ConnectionToken.create {secret: connection_token.secret}.to_json end post '/create_payment_intent' do content_type 'application/json' data = JSON.parse(request.body.read) \# For Terminal payments, the 'payment_method_types' parameter must include # 'card_present'. # To automatically capture funds when a charge is authorized, # set `capture_method` to `automatic`. intent = Stripe::PaymentIntent.create( amount: data['amount'], currency: '{{TERMINAL_CURRENCY}}', payment_method_types: [ '{{TERMINAL_PAYMENT_METHODS}}' ], capture_method: 'automatic', payment_method_options: { card_present: { capture_method: 'manual_preferred' } } ) intent.to_json end post '/process_payment' do content_type 'application/json' data = JSON.parse(request.body.read) tries = 0 begin tries += 1 reader = Stripe::Terminal::Reader.process_payment_intent( data['reader_id'], payment_intent: data['payment_intent_id'] ) reader.to_json rescue Stripe::InvalidRequestError => e case e.code when 'terminal_reader_timeout' \# Temporary networking blip, automatically retry a few times. retry if tries < 3 when 'terminal_reader_offline' # Reader is offline and won't respond to API requests. Make sure the reader is powered on # and connected to the internet before retrying. request.logger.error(e.message) when 'terminal_reader_busy' # Reader is currently busy processing another request, installing updates, or changing settings. # Remember to disable the pay button in your point-of-sale application while waiting for a # reader to respond to an API request. request.logger.error(e.message) when 'intent_invalid_state' # Check PaymentIntent status because it's not ready to be processed. It might have been already # successfully processed or canceled. payment_intent = Stripe::PaymentIntent.retrieve(data['payment_intent_id']) request.logger.error("PaymentIntent is already in #{payment_intent.status} state.") else request.logger.error(e.message) end e.to_json end end post '/simulate_payment' do content_type 'application/json' data = JSON.parse(request.body.read) options = { card_present: { number: data['card_number'] }, type: 'card_present' } reader = Stripe::Terminal::Reader::TestHelpers.present_payment_method( data['reader_id'], options ) reader.to_json end post '/capture_payment_intent' do data = JSON.parse(request.body.read) intent = Stripe::PaymentIntent.capture(data['payment_intent_id']) intent.to_json end import stripe \# This is a public sample test API key. # Don’t submit any personally identifiable information in requests made with this key. # Sign in to see your own test API key embedded in code samples. stripe.api_key = '<>' def create_location(): location = stripe.terminal.Location.create( display_name='{{TERMINAL_LOCATION_NAME}}', phone='{{TERMINAL_LOCATION_PHONE}}', address={ 'line1': '{{TERMINAL_LOCATION_LINE1}}', 'line2': '{{TERMINAL_LOCATION_LINE2}}', 'city': '{{TERMINAL_LOCATION_CITY}}', 'state': '{{TERMINAL_LOCATION_STATE}}', 'country': '{{TERMINAL_LOCATION_COUNTRY}}', 'postal_code': '{{TERMINAL_LOCATION_POSTAL}}', }, ) return location def create_location(): location = stripe.terminal.Location.create( display_name='{{TERMINAL_LOCATION_NAME}}', display_name_kana='{{TERMINAL_LOCATION_NAMEKANA}}', display_name_kanji='{{TERMINAL_LOCATION_NAMEKANJI}}', phone='{{TERMINAL_LOCATION_PHONE}}', address_kana={ 'line1': '{{TERMINAL_LOCATION_LINE1KANA}}', 'line2': '{{TERMINAL_LOCATION_LINE2KANA}}', 'town': '{{TERMINAL_LOCATION_TOWNKANA}}', 'city': '{{TERMINAL_LOCATION_CITYKANA}}', 'state': '{{TERMINAL_LOCATION_STATEKANA}}', 'postal_code': '{{TERMINAL_LOCATION_POSTALCODEKANA}}', }, address_kanji={ 'line1': '{{TERMINAL_LOCATION_LINE1KANJI}}', 'line2': '{{TERMINAL_LOCATION_LINE2KANJI}}', 'town': '{{TERMINAL_LOCATION_TOWNKANJI}}', 'city': '{{TERMINAL_LOCATION_CITYKANJI}}', 'state': '{{TERMINAL_LOCATION_STATEKANJI}}', 'postal_code': '{{TERMINAL_LOCATION_POSTALCODEKANJI}}', }, ) return location @app.route('/create_location', methods=['POST']) def create_location(): data = json.loads(request.data) location = stripe.terminal.Location.create( display_name=data['display_name'], address={ 'line1': data['address']['line1'], 'city': data['address']['city'], 'state': data['address']['state'], 'country': data['address']['country'], 'postal_code': data['address']['postal_code'], }, ) return location @app.route('/create_location', methods=['POST']) def create_location(): location = stripe.terminal.Location.create( display_name='{{TERMINAL_LOCATION_NAME}}', display_name_kana='{{TERMINAL_LOCATION_NAMEKANA}}', display_name_kanji='{{TERMINAL_LOCATION_NAMEKANJI}}', phone='{{TERMINAL_LOCATION_PHONE}}', address_kana={ 'line1': '{{TERMINAL_LOCATION_LINE1KANA}}', 'line2': '{{TERMINAL_LOCATION_LINE2KANA}}', 'town': '{{TERMINAL_LOCATION_TOWNKANA}}', 'city': '{{TERMINAL_LOCATION_CITYKANA}}', 'state': '{{TERMINAL_LOCATION_STATEKANA}}', 'postal_code': '{{TERMINAL_LOCATION_POSTALCODEKANA}}', }, address_kanji={ 'line1': '{{TERMINAL_LOCATION_LINE1KANJI}}', 'line2': '{{TERMINAL_LOCATION_LINE2KANJI}}', 'town': '{{TERMINAL_LOCATION_TOWNKANJI}}', 'city': '{{TERMINAL_LOCATION_CITYKANJI}}', 'state': '{{TERMINAL_LOCATION_STATEKANJI}}', 'postal_code': '{{TERMINAL_LOCATION_POSTALCODEKANJI}}', }, ) return location @app.route('/register_reader', methods=['POST']) def register_reader(): data = json.loads(request.data) reader = stripe.terminal.Reader.create( location=data['location_id'], label='Quickstart - S700 Simulated Reader', registration_code='simulated-s700', label='Quickstart - S710 Simulated Reader', registration_code='simulated-s710', label='Quickstart - V660p Simulated Reader', registration_code='simulated-v660p', label='Quickstart - WisePOS E Simulated Reader', registration_code='simulated-wpe', ) return reader @app.route('/create_payment_intent', methods=['POST']) def secret(): data = json.loads(request.data) \# For Terminal payments, the 'payment_method_types' parameter must include # 'card_present'. # To automatically capture funds when a charge is authorized, # set `capture_method` to `automatic`. intent = stripe.PaymentIntent.create( amount=data['amount'], currency='{{TERMINAL_CURRENCY}}', payment_method_types=[ '{{TERMINAL_PAYMENT_METHODS}}' ], capture_method='automatic', payment_method_options={ "card_present": { "capture_method": "manual_preferred" } } ) return intent @app.route('/process_payment', methods=['POST']) def process_payment(): data = json.loads(request.data) tries = 3 for attempt in range(tries): try: reader = stripe.terminal.Reader.process_payment_intent( data['reader_id'], payment_intent=data['payment_intent_id'], ) return reader except stripe.error.InvalidRequestError as e: if e.code == 'terminal_reader_timeout': \# Temporary networking blip, automatically retry a few times. if attempt < tries - 1: continue else: return e.json_body elif e.code == 'terminal_reader_offline': # Reader is offline and won't respond to API requests. Make sure the reader is powered on # and connected to the internet before retrying. app.logger.error(e) return e.json_body elif e.code == 'terminal_reader_busy': # Reader is currently busy processing another request, installing updates or changing settings. # Remember to disable the pay button in your point-of-sale application while waiting for a # reader to respond to an API request. app.logger.error(e) return e.json_body elif e.code == 'intent_invalid_state': # Check PaymentIntent status because it's not ready to be processed. It might have been already # successfully processed or canceled. payment_intent = stripe.PaymentIntent.retrieve(data['payment_intent_id']) app.logger.error('PaymentIntent is already in %s state.' % payment_intent.status) return e.json_body else: app.logger.error(e) return e.json_body @app.route('/simulate_payment', methods=['POST']) def simulate_payment(): data = json.loads(request.data) options = { "card_present": { "number": data['card_number'] }, "type": "card_present" } reader = stripe.terminal.Reader.TestHelpers.present_payment_method( data['reader_id'], **options ) return reader \# The ConnectionToken's secret lets you connect to any Stripe Terminal reader # and take payments with your Stripe account. # Be sure to authenticate the endpoint for creating connection tokens. @app.route('/connection_token', methods=['POST']) def token(): connection_token = stripe.terminal.ConnectionToken.create() return jsonify(secret=connection_token.secret) @app.route('/capture_payment_intent', methods=['POST']) def capture(): data = json.loads(request.data) intent = stripe.PaymentIntent.capture( data['payment_intent_id'] ) return intent certifi==2026.1.4 chardet==5.2.0 click==8.3.1 Flask==3.1.2 idna==3.11 itsdangerous==2.2.0 Jinja2==3.1.6 MarkupSafe==3.0.3 requests==2.32.5 stripe==15.0.0 toml==0.10.2 Werkzeug==3.1.5 $stripe = new \Stripe\StripeClient('<>'); $data = json_decode(file_get_contents('php://input'), true); $location = $stripe->terminal->locations->create([ 'display_name' => $data['display_name'], 'address' => [ 'line1' => $data['address']['line1'], 'city' => $data['address']['city'], 'state' => $data['address']['state'], 'country' => $data['address']['country'], 'postal_code' => $data['address']['postal_code'], ], ]); $location = $stripe->terminal->locations->create([ 'display_name' => '{{TERMINAL_LOCATION_NAME}}', 'display_name_kana' => '{{TERMINAL_LOCATION_NAMEKANA}}', 'display_name_kanji' => '{{TERMINAL_LOCATION_NAMEKANJI}}', 'phone' => '{{TERMINAL_LOCATION_PHONE}}', 'address_kana' => [ 'line1' => "{{TERMINAL_LOCATION_LINE1KANA}}", 'line2' => "{{TERMINAL_LOCATION_LINE2KANA}}", 'town' => "{{TERMINAL_LOCATION_TOWNKANA}}", 'city' => "{{TERMINAL_LOCATION_CITYKANA}}", 'state' => "{{TERMINAL_LOCATION_STATEKANA}}", 'postal_code' => "{{TERMINAL_LOCATION_POSTALCODEKANA}}", ], 'address_kanji' => [ 'line1' => "{{TERMINAL_LOCATION_LINE1KANJI}}", 'line2' => "{{TERMINAL_LOCATION_LINE2KANJI}}", 'town' => "{{TERMINAL_LOCATION_TOWNKANJI}}", 'city' => "{{TERMINAL_LOCATION_CITYKANJI}}", 'state' => "{{TERMINAL_LOCATION_STATEKANJI}}", 'postal_code' => "{{TERMINAL_LOCATION_POSTALCODEKANJI}}", ], ]); $stripe = new \Stripe\StripeClient('<>'); $reader = $stripe->terminal->readers->create([ 'location' => $json_obj->location_id, 'label' => 'Quickstart - S700 Simulated Reader', 'registration_code' => 'simulated-s700' 'label' => 'Quickstart - S710 Simulated Reader', 'registration_code' => 'simulated-s710' 'label' => 'Quickstart - V660p Simulated Reader', 'registration_code' => 'simulated-v660p' 'label' => 'Quickstart - WisePOS E Simulated Reader', 'registration_code' => 'simulated-wpe' ]); $stripe = new \Stripe\StripeClient('<>'); $intent = $stripe->paymentIntents->create([ 'amount' => $json_obj->amount, 'currency' => '{{TERMINAL_CURRENCY}}', 'payment_method_types' => [ '{{TERMINAL_PAYMENT_METHODS}}' ], 'capture_method' => 'automic', 'payment_method_options' => [ 'card_present' => [ 'capture_method' => 'manual_preferred' ] ] ]); $stripe = new \Stripe\StripeClient('<>'); $reader = $stripe->terminal->readers->processPaymentIntent($json_obj->reader_id, [ 'payment_intent' => $json_obj->payment_intent_id, ]); } catch (\Stripe\Exception\InvalidRequestException $e) { switch($e->getStripeCode()) { case "terminal_reader_timeout": // Temporary networking blip, automatically retry a few times. if ($attempt == $tries) { $shouldRetry = false; echo json_encode(['error' => $e->getMessage()]); } else { $shouldRetry = true; } break; case "terminal_reader_offline": // Reader is offline and won't respond to API requests. Make sure the reader is powered on // and connected to the internet before retrying. $shouldRetry = false; echo json_encode(['error' => $e->getMessage()]); break; case "terminal_reader_busy": // Reader is currently busy processing another request, installing updates or changing settings. // Remember to disable the pay button in your point-of-sale application while waiting for a // reader to respond to an API request. $shouldRetry = false; echo json_encode(['error' => $e->getMessage()]); break; case "intent_invalid_state": // Check PaymentIntent status because it's not ready to be processed. It might have been already // successfully processed or canceled. $shouldRetry = false; $paymentIntent = $stripe->paymentIntents->retrieve($json_obj->payment_intent_id); echo json_encode(['error' => 'PaymentIntent is already in ' . $paymentIntent->status . ' state.']); break; default: $shouldRetry = false; echo json_encode(['error' => $e->getMessage()]); break; } } } while($shouldRetry); $stripe = new \Stripe\StripeClient('<>'); $params = [ "card_present" => [ "number" => $json_obj->card_number ], "type" => "card_present" ]; $reader = $stripe->testHelpers->terminal->readers->presentPaymentMethod($json_obj->reader_id, $params); $stripe = new \Stripe\StripeClient('<>'); $intent = $stripe->paymentIntents->capture($json_obj->payment_intent_id); $stripe = new \Stripe\StripeClient('<>'); $connectionToken = $stripe->terminal->connectionTokens->create(); echo json_encode(array('secret' => $connectionToken->secret)); private static Location createLocation(){ var options = new LocationCreateOptions { DisplayName = "{{TERMINAL_LOCATION_NAME}}", Phone = "{{TERMINAL_LOCATION_PHONE}}", Address = new AddressOptions { Line1 = "{{TERMINAL_LOCATION_LINE1}}", Line2 = "{{TERMINAL_LOCATION_LINE2}}", City = "{{TERMINAL_LOCATION_CITY}}", State = "{{TERMINAL_LOCATION_STATE}}", Country = "{{TERMINAL_LOCATION_COUNTRY}}", PostalCode = "{{TERMINAL_LOCATION_POSTAL}}", }, }; var client = new StripeClient("<>"); var location = client.V1.Terminal.Locations.Create(options); return location; } private static Location createLocation(){ var options = new LocationCreateOptions { DisplayName = "{{TERMINAL_LOCATION_NAME}}", DisplayNameKana = "{{TERMINAL_LOCATION_NAMEKANA}}", DisplayNameKanji = "{{TERMINAL_LOCATION_NAMEKANJI}}", Phone = "{{TERMINAL_LOCATION_PHONE}}", AddressKana = new AddressJapanOptions { Line1 = "{{TERMINAL_LOCATION_LINE1KANA}}", Line2 = "{{TERMINAL_LOCATION_LINE2KANA}}", Town = "{{TERMINAL_LOCATION_TOWNKANA}}", City = "{{TERMINAL_LOCATION_CITYKANA}}", State = "{{TERMINAL_LOCATION_STATEKANA}}", PostalCode = "{{TERMINAL_LOCATION_POSTALCODEKANA}}", }, AddressKanji = new AddressJapanOptions { Line1 = "{{TERMINAL_LOCATION_LINE1KANJI}}", Line2 = "{{TERMINAL_LOCATION_LINE2KANJI}}", Town = "{{TERMINAL_LOCATION_TOWNKANJI}}", City = "{{TERMINAL_LOCATION_CITYKANJI}}", State = "{{TERMINAL_LOCATION_STATEKANJI}}", PostalCode = "{{TERMINAL_LOCATION_POSTALCODEKANJI}}", }, }; var client = new StripeClient("<>"); var location = client.V1.Terminal.Locations.Create(options); return location; } // This is a public sample test API key. // Don't submit any personally identifiable information in requests made with this key. // Sign in to see your own test API key embedded in code samples. services.AddSingleton(new StripeClient("<>")); // The ConnectionToken's secret lets you connect to any Stripe Terminal reader // and take payments with your Stripe account. // Be sure to authenticate the endpoint for creating connection tokens. [Route("connection_token")] [ApiController] public class ConnectionTokenApiController : Controller { private readonly StripeClient _client; public ConnectionTokenApiController(StripeClient client) { _client = client; } [HttpPost] public ActionResult Post() { var options = new ConnectionTokenCreateOptions{}; var connectionToken = _client.V1.Terminal.ConnectionTokens.Create(options); return Json(new {secret = connectionToken.Secret}); } } [Route("create_location")] [ApiController] public class CreateLocationApiController : Controller { private readonly StripeClient _client; public CreateLocationApiController(StripeClient client) { _client = client; } [HttpPost] public ActionResult Post(CreateLocationRequest request) { var options = new LocationCreateOptions { DisplayName = request.DisplayName, Address = new AddressOptions { Line1 = request.Address.Line1, City = request.Address.City, State = request.Address.State, Country = request.Address.Country, PostalCode = request.Address.PostalCode, }, }; var location = _client.V1.Terminal.Locations.Create(options); return Json(location); } } public class CreateLocationRequest { [JsonProperty("display_name")] public string DisplayName { get; set; } [JsonProperty("address")] public CreateLocationAddress Address { get; set; } } public class CreateLocationAddress { [JsonProperty("line1")] public string Line1 { get; set; } [JsonProperty("city")] public string City { get; set; } [JsonProperty("state")] public string State { get; set; } [JsonProperty("country")] public string Country { get; set; } [JsonProperty("postal_code")] public string PostalCode { get; set; } } [Route("create_location")] [ApiController] public class CreateLocationApiController : Controller { private readonly StripeClient _client; public CreateLocationApiController(StripeClient client) { _client = client; } [HttpPost] public ActionResult Post() { var options = new LocationCreateOptions { DisplayName = "{{TERMINAL_LOCATION_NAME}}", DisplayNameKana = "{{TERMINAL_LOCATION_NAMEKANA}}", DisplayNameKanji = "{{TERMINAL_LOCATION_NAMEKANJI}}", Phone = "{{TERMINAL_LOCATION_PHONE}}", AddressKana = new AddressJapanOptions { Line1 = "{{TERMINAL_LOCATION_LINE1KANA}}", Line2 = "{{TERMINAL_LOCATION_LINE2KANA}}", Town = "{{TERMINAL_LOCATION_TOWNKANA}}", City = "{{TERMINAL_LOCATION_CITYKANA}}", State = "{{TERMINAL_LOCATION_STATEKANA}}", PostalCode = "{{TERMINAL_LOCATION_POSTALCODEKANA}}", }, AddressKanji = new AddressJapanOptions { Line1 = "{{TERMINAL_LOCATION_LINE1KANJI}}", Line2 = "{{TERMINAL_LOCATION_LINE2KANJI}}", Town = "{{TERMINAL_LOCATION_TOWNKANJI}}", City = "{{TERMINAL_LOCATION_CITYKANJI}}", State = "{{TERMINAL_LOCATION_STATEKANJI}}", PostalCode = "{{TERMINAL_LOCATION_POSTALCODEKANJI}}", }, }; var location = _client.V1.Terminal.Locations.Create(options); return Json(location); } } [Route("register_reader")] [ApiController] public class RegisterReaderApiController : Controller { private readonly StripeClient _client; public RegisterReaderApiController(StripeClient client) { _client = client; } [HttpPost] public ActionResult Post(RegisterReaderRequest request) { var options = new ReaderCreateOptions { Location = request.LocationId, Label = "Quickstart - S700 Simulated Reader", RegistrationCode = "simulated-s700", Label = "Quickstart - S710 Simulated Reader", RegistrationCode = "simulated-s710", Label = "Quickstart - V660p Simulated Reader", RegistrationCode = "simulated-v660p", Label = "Quickstart - WisePOS E Simulated Reader", RegistrationCode = "simulated-wpe", }; var reader = _client.V1.Terminal.Readers.Create(options); return Json(reader); } } public class RegisterReaderRequest { [JsonProperty("location_id")] public string LocationId { get; set; } } [Route("create_payment_intent")] [ApiController] public class PaymentIntentApiController : Controller { private readonly StripeClient _client; public PaymentIntentApiController(StripeClient client) { _client = client; } [HttpPost] public ActionResult Post(PaymentIntentCreateRequest request) { // For Terminal payments, the 'payment_method_types' parameter must include // 'card_present'. // To automatically capture funds when a charge is authorized, // set `capture_method` to `automatic`. var options = new PaymentIntentCreateOptions { Amount = long.Parse(request.Amount), Currency = "{{TERMINAL_CURRENCY}}", PaymentMethodTypes = new List { "{{TERMINAL_PAYMENT_METHODS}}" }, CaptureMethod = "automatic", PaymentMethodOptions = new PaymentIntentPaymentMethodOptionsOptions { CardPresent = new PaymentIntentPaymentMethodOptionsCardPresentOptions { CaptureMethod = "manual_preferred" } } }; var intent = _client.V1.PaymentIntents.Create(options); return Json(intent); } public class PaymentIntentCreateRequest { [JsonProperty("amount")] public string Amount { get; set; } } } [Route("process_payment")] [ApiController] public class ProcessPaymentApiController : Controller { private readonly StripeClient _client; public ProcessPaymentApiController(StripeClient client) { _client = client; } [HttpPost] public ActionResult Post(ProcessPaymentRequest request) { var options = new ReaderProcessPaymentIntentOptions { PaymentIntent = request.PaymentIntentId, }; var attempt = 0; var tries = 3; while (true) { attempt++; try { var reader = _client.V1.Terminal.Readers.ProcessPaymentIntent(request.ReaderId, options); return Json(reader); } catch (StripeException e) { switch (e.StripeError.Code) { case "terminal_reader_timeout": // Temporary networking blip, automatically retry a few times. if (attempt == tries) { return Json(e.StripeError); } break; case "terminal_reader_offline": // Reader is offline and won't respond to API requests. Make sure the reader is powered on // and connected to the internet before retrying. return Json(e.StripeError); case "terminal_reader_busy": // Reader is currently busy processing another request, installing updates or changing settings. // Remember to disable the pay button in your point-of-sale application while waiting for a // reader to respond to an API request. return Json(e.StripeError); case "intent_invalid_state": // Check PaymentIntent status because it's not ready to be processed. It might have been already // successfully processed or canceled. var paymentIntent = _client.V1.PaymentIntents.Get(request.PaymentIntentId); Console.WriteLine($"PaymentIntent is already in {paymentIntent.Status} state."); return Json(e.StripeError); default: return Json(e.StripeError); } } } } public class ProcessPaymentRequest { [JsonProperty("reader_id")] public string ReaderId { get; set; } [JsonProperty("payment_intent_id")] public string PaymentIntentId { get; set; } } } [Route("simulate_payment")] [ApiController] public class SimulatePaymentApiController : Controller { private readonly StripeClient _client; public SimulatePaymentApiController(StripeClient client) { _client = client; } [HttpPost] public ActionResult Post(SimulatePaymentRequest request) { var parameters = new Stripe.TestHelpers.Terminal.ReaderPresentPaymentMethodOptions { CardPresent = new Stripe.TestHelpers.Terminal.ReaderCardPresentOptions { Number = request.CardNumber }, Type = "card_present" }; var reader = _client.TestHelpers.Terminal.Readers.PresentPaymentMethod(request.ReaderId, parameters); return Json(reader); } public class SimulatePaymentRequest { [JsonProperty("reader_id")] public string ReaderId { get; set; } [JsonProperty("card_number")] public string CardNumber { get; set; } } } [Route("capture_payment_intent")] [ApiController] public class CapturePaymentIntentApiController : Controller { private readonly StripeClient _client; public CapturePaymentIntentApiController(StripeClient client) { _client = client; } [HttpPost] public ActionResult Post(PaymentIntentCaptureRequest request) { var intent = _client.V1.PaymentIntents.Capture(request.PaymentIntentId, null); return Json(intent); } public class PaymentIntentCaptureRequest { [JsonProperty("payment_intent_id")] public string PaymentIntentId { get; set; } } } "github.com/stripe/stripe-go/v85" "github.com/stripe/stripe-go/v85/paymentintent" "github.com/stripe/stripe-go/v85/terminal/connectiontoken" "github.com/stripe/stripe-go/v85/terminal/location" "github.com/stripe/stripe-go/v85/terminal/reader" readertesthelpers "github.com/stripe/stripe-go/v85/testhelpers/terminal/reader" // This is a public sample test API key. // Don’t submit any personally identifiable information in requests made with this key. // Sign in to see your own test API key embedded in code samples. stripe.Key = "<>" http.HandleFunc("/connection_token", handleConnectionToken) http.HandleFunc("/create_location", handleCreateLocation) http.HandleFunc("/register_reader", handleRegisterReader) http.HandleFunc("/process_payment", handleProcessPayment) http.HandleFunc("/simulate_payment", handleSimulatePayment) func createLocation(w http.ResponseWriter, r *http.Request) *stripe.TerminalLocation { params := &stripe.TerminalLocationParams{ Address: &stripe.AddressParams{ Line1: stripe.String("{{TERMINAL_LOCATION_LINE1}}"), Line2: stripe.String("{{TERMINAL_LOCATION_LINE2}}"), City: stripe.String("{{TERMINAL_LOCATION_CITY}}"), State: stripe.String("{{TERMINAL_LOCATION_STATE}}"), Country: stripe.String("{{TERMINAL_LOCATION_COUNTRY}}"), PostalCode: stripe.String("{{TERMINAL_LOCATION_POSTAL}}"), }, DisplayName: stripe.String("{{TERMINAL_LOCATION_NAME}}"), Phone: stripe.String("{{TERMINAL_LOCATION_PHONE}}"), } l, _ := location.New(params) return l } func createLocation(w http.ResponseWriter, r *http.Request) *stripe.TerminalLocation { params := &stripe.TerminalLocationParams{ AddressKana: &stripe.TerminalLocationAddressKanaParams{ Line1: stripe.String("{{TERMINAL_LOCATION_LINE1KANA}}"), Line2: stripe.String("{{TERMINAL_LOCATION_LINE2KANA}}"), Town: stripe.String("{{TERMINAL_LOCATION_TOWNKANA}}"), City: stripe.String("{{TERMINAL_LOCATION_CITYKANA}}"), State: stripe.String("{{TERMINAL_LOCATION_STATEKANA}}"), PostalCode: stripe.String("{{TERMINAL_LOCATION_POSTALCODEKANA}}"), }, AddressKanji: &stripe.TerminalLocationAddressKanjiParams{ Line1: stripe.String("{{TERMINAL_LOCATION_LINE1KANJI}}"), Line2: stripe.String("{{TERMINAL_LOCATION_LINE2KANJI}}"), Town: stripe.String("{{TERMINAL_LOCATION_TOWNKANJI}}"), City: stripe.String("{{TERMINAL_LOCATION_CITYKANJI}}"), State: stripe.String("{{TERMINAL_LOCATION_STATEKANJI}}"), PostalCode: stripe.String("{{TERMINAL_LOCATION_POSTALCODEKANJI}}"), }, DisplayName: stripe.String("{{TERMINAL_LOCATION_NAME}}"), DisplayNameKana: stripe.String("{{TERMINAL_LOCATION_NAMEKANA}}"), DisplayNameKanji: stripe.String("{{TERMINAL_LOCATION_NAMEKANJI}}"), Phone: stripe.String("{{TERMINAL_LOCATION_PHONE}}"), } l, _ := location.New(params) return l } func handleCreateLocation(w http.ResponseWriter, r *http.Request) { var req struct { DisplayName string `json:"display_name"` Address struct { Line1 string `json:"line1"` City string `json:"city"` State string `json:"state"` Country string `json:"country"` PostalCode string `json:"postal_code"` } `json:"address"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } params := &stripe.TerminalLocationParams{ Address: &stripe.AddressParams{ Line1: stripe.String(req.Address.Line1), City: stripe.String(req.Address.City), State: stripe.String(req.Address.State), Country: stripe.String(req.Address.Country), PostalCode: stripe.String(req.Address.PostalCode), }, DisplayName: stripe.String(req.DisplayName), } l, err := location.New(params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("location.New: %v", err) return } writeJSON(w, l) } func handleCreateLocation(w http.ResponseWriter, r *http.Request) { params := &stripe.TerminalLocationParams{ AddressKana: &stripe.TerminalLocationAddressKanaParams{ Line1: stripe.String("{{TERMINAL_LOCATION_LINE1KANA}}"), Line2: stripe.String("{{TERMINAL_LOCATION_LINE2KANA}}"), Town: stripe.String("{{TERMINAL_LOCATION_TOWNKANA}}"), City: stripe.String("{{TERMINAL_LOCATION_CITYKANA}}"), State: stripe.String("{{TERMINAL_LOCATION_STATEKANA}}"), PostalCode: stripe.String("{{TERMINAL_LOCATION_POSTALCODEKANA}}"), }, AddressKanji: &stripe.TerminalLocationAddressKanjiParams{ Line1: stripe.String("{{TERMINAL_LOCATION_LINE1KANJI}}"), Line2: stripe.String("{{TERMINAL_LOCATION_LINE2KANJI}}"), Town: stripe.String("{{TERMINAL_LOCATION_TOWNKANJI}}"), City: stripe.String("{{TERMINAL_LOCATION_CITYKANJI}}"), State: stripe.String("{{TERMINAL_LOCATION_STATEKANJI}}"), PostalCode: stripe.String("{{TERMINAL_LOCATION_POSTALCODEKANJI}}"), }, DisplayName: stripe.String("{{TERMINAL_LOCATION_NAME}}"), DisplayNameKana: stripe.String("{{TERMINAL_LOCATION_NAMEKANA}}"), DisplayNameKanji: stripe.String("{{TERMINAL_LOCATION_NAMEKANJI}}"), Phone: stripe.String("{{TERMINAL_LOCATION_PHONE}}"), } l, err := location.New(params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("location.New: %v", err) return } writeJSON(w, l) } func handleRegisterReader(w http.ResponseWriter, r *http.Request) { var req struct { LocationID string `json:"location_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } params := &stripe.TerminalReaderParams{ Location: stripe.String(req.LocationID), Label: stripe.String("Quickstart - S700 Simulated Reader"), RegistrationCode: stripe.String("simulated-s700"), Label: stripe.String("Quickstart - S710 Simulated Reader"), RegistrationCode: stripe.String("simulated-s710"), Label: stripe.String("Quickstart - V660p Simulated Reader"), RegistrationCode: stripe.String("simulated-v660p"), Label: stripe.String("Quickstart - WisePOS E Simulated Reader"), RegistrationCode: stripe.String("simulated-wpe"), } reader, err := reader.New(params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("reader.New: %v", err) return } writeJSON(w, reader) } // The ConnectionToken's secret lets you connect to any Stripe Terminal reader // and take payments with your Stripe account. // Be sure to authenticate the endpoint for creating connection tokens. func handleConnectionToken(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } params := &stripe.TerminalConnectionTokenParams{} ct, err := connectiontoken.New(params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("pi.New: %v", err) return } writeJSON(w, struct { Secret string `json:"secret"` }{ Secret: ct.Secret, }) } func handleCreate(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { PaymentIntentAmount string `json:"amount"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } amount, _ := strconv.ParseInt(req.PaymentIntentAmount, 10, 64) // For Terminal payments, the 'payment_method_types' parameter must include // 'card_present'. // To automatically capture funds when a charge is authorized, // set `capture_method` to `automatic`. params := &stripe.PaymentIntentParams{ Amount: stripe.Int64(amount), Currency: stripe.String(string(stripe.Currency{{TERMINAL_CURRENCY}})), PaymentMethodTypes: stripe.StringSlice([]string{ "{{TERMINAL_PAYMENT_METHODS}}" }), CaptureMethod: stripe.String("automatic"), PaymentMethodOptions: &stripe.PaymentIntentPaymentMethodOptionsParams{ CardPresent: &stripe.PaymentIntentPaymentMethodOptionsCardPresentParams{ CaptureMethod: stripe.String("manual_preferred"), }, }, } pi, err := paymentintent.New(params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("pi.New: %v", err) return } writeJSON(w, pi) } func handleProcessPayment(w http.ResponseWriter, r *http.Request) { var req struct { ReaderID string `json:"reader_id"` PaymentIntentID string `json:"payment_intent_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } params := &stripe.TerminalReaderProcessPaymentIntentParams{ PaymentIntent: stripe.String(req.PaymentIntentID), } reader, err := reader.ProcessPaymentIntent(req.ReaderID, params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("reader.New: %v", err) return } writeJSON(w, reader) } func handleSimulatePayment(w http.ResponseWriter, r *http.Request) { var req struct { ReaderID string `json:"reader_id"` CardNumber string `json:"card_number"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } params := &stripe.TestHelpersTerminalReaderPresentPaymentMethodParams{ CardPresent: map[string]interface{}{ "number": stripe.String(req.CardNumber), }, Type: stripe.String("card_present"), } reader, err := readertesthelpers.PresentPaymentMethod(req.ReaderID, params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("reader.New: %v", err) return } writeJSON(w, reader) } func handleCapture(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { PaymentIntentID string `json:"payment_intent_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } pi, err := paymentintent.Capture(req.PaymentIntentID, nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("pi.Capture: %v", err) return } writeJSON(w, pi) } require github.com/stripe/stripe-go/v85 v85.0.0 import com.stripe.model.terminal.ConnectionToken; import com.stripe.model.terminal.Reader; import com.stripe.param.terminal.ReaderProcessPaymentIntentParams; import com.stripe.param.terminal.ReaderCreateParams; static class CreateLocationParams { private String display_name; private Address address; static class Address { private String line1; private String city; private String state; private String country; private String postal_code; public String getLine1() { return line1; } public String getCity() { return city; } public String getState() { return state; } public String getCountry() { return country; } public String getPostalCode() { return postal_code; } } public String getDisplayName() { return display_name; } public Address getAddress() { return address; } } static class ReaderParams { private String reader_id; private String location_id; private String card_number; public String getReaderId() { return reader_id; } public String getLocationId() { return location_id; } public String getCardNumber() { return card_number; } } static class ProcessPaymentParams { private String reader_id; private String payment_intent_id; public String getReaderId() { return reader_id; } public String getPaymentIntentId() { return payment_intent_id; } } // This is a public sample test API key. // Don’t submit any personally identifiable information in requests made with this key. // Sign in to see your own test API key embedded in code samples. Stripe.apiKey = "<>"; post("/create_location", (request, response) -> { CreateLocationParams postBody = gson.fromJson(request.body(), CreateLocationParams.class); LocationCreateParams.Address address = LocationCreateParams.Address.builder() .setLine1(postBody.getAddress().getLine1()) .setCity(postBody.getAddress().getCity()) .setState(postBody.getAddress().getState()) .setCountry(postBody.getAddress().getCountry()) .setPostalCode(postBody.getAddress().getPostalCode()) .build(); LocationCreateParams params = LocationCreateParams.builder() .setDisplayName(postBody.getDisplayName()) .setAddress(address) .build(); Location location = Location.create(params); return location.toJson(); }); post("/create_location", (request, response) -> { LocationCreateParams.AddressKana addressKana = LocationCreateParams.AddressKana.builder() .setLine1("{{TERMINAL_LOCATION_LINE1KANA}}") .setLine2("{{TERMINAL_LOCATION_LINE2KANA}}") .setTown("{{TERMINAL_LOCATION_TOWNKANA}}") .setCity("{{TERMINAL_LOCATION_CITYKANA}}") .setState("{{TERMINAL_LOCATION_STATEKANA}}") .setPostalCode("{{TERMINAL_LOCATION_POSTALCODEKANA}}") .build(); LocationCreateParams.AddressKanji addressKanji = LocationCreateParams.AddressKanji.builder() .setLine1("{{TERMINAL_LOCATION_LINE1KANJI}}") .setLine2("{{TERMINAL_LOCATION_LINE2KANJI}}") .setTown("{{TERMINAL_LOCATION_TOWNKANJI}}") .setCity("{{TERMINAL_LOCATION_CITYKANJI}}") .setState("{{TERMINAL_LOCATION_STATEKANJI}}") .setPostalCode("{{TERMINAL_LOCATION_POSTALCODEKANJI}}") .build(); LocationCreateParams params = LocationCreateParams.builder() .setDisplayName("{{TERMINAL_LOCATION_NAME}}") .setDisplayNameKana("{{TERMINAL_LOCATION_NAMEKANA}}") .setDisplayNameKanji("{{TERMINAL_LOCATION_NAMEKANJI}}") .setPhone("{{TERMINAL_LOCATION_PHONE}}") .setAddressKana(addressKana) .setAddressKanji(addressKanji) .build(); Location location = Location.create(params); return location.toJson(); }); // The ConnectionToken's secret lets you connect to any Stripe Terminal reader // and take payments with your Stripe account. // Be sure to authenticate the endpoint for creating connection tokens. post("/connection_token", (request, response) -> { response.type("application/json"); ConnectionTokenCreateParams params = ConnectionTokenCreateParams.builder() .build(); ConnectionToken connectionToken = ConnectionToken.create(params); Map map = new HashMap(); map.put("secret", connectionToken.getSecret()); return gson.toJson(map); }); post("/register_reader", (request, response) -> { ReaderParams postBody = gson.fromJson(request.body(), ReaderParams.class); ReaderCreateParams params = ReaderCreateParams.builder() .setLocation(postBody.getLocationId()) .setLabel("Quickstart - S700 Simulated Reader") .setRegistrationCode("simulated-s700") .setLabel("Quickstart - S710 Simulated Reader") .setRegistrationCode("simulated-s710") .setLabel("Quickstart - V660p Simulated Reader") .setRegistrationCode("simulated-v660p") .setLabel("Quickstart - WisePOS E Simulated Reader") .setRegistrationCode("simulated-wpe") .build(); Reader reader = Reader.create(params); return reader.toJson(); }); post("/create_payment_intent", (request, response) -> { response.type("application/json"); PaymentIntentParams postBody = gson.fromJson(request.body(), PaymentIntentParams.class); // For Terminal payments, the 'payment_method_types' parameter must include // 'card_present'. // To automatically capture funds when a charge is authorized, // set `capture_method` to `automatic`. PaymentIntentCreateParams createParams = PaymentIntentCreateParams.builder() .setCurrency("{{TERMINAL_CURRENCY}}") .setAmount(postBody.getAmount()) .setCaptureMethod(PaymentIntentCreateParams.CaptureMethod.AUTOMATIC) .putPaymentMethodOption( "card_present", PaymentIntentCreateParams.PaymentMethodOptions.CardPresent.builder() .setCaptureMethod(PaymentIntentCreateParams.PaymentMethodOptions.CardPresent.CaptureMethod.MANUAL_PREFERRED) .build() ) {{TERMINAL_PAYMENT_METHODS}} .build(); // Create a PaymentIntent with the order amount and currency PaymentIntent intent = PaymentIntent.create(createParams); return intent.toJson(); }); post("/process_payment", (request, response) -> { ProcessPaymentParams postBody = gson.fromJson(request.body(), ProcessPaymentParams.class); ReaderProcessPaymentIntentParams params = ReaderProcessPaymentIntentParams.builder() .setPaymentIntent(postBody.getPaymentIntentId()) .build(); Reader reader = Reader.retrieve(postBody.getReaderId()); int attempt = 0; int tries = 3; while (true) { attempt++; try { reader = reader.processPaymentIntent(params); return reader.toJson(); } catch (InvalidRequestException e) { switch (e.getCode()) { case "terminal_reader_timeout": // Temporary networking blip, automatically retry a few times. if (attempt == tries) { return e.getStripeError().toJson(); } break; case "terminal_reader_offline": // Reader is offline and won't respond to API requests. Make sure the reader is // powered on and connected to the internet before retrying. return e.getStripeError().toJson(); case "terminal_reader_busy": // Reader is currently busy processing another request, installing updates or // changing settings. Remember to disable the pay button in your point-of-sale // application while waiting for a reader to respond to an API request. return e.getStripeError().toJson(); case "intent_invalid_state": // Check PaymentIntent status because it's not ready to be processed. It might // have been already successfully processed or canceled. PaymentIntent paymentIntent = PaymentIntent.retrieve(postBody.getPaymentIntentId()); Map errorResponse = Collections.singletonMap("error", "PaymentIntent is already in " + paymentIntent.getStatus() + " state."); return new Gson().toJson(errorResponse); default: return e.getStripeError().toJson(); } } } }); post("/simulate_payment", (request, response) -> { ReaderParams postBody = gson.fromJson(request.body(), ReaderParams.class); Reader reader = Reader.retrieve(postBody.getReaderId()); Map paymentOptions = new HashMap<>(); Map cardPresent = new HashMap<>(); cardPresent.put("number", postBody.getCardNumber()); paymentOptions.put("card_present", cardPresent); paymentOptions.put("type", "card_present"); reader = reader.getTestHelpers().presentPaymentMethod(paymentOptions); return reader.toJson(); }); post("/capture_payment_intent", (request, response) -> { response.type("application/json"); PaymentIntentParams postBody = gson.fromJson(request.body(), PaymentIntentParams.class); PaymentIntent intent = PaymentIntent.retrieve(postBody.getPaymentIntentId()); intent = intent.capture(); return intent.toJson(); }); public static Location createLocation() throws StripeException { LocationCreateParams.Address address = LocationCreateParams.Address.builder() .setLine1("{{TERMINAL_LOCATION_LINE1}}") .setLine2("{{TERMINAL_LOCATION_LINE2}}") .setCity("{{TERMINAL_LOCATION_CITY}}") .setState("{{TERMINAL_LOCATION_STATE}}") .setCountry("{{TERMINAL_LOCATION_COUNTRY}}") .setPostalCode("{{TERMINAL_LOCATION_POSTAL}}") .build(); LocationCreateParams params = LocationCreateParams.builder() .setDisplayName("{{TERMINAL_LOCATION_NAME}}") .setPhone("{{TERMINAL_LOCATION_PHONE}}") .setAddress(address) .build(); Location location = Location.create(params); return location; } public static Location createLocation() throws StripeException { LocationCreateParams.AddressKana addressKana = LocationCreateParams.AddressKana.builder() .setLine1("{{TERMINAL_LOCATION_LINE1KANA}}") .setLine2("{{TERMINAL_LOCATION_LINE2KANA}}") .setTown("{{TERMINAL_LOCATION_TOWNKANA}}") .setCity("{{TERMINAL_LOCATION_CITYKANA}}") .setState("{{TERMINAL_LOCATION_STATEKANA}}") .setPostalCode("{{TERMINAL_LOCATION_POSTALCODEKANA}}") .build(); LocationCreateParams.AddressKanji addressKanji = LocationCreateParams.AddressKanji.builder() .setLine1("{{TERMINAL_LOCATION_LINE1KANJI}}") .setLine2("{{TERMINAL_LOCATION_LINE2KANJI}}") .setTown("{{TERMINAL_LOCATION_TOWNKANJI}}") .setCity("{{TERMINAL_LOCATION_CITYKANJI}}") .setState("{{TERMINAL_LOCATION_STATEKANJI}}") .setPostalCode("{{TERMINAL_LOCATION_POSTALCODEKANJI}}") .build(); LocationCreateParams params = LocationCreateParams.builder() .setDisplayName("{{TERMINAL_LOCATION_NAME}}") .setDisplayNameKana("{{TERMINAL_LOCATION_NAMEKANA}}") .setDisplayNameKanji("{{TERMINAL_LOCATION_NAMEKANJI}}") .setPhone("{{TERMINAL_LOCATION_PHONE}}") .setAddressKana(addressKana) .setAddressKanji(addressKanji) .build(); Location location = Location.create(params); return location; } export const fetchConnectionToken = async () => { const response = await fetch('http://localhost:4242/connection_token', { method: 'POST', headers: { 'Content-Type': 'application/json', }, }); const data = await response.json(); if (!data) { throw Error('No data in response from ConnectionToken endpoint'); } if (!data.secret) { throw Error('Missing `secret` in ConnectionToken JSON response'); } return data.secret; }; export const fetchPaymentIntent = async () => { const parameters = { amount: 1000, }; const response = await fetch('http://localhost:4242/create_payment_intent', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(parameters), }); const data = await response.json(); if (!data) { throw Error('No data in response from PaymentIntent endpoint'); } if (!data.client_secret) { throw Error('Missing `client_secret` in ConnectionToken JSON response'); } return data.client_secret; }; export const capturePaymentIntent = async () => { const parameters = { id: 'paymentIntentId', }; const response = await fetch('http://localhost:4242/capture_payment_intent', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(parameters), }); if (response.status >= 200 && response.status < 300) { return true; } else { return false; } }; const { reader, error } = await connectReader({ discoveryMethod: 'internet', reader: selectedReader, }); const { reader, error } = await connectReader({ discoveryMethod: 'tapToPay', reader: selectedReader, locationId: selectedReader.locationId, }); const { reader, error } = await connectReader({ discoveryMethod: 'bluetoothScan', reader: selectedReader, locationId: selectedReader.locationId, }); useEffect(() => { async function init() { try { const granted = await PermissionsAndroid.request( 'android.permission.ACCESS_FINE_LOCATION', { title: 'Location Permission', message: 'Stripe Terminal needs access to your location', buttonPositive: 'Accept', }, ); if (granted === PermissionsAndroid.RESULTS.GRANTED) { console.log('You can use the Location'); setPermissionsGranted(true); } else { Alert.alert( 'Location services are required to connect to a reader.', ); } } catch { Alert.alert( 'Location services are required to connect to a reader.', ); } } if (Platform.OS === 'android') { init(); } else { setPermissionsGranted(true); } }, []); const {error} = await discoverReaders({ discoveryMethod: 'internet', simulated: true, }); const {error} = await discoverReaders({ discoveryMethod: 'tapToPay', simulated: true, }); const {error} = await discoverReaders({ discoveryMethod: 'bluetoothScan', simulated: true, }); await setSimulatedCard("4242424242424242"); const {paymentIntent, error} = await processPaymentIntent({ clientSecret, }); const result = await capturePaymentIntent(); import { StripeTerminalProvider, } from '@stripe/stripe-terminal-react-native'; var terminal = StripeTerminal.create({ onFetchConnectionToken: fetchConnectionToken, onUnexpectedReaderDisconnect: unexpectedDisconnect, }); function unexpectedDisconnect() { // In this function, your app should notify the user that the reader disconnected. // You can also include a way to attempt to reconnect to a reader. console.log("Disconnected from reader") } function fetchConnectionToken() { // Do not cache or hardcode the ConnectionToken. The SDK manages the ConnectionToken's lifecycle. return fetch('/connection_token', { method: "POST" }) .then(function(response) { return response.json(); }) .then(function(data) { return data.secret; }); } var config = {simulated: true}; terminal.discoverReaders(config).then(function(discoverResult) { terminal.connectReader(selectedReader).then(function(connectResult) { function fetchPaymentIntentClientSecret(amount) { const bodyContent = JSON.stringify({ amount: amount }); return fetch('/create_payment_intent', { method: "POST", headers: { 'Content-Type': 'application/json' }, body: bodyContent }) .then(function(response) { return response.json(); }) .then(function(data) { return data.client_secret; }); } // Use test card number to simulate different payment flows within your point of sale application terminal.setSimulatorConfiguration({testCardNumber: '4242424242424242'}); terminal.collectPaymentMethod(client_secret).then(function(result) { terminal.processPayment(result.paymentIntent).then(function(result) { function capture(paymentIntentId) { return fetch('/capture_payment_intent', { method: "POST", headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({"payment_intent_id": paymentIntentId}) }) .then(function(response) { return response.json(); }) .then(function(data) { log('server.capture', data); }); } const terminal = StripeTerminal.create({ onFetchConnectionToken: fetchConnectionToken, onUnexpectedReaderDisconnect: unexpectedDisconnect, }); function unexpectedDisconnect() { // In this function, your app should notify the user that the reader disconnected. // You can also include a way to attempt to reconnect to a reader. console.log("Disconnected from reader") } async function fetchConnectionToken() { // Do not cache or hardcode the ConnectionToken. The SDK manages the ConnectionToken's lifecycle. const response = await fetch('/connection_token', { method: "POST" }); const data = await response.json(); return data.secret; } const config = {simulated: true}; const discoverResult = await terminal.discoverReaders(config); const selectedReader = discoveredReaders[0]; const connectResult = await terminal.connectReader(selectedReader); async function fetchPaymentIntentClientSecret(amount) { const bodyContent = JSON.stringify({ amount: amount }); const response = await fetch('/create_payment_intent', { method: "POST", headers: { 'Content-Type': 'application/json' }, body: bodyContent }); const data = await response.json(); return data.client_secret; } // Use test card number to simulate different payment flows within your point of sale application terminal.setSimulatorConfiguration({testCardNumber: '4242424242424242'}); const collectResult = await terminal.collectPaymentMethod(client_secret); const processResult = await terminal.processPayment(collectResult.paymentIntent); async function capture(paymentIntentId) { const result = await fetch('/capture_payment_intent', { method: "POST", headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({"payment_intent_id": paymentIntentId}) }) const data = result.json(); log('server.capture', data); } Terminal.initWithTokenProvider(APIClient.shared) let config = try InternetDiscoveryConfigurationBuilder().setSimulated(true).build() let config = try TapToPayDiscoveryConfigurationBuilder().setSimulated(true).build() let config = try BluetoothScanDiscoveryConfigurationBuilder().setSimulated(true).build() let params = try PaymentIntentParametersBuilder(amount: 1000, currency: "{{TERMINAL_CURRENCY}}") .setPaymentMethodTypes([{{TERMINAL_PAYMENT_METHODS}}]) .build() Terminal.shared.processPaymentIntent(paymentIntent, collectConfig: nil, confirmConfig: nil) { processResult, processError in let connectionConfig: InternetConnectionConfiguration do { connectionConfig = try InternetConnectionConfigurationBuilder(delegate: self).build() } catch { // Handle the error building the connection configuration print("Error building connection configuration: \(error)") return } let connectionConfig: TapToPayConnectionConfiguration do { connectionConfig = try TapToPayConnectionConfigurationBuilder( delegate: self, locationId: selectedReader.locationId! ).build() } catch { // Handle the error building the connection configuration print("Error building connection configuration: \(error)") return } let connectionConfig: BluetoothConnectionConfiguration do { connectionConfig = try BluetoothConnectionConfigurationBuilder( delegate: self, // When connecting to a physical reader, your integration should specify either the // same location as the last connection (selectedReader.locationId) or a new location // of your user's choosing. // // Since the simulated reader is not associated with a real location, we recommend // specifying its existing mock location. locationId: selectedReader.locationId! ).build() } catch { // Handle the error building the connection configuration print("Error building connection configuration: \(error)") return } extension ViewController: MobileReaderDelegate { func reader(_ reader: Reader, didDisconnect reason: DisconnectReason) { // Handle reader disconnect } func reader(_ reader: Reader, didRequestReaderInput inputOptions: ReaderInputOptions = []) { readerMessageLabel.text = Terminal.stringFromReaderInputOptions(inputOptions) } func reader(_ reader: Reader, didRequestReaderDisplayMessage displayMessage: ReaderDisplayMessage) { readerMessageLabel.text = Terminal.stringFromReaderDisplayMessage(displayMessage) } func reader(_ reader: Reader, didStartInstallingUpdate update: ReaderSoftwareUpdate, cancelable: Cancelable?) { // Show UI communicating that a required update has started installing } func reader(_ reader: Reader, didReportReaderSoftwareUpdateProgress progress: Float) { // Update the progress of the install } func reader(_ reader: Reader, didFinishInstallingUpdate update: ReaderSoftwareUpdate?, error: Error?) { // Report success or failure of the update } func reader(_ reader: Reader, didReportAvailableUpdate update: ReaderSoftwareUpdate) { // Show UI communicating that an update is available } } extension ViewController: InternetReaderDelegate { func reader(_ reader: Reader, didDisconnect reason: DisconnectReason) { print("Disconnected from reader: \(reader)") } } extension ViewController: TapToPayReaderDelegate { func tapToPayReader(_ reader: Reader, didStartInstallingUpdate update: ReaderSoftwareUpdate, cancelable: Cancelable?) { // In your app, let the user know that an update is being installed on the reader } func tapToPayReader(_ reader: Reader, didReportReaderSoftwareUpdateProgress progress: Float) { // The update or configuration process has reached the specified progress (0.0 to 1.0) // If you are displaying a progress bar or percentage, this can be updated here } func tapToPayReader(_ reader: Reader, didFinishInstallingUpdate update: ReaderSoftwareUpdate?, error: Error?) { // The reader has finished installing an update // If `error` is nil, it is safe to proceed and start collecting payments // Otherwise, check the value of `error` for more information on what went wrong } func tapToPayReader(_ reader: Reader, didRequestReaderDisplayMessage displayMessage: ReaderDisplayMessage) { // This is called to request that a prompt be displayed in your app. readerMessageLabel.text = Terminal.stringFromReaderDisplayMessage(displayMessage) } func tapToPayReader(_ reader: Reader, didRequestReaderInput inputOptions: ReaderInputOptions = []) { // This is called when the reader begins waiting for input readerMessageLabel.text = Terminal.stringFromReaderInputOptions(inputOptions) } } func fetchConnectionToken(_ completion: @escaping ConnectionTokenCompletionBlock) { let config = URLSessionConfiguration.default let session = URLSession(configuration: config) let url = URL(string: "/connection_token", relativeTo: APIClient.backendUrl)! var request = URLRequest(url: url) request.httpMethod = "POST" let task = session.dataTask(with: request) { (data, response, error) in if let data = data { do { // Warning: casting using 'as? [String: String]' looks simpler, but isn't safe: let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] if let secret = json?["secret"] as? String { completion(secret, nil) } else { let error = NSError(domain: "com.stripe-terminal-ios.example", code: 2000, userInfo: [NSLocalizedDescriptionKey: "Missing 'secret' in ConnectionToken JSON response"]) completion(nil, error) } } catch { completion(nil, error) } } else { let error = NSError(domain: "com.stripe-terminal-ios.example", code: 1000, userInfo: [NSLocalizedDescriptionKey: "No data in response from ConnectionToken endpoint"]) completion(nil, error) } } task.resume() } func capturePaymentIntent(_ paymentIntentId: String, completion: @escaping ErrorCompletionBlock) { let config = URLSessionConfiguration.default let session = URLSession(configuration: config, delegate: nil, delegateQueue: OperationQueue.main) let url = URL(string: "/capture_payment_intent", relativeTo: APIClient.backendUrl)! let parameters = "{\"payment_intent_id\": \"\(paymentIntentId)\"}" var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.httpBody = parameters.data(using: .utf8) let task = session.dataTask(with: request) {(data, response, error) in if let response = response as? HTTPURLResponse, let data = data { switch response.statusCode { case 200..<300: completion(nil) case 402: let description = String(data: data, encoding: .utf8) ?? "Failed to capture payment intent" completion(NSError(domain: "com.stripe-terminal-ios.example", code: 2, userInfo: [NSLocalizedDescriptionKey: description])) default: completion(error ?? NSError(domain: "com.stripe-terminal-ios.example", code: 0, userInfo: [NSLocalizedDescriptionKey: "Other networking error encountered."])) } } else { completion(error) } } task.resume() } // Move this to your App initialization Terminal.initWithTokenProvider(APIClient.shared) let config = try InternetDiscoveryConfigurationBuilder().setSimulated(true).build() let config = try TapToPayDiscoveryConfigurationBuilder().setSimulated(true).build() let config = try BluetoothScanDiscoveryConfigurationBuilder().setSimulated(true).build() let connectionConfig = try InternetConnectionConfigurationBuilder(delegate: self).build() let connectionConfig = try TapToPayConnectionConfigurationBuilder( delegate: self, locationId: reader.locationId! ).build() let connectionConfig = try BluetoothConnectionConfigurationBuilder( delegate: self, // When connecting to a physical reader, your integration should specify either the // same location as the last connection (reader.locationId) or a new location // of your user's choosing. // // Since the simulated reader is not associated with a real location, we recommend // specifying its existing mock location. locationId: reader.locationId! ).build() let params = try PaymentIntentParametersBuilder(amount: 1000, currency: "{{TERMINAL_CURRENCY}}") .setPaymentMethodTypes([{{TERMINAL_PAYMENT_METHODS}}]) .build() let processedIntent = try await Terminal.shared.processPaymentIntent(paymentIntent, collectConfig: nil, confirmConfig: nil) // MARK: - Mobile Reader Delegate extension TerminalManager: @MainActor MobileReaderDelegate { func reader(_ reader: Reader, didDisconnect reason: DisconnectReason) { statusMessage = "Reader disconnected" currentState = .initial buttonTitle = "Discover Readers" connectedReader = nil } func reader(_ reader: Reader, didRequestReaderInput inputOptions: ReaderInputOptions = []) { statusMessage = Terminal.stringFromReaderInputOptions(inputOptions) } func reader(_ reader: Reader, didRequestReaderDisplayMessage displayMessage: ReaderDisplayMessage) { statusMessage = Terminal.stringFromReaderDisplayMessage(displayMessage) } func reader(_ reader: Reader, didStartInstallingUpdate update: ReaderSoftwareUpdate, cancelable: Cancelable?) { statusMessage = "Installing update..." } func reader(_ reader: Reader, didReportReaderSoftwareUpdateProgress progress: Float) { statusMessage = "Update progress: \(Int(progress * 100))%" } func reader(_ reader: Reader, didFinishInstallingUpdate update: ReaderSoftwareUpdate?, error: Error?) { if let error = error { statusMessage = "Update failed: \(error.localizedDescription)" } else { statusMessage = "Update complete" } } func reader(_ reader: Reader, didReportAvailableUpdate update: ReaderSoftwareUpdate) { statusMessage = "Update available" } } // MARK: - Internet Reader Delegate extension TerminalManager: @MainActor InternetReaderDelegate { func reader(_ reader: Reader, didDisconnect reason: DisconnectReason) { statusMessage = "Reader disconnected" currentState = .initial buttonTitle = "Discover Readers" connectedReader = nil } } // MARK: - Tap To Pay Reader Delegate extension TerminalManager: @MainActor TapToPayReaderDelegate { func tapToPayReader(_ reader: Reader, didStartInstallingUpdate update: ReaderSoftwareUpdate, cancelable: Cancelable?) { statusMessage = "Installing update..." } func tapToPayReader(_ reader: Reader, didReportReaderSoftwareUpdateProgress progress: Float) { statusMessage = "Update progress: \(Int(progress * 100))%" } func tapToPayReader(_ reader: Reader, didFinishInstallingUpdate update: ReaderSoftwareUpdate?, error: Error?) { if let error = error { statusMessage = "Update failed: \(error.localizedDescription)" } else { statusMessage = "Update complete" } } func tapToPayReader(_ reader: Reader, didRequestReaderDisplayMessage displayMessage: ReaderDisplayMessage) { statusMessage = Terminal.stringFromReaderDisplayMessage(displayMessage) } func tapToPayReader(_ reader: Reader, didRequestReaderInput inputOptions: ReaderInputOptions = []) { statusMessage = Terminal.stringFromReaderInputOptions(inputOptions) } } func fetchConnectionToken(_ completion: @escaping ConnectionTokenCompletionBlock) { Task { do { let secret = try await fetchConnectionTokenAsync() completion(secret, nil) } catch { completion(nil, error) } } } private func fetchConnectionTokenAsync() async throws -> String { let url = URL(string: "/connection_token", relativeTo: APIClient.backendUrl)! var request = URLRequest(url: url) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { throw NSError(domain: "com.stripe-terminal-ios.example", code: 1000, userInfo: [NSLocalizedDescriptionKey: "Invalid response from ConnectionToken endpoint"]) } let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] guard let secret = json?["secret"] as? String else { throw NSError(domain: "com.stripe-terminal-ios.example", code: 2000, userInfo: [NSLocalizedDescriptionKey: "Missing 'secret' in ConnectionToken JSON response"]) } return secret } func capturePaymentIntent(_ paymentIntentId: String) async throws { let url = URL(string: "/capture_payment_intent", relativeTo: APIClient.backendUrl)! let parameters = ["payment_intent_id": paymentIntentId] let jsonData = try JSONSerialization.data(withJSONObject: parameters) var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.httpBody = jsonData let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw NSError(domain: "com.stripe-terminal-ios.example", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid response"]) } switch httpResponse.statusCode { case 200..<300: return case 402: let description = String(data: data, encoding: .utf8) ?? "Failed to capture payment intent" throw NSError(domain: "com.stripe-terminal-ios.example", code: 2, userInfo: [NSLocalizedDescriptionKey: description]) default: throw NSError(domain: "com.stripe-terminal-ios.example", code: 0, userInfo: [NSLocalizedDescriptionKey: "Capture failed with status code \(httpResponse.statusCode)"]) } } @interface ViewController () @interface ViewController () @interface ViewController () [SCPTerminal initWithTokenProvider:APIClient.shared]; NSError *configError = nil; SCPInternetDiscoveryConfiguration *config = [[[SCPInternetDiscoveryConfigurationBuilder new] setSimulated:YES] build:&configError]; if (configError) { NSLog(@"Unexpected error building discovery configuration!"); } else { self.discoverCancelable = [[SCPTerminal shared] discoverReaders:config delegate:self completion:^(NSError * _Nullable error) { NSError *configError = nil; SCPTapToPayDiscoveryConfiguration *config = [[[SCPTapToPayDiscoveryConfigurationBuilder new] setSimulated:YES] build:&configError]; if (configError) { NSLog(@"Unexpected error building discovery configuration!"); } else { self.discoverCancelable = [[SCPTerminal shared] discoverReaders:config delegate:self completion:^(NSError * _Nullable error) { NSError *configError = nil; SCPBluetoothProximityDiscoveryConfiguration *config = [[[SCPBluetoothProximityDiscoveryConfigurationBuilder new] setSimulated:YES] build:&configError]; if (configError) { NSLog(@"Unexpected error building discovery configuration!"); } else { self.discoverCancelable = [[SCPTerminal shared] discoverReaders:config delegate:self completion:^(NSError * _Nullable error) { NSError *error = nil; SCPPaymentIntentParameters *paymentIntentParams = [[[[SCPPaymentIntentParametersBuilder alloc] initWithAmount:1000 currency:@"{{TERMINAL_CURRENCY}}"] setPaymentMethodTypes:@[{{TERMINAL_PAYMENT_METHODS}}]] build:&error]; [[SCPTerminal shared] processPaymentIntent:intent collectConfig:nil confirmConfig:nil completion:^(SCPPaymentIntent * _Nullable processedIntent, NSError * _Nullable processError) { // When connecting to a physical reader, your integration should specify either the // same location as the last connection (selectedReader.locationId) or a new location // of your user's choosing. // // Since the simulated reader is not associated with a real location, we recommend // specifying its existing mock location. NSError *configError = nil; SCPBluetoothConnectionConfiguration *config = [[[SCPBluetoothConnectionConfigurationBuilder alloc] initWithDelegate:self locationId:selectedReader.locationId] build:&configError]; if (configError) { NSLog(@"Error building connection configuration, check location id!"); } else { NSError *configError = nil; SCPInternetConnectionConfiguration *config = [[[[SCPInternetConnectionConfigurationBuilder alloc] initWithDelegate:self] setFailIfInUse:YES] build:&configError]; if (configError) { NSLog(@"Error building connection configuration, check location id!"); } else { NSError *configError = nil; SCPTapToPayConnectionConfiguration *config = [[[SCPTapToPayConnectionConfigurationBuilder alloc] initWithDelegate:self locationId:selectedReader.locationId] build:&configError]; if (configError) { NSLog(@"Error building connection configuration, check location id!"); } else { #pragma mark - SCPBluetoothReaderDelegate - (void)reader:(SCPReader *)reader didRequestReaderInput:(SCPReaderInputOptions)inputOptions { // Update UI requesting reader input self.readerMessageLabel.text = [SCPTerminal stringFromReaderInputOptions:inputOptions]; } - (void)reader:(SCPReader *)reader didRequestReaderDisplayMessage:(SCPReaderDisplayMessage)displayMessage { // Update UI showing reader message self.readerMessageLabel.text = [SCPTerminal stringFromReaderDisplayMessage:displayMessage]; } - (void)reader:(nonnull SCPReader *)reader didStartInstallingUpdate:(SCPReaderSoftwareUpdate *)update cancelable:(nullable SCPCancelable *)cancelable { // Show UI communicating that a required update has started installing } - (void)reader:(nonnull SCPReader *)reader didReportReaderSoftwareUpdateProgress:(float)progress { // Update the progress of the install } - (void)reader:(nonnull SCPReader *)reader didFinishInstallingUpdate:(nullable SCPReaderSoftwareUpdate *)update error:(nullable NSError *)error { // Report success or failure of the update } - (void)reader:(nonnull SCPReader *)reader didReportAvailableUpdate:(SCPReaderSoftwareUpdate *)update { // An update is available for the connected reader. Show this update in your application. // This update can be installed using Terminal.shared.installAvailableUpdate. } #pragma mark - SCPInternetReaderDelegate - (void)reader:(SCPReader *)reader didDisconnect:(SCPDisconnectReason)reason { // Handle reader disconnects here. } #pragma mark - SCPTapToPayReaderDelegate - (void)tapToPayReader:(nonnull SCPReader *)reader didStartInstallingUpdate:(nonnull SCPReaderSoftwareUpdate *)update cancelable:(nullable SCPCancelable *)cancelable { // In your app, let the user know that an update is being installed on the reader } - (void)tapToPayReader:(nonnull SCPReader *)reader didReportReaderSoftwareUpdateProgress:(float)progress { // The update or configuration process has reached the specified progress (0.0 to 1.0) // If you are displaying a progress bar or percentage, this can be updated here } - (void)tapToPayReader:(nonnull SCPReader *)reader didFinishInstallingUpdate:(nullable SCPReaderSoftwareUpdate *)update error:(nullable NSError *)error { // The reader has finished installing an update // If `error` is nil, it is safe to proceed and start collecting payments // Otherwise, check the value of `error` for more information on what went wrong } - (void)tapToPayReader:(nonnull SCPReader *)reader didRequestReaderDisplayMessage:(SCPReaderDisplayMessage)displayMessage { // This is called to request that a prompt be displayed in your app. self.readerMessageLabel.text = [SCPTerminal stringFromReaderDisplayMessage:displayMessage]; } - (void)tapToPayReader:(nonnull SCPReader *)reader didRequestReaderInput:(SCPReaderInputOptions)inputOptions { // This is called when the reader begins waiting for input self.readerMessageLabel.text = [SCPTerminal stringFromReaderInputOptions:inputOptions]; } - (void)fetchConnectionToken:(SCPConnectionTokenCompletionBlock)completion { NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; NSURL *url = [NSURL URLWithString:@"/connection_token" relativeToURL:[APIClient backendUrl]]; if (!url) { NSAssert(NO, @"Invalid backend URL"); } NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; request.HTTPMethod = @"POST"; NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { id jsonObject = nil; NSError *jsonSerializationError; if (data) { jsonObject = [NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingOptions)kNilOptions error:&jsonSerializationError]; } else { NSError *error = [NSError errorWithDomain:@"com.stripe-terminal-ios.example" code:1000 userInfo:@{NSLocalizedDescriptionKey: @"No data in response from ConnectionToken endpoint"}]; completion(nil, error); } if (!(jsonObject && [jsonObject isKindOfClass:[NSDictionary class]])) { completion(nil, jsonSerializationError); return; } NSDictionary *json = (NSDictionary *)jsonObject; id secret = json[@"secret"]; if (!(secret && [secret isKindOfClass:[NSString class]])) { NSError *error = [NSError errorWithDomain:@"com.stripe-terminal-ios.example" code:2000 userInfo:@{NSLocalizedDescriptionKey: @"Missing 'secret' in ConnectionToken JSON response"}]; completion(nil, error); return; } completion((NSString *)secret, nil); }]; [task resume]; } - (void)capturePaymentIntent:(NSString *)paymentIntentId completion:(SCPErrorCompletionBlock)completion { NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; NSURL *url = [NSURL URLWithString:@"/capture_payment_intent" relativeToURL:[APIClient backendUrl]]; NSString *parameters = [NSString stringWithFormat:@"{\"payment_intent_id\":\"%@\"}", paymentIntentId]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; request.HTTPMethod = @"POST"; request.HTTPBody = [parameters dataUsingEncoding:NSUTF8StringEncoding]; NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (response) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; if (httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) { completion(nil); } else if (httpResponse.statusCode == 402) { NSString *description = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; if (!description) { description = @"Failed to capture payment intent"; } NSError *error = [NSError errorWithDomain:@"com.stripe-terminal-ios.example" code:2 userInfo:@{NSLocalizedDescriptionKey: description}]; completion(error); } else { if(error) { completion(error); } else { NSError *error = [NSError errorWithDomain:@"com.stripe-terminal-ios.example" code:0 userInfo:@{NSLocalizedDescriptionKey: @"Other networking error occurred"}]; completion(error); } } } else { completion(error); } }]; [task resume]; } private val paymentIntentParams = PaymentIntentParameters.Builder(listOf(PaymentMethodType.CARD_PRESENT)) .setAmount(500) .setCurrency("{{TERMINAL_CURRENCY}}") .build() private val discoveryConfig = DiscoveryConfiguration.InternetDiscoveryConfiguration(isSimulated = true) private val discoveryConfig = DiscoveryConfiguration.TapToPayDiscoveryConfiguration(isSimulated = true) private val discoveryConfig = DiscoveryConfiguration.BluetoothDiscoveryConfiguration(isSimulated = true) Terminal.getInstance() .processPaymentIntent( intent = paymentIntent, callback = processPaymentIntentCallback, ) // Check for location permissions if (!isGranted(Manifest.permission.ACCESS_FINE_LOCATION)) { // If we don't have them yet, request them before doing anything else requestPermissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)) } else if (!Terminal.isInitialized() && verifyGpsEnabled()) { initialize() } if (!isGranted(Manifest.permission.ACCESS_FINE_LOCATION)) add(Manifest.permission.ACCESS_FINE_LOCATION) private fun onPermissionResult(result: Map) { val deniedPermissions: List = result .filter { !it.value } .map { it.key } // If we receive a response to our permission check, initialize if (deniedPermissions.isEmpty() && !Terminal.isInitialized() && verifyGpsEnabled()) { initialize() } } try { Terminal.init( context = applicationContext, logLevel = LogLevel.VERBOSE, tokenProvider = TokenProvider(), listener = TerminalEventListener(), offlineListener = null, ) } catch (e: TerminalException) { throw RuntimeException( "Location services are required to initialize " + "the Terminal.", e ) } Terminal.getInstance().discoverReaders(discoveryConfig, discoveryListener, discoveryCallback) Terminal.getInstance().createPaymentIntent(paymentIntentParams, createPaymentIntentCallback) class TokenProvider : ConnectionTokenProvider { override fun fetchConnectionToken(callback: ConnectionTokenCallback) { try { val token = ApiClient.createConnectionToken() callback.onSuccess(token) } catch (e: ConnectionTokenException) { callback.onFailure(e) } } } class StripeTerminalApplication : Application() { override fun onCreate() { super.onCreate() // If you already have a class that extends 'Application', // put whatever code you had in the 'onCreate' method here. TerminalApplicationDelegate.onCreate(this) } } class StripeTerminalApplication : Application() { override fun onCreate() { super.onCreate() // If you already have a class that extends 'Application', // put whatever code you had in the 'onCreate' method here. // Skip initialization if running in the TTPA process. if (TapToPay.isInTapToPayProcess()) return TerminalApplicationDelegate.onCreate(this) } } import com.stripe.stripeterminal.external.callable.InternetReaderListener import com.stripe.stripeterminal.external.callable.TapToPayReaderListener // When connecting to a physical reader, your integration should specify either the // same location as the last connection (reader.locationId) or a new location // of your user's choosing. // // Since the simulated reader is not associated with a real location, we recommend // specifying its existing mock location. val connectionConfig = ConnectionConfiguration.BluetoothConnectionConfiguration( locationId = reader.location!!.id!!, autoReconnectOnUnexpectedDisconnect = true, bluetoothReaderListener = TerminalBluetoothReaderListener(), ) val connectionConfig = ConnectionConfiguration.InternetConnectionConfiguration( failIfInUse = true, internetReaderListener = object : InternetReaderListener { override fun onDisconnect(reason: DisconnectReason) { // Show UI that your reader disconnected } }, ) val connectionConfig = ConnectionConfiguration.TapToPayConnectionConfiguration( locationId = reader.location?.id ?: throw RuntimeException("No location ID available"), autoReconnectOnUnexpectedDisconnect = true, tapToPayReaderListener = object : TapToPayReaderListener {} ) Terminal.getInstance().connectReader( reader = reader, config = connectionConfig, connectionCallback = readerCallback, ) @Throws(Exception::class) internal fun createPaymentIntent( amount: Int, currency: String, callback: Callback ) { service.createPaymentIntent(amount, currency).enqueue(callback) } internal fun capturePaymentIntent(id: String) { service.capturePaymentIntent(id).execute() } import com.stripe.example.ServerPaymentIntent /** * Create a payment intent on the backend */ @FormUrlEncoded @POST("create_payment_intent") fun createPaymentIntent( @Field("amount") amount: Int, @Field("currency") currency: String ): Call implementation "com.stripe:stripeterminal:5.4.0" implementation("com.stripe:stripeterminal-taptopay:5.4.0") implementation("com.stripe:stripeterminal-core:5.4.0") .setAmount(500) private final DiscoveryConfiguration discoveryConfig = new DiscoveryConfiguration.InternetDiscoveryConfiguration(0, null, true, DiscoveryFilter.None.INSTANCE); private final TapToPayDiscoveryConfiguration discoveryConfig = new DiscoveryConfiguration.TapToPayDiscoveryConfiguration(true); private final DiscoveryConfiguration discoveryConfig = new DiscoveryConfiguration.BluetoothDiscoveryConfiguration(0, true); Terminal.getInstance() .processPaymentIntent(paymentIntent, new CollectPaymentIntentConfiguration.Builder().build(), new ConfirmPaymentIntentConfiguration.Builder().build(), processPaymentIntentCallback); Terminal.getInstance() .createPaymentIntent(paymentIntentParams, createPaymentIntentCallback); try { Terminal.init( getApplicationContext(), LogLevel.VERBOSE, new TokenProvider(), new TerminalEventListener(), null); } catch (TerminalException e) { throw new RuntimeException( "Location services are required to initialize " + "the Terminal.", e ); } Terminal.getInstance() .discoverReaders(discoveryConfig, discoveryListener, discoveryCallback); if (!isGranted(Manifest.permission.ACCESS_FINE_LOCATION)) { if (!isGranted(Manifest.permission.ACCESS_FINE_LOCATION)) { deniedPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION); } void onPermissionResult(Map result) { List deniedPermissions = result.entrySet().stream() .filter(it -> !it.getValue()) .map(it -> it.getKey()) .collect(Collectors.toList()); // If we receive a response to our permission check, initialize if (deniedPermissions.isEmpty() && !Terminal.isInitialized() && verifyGpsEnabled()) { initialize(); } } public class TokenProvider implements ConnectionTokenProvider { @Override public void fetchConnectionToken(ConnectionTokenCallback callback) { try { final String token = ApiClient.createConnectionToken(); callback.onSuccess(token); } catch (ConnectionTokenException e) { callback.onFailure(e); } } } public class StripeTerminalApplication extends Application { @Override public void onCreate() { super.onCreate(); // If you already have a class that extends 'Application', // put whatever code you had in the 'onCreate' method here. TerminalApplicationDelegate.onCreate(this); } } public class StripeTerminalApplication extends Application { @Override public void onCreate() { super.onCreate(); // If you already have a class that extends 'Application', // put whatever code you had in the 'onCreate' method here. // Skip initialization if running in the TTPA process. if (TapToPay.isInTapToPayProcess()) return; TerminalApplicationDelegate.onCreate(this); } } import com.stripe.stripeterminal.external.callable.InternetReaderListener; import com.stripe.stripeterminal.external.callable.TapToPayReaderListener; ConnectionConfiguration.InternetConnectionConfiguration connectionConfig = new ConnectionConfiguration.InternetConnectionConfiguration( true, new InternetReaderListener() { @Override public void onDisconnect(@NonNull DisconnectReason reason) { final MainActivity activity = activityRef.get(); if (activity != null) { activity.runOnUiThread(() -> { // Show UI that your reader disconnected }); } } } ); ConnectionConfiguration.TapToPayConnectionConfiguration connectionConfig = new ConnectionConfiguration.TapToPayConnectionConfiguration( locationId, true, new TapToPayReaderListener() {} ); ConnectionConfiguration.BluetoothConnectionConfiguration connectionConfig = new ConnectionConfiguration.BluetoothConnectionConfiguration( locationId, true, new TerminalBluetoothReaderListener() ); Terminal.getInstance().connectReader( reader, connectionConfig, new ReaderCallback() { @Override public void onSuccess(@NonNull Reader reader) { final MainActivity activity = activityRef.get(); if (activity != null) { activity.runOnUiThread(() -> { // Update UI w/ connection success activity.updateReaderConnection(true); }); } } @Override public void onFailure(@NonNull TerminalException e) { final MainActivity activity = activityRef.get(); if (activity != null) { activity.runOnUiThread(() -> { // Update UI w/ connection failure }); } } } ); public static void createPaymentIntent( Integer amount, String currency, Callback callback) { mService.createPaymentIntent(amount, currency).enqueue(callback); } public static void capturePaymentIntent(@NonNull String id) throws IOException { mService.capturePaymentIntent(id).execute(); } /** * Create a payment intent on the backend */ @FormUrlEncoded @POST("create_payment_intent") Call createPaymentIntent( @Field("amount") Integer amount, @Field("currency") String currency ); /** * Create a payment intent on the backend */ @FormUrlEncoded @POST("create_payment_intent") Call createPaymentIntent( @Field("amount") Integer amount, @Field("currency") String currency ); implementation 'com.stripe:stripeterminal:5.4.0' implementation 'com.stripe:stripeterminal-taptopay:5.4.0' implementation 'com.stripe:stripeterminal-core:5.4.0' 1. Build the server ~~~ npm install ~~~ 2. Run the server ~~~ npm start ~~~ 1. Run the server ~~~ go run server.go ~~~ 1. Build the server ~~~ pip3 install -r requirements.txt ~~~ 2. Run the server ~~~ export FLASK_APP="server.py" && python3 -m flask run --port=4242 ~~~ 1. Build the server ~~~ bundle install ~~~ 2. Run the server ~~~ ruby server.rb ~~~ 1. Build the server ~~~ composer install ~~~ 2. Run the server ~~~ php -S 127.0.0.1:4242 --docroot=public ~~~ 1. Build the server ~~~ dotnet restore ~~~ 2. Run the server ~~~ dotnet run ~~~ 1. Build the server ~~~ mvn package ~~~ 2. Run the server ~~~ java -cp target/sample-jar-with-dependencies.jar com.stripe.sample.Server ~~~ ## 次のステップ #### [リーダーに接続する](https://docs.stripe.com/terminal/payments/connect-reader.md) アプリをリーダーに接続する詳細をご説明します。 #### [フリート管理](https://docs.stripe.com/terminal/fleet/locations-and-zones.md) 複数のリーダーを実際の使用店舗ごとにグループ化して管理します。 #### [Connect](https://docs.stripe.com/terminal/features/connect.md) Stripe Terminal を Connect プラットフォームに組み込みます。