系统接入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版本串接流程

image-20240422143444016

  1. 由商家网站跳转到LINE App的付款页面
  2. LINE Pay用户选择付款方式并输入密码,确认付款完成支付
  3. LINE Pay用户在LINE App确认付款信息
  4. LINE服务器在等待付款页面收到用户付款成功的状态的时候,会跳转到商家系统按照规范定义好的“confirm url”
  5. 商家服务器confirm url接收到订单号、交易号等信息,调用LINE Pay的Confirm API完成交易

个人理解可能有所不同,具体还是需要参考官方文档:https://pay.line.me/jp/developers/apis/onlineApis?locale=zh_TW

串接 LINE Pay 的前置工作

申请sandBox账号

  1. 🖇这个地方申请沙盒账号

  1. 提交之后会收到邮件,里面会有sandBox账号的信息

image-20240422145510360

获取Channel ID & Secret Key

申请完SandBox账号之后登录管理平台,点出“管理連結金鑰”选项,点击查询按钮,在邮箱接收验证码之后填写进去,验证成功便会出现Channel ID & Secret Key

image-20240422153701664

密钥信息会在串接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就行(不过好像测试环境不设置也可以)

image-20240422163446309

串接流程

用户认证

LINE Pay的API调用的时候有一定的认证要求,GET和POST方式的请求也不同,主要是这几个:

KeyData typeRequiredDescription
Content-TypeStringYapplication/json
X-LINE-ChannelIdStringY金流整合資訊 - Channel ID
X-LINE-MerchantDeviceProfileIdStringN實體商家支援 - Device Type
X-LINE-Authorization-NonceStringYUUID or timestamp(時間戳)
X-LINE-AuthorizationStringYHMAC 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 idchannel secret key 并启动项目、请求四个URL

四个URL的请求是刚好的顺序,请求支付(->回调)->确认支付->退款->获取订单信息

测试完之后我们基本熟悉了整一个流程,接下来一步是移植到我们的项目当中,主要步骤如下:

1. 请求一笔新订单

image-20240423171441728

我们的前端在选择LINE Pay的方式进行确认付款之后,前端会携带着购买的相关参数请求后端,后端收到请求之后会先在系统上创建新订单,再跳转到LINE Pay的请求付款的API(这一步就可以直接使用上面lixchengyu写的Demo的一些方法)

LINE Pay要求的请求体如下:

