Stripe Payment 101: ECサイトで、決済成功率（CVR）の高い決済フローを簡単に組み込む方法

注文の成功率（コンバージョン率）を高く保つことは、ECサイトを運用する上で収益性にも影響が出るとても重要なミッションです。多くの事業者が、マーケティング施策によって認知や関心を獲得し、ECサイトの情報を充実させることで商品を検討してもらい、最後に注文してもらうことを目指します。各ステップでどれくらいのユーザーが離脱しているかや、施策の効果の測定などをアクセス解析ツールによって分析されている方も多いでしょう。

![スクリーンショット 2024-04-17 17.40.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/0585c68c-3597-b79e-653d-b3bd49839b77.png)

## 最後のステップで売上を逃さない

このようなファネルを用いたコンバージョン率や収益性に関する分析で、見落とされがちなのが「決済」です。オフラインの購入体験では、「買いたいものをレジに持っていき、現金やカード・ウォレットを用いて支払いを行う」シンプルなフローですが、オンラインでは途端に複雑化します。ECサイトのアカウントを作成してログインし、商品の配送先や請求先住所を入力し、クレジットカード情報を登録する。そして場合によっては、決済ボタンをクリックした後に本人確認のため追加認証が始まったり、カード会社が支払いを拒否したというエラーが発生したりして、追加の操作を始める必要が出てくることもあります。

このようなECサイトの購入フローにおけるラストワンマイルをどれだけストレスなく完了させることができるかに注目することで、コンバージョン率や売上を増加することが期待できます。

![スクリーンショット 2024-04-17 17.36.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/25e07f14-05c0-43b5-853c-3b842e3ec20f.png)

