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

本記事は [LINE Pay APIを使ってアプリに決済を組み込む方法](https://qiita.com/nkjm/items/b4f70b4daaf343a2bedc) の C# 版です。Windows 環境および Mac 環境いずれでも動作します。

# 概要

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

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

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

- C# の基本的な知識
- [Visual Studio Code](https://code.visualstudio.com/) と C# 拡張機能
- [dotnet core sdk](https://www.microsoft.com/net/learn/get-started-with-dotnet-tutorial) 
- 固定 Global IPアドレス *\*LINE Payを呼び出すには呼び出し元IPを指定する必要があるため*

# 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 の間だけで決済を実行できる仕組みです。

今回は都度決済の流れで、サービスプロバイダーとして C# ベースの 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")

次に同じくLINE Pay コンソールの決済連動管理 > 決済サーバ IP 管理にサービスプロバイダーのアプリの IP を登録します。LINE Pay API は呼び出し元 IP を事前に登録しておく必要があり、それをここで登録しておきます。
![スクリーンショット_2018-03-09_15_58_16.png](https://qiita-image-store.s3.amazonaws.com/0/26079/ac2cca36-7d72-8b45-c757-bb9d06dd96bf.png "スクリーンショット_2018-03-09_15_58_16.png")

**＊後述していますが、固定 Global IP が利用できる方はそのままそれを設定してください。開発フェーズで固定 Global IP の取得が難しい場合、プロキシを経由させることでワークアラウンドとすることができます。**

## Webアプリ

### Web アプリの開発

1\. Web サービスのサンプルをクローン。

```
git clone -b before https://github.com/kenakamu/line-pay-csharp-demo
```

2\. クローンしたディレクトリに移動し、必要なモジュールをリストアおよびビルド。

```
cd line-pay-csharp
dotnet build
```

3\. VS Code を起動。

```
code .
```

4\. F5 をクリックしてデバッグ開始。初めてのデバッグ時に構成がない場合は、「.NET Core」を選択。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/623d0869-992e-8519-7f1a-a9ac532e1537.png)

5\. 以下の画面が起動したら準備完了。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/214116/8fd7f4eb-b506-a65a-a62f-30839a45aaac.png)

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

### 決済機能の実装

1\. LinePayCSharpDemo フォルダに移動後、LinePayCSharp NuGet を追加。

```shell
cd LinePayCSharpDemo
dotnet add package LinePayCSharp
```

2\. 構成情報を作るため、LinePayCSharpDemo フォルダ内 appsettings.json に LinePay 要素と ServerUri 要素を追加。

```json:appsettings.json
{
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  },
  "LinePay": {
    "ChannelId": "あなたの LINE PAY CHANNEL ID",
    "ChannelSecret": "あなたの LINE PAY CHANNEL SECRET",
    "IsSandbox": true
  },  
  "ServerUri": "http://localhost:5000"
}
```

3\. 設定情報を反映するよう、LinePayCSharpDemo\Models\AppSettings.cs を以下のコードで差し替え。

```csharp:AppSettings.cs
namespace LinePayCSharpDemo.Models
{
    public class AppSettings
    {
      public LinePay LinePay { get; set; }
      public string ServerUri{ get; set; }
    }
    public class LinePay
    {
        public string ChannelId { get; set; }
        public string ChannelSecret { get; set; }
        public bool IsSandbox { get; set; }
    }    
}
```

4\. ユーザーの発注情報をキャッシュするため、CacheService.cs ファイルを追加し、以下のコードと差し替え。
※本番ではデータベースなど情報を確実に保持できるようにしてください。

```csharp:CacheService.cs
using System;
using System.Collections.Generic;

namespace LinePayCSharpDemo 
{
    public static class CacheService
    {
        public static Dictionary<Int64, object> Cache = new Dictionary<long, object>();
    }
}
```


5\. LinePayCSharpDemo\Controllers\PayController.cs を開き、以下のコードをに差し替え。ここでは必要な名前空間の追加、メンバプロパティおよびコンストラクタを追加。

```csharp:PayController.cs
using Line.Pay;
using Line.Pay.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Threading.Tasks;
using LinePayCSharpDemo.Models;

namespace LinePayCSharpDemo.Controllers
{
    [Route("api/[controller]")]
    public class PayController : Controller
    {
        private LinePayClient client;
        private AppSettings appsettings;

        public PayController(IOptions<AppSettings> options)
        {
            appsettings = options.Value;

            // LinePay クライアントの作成
            client = new LinePayClient(
                appsettings.LinePay.ChannelId,
                appsettings.LinePay.ChannelSecret,
                appsettings.LinePay.IsSandbox);
        }
    }
}
```

5\. 続けて PayController.cs に /pay/reserve へのアクセス時の処理を実装。

```csharp:PayController.cs
[HttpGet]
[Route("reserve")]
public async Task<IActionResult> Reserve()
{
    // 決済予約の作成
    var reserve = new Reserve()
    {
        ProductName = "チョコレート",
        Amount = 1,
        Currency = Currency.JPY,
        OrderId =  Guid.NewGuid().ToString(),
        ConfirmUrl = $"{appsettings.ServerUri}/api/pay/confirm"   
    };
    var response = await client.ReserveAsync(reserve);
    CacheService.Cache.Add(response.Info.TransactionId, reserve);
    return Redirect(response.Info.PaymentUrl.Web);
}
```

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

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

6\. 続けてユーザー承認後の通知 URL での処理を実装。

```csharp:PayController.cs
[HttpGet]
[Route("confirm")]
public async Task<IActionResult> Confirm()
{
    // transactionId を取得して、キャッシュから決済予約を取得
    var transactionId = Int64.Parse(HttpContext.Request.Query["transactionId"]);
    var reserve = CacheService.Cache[transactionId] as Reserve;
    // 決済確認の作成
    var confirm = new Confirm()
    {
        Amount = reserve.Amount,
        Currency = reserve.Currency
    };
    var response = await client.ConfirmAsync(transactionId, confirm);
    return new OkObjectResult("決済が完了しました。");
} 
```

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

### テスト

1\. F5 キーを押下して、デバッグ開始。ブラウザが起動するので LINE Pay ボタンをクリック。

2\. ログインフォームが表示されたら LINE ID でログイン。
**※これは 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")

3\. ログインすると下図のような承認画面が表示。
![スクリーンショット 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")

4\. PAY NOWをクリックして決済を承認すると、下図のように決済が完了しメッセージが表示される。
![スクリーンショット 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 で動作させる必要があります。Azure などにデプロイしてスマートフォンからの動作を確認してみてください。

## 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)を参照ください。

### Bot アプリの開発

1\. 必要なパッケージをインストール。

```
dotnet add package Line.Messaging
```

2\. appsettings.json に LineBot 要素を追加し、ChannelSecret と ChannelAccessToken を入れ替え。

```json:appsettings.json
"LineBot": {
    "ChannelSecret":"あなたの Messaging API CHANNEL SECRET",
    "ChannelAccessToken":"あなたの Messaging API CHANNEL ACCESS TOKEN"
  },
```

3\. Models\AppSettings.cs にも対応する項目を追加。

```csharp:AppSettings.cs
namespace LinePayCSharpDemo.Models
{
    public class AppSettings
    {
      public LinePay LinePay { get; set; }
      public LineBot LineBot { get; set; }
      public string ServerUri{ get; set; }
    }
    public class LinePay
    {
        public string ChannelId { get; set; }
        public string ChannelSecret { get; set; }
        public bool IsSandbox { get; set; }
    }    
    public class LineBot
    {
        public string ChannelSecret { get; set; }
        public string ChannelAccessToken { get; set; }
    }    
}
```

4\. Controllers フォルダに LineBotController.cs を追加しコードを追加。

```csharp:LineBotController.cs
using System;
using Line.Pay;
using Line.Pay.Models;
using Line.Messaging;
using Line.Messaging.Webhooks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;
using LinePayCSharpDemo.Models;

namespace LinePayCSharpDemo.Controllers
{
    [Produces("application/json")]
    [Route("api/[controller]")]
    public class LineBotController : Controller
    {
        private static LineMessagingClient lineMessagingClient;
        private static LinePayClient linePayClient;
        private AppSettings appsettings;

        public LineBotController(IOptions<AppSettings> options)
        {
            appsettings = options.Value;
            lineMessagingClient = new LineMessagingClient(appsettings.LineBot.ChannelAccessToken);
            linePayClient = new LinePayClient(
                appsettings.LinePay.ChannelId,
                appsettings.LinePay.ChannelSecret,
                appsettings.LinePay.IsSandbox);
        }

        /// <summary>
        /// POST: api/Messages
        /// Receive a message from a user and reply to it
        /// </summary>
        [HttpPost]
        public async Task<IActionResult> Post([FromBody]JToken req)
        { 
            var events = WebhookEventParser.Parse(req.ToString());
          
            var app = new LineBotApp(lineMessagingClient, linePayClient, appsettings);
            await app.RunAsync(events);
            return new OkResult();
        }

        [HttpGet]
        [Route("confirm")]
        public async Task<IActionResult> Confirm()
        {
            // transactionId を取得して、キャッシュから決済予約を取得
            var transactionId = Int64.Parse(HttpContext.Request.Query["transactionId"]);
            var reserve = CacheService.Cache[transactionId] as Reserve;

            // 決済確認の作成
            var confirm = new Confirm()
            {
                Amount = reserve.Amount,
                Currency = reserve.Currency
            };

            var response = await linePayClient.ConfirmAsync(transactionId, confirm);
            var app = new LineBotApp(lineMessagingClient, linePayClient, appsettings);
            await app.SendPayConfirm(reserve);
            return new OkResult();
        } 
    }
}
```

5\. LinePayCSharpDemo フォルダに LineBotApp.cs を追加し、以下コードと差し替え。

```csharp:LineBotApp.cs
using Line.Pay;
using Line.Pay.Models;
using Line.Messaging;
using Line.Messaging.Webhooks;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using LinePayCSharpDemo.Models;

namespace LinePayCSharpDemo
{
    internal class LineBotApp : WebhookApplication
    {
        private LineMessagingClient messagingClient;
        private LinePayClient payClient;
        private AppSettings appsettings;
        public LineBotApp(LineMessagingClient lineMessagingClient, LinePayClient linePayClient, AppSettings appsettings)
        {
            this.messagingClient = lineMessagingClient;
            this.payClient = linePayClient;
            this.appsettings = appsettings;
        }

        #region Handlers

        protected override async Task OnMessageAsync(MessageEvent ev)
        {
            switch (ev.Message.Type)
            {
                case EventMessageType.Text:
                    await HandleTextAsync(ev.ReplyToken, ((TextEventMessage)ev.Message).Text, ev.Source.UserId);
                    break;
            }
        }

        #endregion

        private async Task HandleTextAsync(string replyToken, string userMessage, string userId)
        {
            userMessage = userMessage.ToLower().Replace(" ", "");
            if (userMessage == "チョコレート")
            {
                var reserve = new Reserve()
                {
                    ProductName = "チョコレート",
                    Amount = 1,
                    Currency = Currency.JPY,
                    OrderId = Guid.NewGuid().ToString(),
                    ConfirmUrl = $"{appsettings.ServerUri}/api/linebot/confirm",
                    ConfirmUrlType = ConfirmUrlType.SERVER
                };

                var response = await payClient.ReserveAsync(reserve);
                // ユーザーの情報を設定
                reserve.Mid = userId;
                CacheService.Cache.Add(response.Info.TransactionId, reserve);
                var replyMessage = new TemplateMessage(
                     "Button Template",
                     new ButtonsTemplate(
                         text: $"{reserve.ProductName}を購入するには下記のボタンで決済に進んでください",
                         actions: new List<ITemplateAction> {
                         new UriTemplateAction("LINE Pay で決済", response.Info.PaymentUrl.Web)
                    }));

                await messagingClient.ReplyMessageAsync(replyToken, new List<ISendMessage> { replyMessage });
            }
        }

        public async Task SendPayConfirm(Reserve reserve)
        {
            await messagingClient.PushMessageAsync(reserve.Mid, new List<ISendMessage>(){
                new StickerMessage("2", "144"),
                new TextMessage("ありがとうございます、チョコレートの決済が完了しました。")
            });
        }
    }
}
```

6\. Channel 設定の Webhook URL には https://クラウド上のアドレス/api/linebot を設定。

### テスト

1\. Bot をスマートフォンから友達に追加し、「チョコレート」と送信。決済用のボタンが返る。
![IMG_290BC0E8B7D3-1.jpeg](https://qiita-image-store.s3.amazonaws.com/0/26079/ca9ec805-babc-e902-9e24-29c19402edbf.jpeg "IMG_290BC0E8B7D3-1.jpeg")

2\. ボタンをクリックして決済を実行。完了時にステッカーとメッセージを受信。
![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 C# SDK](https://github.com/kenakamu/line-pay-csharp)