ItemData typeLengthRequirementDescription
amountNumberY付款金額 = sum(packages[].amount) + sum(packages[].userFee) + options.shipping.feeAmount
currencyString3Y貨幣(ISO 4217) 支援貨幣:USD、JPY、TWD、THB
orderIdString100Y商家訂單編號 商家管理的唯一ID
packages[].idString50YPackage list的唯一ID
packages[].amountNumberY一個Package中的商品總價 =sum(products[].quantity * products[].price)
packages[].userFeeNumberN手續費:在付款金額中含手續費時設定
packages[].nameString100NPackage名稱 (or Shop Name)
packages[].products[].idString50N商家商品ID
packages[].products[].nameString4000Y商品名
packages[].products[].imageUrlString500N商品圖示的URL
packages[].products[].quantityNumberY商品數量
packages[].products[].priceNumberY各商品付款金額
packages[].products[].originalPriceNumberN各商品原金額
redirectUrls.appPackageNameString4000N在Android環境切換應用時所需的資訊,用於防止網路釣魚攻擊(phishing)
redirectUrls.confirmUrlString500Y使用者授權付款後,跳轉到該商家URL
redirectUrls.confirmUrlTypeStringN使用者授權付款後,跳轉的confirmUrl類型
redirectUrls.cancelUrlString500Y使用者通過LINE付款頁,取消付款後跳轉到該URL
options.payment.captureBooleanN是否自動請款 true(預設):呼叫Confirm API,統一進行授權/請款處理 false:呼叫Confirm API只能完成授權,需要呼叫Capture API完成請款
options.payment.payTypeStringN付款類型 NORMAL PREAPPROVED
options.display.localeStringN等待付款頁的語言程式碼,預設為英文(en) 支援語言:en、ja、ko、th、zh_TW、zh_CN
options.display.checkConfirmUrlBrowserBooleanN檢查將用於訪問confirmUrl的瀏覽器 true:如果跟請求付款的瀏覽器不同,引導使用LINE Pay請求付款的瀏覽器 false:無需檢查瀏覽器,直接訪問confirmUrl
options.shipping.typeStringN收貨地選項 NO_SHIPPING FIXED_ADDRESS SHIPPING
options.shipping.feeAmountStringN運費
options.shipping.feeInquiryUrlString500N查詢配送方式的URL
options.shipping.feeInquiryTypeStringN運費查詢類型 CONDITION:收貨地發生變化,就查詢配送方式(運費) FIXED:作為固定值,收貨地發生變化,也不會查詢配送方式
options.shipping.address.countryString2N收貨國家
options.shipping.address.postalCodeString10N收貨地郵政編碼
options.shipping.address.stateString100N收貨地區
options.shipping.address.cityString100N收貨省市區
options.shipping.address.detailString1000N收貨地址
options.shipping.address.optionalString1000N詳細地址資訊
options.shipping.address.recipient.firstNameString200N收貨人名
options.shipping.address.recipient.lastNameString200N收貨人姓
options.shipping.address.recipient.firstNameOptionalString200N詳細名資訊
options.shipping.address.recipient.lastNameOptionalString200N詳細姓資訊
options.shipping.address.recipient.emailString100N收貨人電子郵件
options.shipping.address.recipient.phoneNoString100N收貨人電話號碼
options.familyService.addFriends[].typeStringN新增好友的服務類型 lineAt
options.familyService.addFriends[].idList[]ListN各服務類型的ID list
options.extra.branchNameString200N商店或分店名稱(僅會顯示前 100 字元)
options.extra.branchIdString32N商店或分店代號,可支援英數字及特殊字元
options.extra.promotionRestrictionobjectN點數限制資訊 useLimit: 不可使用點數折抵的金額rewardLimit: 不可回饋點數的金額"promotionRestriction": { "useLimit": 100, "rewardLimit": 100}

测试请求的json可以参照Demo,其中packages列表表示多个店铺,每个店铺(package)可以有多个商品,在请求的时候要避免传入里面没有的,否则会报异常。

成功请求之后,根据订单的状态变为成功或者取消,LINE Pay服务器会跳转到回调地址。

image-20240423173020433

成功请求之后,会返回返回码0000的响应,其中paymentUrl.web或者paymentUrl.app就是我们要跳转的网址

2. 跳转到付款页面模拟付款

前端拿到要跳转的页面之后,可以根据终端的不同选择跳转paymentUrl.web还是paymentUrl.app,区别就是会不会直接跳到LINE App,电脑版看到的界面是这样的

image-20240424090840472

使用一个LINE账号登录完之后就可以看到这个页面,可以放心点击支付,这只是测试使用,不会真实扣款

image-20240424091902609

3. 跳转到回调地址,完成支付的确认

成功支付完成之后,LINE Pay就会跳转到前面请求的时候携带的设置好的回调地址,也就是跳转到后端服务器的一个接口,LINE Pay那边会同市携带transactionId和orderId。这个接口的具体定义的要求可以参照官方的文档,接收这两个参数,并在拿到这两个参数之后完成相关的订单确认的绑定。

image-20240424095018678

然后我们需要通过查询和计算订单金额,与订单号、交易货币一起请求确认支付的API。(测试的时候发现虽然点击了确认付款但后端没有请求确认支付的API这种情况,就会出现虽然请求的时候已经生成了交易号,但拿着这个交易号去获取交易信息拿到没有相关的交易号的响应)

4. 跳转到前端付款成功的页面

完成上一步的确认之后,就可以跳转到实现定义好的通用的支付成功的页面了

5. 订单申请退款

要能成功调用退款的API,首先得确保这个交易已经完成,也就是通过/v3/paymentsAPI能够正常返回交易信息的

请求退款的时候,除了最基础的header之外,还需要准备的参数就是交易号以及退款的金额,其他的就根据退款调用的返回结果和业务差异有所不同了

以上几个步骤完成之后业务也就基本完成了,等审核完成之后,把沙盒地址换成真实的地址之后,就可以正常使用LINE Pay

由于涉及到保密的要求,没办法把代码开源出来,可以参考lixchengyu/line-pay进行调试,很容易可以理解整个逻辑