実際にウェルネスブランド「[TENTIAL](https://tential.jp/)」では、決済フローの改善に取り組むことによって、クレジットカードの承認率（オーソリ率）を業界平均を上回る98％を達成しました。

> 「以前は可視化できなかったオーソリ率が、Stripe の導入で明確化されました。業界平均のオーソリ率が90～95％といわれる中で、当社は98％というハイスコアを維持できています」（湧川氏）
> https://eczine.jp/article/detail/14030

普段はあまり注目されることのない決済フォームですが、顧客が最後に必ず通過する最重要ステップでもあります。この記事では、ECサイトにおける決済フォームを効率的に開発する方法や、決済を通じてコンバージョン率や売上を増やす方法について紹介します。

## 90%の顧客はカゴ落ち経験があり、3分以上注文に時間がかかると50％近くの顧客が離脱する

Stripeでは、定期的にECサイトの決済フローに関する調査とレポートの公開を行っています。2022年に公開された[EC サイトにおける決済フローの現状 アジア太平洋地域 2022 年版](https://stripe.com/jp/guides/state-of-asia-pacific-checkouts-2022)では、調査対象となった多くのウェブサイトが、以下の4つの問題を決済フォームに抱えていることを指摘しています。

- **54%** が、入力時にクレジットカード番号を自動検証していない
- **54%** がクレジットカードのブランドや種類を入力時に検証していない
- **45%** でのサイトで、クレジットカードの有効期限が切れていることを即座にフィードバックしていない
- **32%** が、フォームのクレジットカード番号を区切らずに表示している

![スクリーンショット 2024-04-17 17.58.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/45b9f8be-1a82-c55d-e1ec-0ad6d367f5da.png)
https://stripe.com/jp/guides/state-of-asia-pacific-checkouts-2022

これらの問題を抱えている決済フォームでは、注文確定操作を行うまで、カードや入力した情報に誤りがあることに気づくことができません。そのため、エラーが表示されてから、エラー内容を確認し、決済フォームに戻って情報を修正する作業を顧客は行う必要があります。このような手戻りステップが発生すると、顧客が購入を諦めてしまう「カゴ落ち」が発生しやすくなります。実際にStripeのレポートでは、「注文完了までに3分以上経過すると、49％の顧客が購入を諦める」という結果が出ています。

![スクリーンショット 2024-05-30 14.42.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/bc938806-28de-a907-797b-c176f5592daa.png)
https://stripe.com/jp/guides/state-of-asia-pacific-checkouts-2022

## ストレスのない決済フォームをStripeで実装する方法

Stripeを利用している場合、これらのクレジットカードフォームの問題の解決は簡単です。有効期限の確認やカード番号の検証とエラーメッセージの表示については、埋め込み・リダイレクトどちらの方法でも、Stripeが自動的に実施するよう作りになっています。

![スクリーンショット 2024-05-30 14.52.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/f51b2103-0571-24fb-0c49-34f10af8f658.png)

### リダイレクト型の決済フォームを実装する方法

Stripeを利用して、リダイレクト型の決済フォームを提供する場合、[Stripe Checkout](https://stripe.com/jp/payments/checkout)を利用します。

https://stripe.com/jp/payments/checkout

Stripe Checkoutでは、Stripe APIをサーバー側で実行し、Checkout Sessionを作成することで、リダイレクト先のURLを作成できます。

```bash
curl https://api.stripe.com/v1/checkout/sessions \
  -u sk_から始まるAPIキー: \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"="T-shirt" \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  -d "mode"=payment \
  -d "success_url"="https://example.com/success" \
  -d "cancel_url"="https://example.com/cancel"
```

このAPIを各言語のSDKから実行し、リダイレクト処理を行うことで、顧客をStripeが提供する決済フォームへリダイレクトできます。以下に例として、[Hono](https://hono.dev)を利用したコードを用意しました。

```js
const { Hono } = require("hono")
const { env } = require("hono/adapter")

const app = new Hono()

app.get('/checkout', async context => {
    // Load the Stripe API key from context.
    const { STRIPE_API_KEY: stripeKey } = env(context)

    // Instantiate the Stripe client object 
    const stripe = new Stripe(stripeKey, {
      maxNetworkRetries: 3,
      timeout: 30 * 1000,
    })
    const session = await stripe.checkout.sessions.create({
        line_items: [
        {
            price_data: {
            currency: "usd",
            product_data: {
                name: "T-shirt",
            },
            unit_amount: 2000,
            },
            quantity: 1,
        },
        ],
        mode: "payment",
        success_url: "https://example.com/success",
        cancel_url: "https://example.com/cancel",
    })
    return context.redirect(session.url, 303)
})
export default app
```

このサンプルコードでは、`https://localhost:8787/checkout`のようなURLでアクセスすることで、決済フォームに移動できます。

### 決済フォームをサイトへ埋め込む方法

この決済フォームをサイトに埋め込むこともできます。その場合は、Checkout Sessionsを作成するAPIの引数を次のように変更しましょう。

```diff
curl https://api.stripe.com/v1/checkout/sessions \
  -u sk_test_YOUR_SECRET_KEY: \
  -d "line_items[0][price_data][currency]=usd" \
  -d "line_items[0][price_data][product_data][name]=T-shirt" \
  -d "line_items[0][price_data][unit_amount]=2000" \
  -d "line_items[0][quantity]=1" \
  -d "mode=payment" \
-  -d "success_url"="https://example.com/success" \
-  -d "cancel_url"="https://example.com/cancel"
+  -d "return_url=https://example.com/success" \
+  -d "ui_mode=embedded"
```

このAPIレスポンスには、`client_secret`が含まれています。この値を利用して、UI側で決済フォームを埋め込む処理を追加しましょう。

```html
<div id="checkout"></div>
<script>
window.addEventListener('load', async () => {
    const stripe = new window.Stripe("${STRIPE_PUBLISHABLE_KEY}")
    const checkout = await stripe.initEmbeddedCheckout({
        clientSecret: "${clientSecret}",
    });
    checkout.mount('#checkout')
})
</script>
```

これによって、カード情報や住所フォーム、クーポンコード入力欄などの機能をもった決済フォームをサイトに埋め込むことができます。

![スクリーンショット 2024-05-30 17.10.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/eb98775b-9cfe-850c-41f5-4e912348a499.png)

#### 決済情報フォームだけを埋め込む方法

デザイン性を高めるため、クレジットカードなどの決済情報を入力するフォームだけを埋め込むこともできます。その場合は、Payment Intents APIを利用して`client_secret`を作成します。

```bash
curl https://api.stripe.com/v1/payment_intents \
  -u sk_test_YOUR_SECRET_KEY: \
  -d amount=1000 \
  -d currency=jpy
```

サーバー側で生成したPayment Intentsの`client_secret`を利用して、決済フォームの埋め込みを行いましょう。

```html
<form id="payment-form">
    <div id="payment-element"></div>
    <div id="result"></div>
    <button type="submit">Order</button>
</form>
<script>
  window.addEventListener('load', () => {
    const stripe = new window.Stripe("${STRIPE_PUBLISHABLE_KEY}")
    const options = {
      clientSecret: "${client_secret}",
      appearance: {
        theme: 'stripe'
      }
    }
    const elements = stripe.elements(options)
    const paymentElement = elements.create('payment')
    paymentElement.mount('#payment-element')
  
     document.getElementById('payment-form').addEventListener('submit', async e => {
      e.preventDefault()
      const result = await stripe.confirmPayment({
        elements,
        confirmParams: {
          return_url: 'https://example.com/thanks'
        }
      })
    })
  })
</script>
```

#### 必要な機能やデザイン性・ユースケースから組み込み方法を選択しよう

Stripeを利用した高機能な決済・注文フォームの作り方を３種類紹介しました。これ以外にもノーコードで決済リンクを作成できる[Payment Links](https://stripe.com/jp/payments/payment-links)などもStripeでは提供しています。

どの機能を利用して組み込みを行うのがよいかについては、Stripeのドキュメントにある比較表や、[クイックスタート](https://docs.stripe.com/payments/quickstart)で紹介されているサンプルコードを比較してご検討ください。

![スクリーンショット 2024-05-30 17.15.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/8fadcc3b-3848-83c0-16d4-2f55b18b575c.png)
https://docs.stripe.com/payments/accept-a-payment/web/compare-integrations

### 請求先・発送先住所フォームも、効率的に入力できるように実装しよう

ECサイトの決済フローに登場するフォームは、決済情報フォームだけではありません。[レポート](https://stripe.com/jp/guides/state-of-asia-pacific-checkouts-2022)では、決済フォームだけでなく住所フォームの実装についても言及しています。「**39%** のサイトが、自動入力機能を提供することなく、顧客に手動での住所入力を求めている」という結果が出ており、住所のオートコンプリート機能を決済フローで提供することを提案しています。

![JP_Stripes宮崎.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/14adf2d5-0501-3d3e-584a-4e24c1672427.png)

「[TENTIAL](https://tential.jp/)の湧川氏も、決済フローのユーザー体験について、一貫性がある利便性の高い動線を作れることの重要性を強調されています。

> 「当社が大事にしているのは、ECサイト上での認知から購入、そしてお客様の元に商品が届くまでの一気通貫した顧客体験設計です。その中で、決済体験では、『利便性の高い購入フォーム』『安全に使っていただける決済基盤』の2点が重要だと考えています」（湧川氏）
> https://eczine.jp/article/detail/14030

ここからは、決済フォームだけでなく、住所フォームについても保守性とユーザー体験を高いレベルで提供する方法について紹介します。

#### 住所フォームの実装コストは安くない

ECサイトやSaaSを内製する場合、開発工数が増える要因の一つがフォームです。特に住所フォームについては、入力する項目やバリデーションすべき情報が多くなりやすく、コードの量が多くなりがちです。シンプルなHTMLフォームを提供するだけで済ませることも可能ですが、それでも以下のように少ないコードをアプリケーションへ追加する必要があります。

```html
    <form class="grid grid-cols-12 gap-y-6 gap-x-4 section mb-8">
        <div class="col-span-12 text-2xl font-bold tracking-tight text-gray-900 section-title">配送先住所</div>
        <label class="text-sm col-span-12">
            メールアドレス
            <input name="email" class="block w-full mt-2 text-sm border border-gray-300 p-2.5 shadow-inner">
            </input>
        </label>
        <label class="text-sm col-span-6">
            姓
            <input name="firstname" class="block w-full mt-2 text-sm border border-gray-300 p-2.5 shadow-inner">
            </input>
        </label>
        <label class="text-sm col-span-6">
            名
            <input name="lastname" class="block w-full mt-2 text-sm border border-gray-300 p-2.5 shadow-inner">
            </input>
        </label>
        <label class="text-sm col-span-6">
            郵便番号
            <input name="postal" class="block w-full mt-2 text-sm border border-gray-300 p-2.5 shadow-inner">
            </input>
        </label>
        <label class="text-sm col-span-6">
            都道府県
            <input name="city" class="block w-full mt-2 text-sm border border-gray-300 p-2.5 shadow-inner">
            </input>
        </label>
        <label class="text-sm col-span-6">
            市区町村
            <input name="city" class="block w-full mt-2 text-sm border border-gray-300 p-2.5 shadow-inner">
            </input>
        </label>
        <label class="text-sm col-span-12">
            番地・建物名
            <input name="line1" class="block w-full mt-2 text-sm border border-gray-300 p-2.5 shadow-inner">
            </input>
            <input name="line2" class="block w-full mt-4 text-sm border border-gray-300 p-2.5 shadow-inner">
            </input>
        </label>
        <label class="text-sm col-span-6">
            会社名
            <input name="company" class="block w-full mt-2 text-sm border border-gray-300 p-2.5 shadow-inner">
            </input>
        </label>
        <label class="text-sm col-span-6">
            電話番号
            <input name="phone" class="block w-full mt-2 text-sm border border-gray-300 p-2.5 shadow-inner">
            </input>
        </label>
    </form>
```

実際のサービス提供では、このHTMLに加えて住所の自動入力や電話番号・郵便番号のバリデーションなどをJavaScriptで実装する必要があります。さらに海外にも商品やサービスを提供する場合には、住所フォームの各項目の翻訳だけでなく、その国に最適化されたフォームレイアウトを用意することも重要です。日本とアメリカだけで比較しても、各項目の表示順序が異なります。

|アメリカ|日本|
|:--|:--|
|![スクリーンショット 2024-05-30 16.21.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/e536e333-cdda-99ce-f91d-bc556777216c.png)|![スクリーンショット 2024-05-30 16.21.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/2f1b0dc5-a6e8-fc63-68e6-7ba95ec1a369.png)|

ECサイトの住所フォームにおけるユーザー体験をより良くするには、このような要件をクリアできるコードを実装する必要があります。

#### Stripeの埋め込み住所要素`AddressElements`でコード量を削減する

Stripeでは、顧客の決済体験をより良くするため、決済フォームだけでなく住所フォームについてもSDKによるサポートを行っています。Stripeの住所要素は、決済フォーム同様JavaScriptでサイトに要素を埋め込む形で実装します。そのため、HTMLファイルには、要素を埋め込むための`div`要素を配置するだけで済むようになります。

```diff_html
+   <script src="https://js.stripe.com/v3" async></script>
    <form class="grid grid-cols-12 gap-y-6 gap-x-4 section mb-8">
+        <div id="address-element"></div>
    </form>
```

JavaScriptによる埋め込みコードもとてもシンプルで、10行未満のコードを追加するだけです。

```javascript
const stripe = new Stripe("${STRIPE_PUBLISHABLE_KEY}")
const addressElement = elements.create('address', {
  mode: 'billing',
  appearance: {
    theme: 'stripe'
  },
})
addressElement.mount('#address-element')
```

これだけで国ごとのローカライズや住所情報のオートコンプリートなどに対応した住所フォーム要素をサイトに追加できます。

![スクリーンショット 2024-05-30 16.21.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/2f1b0dc5-a6e8-fc63-68e6-7ba95ec1a369.png)

##### 見た目のカスタマイズは、JavaScriptで実施

決済フォームや住所フォームの見た目をカスタマイズする場合、`elements()`関数の引数でCSSの値を設定できます。要素ごとのスタイル設定だけでなく、CSS変数を利用して配色だけを変更するようなカスタマイズにも対応しています。

```diff_javascript
  const stripe = Stripe("{{ stripePublishableKey }}");

  const appearance = {
-    theme: 'stripe',
+    rules: {
+      ".Label, .Tab": {
+        fontWeight: '500',
+      },
+      ".Input, .Tab": {
+        // borderColor: 'teal'
+      },
+      ".Input::placeholder": {
+        color: "#ccc"
+      }
+    },
+    variables: {
+      colorPrimary: '#3C9BAA'
+    }
  }
  const elements = stripe.elements({
    clientSecret: "{{ clientSecret }}",
    appearance
  });
```



##### 決済フォーム要素（`PaymentElements`）と併用して、送信処理も効率化

また、決済フォーム要素（`PaymentElements`）と合わせて利用することで、顧客が入力した住所情報を追加の実装なしでStripeに送信することもできます。

```html
<form id="payment-form" style="max-width: 80%; margin: 30px auto;">
    <div id="payment-element"></div>
    <div id="address-element"></div>
    <div id="result"></div>
    <button type="submit">Order</button>
</form>
<script>
  window.addEventListener('load', () => {
    const stripe = new window.Stripe("${STRIPE_PUBLISHABLE_KEY}")
    /**
     * Render the payment form
     */
    const options = {
      clientSecret: "${paymentIntent.client_secret}",
      appearance: {
        theme: 'stripe'
      }
    }
    const elements = stripe.elements(options)
    const paymentElement = elements.create('payment')
    paymentElement.mount('#payment-element')
    const addressElement = elements.create('address', {
      mode: 'billing',
      appearance: {
        theme: 'stripe'
      },
    })
    addressElement.mount('#address-element')
  
     document.getElementById('payment-form').addEventListener('submit', async e => {
      e.preventDefault()
      const result = await stripe.confirmPayment({
        elements,
        confirmParams: {
          return_url: 'https://example.com/thanks'
        }
      })
    })
  })
</script>
```

Stripeを活用することで、決済フォームだけでなく住所フォームについても、より少ないコードで高機能なものを実装することができます。

#### Stripe Checkout (リダイレクト・埋め込み)で、請求先・配送先住所を入力できるようにする方法

Stripe Checkoutを利用している場合は、パラメータの追加のみで住所フォームを追加できます。`billing_address_collection`をリクエストパラメータに追加すると、決済フォームに請求先住所フォームが追加されます。

```diff
curl https://api.stripe.com/v1/checkout/sessions \
  -u sk_test_YOUR_SECRET_KEY: \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"="T-shirt" \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  -d "mode"=payment \
  -d "success_url"="https://example.com/success" \
  -d "cancel_url"="https://example.com/cancel" \
+  -d "billing_address_collection"=required
```

追加される住所情報フォームには、GoogleのAPIを利用した郵便番号のオートコンプリート機能も実装されています。そのため、顧客は郵便番号と番地やマンション情報などだけを入力するだけで、住所情報を送信できます。

![スクリーンショット 2024-05-30 15.57.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/de5a339f-17ff-84bb-c977-c90ff6d9073d.png)

また、ギフト系のECサイトのような「請求先と発送先が異なる場合」についてもStripeは対応しています。この場合は、`shipping_address_collection[allowed_countries]`を設定しましょう。この値に発送可能な国を指定することで、日本以外の国への発送についても対応できます。

```diff
curl https://api.stripe.com/v1/checkout/sessions \
  -u sk_test_YOUR_SECRET_KEY: \
  -d "line_items[0][price_data][currency]"=usd \
  -d "line_items[0][price_data][product_data][name]"="T-shirt" \
  -d "line_items[0][price_data][unit_amount]"=2000 \
  -d "line_items[0][quantity]"=1 \
  -d "mode"=payment \
  -d "success_url"="https://example.com/success" \
  -d "cancel_url"="https://example.com/cancel" \
+  -d "shipping_address_collection[allowed_countries][]"="US" \
+  -d "shipping_address_collection[allowed_countries][]"="JP"
```

Stripe Checkoutの決済フォームでは、[配送先住所]と[請求先住所]の２つのフォームが表示されます。また、「**請求先情報は配送先情報と同じ**」のチェックボックスをオンにすることで、配送先住所だけ入力することで、決済フォームを送信できます。

![スクリーンショット 2024-05-30 16.00.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/fd631e68-cbb1-e661-4b41-9cb1f5c6b883.png)


## まとめ

ECサイトの運営において、コンバージョン率を高めることは収益性を向上させるための重要な要素です。特に「決済フロー」はユーザーが最後に通過する最も重要なステップであり、ここでのユーザー離脱を防ぐことが成功の鍵となります。

Stripeの導入事例であるウェルネスブランド「TENTIAL」のように、決済フローを最適化することで、クレジットカードの承認率を業界平均を上回る98％にまで向上させることが可能です。また、Stripeを用いることで、クレジットカード情報の自動検証やエラーメッセージの即時表示を簡単に実装でき、ユーザーのストレスを軽減することができます。

さらに、住所フォームの自動入力機能の実装や、各国に対応したフォームレイアウトの最適化も重要です。Stripeの住所要素を利用することで、少ないコードで高機能な住所入力フォームを提供でき、ユーザー体験を大幅に向上させることができます。

全体として、決済フローおよび住所フォームの最適化は、コンバージョン率の向上とユーザーの満足度を高めるために極めて重要です。これにより、ECサイトはより高い収益を上げ、強固な顧客基盤を築くことができるでしょう。

関連ドキュメントやガイドを参照することで、具体的な実装方法やさらなる最適化のヒントを得ることができます。

## 関連記事・ドキュメント

https://stripe.com/jp/resources/more/payment-acceptance-101

https://stripe.com/jp/sessions/2023/why-payments-experience-is-customer-experience

https://stripe.com/jp/guides/state-of-asia-pacific-checkouts-2022