iT邦幫忙

第 11 屆 iThome 鐵人賽

4
Software Development

Line Bot 心得分享 LineMessagingApi + LUIS + BotFramework系列 第 22

[Day22] 如何製作 LINE Bot 行動支付機器人 - LINE Pay

今天要介紹的是 LINE Pay,這是 LINE 推出的行動支付服務,聽說可以用來繳水費和電費等帳單,今天就來看看如何使用 LINE Pay 製作行動支付機器人。


LINE Pay 運作方式

LINE Pay 的付款方式很多,這邊我挑 「一般付款」 來作範例。

主要分為兩大步驟「Reserve」和「Confirm」。

  • Reserve API 授權付款
  • Confirm API 確認付款

1. 用戶結帳時,呼叫 Reserve API 授權付款。

POST /v2/payments/request

2. 成功後取得 transactionId 和 paymentUrl。

https://ithelp.ithome.com.tw/upload/images/20200205/20106865QwB6XAAcrO.jpg

3. 使用 paymentUrl 轉到 LINE Pay 的付款頁面。

https://ithelp.ithome.com.tw/upload/images/20200205/20106865ZbYxc32PwS.jpg

4. 付款完成 LINE Pay 會使用 confirmUrl 轉回我們的網站。

https://ithelp.ithome.com.tw/upload/images/20200201/201068655qNHtNZcpt.jpg

5. 在網站呼叫 Confirm API 確認付款。

POST /v2/payments/{transactionId}/confirm

6. 成功後就完成整個 LINE Pay 付款流程。

範例只是讓大家了解流程,實務上作法很多,建議直接看 官方文件


LINE Pay 服務註冊

1. 首先進入 LINE Pay 首頁。

https://pay.line.me/jp/developers/main/main?locale=zh_TW

2. 申請 Sandbox 帳號。

https://ithelp.ithome.com.tw/upload/images/20200201/20106865Pe0oEKsqNi.jpg

3. 填寫相關資料後送出。

https://ithelp.ithome.com.tw/upload/images/20200201/2010686518WJLJhBy9.jpg

4. 到 Email 收信,會看到一組測試帳號。

https://ithelp.ithome.com.tw/upload/images/20200201/20106865sRCMatpL9m.jpg

5. 點擊 商家後台 進入管理頁面,設定伺服器 IP 白名單。

https://ithelp.ithome.com.tw/upload/images/20200201/20106865faU2chbKix.jpg

6. 開啟金鑰管理,記住 Channel ID 和 Channel Secret Key 之後程式會用到。

https://ithelp.ithome.com.tw/upload/images/20200201/20106865Vu0b6TL12v.jpg


程式部分

1. 使用 Flex Message 製作結帳畫面。

var flexMessage = new FlexMessage("購物車")
{
    Contents = new BubbleContainer
    {
        Body = new BoxComponent
        {
            Layout = BoxLayout.Vertical,
            Contents = new List<IFlexComponent>
            {
                new TextComponent
                {
                    Text = "購物車",
                    Weight = Weight.Bold,
                    Size = ComponentSize.Xxl,
                    Margin = Spacing.Md
                },
                new SeparatorComponent
                {
                    Margin = Spacing.Xxl
                },
                new BoxComponent
                {
                    Layout = BoxLayout.Vertical,
                    Margin = Spacing.Xxl,
                    Spacing = Spacing.Sm,
                    Contents = new List<IFlexComponent>
                    {
                        new BoxComponent
                        {
                            Layout = BoxLayout.Horizontal,
                            Contents = new List<IFlexComponent>
                            {
                                new TextComponent
                                {
                                    Text= "波霸紅茶拿鐵",
                                    Size = ComponentSize.Sm,
                                    Flex = 0,
                                    Weight = Weight.Bold
                                },
                                new TextComponent
                                {
                                    Text= "NT$60",
                                    Size = ComponentSize.Sm,
                                    Align = Align.End,
                                    Weight = Weight.Bold
                                }
                            }
                        },
                        new BoxComponent
                        {
                            Layout = BoxLayout.Horizontal,
                            Contents = new List<IFlexComponent>
                            {
                                new TextComponent
                                {
                                    Text= "四季春春青茶",
                                    Size = ComponentSize.Sm,
                                    Flex = 0,
                                    Weight = Weight.Bold
                                },
                                new TextComponent
                                {
                                    Text= "NT$30",
                                    Size = ComponentSize.Sm,
                                    Align = Align.End,
                                    Weight = Weight.Bold
                                }
                            }
                        }
                    }
                },
                new SeparatorComponent
                {
                    Margin = Spacing.Xxl
                },
                new BoxComponent
                {
                    Layout = BoxLayout.Horizontal,
                    Margin = Spacing.Md,
                    Contents = new List<IFlexComponent>
                    {
                        new TextComponent
                        {
                            Text= "總金額",
                            Size = ComponentSize.Sm,
                            Flex = 0,
                            Weight = Weight.Bold
                        },
                        new TextComponent
                        {
                            Text= "NT$90",
                            Size = ComponentSize.Sm,
                            Align = Align.End,
                            Weight = Weight.Bold
                        }
                    }
                },
                new SeparatorComponent
                {
                    Margin = Spacing.Md
                }
            }
        },
        Footer = new BoxComponent
        {
            Layout = BoxLayout.Vertical,
            Contents = new List<IFlexComponent>
            {
                new ButtonComponent
                {
                    Style = ButtonStyle.Primary,
                    Action = new PostbackTemplateAction("結帳", "結帳")
                }
            }
        }
    }
};

