Ionic / Capacitor / Stripeで、web / ネイティブアプリ両方に対応したサブスクリプション申し込みフォームを実装する

この記事は、[Ionic Framework / Capacitor / Stencil Advent Calendar 2022](https://qiita.com/advent-calendar/2022/ionic_jp) 13日目の記事です。

IonicとCapacitorを利用することで、WebとiOS/Android両方に対応したアプリケーションが作れます。

そしてCapacitorのStripeプラグインを利用することで、決済フォームの組み込みについても１ソースで対応できます。

https://github.com/capacitor-community/stripe

今回の記事では、Capacitor Stripeプラグインを、Ionic(React)アプリに組み込む方法を紹介します。

## アプリのセットアップ

まずはアプリをセットアップしましょう。

セットアップは、Ionic CLIで行います。

```bash
$ ionic start
```

ブラウザで設定ができるウィザードが用意されています。が、今回は対話形式で設定を行いますので`n`を選びました。

```
? Use the app creation wizard? No
```

IonicとCapacitorは、　Angular / Vueでも利用できます。

React以外を使われる方は、ここで他のフレームワークを選びましょう。

```bash
? Framework: (Use arrow keys)
❯ Angular | https://angular.io 
  React   | https://reactjs.org 
  Vue     | https://vuejs.org 
```

プロジェクト名を指定しましょう。これがアプリケーションのディレクトリ名になります。

```bash
Please enter the full name of your app. You can change this at any time. To
bypass this prompt next time, supply name, the first argument to ionic start.

? Project name: 
```

テンプレートが選べますが、今回は`blank`を選びます。

```bash
? Starter template: (Use arrow keys)
❯ blank        | A blank starter project 
  list         | A starting project with a list 
  my-first-app | A template for the "Build Your First Ap
p" tutorial 
  sidemenu     | A starting project with a side menu wit
h navigation in the content area 
  tabs         | A starting project with a simple tabbed
(Move up and down to reveal more choices)
```

Ionicアカウントは、今回のサンプルだけであれば不要ですので`no`で進みます。

```bash
? Create free Ionic account? (y/N) 
```

このようなメッセージが表示されれば、準備完了です。

```bash
Your Ionic app is ready! Follow these next steps:

- Go to your new project: cd ./ionic-capacitor-stripe
- Run ionic serve within the app directory to see your app in the browser
- Run ionic capacitor add to add a native iOS or Android project using Capacitor
- Generate your app icon and splash screens using cordova-res --skip-config
--copy
- Explore the Ionic docs for components, tutorials, and more:
https://ion.link/docs
- Building an enterprise app? Ionic has Enterprise Support and Features:
https://ion.link/enterprise-edition
```


最後に一度アプリを起動させてみます。

```bash
$ npm start
Compiled successfully!

You can now view ionic-capacitor-stripe in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://192.168.86.21:3000


Note that the development build is not optimized.
To create a production build, use npm run build.

webpack compiled successfully
No issues found.

```

以下の画面が立ち上がれば準備完了です。

![スクリーンショット 2022-12-09 14.33.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/cc593a62-6bd9-1344-f674-5bf1c08fd879.png)

## Express.jsでAPIサーバーを用意する

Stripeの決済・サブスクリプション申し込みには、サーバー側の処理が必要です。

そのため、以下のコマンドで別途Express.jsのアプリを用意しましょう。

```bash
$ mkdir simple-express
$ cd simple-express
$ npx express-generator
Need to install the following packages:
  express-generator@4.16.1
Ok to proceed? (y) y
```

ライブラリのインストールを行いましょう。

```bash
$ npm install
$ npm install stripe
```

`app.js`を編集して、サブスクリプションの申し込み部分のみにフォーカスしたAPIを、実装します。

```diff_javascript
const createError = require('http-errors')
const express = require('express')
const path = require('path')
const cookieParser = require('cookie-parser')
const logger = require('morgan')
+const stripe = require('stripe')('sk_test_から始まるシークレットキー')

const app = express()

// view engine setup
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'jade')

app.use(logger('dev'))
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(express.static(path.join(__dirname, 'public')))

+app.use((request, response, next) => {
+  response.header('Access-Control-Allow-Origin', '*')
+  response.header('Access-Control-Allow-Methods', 'GET,POST')
+  response.header(
+    'Access-Control-Allow-Headers',
+    'Content-Type, Authorization, access_token'
+  )
+  if (request.method.toLocaleUpperCase() === 'OPTIONS') {
+    return response.send(200)
+  }
+  next()
+})

+app.post('/create_subscription', async (req, res) => {
+  const customer = await stripe.customers.create()
+  const product = await stripe.products.create({
+      name: 'デモ用'
+  })
+  const subscription = await stripe.subscriptions.create({
+    customer: customer.id,
+    items: [{
+        price_data: {
+            unit_amount: 1000,
+            currency: 'jpy',
+            recurring: {
+                interval: 'month',
+            },
+            product: product.id,
+            quantity: 1,
+        }
+    }],
+    payment_behavior: 'default_incomplete',
+    expand: ['latest_invoice.payment_intent'],
+    payment_settings: {
+        save_default_payment_method: 'on_subscription',
+    },
+  })
+  res.json({
+      subscriptionId: subscription.id,
+      clientSecret: subscription.latest_invoice.payment_intent.client_secret
+  })
+
+})

```

サーバーを起動して、データが作成できているかを確認しましょう。


```bash
export PORT=3002;yarn start
```

ポート`3000`をIonicアプリが利用しますので、環境変数で変更しています。

curlでAPIを呼び出して、サブスクリプションが作成できていることを確認しましょう。

```bash
$ curl http://localhost:3002/create_subscription | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   127  100   127    0     0     68      0  0:00:01  0:00:01 --:--:--    69
{
  "subscriptionId": "sub_1123xxxx",
  "clientSecret": "pi_xxxx_secret_xxxx"
}
```

## Capacitor Stripeプラグインを追加する

いよいよ申し込みフォームを組み込みます。

### ライブラリの追加と環境変数の設定
まずはライブラリを追加しましょう。

```bash
$ npm install @capacitor-community/stripe @stripe-elements/stripe-elements
```

`.env`を作成し、環境変数でStripeの公開可能APIキーを設定します。

```bash
REACT_APP_STRIPE_PUBLISHABLE_API_KEY=pk_test_から始まる公開可能キー
```

### Providerを設定する

CapacitorのStripeプラグインは、環境に応じてweb / iOS / AndroidそれぞれのStripe SDKを内部で呼び出します。

SDKの用意するクライアントをセットアップするためのProviderを設定しましょう。

`src/index.tsx`を次のように変更します。

```diff_typescript
import reportWebVitals from './reportWebVitals';
+import { CapacitorStripeProvider } from '@capacitor-community/stripe/dist/esm/react/provider';

const container = document.getElementById('root');
const root = createRoot(container!);
root.render(
  <React.StrictMode>
+    <CapacitorStripeProvider
+      publishableKey={process.env.REACT_APP_STRIPE_PUBLISHABLE_API_KEY as string}
+      fallback={<p>Loading...</p>}
+    >
      <App />
+    </CapacitorStripeProvider>
  </React.StrictMode>
);
```

環境変数を正しく読み込むために、`npm start`を実行中の場合は、一度停止して再実行しましょう。

### カード情報入力フォームを表示させる

セットアップができれば、あとはボタンクリックに反応して決済画面を表示させましょう。

`src/components/ExploreContainer.tsx`を次のように変更します。

```diff_typescript
+import { useCapacitorStripe } from '@capacitor-community/stripe/dist/esm/react/provider';
+import { IonButton } from '@ionic/react';

interface ContainerProps { }

const ExploreContainer: React.FC<ContainerProps> = () => {
+  const { stripe } = useCapacitorStripe();
  return (
    <div className="container">
      <strong>Ready to create an app?</strong>
      <p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
+      <IonButton
+        onClick={async () => {
+          const subscription = await fetch('http://localhost:3002/create_subscription', {
+            method: 'post'
+          })
+            .then(data => data.json())
+          await stripe.createPaymentSheet({
+            paymentIntentClientSecret: subscription.clientSecret
+          })
+          await stripe.presentPaymentSheet()
+        }}
+      >
+        Subscribe
+      </IonButton>
    </div>
  );
};
```

はじめにAPIを呼び出してClient Secretを取得します。

その後、Payment Sheetの作成と表示を実装しています。

## アプリで動作を確認する

エラーが出ていなければ、アプリ上にボタンが表示されます。

![スクリーンショット 2022-12-09 16.54.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/3e1a1180-0d6e-f5dc-f141-7fcf36780e60.png)

ボタンをクリックすると、決済フォームが画面下から出てきます。

クリックすると、![スクリーンショット 2022-12-09 16.54.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/95f6e856-580f-e766-f769-175e68e12112.png)

テスト用カード情報を入力して、「支払う」ボタンをクリックすると、サブスクリプションの申し込みが完了します。

## カスタマイズやiOS / Androidへの対応方法など

カスタマイズやネイティブアプリ用の設定については、日本語のドキュメントが用意されています。

https://ja.stripe.capacitorjs.jp/docs/configuration

https://ja.stripe.capacitorjs.jp/docs/payment-sheet


## おわりに

このように、通常のシングルページアプリケーション（SPA）開発と変わらない流れで、iOS / Androidにも対応したアプリを構築できます。

Capacitorプラグインを組み合わせることで、よりネイティブアプリらしい体験の提供もできますので、ぜひお試しください。