株式会社OKANで「おかんPay」の開発(フロント/バックエンド)に携わっているsuzu_Dと申します。
このアプリを簡単に説明すると、「オフィスおかん」という置型の社食サービスで、小銭を使わずキャッシュレス決済ができるアプリとなっております。
今まで当アプリではクレジットカードを登録し支払う、という方法でのみの決済でしたが、
2019年12月の目玉アップデートとして、当アプリ経由でLINE Pay決済ができる機能をリリースしました。
今回は、技術的なお話というよりLINE Payを組み込む際に、今現在ネットの情報だけだと自分的には不足している部分があり
「そもそもLINE Pay APIに通信できない・・・」といった事が多発し、LINE Payの技術スタッフの方と何回かやり取りさせていただいたので、
その中で改めて知った、LINE Payの知見を、自分なりに纏めてアウトプットし、
もし皆さんが関わっているプロダクトで「LINE Pay決済」を導入する機会がありましたら
少しでも参考になればなと思い、本記事を寄稿しました。
本文献の購読対象者と、目的・出来るようになること
購読対象者
・開発中のネイティブアプリやWEBアプリでLINE Pay決済を組み込もうとしている方
・実際にいまLINE Payを組込中で「そもそもLINE Pay APIと通信がうまくいかない!」と嘆いている方
目的・出来るようになること
・自プロダクトでLINE Payで決済ができる仕組みが、最低限なんとなく理解できて、LINE Pay APIとREST通信出来るようになる。
(この記事の下部にPostmanで通信する際の例を記載しています。)
LINE Pay APIのバージョンと参考ガイドについて
当記事で対象としているLINE Pay APIのバージョン
当記事で対象としているLINE Pay APIのバージョンは
v3(2019/12 時点)
の情報になります、v3以前のバージョンや、これ以降APIのバージョンが上がった際
参考にならない記述が出る可能性があることを、ご了承願います。
当記事で参考にしているLINE Pay API ガイド
LINE Pay APIの環境について
API通信に使う環境として、主に使うのは以下2つになると思います。
- 本番環境
- テスト加盟店環境
API通信に使う環境について
本番環境
加盟店申込後に審査が完了してから利用可能になる環境です。
実際のお金のやりとりをする環境になります。
加盟店申込後にLINE Pay側から連絡があり、申請したアカウント(メールアドレス)にて
LINE PayのMy Pageからログインを行うと、取引の履歴を見たり、決済のキャンセル・返金を行うことが出来ます。
また、本番のAPI通信に必要なキーの情報を見ることが出来ます。
テスト加盟店環境
Sandbox生成後利用可能になる環境です。
このページにて登録したメールアドレス宛に、テスト加盟店環境にログインできるアカウントが通知されます。
通知されたアカウントでLINE PayのMy Pageからログインを行うと、テスト加盟店環境で行った取引の履歴を見たり、決済のキャンセル・返金を行うことが出来ます。また、テスト加盟店環境にてAPI通信をするために必要なキーの情報を見ることが出来ます。
こちらも、本番環境同様、決済処理の通信を行った場合、実際のお金のやり取りが発生する(LINE Pay残高が減る)のですが、毎日23:55と2:55に支払いを行った決済に対して自動的に決済のキャンセル・返金処理が行われます。
アプリに決済機能を実装し、LINE Pay決済をテスト的につかう場合はこちらの環境を使うことをおすすめします。
各環境にAPI通信するために必要なエンドポイントとキーについて
API通信する際のエンドポイント
エンドポイント(本番、テスト加盟店環境 共通)
https://api-pay.line.me
例 - Request APIをコールする場合のエンドポイント
https://api-pay.line.me/v3/payments/request
各環境にAPI通信するためのキー、Channel IDとChannel SecretKeyについて
LINE Pay API通信する際、必要な認証情報としてChannel IDとChannel SecretKeyを使用することになります。
このキーが本番環境、もしくはテスト加盟店環境の違いになるので、本番環境用とテスト用で接続先の設定ファイルを作る際は間違えないようにしてください(テスト用の設定ファイルに本番用のChannel IDとChannel SecretKeyを登録してしまった等の間違いをしないようにしてください。)
確認方法は以下になります。
①本番環境、もしくはテスト加盟店環境のアカウントにてLINE PayのMy Pageからログインを行う
↓
②ログイン後画面にて「決済連動管理」>「連動キー管理」と進み、ログイン用のパスワードを入力する
↓
③Channel ID とChannel Secret Keyを確認することが出来る(このChannel ID とChannel Secret Keyは、絶対外部に漏らさないようにしてください。)
このChannel IDとChannel SecretKeyによって、どの加盟店での決済かを判別しているようです、APIのリクエストを投げる際、共通ヘッダーにキーを設定することによって、通信が可能となります、このキーの設定が間違っている場合にhttps://api-pay.line.me
に通信を行うと以下のようなレスポンスが返ってきます、
{
"returnCode": "1106",
"returnMessage": "Header information error. request verification Failed"
}
このキーを共通ヘッダーに設定する際、少々特別な処理が必要になります、詳しい説明は後述する共通ヘッダーの設定方法を参照してください。
※SandBoxについて
本番環境、テスト加盟店環境 の双方どちらも「SandBox」で通信できる環境があります。
こちらの環境は以下のエンドポイントで通信が行えます
https://sandbox-api-pay.line.me
テスト的に使うのであれば、この環境を使いたいところですが、
SandBox環境だと決済の流れが、実際アプリではなくエミュレータのようなもので動き、
本物の決済の流れが体感的に分かりにくいのと、お試しでREST通信をしたい場合(Postman等を使用して疎通確認したい場合)
何らかの原因でエラーがでてしまい、通信が成功しないなどあるので、テスト的に使う場合はhttps://sandbox-api-pay.line.me
のエンドポイントは使わず、少額の決済であれば、実際のお金を幾らかLINE Payに入金しておき、テスト加盟店環境
のhttps://api-pay.line.me
をコールするようにした方がよいと考えます。
(PostmanがSandbox環境にて使えない原因をLINE Payの技術スタッフの方に問い合わせしたところ、通信時に特殊なパラメータが付与されている為のことだそうです。)
環境まとめ
本番環境 | テスト加盟店環境 | Sandbox(本番・テスト加盟店環境) | |
---|---|---|---|
利用制限 | 加盟店に申込後、審査完了後利用可能 | なし、無料利用可能 | なし、無料利用可能(テスト加盟店環境の場合) |
利用料 | 加盟店契約により変動 | 無料 | 無料 (テスト加盟店環境の場合) |
利用方法 | 加盟店申込をする | Sandbox環境を作成する(ややこしいが、この環境がテスト加盟店環境という) | |
接続先 | https://api-pay.line.me | https://api-pay.line.me | https://sandbox-api-pay.line.me |
決済処理時の動き | LINEアカウントの残高を利用 | LINEアカウントの残高を利用 | Sandboxの架空残高を利用(この利用に関しては実際に試していないのでどんな感じで決済されるかについてはよく分かっていない) |
返金方法 | MyPageから手動 | MyPageから手動 or 毎日23:55と2:55に自動的に返金 | 架空残高を使うので特に無し |
LINE Pay APIにPOST通信する為の共通ヘッダーについて
共通ヘッダーについて
共通ヘッダーに設定する値
リクエストを送る時の、最低限の必要な共通ヘッダーが下記になります。
キー | データの型 | 設定する値 |
---|---|---|
Content-Type | String | 'application/json' |
X-LINE-ChannelId | String | LINE PayのMy Pageで確認したChannel ID |
X-LINE-Authorization-Nonce | String | 1回限りのランダムな値を設定する、UUIDを生成したり、現在日時のミリ秒等を生成して設定する。 |
X-LINE-Authorization | String | ※後述 |
X-LINE-Authorization-Nonceに設定する値としては、一度切りに使う値を生成し使ってください、(例えばJavaScriptでヘッダーを実装する場合var nonce = (new Date()).getTime();
と現在のミリ秒を生成しnonceの値に設定する、といった実装です。)
X-LINE-Authorizationに関しては少々ややこしいので、次項に詳細を書きます。
X-LINE-Authorization に設定する値について
X-LINE-Authorizationに設定する値はガイドによるとこう記述されています。
HTTP Method : GET
Signature = Base64(HMAC-SHA256(Your ChannelSecret, (Your ChannelSecret + URL Path + Query String + nonce))) Query String : ?を除いたクエリ文字列(例 : Name1=Value1&Name2=Value2...)
HTTP Method : POST
Signature = Base64(HMAC-SHA256(Your ChannelSecret, (Your ChannelSecret + URL Path + RequestBody + nonce)))
今回は「POST」の時のX-LINE-Authorizationについて説明します。
POST時にX-LINE-Authorizationを設定するために、以下の4つの値が必要です。
①Your ChannelSecret - LINE PayのMy Pageで確認したChannel SecretKey
②URL Path - POSTするURLのパス(例えば https://api-pay.line.me/v3/payments/request
等)
③RequestBody - POSTする時のRequest Body
④nonce - X-LINE-Authorization-Nonceに設定した値
そして上記の値を文字列連結し、ハッシュ メッセージ認証コード (HMAC) で計算します。
(HMACとは?)
この計算に使う、秘密鍵とメッセージ(データ)とハッシュ関数は以下になります
秘密鍵: LINE PayのMy Pageで確認したChannel SecretKey
メッセージ(データ):上記①〜④を文字列連結した値
ハッシュ関数:SHA256
そして、ハッシュ化した値をBase64 でエンコードし、
エンコードした値をX-LINE-Authorizationに設定します。
この処理をJAVAで書いた例はガイドに記載されてあります。
JavaScriptで書く場合は以下のようになるかと思います。
var crypto = require('crypto-js');// crypto-jsという暗号化、復号化のライブラリを使う
var channelSecret = '上記①の値';
var path = '上記②の値';
var body = '上記③の値';
var nonce = (new Date()).getTime(); // nonceに使うランダムな値の生成をする
var message = channelSecret + path + body + nonce ;
var hash = crypto.HmacSHA256(message,channelSecret); // channelSecretの値をキーにSHA256ハッシュ関数を使用してハッシュ (HMAC) を計算
var hashHeader = CryptoJS.enc.Base64.stringify(hash); // ここの値がX-LINE-Authorizationに設定する値になる
また、今回自分が実際実装した言語はruby
を使ったので
ruby
で実装する場合は下記のように①〜④の値を受けとって処理するようなメソッドを実装すればよいと思います。
def authorization(channel_secret, params, url, nonce)
message = "#{channel_secret}#{url}#{params.to_json}#{nonce}"
hash = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), channel_secret, message)
Base64.strict_encode64(hash)
end
ヘッダーの実装例(ruby)
rubyでヘッダー部分を実装するとしたら、以下のようになります。
def headers(params, url)
# ヘッダーを作る処理をする時にPOSTするURLのパスとPOSTする時のRequest Bodyを渡す
nonce = authorization_nonce
{
'Content-Type' => 'application/json',
'X-LINE-ChannelId' => channel_id,
'X-LINE-Authorization-Nonce' => nonce,
'X-LINE-Authorization' => authorization(params, url, nonce),
}
end
def channel_id
Rails.application.credentials.dig(# ここはcredentials.yml.encに設定した値を取り出す).to_s
end
def channel_secret
Rails.application.credentials.dig(# ここはcredentials.yml.encに設定した値を取り出す).to_s
end
def authorization_nonce
Time.zone.now.to_s(:db_jst)
end
def authorization(params, url, nonce)
message = "#{channel_secret}#{url}#{params.to_json}#{nonce}"
hash = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), channel_secret, message)
Base64.strict_encode64(hash)
end
決済するためのAPIについて
「決済」を行うためのAPI
商品の決済をする為に必要最低限のAPIは以下の2つのみです。
・Request API (POST /v3/payments/request)
https://pay.line.me/documents/online_v3_ja.html#request-api
・Confirm API (POST /v3/payments/{transactionId}/confirm)
https://pay.line.me/documents/online_v3_ja.html#confirm-api
その他決済取り消し等のAPIありますが、本記事では取り上げません。
また、上記のAPIを毎回コールし決済するパターンを「一般決済」と言い、
上記APIを初回のみコールし、以降上記APIをコールしなくても決済が出来るパターンを「自動決済」と言うのですが
今回は一般決済のみの事を記述します。
決済の処理をアプリに組み込む際のざっくりな流れ(一例)
①Request API (POST /v3/payments/request)をコールする
↓
②成功時のレスポンスにLINE Pay取引番号(transactionId)と
決済用のURL(info.paymentUrl.web 又は info.paymentUrl.web.app) が返ってくる
↓
③アプリまたはWEB上で ②のレスポンス内にある決済用のURL(info.paymentUrl.web 又は info.paymentUrl.app)
に遷移する処理をする。
↓
④(LINEが端末にインストールされている状態でinfo.paymentUrl.appのURLに遷移した場合)
LINEアプリが立ち上がり LINE Payの支払画面に遷移するので、LINEアプリ内で決済の操作を行う
↓
⑤決済が完了すると ①のRequest APIをコール時に設定した画面に遷移する(設定の仕方は後述)
↓
⑥ ⑤にて遷移した瞬間にConfirm API (POST /v3/payments/{transactionId}/confirm)をコールする処理をする、
{transactionId} には②のレスポンスに含まれていたLINE Pay取引番号(transactionId)を設定する。
設定例: /v3/payments/2019010100000000000/confirm
↓
⑦決済が完了する。
Request APIについて
Request API のざっくりとした説明
「こんな商品を買うから、購入するために整理券ちょうだい」的なAPIです。
「商品の値段は○○で、個数が○○で〜」といった情報をRequest Bodyに詰め込んで送ると
「整理券あげるよ、整理番号(LINE Pay取引番号)はこれだよ、この場所(決済用のURL)で買ってね」
というようなレスポンスが返ってきます。
返ってきた、整理番号(LINE Pay取引番号)は次にコールするConfirm APIで使う事になるので
どこかに保持しておく必要があります。
Request API のRequest Bodyの例とレスポンス
実際通信する時のRequest Bodyの例は以下のようになっています。
必要最低限のことだけ記述してあるので、「商品の送料、配送先」等、詳細にせってしたい場合は公式ドキュメントを参照してください。
Request Bodyの例-コピペ用
{
"amount": 300,
"currency": "JPY",
"orderId": "20190101",
"packages": [
{
"id": "1",
"amount": 300,
"name": "LINE Pay商品",
"products": [
{
"name": "豚の角煮(赤みそ)",
"imageUrl": "https://exmple.jpg",
"quantity": 1,
"price": 100
},
{
"name": "れんこんサラダ",
"imageUrl": "https://exmple.jpg",
"quantity": 2,
"price": 100
}
]
}
],
"redirectUrls": {
"confirmUrl": "myapp://confirm",
"cancelUrl": "myapp://cancel"
}
}
Request Bodyの例-各パラメータの簡単な説明
{
"amount": 300, // 合計金額を設定、この金額はpackages[].amountの合計金額と一致していないとエラーになる。
"currency": "JPY", // 決済通貨の種類 日本であれば"JPY"で問題ないかと
"orderId": "20190101", // 実装者側で発行するユニークなID、特に決まっていなければ、ランダムなUUIDを生成したり、現在日付のTIME_STAMP等設定すれば良い
"packages": [
{
"id": "1", // 特に決まっていなくても設定する必要あり
"amount": 300, // products[].priceの合計金額、一致してないとエラーになる
"name": "LINE Pay商品", // 購入する商品の全体的な名前、特に決まっていなければ、商品と同じ名前でよいと思う。
"products": [
{
"name": "豚の角煮(赤みそ)", // 商品の名前 ここの値がLINE Payの決済画面で表示される。
"imageUrl": "https://linepay.exmple.image.jpg", // 商品の画像のサムネイル LINE Payの決済画面で表示される。
"quantity": 1, // 商品の個数
"price": 100 // 商品の値段
},
{
"name": "れんこんサラダ",
"imageUrl": "https://linepay.exmple.image.jpg",
"quantity": 2,
"price": 100
}
]
}
],
"redirectUrls": {
"confirmUrl": "myapp://confirm", // LINE Payで商品決済したときに遷移するURL、遷移させたいのがWEBページならhttp://~ ネイティブアプリならアプリのDeepLinkを設定する
"cancelUrl": "myapp://cancel" // LINE Payで商品決済をキャンセルしたときに遷移するURL
}
}
※"imageUrl" に設定している値は一例なので、実際にサムネイルとして何か画像を表示してみたい場合は、実在するURLを入れてください、若しくは設定しないでください。
上記のリクエストを飛ばし、成功すると以下のようなレスポンスが返ってきます(~の部分は可変します)
{
"returnCode": "0000",
"returnMessage": "Success.",
"info": {
"paymentUrl": {
"web": "https://web-pay.line.me/web/payment/wait?~~~~~",
"app": "line://pay/payment/~~~~~"
},
"transactionId": ~~~~~~~~,
"paymentAccessToken": "~~~~~~~~"
}
}
端末にLINEがインストールされており、LINE Payが使える設定にしている場合に、上記のレスポンスの「info.paymentUrl.app」に遷移すると以下のような画面に遷移します。
※上記の画像にサムネイルが表示されていますがRequest BodyのimageUrlで設定した「linepay.exmple.image.jpg」とは異なる画像を使っています。「linepay.exmple.image.jpg」というURLはあくまで一例であって実際には存在しませんのでご了承ください。
※上記の画像で決済やキャンセルを行うとRequest Bodyで設定したconfirmUrlやcancelUrlに遷移します。
Confirm APIのRequest Bodyの例とレスポンス
Confirm API のざっくりとした説明
「さっき教えてもらった場所で決済したよ、承認してね。」的なAPIです。
LINE Payのアプリで決済処理をせずにコールするとエラーになるので注意が必要です。
Request Bodyの例-コピペ用
{
"amount": 300,
"currency": "JPY"
}
Request Bodyの例-各パラメータの簡単な説明
{
"amount": 300, //商品の合計金額 Request APIの時と違う金額だとエラーになるので注意
"currency": "JPY" // 決済通貨の種類
}
上記のリクエストを飛ばし、成功すると以下のようなレスポンスが返ってきます(~の部分は可変します)
{
"returnCode": "0000",
"returnMessage": "Success.",
"info": {
"transactionId": ~~~~~~~~,
"orderId": "20190101",
"payInfo": [
{
"method": "BALANCE",
"amount": 300
}
],
"packages": [
{
"id": "1",
"amount": 300,
"name": "LINE Pay商品",
"products": [
{
"name": "豚の角煮(赤みそ)",
"imageUrl": "https://exmple.jpg",
"quantity": 1,
"price": 100
},
{
"name": "れんこんサラダ",
"imageUrl": "https://exmple.jpg",
"quantity": 2,
"price": 100
}
]
}
],
}
}
上記のようなレスポンスが返ってきた場合、実際にLINE Payの残高が減り、LINE PayのMy Pageにて取引の履歴が残っていることを確認できるかと思います。
LINE PayのMy Page
[取引管理]>[取引内訳]
以上で、LINE Pay APIにて一般決済する時の処理の流れになります。
(おまけ)Postmanで疎通確認を行う
まだ実際にはアプリには組み込まないけど、LINE Pay APIを叩いたらどんなレスポンスが返ってくるか見たい場合は
「Postman」を使うのが便利です、ここではPostmanを使ってLINE PayのAPIをコールするときの設定を明記します。(Postmanの導入方法についてはここでは説明しませんので、各自で調べていただくよう、宜しくお願いします。)
各種設定
環境ファイルの設定(Channel ID とChannel SecretKeyの設定)
①赤枠部分の歯車のアイコンをクリックします。
↓
②赤枠部分の[Add]をクリックします。
↓
③以下のように設定し[Add]をクリックします。
VARIABLE | INITIAL VALUE | CURRENT VALUE |
---|---|---|
channelId | LINEのMyPageで確認したChannel ID | ←INITIAL VALUEで設定した値と同じ |
channelSecret | LINEのMyPageで確認したChannel SecretKey | ←INITIAL VALUEで設定した値と同じ |
↓
④赤枠部分のように環境ファイルを先程追加したものに設定しておきます。
Headersの設定
以下のように設定します。
KEY | VALUE |
---|---|
Content-Type | application/json |
X-LINE-ChannelId | {{channelId}} |
X-LINE-Authorization-Nonce | {{nonce}} |
X-LINE-Authorization | {{authorization}} |
Content-Typeは文字列で'application/json'と設定しておきます。
その他の設定はPostmanの環境ファイルから取得するようにします。
{{channelId}}
等、中括弧で囲むと、先程設定した設定ファイルのkey参照し、その値を参照するようになります。
endpointの設定
赤枠部分に以下のように設定してください。
Request API (POST /v3/payments/request)をコールする場合の実装例です
POST
https://api-pay.line.me/v3/payments/request
Bodyの設定
rawを選択し、以下のように設定します。
以下のBodyはRequest API (POST /v3/payments/request)をコールする場合の実装例です
{
"amount": 300,
"currency": "JPY",
"orderId": "20190101",
"packages": [
{
"id": "1",
"amount": 300,
"name": "LINE Pay商品",
"products": [
{
"name": "豚の角煮(赤みそ)",
"imageUrl": "",
"quantity": 1,
"price": 100
},
{
"name": "れんこんサラダ",
"imageUrl": "",
"quantity": 2,
"price": 100
}
]
}
],
"redirectUrls": {
"confirmUrl": "myapp://confirm",
"cancelUrl": "myapp://cancel"
}
}
Pre-request Scriptの設定
ヘッダーの「X-LINE-Authorization」に設定するための処理を記述します。
var crypto = require('crypto-js');
var time = (new Date()).getTime();
pm.environment.set("nonce", time);
var path = pm.request.url.getPath();
var body = pm.request.body.toString();
var message = pm.environment.get("channelSecret") + path + body + pm.environment.get("nonce") ;
var hash = crypto.HmacSHA256(message,pm.environment.get("channelSecret"));
var hashHeader = CryptoJS.enc.Base64.stringify(hash);
pm.environment.set("authorization",hashHeader);
pm.〜
で始まる記述はPostman独自の処理で、リクエストBodyを取得したり、環境ファイルの設定を読み込んだり書き込んだりするための記述です。
レスポンス
Sendした結果、以下のようなレスポンスが返ってきたら成功です。
以下の結果はRequest API (POST /v3/payments/request)をコールした際のレスポンスです。
returnCodeが'0000'以外の場合はLINE APIへの疎通は成功しているが、処理が成功していません
各APIのReturn Codesの欄を参考にどこがミスをしているか探してみてください。
またこのあと実際の決済確定処理(Confirm API)をしたい場合は以下のようにしてください。
①Request APIのレスポンスの"info.paymentUrl.app"に設定されている「line://~~~」で始まっているURLを、LINEアプリを入れていて、LINE Payの設定が終わっている端末でアクセスする(自分の場合、SlackがPCとAndroid実機に入っていたので、URLを直接自分宛てに送ってアクセスしていた。)
↓
②アプリ内で決済処理(任意のパスワードを入力して決済を確定する)が終わったら、「endpointの設定」と「Bodyの設定」を以下のようにする
endpointの設定
POST
https://api-pay.line.me/v3/payments/{Request APIのレスポンスの"info.transactionId"の値を設定する}/confirm
Bodyの設定
amountはrequest API送った時のamountと同じ値にしてください。
{
"amount": 100,
"currency": "JPY"
}
↓
sendした結果、returnCodeが'0000'で返って来て、実際にLINE Pay残高が減っていれば成功です。