await _messagingClient.ReplyMessageAsync(ev.ReplyToken,
    new List<ISendMessage> { flexMessage });

2. 呼叫 Reserve API 授權付款並回傳 paymentUrl。

protected override async Task OnPostbackAsync(PostbackEvent ev)
{
    if (ev.Postback.Data == "結帳")
    {
        dynamic dy = null as dynamic;

        //呼叫 API 授權付款
        using (var httpClient = new HttpClient())
        {
            //設定 Header
            httpClient.DefaultRequestHeaders
                .Add("X-LINE-ChannelId", "123456789");
            httpClient.DefaultRequestHeaders
                .Add("X-LINE-ChannelSecret", "abcde12345");

            //設定 Body
            var body = new
            {
                amount = 90,
                productName = "iBot",
                productImageUrl = "https://xxxxx/image.png",
                confirmUrl = "https://xxxxx/api/linebot/pay/confirm",
                //訂單編號,不能重複
                orderId = Guid.NewGuid().ToString(),
                currency = "TWD"
            };

            var content = new StringContent(JsonConvert.SerializeObject(body),
                Encoding.UTF8, "application/json");

            var response = await httpClient.PostAsync(
                "https://sandbox-api-pay.line.me/v2/payments/request", content);

            var result = await response.Content.ReadAsStringAsync();

            dy = JObject.Parse(result);
        }

        //新增訂單
        var order = new Order
        {
            UserId = ev.Source.UserId,
            Amount = 90,
            Status = "未付款",
            OrderId = Guid.NewGuid().ToString(),
            TransactionId = dy.info.transactionId
        };
        _db.Orders.Add(order);
        await _db.SaveChangesAsync();

        //回覆付款連結
        var flexMessage = new FlexMessage("付款")
        {
            Contents = new BubbleContainer
            {
                Body = new BoxComponent
                {
                    Layout = BoxLayout.Vertical,
                    Contents = new List<IFlexComponent>
                    {
                        new TextComponent
                        {
                            Text = "使用 LINE Pay 付款",
                            Weight = Weight.Bold,
                            Size = ComponentSize.Md,
                            Align = Align.Center
                        }
                    }
                },
                Footer = new BoxComponent
                {
                    Layout = BoxLayout.Vertical,
                    Contents = new List<IFlexComponent>
                    {
                        new ButtonComponent
                        {
                            Action = new UriTemplateAction("Pay NT$90",
                                $"line://app/123456789-OXOXOXOX?url={dy.info.paymentUrl.web}")
                        }
                    }
                },
                Styles = new BubbleStyles
                {
                    Footer = new BlockStyle
                    {
                        Separator = true
                    }
                }
            }
        };

        await _messagingClient.ReplyMessageAsync(ev.ReplyToken,
            new List<ISendMessage> { flexMessage });
    }
}

