系统接入LINE Pay的支付方式
LINE Pay是一个日本公司的。LINE Pay是LY Corporation的移动支付平台,用户可于合作商家以QR码或NFC等非接触方式结账付款,并可在LINE好友间免手续费转账。LINE Pay现于泰国和台湾之间可跨境使用。
类似微信支付和支付宝支付,LINE Pay支持作为第三方支付方式接入网站。
我接手的一个多商户网站的客户是台湾的,有接入LINE Pay的需求,于是有了这篇文章。
参考文档
异常代码:pay.line.me/tw/developers/apis/onlineApis?locale=zh_TW
API及其请求和响应参数:pay.line.me/tw/developers/apis/onlineApis?locale=zh_TW
获取Channel信息的管理平台:LINE Pay 行動支付
LINE Pay LOGO规范:pay.line.me/tw/developers/logo/logoGuide?locale=zh_TW
一些前置问题及分析
1. 哪些配置是固定的,哪些配置是因人而异的
因为我们这个系统是属于多商户,这意味着一个商家如果要接入LINE Pay,就得创建一个商户ID,我们在准备ChannelId和ChannelSecretKey的时候就不能只是放在配置文件了,而是要根据商户去获取,通常写在跟商户表里面。
2. 台币的单位
系统中使用的货币Java类型是BigDecimal,在发送请求的时候却报了异常:1124 金額有誤(scale)
,尝试将类型改成Double和Float却没有效果之后,换成Integer便成功了,先是搜索台币是不是没有角分,得到答案之后再跟客户做了确认,确实是这样。
3.电脑端怎么完成测试,总是提示跳转LINE App
电脑端调试的时候可以使用浏览器开发者工具里面的模拟器,这时候只需要填入邮箱和密码就不会提示跳转LINE了,实际投入使用的时候要根据终端进行判断,如果是手机端可以跳转app
字段里面的地址
LINE Pay 官方提供的PC版本串接流程
- 由商家网站跳转到LINE App的付款页面
- LINE Pay用户选择付款方式并输入密码,确认付款完成支付
- LINE Pay用户在LINE App确认付款信息
- LINE服务器在等待付款页面收到用户付款成功的状态的时候,会跳转到商家系统按照规范定义好的“confirm url”
- 商家服务器confirm url接收到订单号、交易号等信息,调用LINE Pay的Confirm API完成交易
个人理解可能有所不同,具体还是需要参考官方文档:https://pay.line.me/jp/developers/apis/onlineApis?locale=zh_TW
串接 LINE Pay 的前置工作
申请sandBox账号
- 在🖇这个地方申请沙盒账号
- 提交之后会收到邮件,里面会有sandBox账号的信息
获取Channel ID & Secret Key
申请完SandBox账号之后登录管理平台,点出“管理連結金鑰”选项,点击查询按钮,在邮箱接收验证码之后填写进去,验证成功便会出现Channel ID & Secret Key
密钥信息会在串接LINE Pay的API时候,作为header一并携带过去,在前置分析中已经分析了一般单商户,只有一个收款方,可以直接将密钥信息连同LINE Pay的域名、回调地址一并放在application.yml
文件中
# LINE Pay
line-pay:
pattern: "prod" # "local" | "prod" 看是否配置代理
url: "https://sandbox-api-pay.line.me"
channelId: "xxx"
channelSecretKey: "xxxxxxxxxxxxxxxxxxxx"
confirmUrl: ${spring.domain}/api/order/pay/lineConfirm
cancelUrl: ${spring.domain}/api/order/linePay/cancel
因为我需要串接的这个系统是属于多商户的,所以channelId和channelSecretKey我是不放在这里而通过业务中根据商户去数据库中查询的
白名单设置
LINE Pay 为了提高安全性,要把商店付款的服务器以白名单的方式进行管理,组织来路不明的请求,这里由于我们开发阶段所以填入我们的ip跟本机ip就行(不过好像测试环境不设置也可以)
串接流程
用户认证
LINE Pay的API调用的时候有一定的认证要求,GET和POST方式的请求也不同,主要是这几个:
Key | Data type | Required | Description |
---|---|---|---|
Content-Type | String | Y | application/json |
X-LINE-ChannelId | String | Y | 金流整合資訊 - Channel ID |
X-LINE-MerchantDeviceProfileId | String | N | 實體商家支援 - Device Type |
X-LINE-Authorization-Nonce | String | Y | UUID or timestamp(時間戳) |
X-LINE-Authorization | String | Y | HMAC Base64 簽章 |
具体的要求可以从LINE 官方API文档查看,上面也有一个Java的例子。我在GitHub上面找到了一个写得很规范的Demo:lixchengyu/line-pay: Integrate LINE Pay A/paymentsPI (github.com),省去我们写映射类的麻烦。作者还给本地配置了证书使得可以正常地通过https
请求本机
由于官方没有提供相关的SDK,这份规范的Demo可以帮助我们高效地串接LINE Pay,不用关心Header怎么封装,当然也可以自己参照官方的文档慢慢做:Signature = Base64(HMAC-SHA256(Your ChannelSecret, (Your ChannelSecret + URI + RequestBody + nonce)))
作者的README文档大致可以分成三部分:证书的生成与配置、设置channel id
和 channel secret key
并启动项目、请求四个URL
四个URL的请求是刚好的顺序,请求支付(->回调)->确认支付->退款->获取订单信息
测试完之后我们基本熟悉了整一个流程,接下来一步是移植到我们的项目当中,主要步骤如下:
1. 请求一笔新订单
我们的前端在选择LINE Pay的方式进行确认付款之后,前端会携带着购买的相关参数请求后端,后端收到请求之后会先在系统上创建新订单,再跳转到LINE Pay的请求付款的API(这一步就可以直接使用上面lixchengyu写的Demo的一些方法)
LINE Pay要求的请求体如下:
Item | Data type | Length | Requirement | Description |
---|---|---|---|---|
amount | Number | Y | 付款金額 = sum(packages[].amount) + sum(packages[].userFee) + options.shipping.feeAmount | |
currency | String | 3 | Y | 貨幣(ISO 4217) 支援貨幣:USD、JPY、TWD、THB |
orderId | String | 100 | Y | 商家訂單編號 商家管理的唯一ID |
packages[].id | String | 50 | Y | Package list的唯一ID |
packages[].amount | Number | Y | 一個Package中的商品總價 =sum(products[].quantity * products[].price) | |
packages[].userFee | Number | N | 手續費:在付款金額中含手續費時設定 | |
packages[].name | String | 100 | N | Package名稱 (or Shop Name) |
packages[].products[].id | String | 50 | N | 商家商品ID |
packages[].products[].name | String | 4000 | Y | 商品名 |
packages[].products[].imageUrl | String | 500 | N | 商品圖示的URL |
packages[].products[].quantity | Number | Y | 商品數量 | |
packages[].products[].price | Number | Y | 各商品付款金額 | |
packages[].products[].originalPrice | Number | N | 各商品原金額 | |
redirectUrls.appPackageName | String | 4000 | N | 在Android環境切換應用時所需的資訊,用於防止網路釣魚攻擊(phishing) |
redirectUrls.confirmUrl | String | 500 | Y | 使用者授權付款後,跳轉到該商家URL |
redirectUrls.confirmUrlType | String | N | 使用者授權付款後,跳轉的confirmUrl類型 | |
redirectUrls.cancelUrl | String | 500 | Y | 使用者通過LINE付款頁,取消付款後跳轉到該URL |
options.payment.capture | Boolean | N | 是否自動請款 true(預設):呼叫Confirm API,統一進行授權/請款處理 false:呼叫Confirm API只能完成授權,需要呼叫Capture API完成請款 | |
options.payment.payType | String | N | 付款類型 NORMAL PREAPPROVED | |
options.display.locale | String | N | 等待付款頁的語言程式碼,預設為英文(en) 支援語言:en、ja、ko、th、zh_TW、zh_CN | |
options.display.checkConfirmUrlBrowser | Boolean | N | 檢查將用於訪問confirmUrl的瀏覽器 true:如果跟請求付款的瀏覽器不同,引導使用LINE Pay請求付款的瀏覽器 false:無需檢查瀏覽器,直接訪問confirmUrl | |
options.shipping.type | String | N | 收貨地選項 NO_SHIPPING FIXED_ADDRESS SHIPPING | |
options.shipping.feeAmount | String | N | 運費 | |
options.shipping.feeInquiryUrl | String | 500 | N | 查詢配送方式的URL |
options.shipping.feeInquiryType | String | N | 運費查詢類型 CONDITION:收貨地發生變化,就查詢配送方式(運費) FIXED:作為固定值,收貨地發生變化,也不會查詢配送方式 | |
options.shipping.address.country | String | 2 | N | 收貨國家 |
options.shipping.address.postalCode | String | 10 | N | 收貨地郵政編碼 |
options.shipping.address.state | String | 100 | N | 收貨地區 |
options.shipping.address.city | String | 100 | N | 收貨省市區 |
options.shipping.address.detail | String | 1000 | N | 收貨地址 |
options.shipping.address.optional | String | 1000 | N | 詳細地址資訊 |
options.shipping.address.recipient.firstName | String | 200 | N | 收貨人名 |
options.shipping.address.recipient.lastName | String | 200 | N | 收貨人姓 |
options.shipping.address.recipient.firstNameOptional | String | 200 | N | 詳細名資訊 |
options.shipping.address.recipient.lastNameOptional | String | 200 | N | 詳細姓資訊 |
options.shipping.address.recipient.email | String | 100 | N | 收貨人電子郵件 |
options.shipping.address.recipient.phoneNo | String | 100 | N | 收貨人電話號碼 |
options.familyService.addFriends[].type | String | N | 新增好友的服務類型 lineAt | |
options.familyService.addFriends[].idList[] | List | N | 各服務類型的ID list | |
options.extra.branchName | String | 200 | N | 商店或分店名稱(僅會顯示前 100 字元) |
options.extra.branchId | String | 32 | N | 商店或分店代號,可支援英數字及特殊字元 |
options.extra.promotionRestriction | object | N | 點數限制資訊 useLimit: 不可使用點數折抵的金額rewardLimit: 不可回饋點數的金額"promotionRestriction": { "useLimit": 100, "rewardLimit": 100} |
测试请求的json可以参照Demo,其中packages列表表示多个店铺,每个店铺(package)可以有多个商品,在请求的时候要避免传入里面没有的,否则会报异常。
成功请求之后,根据订单的状态变为成功或者取消,LINE Pay服务器会跳转到回调地址。
成功请求之后,会返回返回码0000的响应,其中paymentUrl.web
或者paymentUrl.app
就是我们要跳转的网址
2. 跳转到付款页面模拟付款
前端拿到要跳转的页面之后,可以根据终端的不同选择跳转paymentUrl.web
还是paymentUrl.app
,区别就是会不会直接跳到LINE App,电脑版看到的界面是这样的
使用一个LINE账号登录完之后就可以看到这个页面,可以放心点击支付,这只是测试使用,不会真实扣款
3. 跳转到回调地址,完成支付的确认
成功支付完成之后,LINE Pay就会跳转到前面请求的时候携带的设置好的回调地址,也就是跳转到后端服务器的一个接口,LINE Pay那边会同市携带transactionId和orderId。这个接口的具体定义的要求可以参照官方的文档,接收这两个参数,并在拿到这两个参数之后完成相关的订单确认的绑定。
然后我们需要通过查询和计算订单金额,与订单号、交易货币一起请求确认支付的API。(测试的时候发现虽然点击了确认付款但后端没有请求确认支付的API这种情况,就会出现虽然请求的时候已经生成了交易号,但拿着这个交易号去获取交易信息拿到没有相关的交易号的响应)
4. 跳转到前端付款成功的页面
完成上一步的确认之后,就可以跳转到实现定义好的通用的支付成功的页面了
5. 订单申请退款
要能成功调用退款的API,首先得确保这个交易已经完成,也就是通过/v3/payments
API能够正常返回交易信息的
请求退款的时候,除了最基础的header之外,还需要准备的参数就是交易号以及退款的金额,其他的就根据退款调用的返回结果和业务差异有所不同了
以上几个步骤完成之后业务也就基本完成了,等审核完成之后,把沙盒地址换成真实的地址之后,就可以正常使用LINE Pay
了
由于涉及到保密的要求,没办法把代码开源出来,可以参考lixchengyu/line-pay进行调试,很容易可以理解整个逻辑
参与讨论
(Participate in the discussion)
参与讨论