LINE Pay APIを使ってアプリに決済を組み込む方法


# 概要

この記事ではLINE Pay APIを使って決済の仕組みをアプリケーションに組み込む方法を解説します。誰でも無償で申請できるLINE PayのSandbox環境を使って、決済の仕組みを開発し、シミュレートすることができます。

また、一般的なWebアプリに加えて、Botに決済を組み込む方法もカバーしていきます。アプリおよびBotはNode.jsで開発します。

# 必要なスキルとリソース

- Node.jsの基本的な知識

# LINE Pay APIを使った決済の流れ

3つの登場人物が存在します。一つ目は **サービスプロバイダー** です。これは有償で商品またはサービスを提供する事業主（おそらくあなた）で、実質的に何らかのアプリとなります。二つ目は その商品またはサービスを購入する **ユーザー** です。そして三つ目は **LINE Pay**です。サービスプロバイダーはLINE PayのAPIに、ユーザーはLINE Payのアプリにアクセスして下記の流れで決済をおこなうことになります。

![payment_flow.png](https://qiita-image-store.s3.amazonaws.com/0/26079/e38253b8-c928-f3b3-dc86-55cb0746d9c9.png "payment_flow.png")
*SP = サービスプロバイダー*

### 決済予約

サービスプロバイダーは商品、金額など決済情報を決済予約のAPI（Reserve API）に送信し、決済URLを取得します。

### ユーザーによる承認

取得した決済URLをユーザーに提供し、ユーザーが決済URLに進みます。LINE Payが起動して商品と金額が表示され、ユーザーはその情報を確認の上、決済承認をおこないます。

### 決済実行

ユーザーが承認すると、任意のURLへのリダイレクトまたは任意のURLへのPOSTリクエストにてサービスプロバイダーに通知されます。その時点で決済を実行できる状態となっていますので、あとは決済実行のAPI（Confirm API）にアクセスすれば決済完了となります。

---

以上が基本的な都度決済の流れです。これ以外にも継続決済という仕組みも存在します。継続決済は決済予約時に継続決済フラグを立てて予約し、ユーザーもそれを承認することで、次回からはユーザーによる承認を必要とせずにサービスプロバイダーとLINE Payの間だけで決済を実行できる仕組みです。

今回は都度決済の流れで、サービスプロバイダーとしてNode.jsベースのWebアプリを使うパターンと、Botを使うパターンの実装手順をカバーしていきます。


# 実装手順

## LINE Pay Sandboxの申請と設定

実際に決済するには加盟店登録が必要ですが、開発して動作を確認するフェーズであればSandboxが利用できます。こちらは下記のURLから申請すると払い出されるLINE Pay API用のアカウントで、誰でもすぐに利用できます。

https://pay.line.me/jp/developers/techsupport/sandbox/creation?locale=ja_JP

アカウントが払い出されたらLINE Payコンソールの決済連動管理 > 連動キー管理からChannel IDとChannel Secret Keyを確認します。これらの値はLINE PayのAPIコールに必要になります。
![スクリーンショット_2018-03-09_15_57_52.png](https://qiita-image-store.s3.amazonaws.com/0/26079/e395e2a4-f8b8-21ab-16fc-d219a33710f9.png "スクリーンショット_2018-03-09_15_57_52.png")

## Webアプリ

### Webサイトの作成

Webサービスのサンプルをクローンします。このサンプルはNode.jsとExpressベースで動作するシンプルなWebサイトです。

```
$ git clone -b web-only https://github.com/nkjm/line-pay-bootcamp.git
```

クローンしたディレクトリに移動し、依存するパッケージをインストールします。

```
$ cd line-pay-bootcamp/
$ npm install
```

下記のコマンドを実行してサーバーを起動します。

```
$ npm start
Server is listening to 5000...
```

ブラウザを起動し、http://localhost:5000 にアクセスします。下図にように表示されればOKです。

![スクリーンショット 2018-04-04 22.16.14.png](https://qiita-image-store.s3.amazonaws.com/0/26079/3033c42f-3596-7d7f-42a9-c78c9eba258c.png "スクリーンショット 2018-04-04 22.16.14.png")

「LINE Pay」のボタンをクリックすると http://localhost:5000/pay/reserve に遷移しますが、このページは現時点では実装していないのでエラーになります。ここからこの先の決済機能を実装していきます。


### 決済機能の実装

まずLINE Pay APIのNode.js用SDKと必要なパッケージをインストールします。

```
$ npm install -s dotenv line-pay memory-cache uuid 
```

- dotenv: .envファイルから環境変数をロードするためのパッケージ（詳しくは後述）
- [line-pay](https://github.com/nkjm/line-pay): LINE Pay APIのNode.js用SDK
- memory-cache: インメモリーのキー・バリューストア
- uuid: UUIDを生成するためのライブラリ。注文IDを生成するために利用

環境変数を保存するための.envファイルを作成します。環境変数はChannel Secretのような環境ごとに異なるか、あるいは秘匿性の高い情報を保存するのに利用します。

```shell:/.env
LINE_PAY_CHANNEL_ID=あなたのCHANNEL ID
LINE_PAY_CHANNEL_SECRET=あなたのCHANNEL SECRET
LINE_PAY_CONFIRM_URL=http://localhost:5000/pay/confirm
LINE_PAY_HOSTNAME=プロキシのホスト名
```

**＊LINE PayではAPIへのアクセス元Global IPアドレスを固定する必要があります。今回のチュートリアルではこの送信元を固定するためにプロキシを利用します。LINE_PAY_HOSTNAMEはそのホスト名を示す環境変数になります。**

web.jsを編集して、LINE Pay API SDKを設定します。

```javascript:/web.js
// 末尾に追加
// Import environment variables from .env file.
require("dotenv").config();

// Import packages.
const uuid = require("uuid/v4");
const cache = require("memory-cache");

// Instanticate LINE Pay API SDK.
const line_pay = require("line-pay");
const pay = new line_pay({
    channelId: process.env.LINE_PAY_CHANNEL_ID,
    channelSecret: process.env.LINE_PAY_CHANNEL_SECRET,
    hostname: process.env.LINE_PAY_HOSTNAME,
    isSandbox: true
})
```

続けてweb.jsを編集し、/pay/reserveへのアクセス時の処理を実装します。

```javascript:/web.js
// 末尾に追加
// Router configuration to start payment.
app.use("/pay/reserve", (req, res) => {
    let options = {
        productName: "チョコレート",
        amount: 1,
        currency: "JPY",
        orderId: uuid(),
        confirmUrl: process.env.LINE_PAY_CONFIRM_URL
    }

    pay.reserve(options).then((response) => {
        let reservation = options;
        reservation.transactionId = response.info.transactionId;

        console.log(`Reservation was made. Detail is following.`);
        console.log(reservation);

        // Save order information
        cache.put(reservation.transactionId, reservation);

        res.redirect(response.info.paymentUrl.web);
    })
})
```

上記のルーター設定では、決済に必要な情報（商品名、金額、通過、注文ID、ユーザー承認後の通知URL）を指定して決済を予約しています。今回は1円のチョコレートを決済する形です。実際にはこの商品情報はユーザーが選択した商品に合わせて動的に設定することになるでしょう。

決済予約が完了すると、ユーザーが当該決済を承認するためのURLとこの注文を一意に識別するためのトランザクションIDがレスポンスとして提供されます。そこでまずトランザクションID含む注文情報をデータベースに保存し、そのあと決済承認URLにユーザーをリダイレクトしています。

続けてweb.jsを編集し、ユーザー承認後の通知URLでの処理を実装します。

```javascript:/web.js
// Router configuration to receive notification when user approves payment.
app.use("/pay/confirm", (req, res) => {
    if (!req.query.transactionId){
        throw new Error("Transaction Id not found.");
    }

    // Retrieve the reservation from database.
    let reservation = cache.get(req.query.transactionId);
    if (!reservation){
        throw new Error("Reservation not found.");
    }

    console.log(`Retrieved following reservation.`);
    console.log(reservation);

    let confirmation = {
        transactionId: req.query.transactionId,
        amount: reservation.amount,
        currency: reservation.currency
    }

    console.log(`Going to confirm payment with following options.`);
    console.log(confirmation);

    pay.confirm(confirmation).then((response) => {
        res.send("決済が完了しました。");
    });
})
```

上記ルーター設定では、通知に含まれるトランザクションIDから注文情報を引き当て、その情報を元に決済を実行しています。決済が完了したら完了メッセージを送信しています。

この状態でNode.jsを再起動してブラウザで http://localhost:5000 にアクセスし、LINE Payボタンをクリックしてください。下図のようにログインフォームが表示されます。＊これはSandbox特有の画面で実際のProduction環境では異なります。
![スクリーンショット_2018-04-04_22_42_48.png](https://qiita-image-store.s3.amazonaws.com/0/26079/432b531a-56fd-41f4-78eb-4de65cbec7dc.png "スクリーンショット_2018-04-04_22_42_48.png")

ログインすると下図のような承認画面が表示されます。
![スクリーンショット 2018-04-04 22.45.23.png](https://qiita-image-store.s3.amazonaws.com/0/26079/5c439f41-3c94-dc9a-29ba-fbe95232771f.png "スクリーンショット 2018-04-04 22.45.23.png")

PAY NOWをクリックして決済を承認すると、下図のように決済が完了し、元のページにはweb.jsで設定した決済完了メッセージが表示されます。
![スクリーンショット 2018-04-04 22.46.09.png](https://qiita-image-store.s3.amazonaws.com/0/26079/53a323d4-87ce-b907-29b5-4768584ae4e6.png "スクリーンショット 2018-04-04 22.46.09.png")

![スクリーンショット 2018-04-04 22.46.14.png](https://qiita-image-store.s3.amazonaws.com/0/26079/0e12a424-a269-11b4-d6e3-e7a5440cf799.png "スクリーンショット 2018-04-04 22.46.14.png")

### クラウドにデプロイする

スマートフォンで動作を確認するにはこのWebアプリをアクセス可能なURLで動作させる必要があります。今回はherokuにデプロイしてスマートフォンからの動作を確認します。

heroku上でアプリを作成します。

```
$ heroku apps:create あなたのアプリ名
```

.envの内容をheroku上の環境変数にプッシュするためのプラグインをインストールします。

```
$ heroku plugins:install heroku-config
```

環境変数をプッシュします。

```
$ heroku config:push
```

ただし、LINE_PAY_CONFIRM_URLはheroku上ではherokuのURLに変更する必要があるのでそれを反映させます。

```
$ heroku config:set LINE_PAY_CONFIRM_URL=https://あなたのアプリ名.herokuapp.com/pay/confirm
```

herokuにソースコードをプッシュします。

```
$ git branch master
$ git checkout master
$ git add .
$ git commit -m "Add heroku support"
$ git push heroku master
```

これでスマートフォンから下記URLにアクセスし、同じように挙動を確認してみてください。

https://あなたのアプリ名.herokuapp.com

## Bot

さきほどはWebアプリケーションに決済を組み込みましたが、この決済の流れはBotでも同様に組み込むことが可能です。

ユーザーとの会話の中で、ユーザーが何らかの商品・サービス購入の意思を示した時点で決済を予約し、決済URLをテンプレートメッセージのボタンなどで提供します。ユーザーがそのURLをタップするとそのままLINEアプリの中でLINE Payが起動してユーザーに承認を求めます。ユーザーが承認すれば通知がくるので、そのタイミングで決済を実行します。これらをすべてLINEアプリの中で完結することができ、一切画面を開発する必要がありません。

Botを作成するにはMessaging APIのChannelを作成する必要があります。この手順について詳しくは[こちらのページ](https://qiita.com/nkjm/items/38808bbc97d6927837cd#channel%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B)を参照ください。

Webhook URLには https://あなたのアプリ名.herokuapp.com/webhook を設定します。このURLは後述のbot.jsでルーター設定をおこないます。

Channelを作成したらそのChannel Secretおよびアクセストークンを参照して環境変数をセットします。

```
$ heroku config:set LINE_BOT_CHANNEL_SECRET=あなたのChannel Secret
$ heroku config:set LINE_BOT_ACCESS_TOKEN=あなたのAccess Token
```


必要なパッケージをインストールします。

```
$ npm install -s @line/bot-sdk
```

次に、メインのスクリプトとなるbot.jsを作成します。

```javascript:bot.js
"use strict"

// Import environment variables from .env file.
require("dotenv").config();

// Import packages.
const express = require("express");
const app = express();
const uuid = require("uuid/v4");
const cache = require("memory-cache");

// Launch server.
app.listen(process.env.PORT || 5000, () => {
    console.log(`server is listening to ${process.env.PORT || 5000}...`);
});

// Instanticate LINE Pay API SDK.
const line_pay = require("line-pay");
const pay = new line_pay({
    channelId: process.env.LINE_PAY_CHANNEL_ID,
    channelSecret: process.env.LINE_PAY_CHANNEL_SECRET,
    hostname: process.env.LINE_PAY_HOSTNAME,
    isSandbox: true
})

// Instantiate LINE Messaging API SDK.
const line_bot = require("@line/bot-sdk");
const bot_config = {
    channelAccessToken: process.env.LINE_BOT_ACCESS_TOKEN,
    channelSecret: process.env.LINE_BOT_CHANNEL_SECRET
}
const bot_middleware = line_bot.middleware(bot_config);
const bot_client = new line_bot.Client(bot_config);

// Webhook for Messaging API.
app.post("/webhook", bot_middleware, (req, res, next) => {
    res.sendStatus(200);

    req.body.events.map((event) => {
        // We skip connection validation message.
        if (event.replyToken == "00000000000000000000000000000000" || event.replyToken == "ffffffffffffffffffffffffffffffff") return;

        // We process payment if user says "チョコレート".
        if (event.type === "message"){
            if (event.message.text === "チョコレート"){
                let product_name = "チョコレート";
                let reservation = {
                    productName: product_name,
                    amount: 1,
                    currency: "JPY",
                    orderId: uuid(),
                    confirmUrl: process.env.LINE_PAY_CONFIRM_URL,
                    confirmUrlType: "SERVER"
                }

                pay.reserve(reservation).then((response) => {
                    // Add transactionId and userId to reservation object.
                    reservation.transactionId = response.info.transactionId;
                    reservation.userId = event.source.userId;

                    console.log(`Reservation was made. Detail is following.`);
                    console.log(reservation);

                    // Save order information
                    cache.put(response.info.transactionId, reservation);

                    // Send Pay by LINE Pay button.
                    let message = {
                        type: "template",
                        altText: `${product_name}を購入するには下記のボタンで決済に進んでください`,
                        template: {
                            type: "buttons",
                            text: `${product_name}を購入するには下記のボタンで決済に進んでください`,
                            actions: [
                                {type: "uri", label: "LINE Payで決済", uri: response.info.paymentUrl.web},
                            ]
                        }
                    }
                    return bot_client.replyMessage(event.replyToken, message);
                });
            }
        }
    });
});

// If user approve the payment, LINE Pay app call this webhook.
app.get("/pay/confirm", (req, res, next) => {
    if (!req.query.transactionId){
        console.log("Transaction Id not found.");
        return res.status(400).send("Transaction Id not found.");
    }

    // Retrieve the reservation from database.
    let reservation = cache.get(req.query.transactionId);
    if (!reservation){
        console.log("Reservation not found.");
        return res.status(400).send("Reservation not found.")
    }

    console.log(`Retrieved following reservation.`);
    console.log(reservation);

    let confirmation = {
        transactionId: req.query.transactionId,
        amount: reservation.amount,
        currency: reservation.currency
    }

    console.log(`Going to confirm payment with following options.`);
    console.log(confirmation);

    // Capture payment.
    return pay.confirm(confirmation).then((response) => {
        res.sendStatus(200);

        // Reply to user that payment has been completed.
        let messages = [{
            type: "sticker",
            packageId: 2,
            stickerId: 144
        },{
            type: "text",
            text: "ありがとうございます、チョコレートの決済が完了しました。"
        }]
        return bot_client.pushMessage(reservation.userId, messages);
    });
});
```

最後にpackage.jsonのstart scriptを下記のように修正し、サーバー起動時にbot.jsが使われるように変更します。

```javascript:/package.json
　// 省略
  "scripts": {
    "start": "node bot.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  // 省略
```

これでherokuに再度ソースコードをプッシュします。

```
$ git add .
$ git commit -m "Add bot"
$ git push heroku master
```

Channel設定のWebhook URLには https://あなたのアプリ名.herokuapp.com/webhook を設定します。

その後、Botをスマートフォンから友達に追加し、「チョコレート」と話しかけると決済のためのボタンを提供してくれるはずです。
![IMG_290BC0E8B7D3-1.jpeg](https://qiita-image-store.s3.amazonaws.com/0/26079/ca9ec805-babc-e902-9e24-29c19402edbf.jpeg "IMG_290BC0E8B7D3-1.jpeg")

決済が完了したらブラウンが祝福してくれるはずです。
![IMG_9950B1D6CA62-1.jpeg](https://qiita-image-store.s3.amazonaws.com/0/26079/3bf2a5eb-ad79-8db7-be0c-0dc92d6c80c4.jpeg "IMG_9950B1D6CA62-1.jpeg")


# さらに学ぶには

LINE Payで利用できるAPIはこちらのドキュメントで網羅されています。
https://pay.line.me/jp/developers/documentation/download/tech?locale=ja_JP

上記ドキュメントやLINE Pay SDKのメソッドなどを参照してどのような決済体験ができるかアイデアを膨らませていただければこれ幸いです。