3. 使用 LIFF 轉址到 paymentUrl 付款畫面,使用 Pazor Page。

@page
@model iBotTest.LIFFModel
@{ 
    Layout = null;
}

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>iBot</title>
</head>
<body>
    <script>
        //轉址到付款頁面
        var url = new URL(location.href);
        location.href = url.searchParams.get("url");
    </script>
</body>
</html>

4. 呼叫 Confirm API 確認付款並使用 Push API 回覆結果。

[HttpGet("pay/confirm")]
public async Task<string> PayConfirm([FromQuery]string transactionId)
{
    var lineMessagingClient = 
        new LineMessagingClient(_lineBotConfig.accessToken);

    var order = await _db.Orders
        .Where(it => it.TransactionId == transactionId)
        .FirstOrDefaultAsync();

    dynamic dy = null as dynamic;

    //呼叫 API 確認付款
    using (var httpClient = new HttpClient())
    {
        //設定 Header
        httpClient.DefaultRequestHeaders
            .Add("X-LINE-ChannelId", "123456789");
        httpClient.DefaultRequestHeaders
            .Add("X-LINE-ChannelSecret", "abcde12345");

        //設定 Body
        var body = new
        {
            amount = 90,
            currency = "TWD"
        };

        var content = new StringContent(JsonConvert.SerializeObject(body),
            Encoding.UTF8, "application/json");

        var response = await httpClient.PostAsync(
            $"https://sandbox-api-pay.line.me/v2/payments/{transactionId}/confirm", content);

        var result = await response.Content.ReadAsStringAsync();

        dy = JObject.Parse(result);
    }

    //更新付款狀態
    order.Status = "已付款";
    await _db.SaveChangesAsync();

    //回覆結果
    if (dy.returnCode == "0000")
        await lineMessagingClient.PushMessageAsync(order.UserId, "付款完成!!");
    else
        await lineMessagingClient.PushMessageAsync(order.UserId, "付款失敗!!");

    return "付款完成!!";
}

結果

1. 購物車結帳,呼叫 Reserve API 授權付款。

https://ithelp.ithome.com.tw/upload/images/20200201/20106865MDERTwfdOD.jpg

2. 使用 LINE Pay 付款。

https://ithelp.ithome.com.tw/upload/images/20200201/20106865A1eoB7MzgH.jpg

3. 開啟 LIFF 視窗並轉址到 paymentUrl。

https://ithelp.ithome.com.tw/upload/images/20200201/20106865Ql7QnSrs65.jpg

4. 付款完成。

https://ithelp.ithome.com.tw/upload/images/20200201/20106865kNOw5QKcYC.jpg

5. 使用 confirmUrl 轉回我們的網站,並呼叫 Confirm API 確認付款。

https://ithelp.ithome.com.tw/upload/images/20200201/2010686557xisCZG0R.jpg

字好小,這裡使用 Razor Page 會比較好。

6. 使用 Push API 回覆結果。

https://ithelp.ithome.com.tw/upload/images/20200205/20106865UWr42QrWLU.jpg

7. 資料庫結果。

https://ithelp.ithome.com.tw/upload/images/20200205/20106865u7iVeRwvbN.jpg


結語

下一篇要介紹 Account Link,今天就到這裡,感謝大家觀看。 (´・ω・`)

本文為教學用途,程式省略很多細節,不能直接用在實務上。


參考文章

[30 天教你如何玩弄 Line bot API] 第 23 天:來購物啊!- LINE Pay API
Day 06 - Linepay (4) 付款 reserve API


上一篇
[Day21] 如何在 LINE Bot 使用藍芽偵測用戶位置 - LINE Beacon
下一篇
[Day23] 如何在 LINE Bot 整合網站帳號 - Account Link
系列文
Line Bot 心得分享 LineMessagingApi + LUIS + BotFramework27
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
arguskao
iT邦新手 4 級 ‧ 2022-06-09 20:42:22

這大概是我看過最最清楚的講解了,謝謝您的教學!

謝謝!!

arguskao iT邦新手 4 級 ‧ 2022-06-11 08:31:55 檢舉

因為台灣的教學,都只有一部分,只有串的那部份,沒有教人家怎麼寫畫面,當然....因為我太菜了....

我要留言

立即登入留言