
線上購物已成為目前消費行為的主流趨勢,而商家為了擁有更多元的付款方式以及方便對帳及退款的後台管理,通常就會仰賴第三方金流服務。
目前台灣主流的金流商為綠界科技、紅陽科技及藍新科技,三者提供的付款功能除了基本的信用卡收退款外也都各自有不同的服務,因此商家可以根據自己的商業需求來決定金流商。
本篇文章將會以藍新金流多功能付款 (MPG) 中的信用卡及 ATM 付款來做示範,在開始前需要做一些小準備:
- 一個 Rails App
- 藍新金流的測試帳號,須包含 Hash Key、Hash IV 及商店代號
- 藍新金流 API 文件(本篇以 MPG 服務為例)
如果都已經準備好了,那我們就進入正題吧 ─=≡Σ(((っ゚∀゚)っ
建立 Service
先在專案資料夾內建立 app/service/newebpay/mpg.rb
,我們會將金流的邏輯主要都寫在 mpg.rb
這個檔案內,如果不知道什麼是 Service Object 可以參考 Rails 程式碼整理術(進階)。
module Newebpay
class Mpg
def initialize
end
def test
"Test success!"
end
end
end
進 rails console
測試一下,看起來是沒問題的!
> Newebpay::Mpg.new.test
=> "Test success!"
開始串接 API
要開始串接 API 最重要的一件事,就是看看手冊內的需求是什麼。
🧙 傳送門:Newebpay MPG 串接手冊
資料交換
在與第三方 API 溝通時,要先確認我們的 request 需要哪些資料以及如何傳送,而藍新也提供了相當清楚的資訊:
- 以「Form Post」方式傳送交易資料至藍新金流進行交易
- Post 參數內需包含
MerchantID
、TradeInfo
、TradeSha
、Version
TradeInfo
則由複數參數組成TradeInfo
與TradeSha
這兩個參數需要進行加密的動作
參數設定
透過上圖,其實除了 TradeInfo
外欄位資訊都相當單純,因此我們可以先主攻 TradeInfo
來看看該帶入哪些資訊!
在文件 TradeInfo 內含參數欄位 這個分類中,最重要的是要先找到「必填」的資訊有哪些,以及選擇要使用哪種支付服務,最後再來將這些資訊整理起來:
# TradeInfo 欄位
{
# 這些是藍新在傳送參數時的必填欄位
MerchantID: "MS12345567",
MerchantOrderNo: "yuchan6666",
TimeStamp: Time.now.to_i.to_s,
RespondType: "JSON",
Amt: 123,
Version: "1.6",
ItemDesc: "第一次串接就成功!",
Email: "[email protected]",
LoginType: 0
# --------------------------
# 將要選擇的付款服務加上參數
CREDIT: 1,
VACC: 1,
# 即時付款完成後,以 form post 方式要導回的頁面
ReturnURL: "商品訂單頁面"
# 訂單完成後,以背景 post 回報訂單狀況
NotifyURL: "處理訂單的網址"
}
我自己是以 NotifyURL
去處理訂單的更新資訊,並且由 ReturnURL
處理訂單成功或失敗的轉址。其實除了上面使用到的參數外,像交易限制秒數及繳費有效期限等等也蠻常使用的,使用者可以看看自己有哪些需求再適時加上。
好的,既然 TradeInfo
已經知道要哪些資訊了,那麼就來排排看要送 post 的資料會是怎樣的結構。確定好內容後,就可以開始開始思考我們的 service 怎麼寫了!
{
MerchantID: "MS123456789",
TradeInfo: {
MerchantID: "MS123456789",
RespondType: "JSON",
TimeStamp: "1400137200",
Version: "1.6",
# etc...
},
TradeSha: # TradeInfo 經 AES 加密後再 SHA256 加密,
Version: "1.6"
}
開始動工 service
1. 參數處理
抓回我們一開始寫的 mpg.rb
,可以將重複使用的固定資訊在 initialize
時就準備好,並且將 form post 要使用的參數放入 service 中。
module Newebpay
class Mpg
attr_accessor :info
def initialize(params)
@key = "hash key"
@iv = "hash iv"
@merchant_id = "merchant id"
@info = {} # 使用 attr_accessor 讓 info 方便存取
set_info(params)
end
def form_info
{
MerchantID: @merchant_id
TradeInfo: trade_info,
TradeSha: trade_sha,
Version: "1.6"
}
end
private
def trade_info
# AES256 加密後的資訊
end
def trade_sha
# SHA256 加密後的資訊
end
def set_info(order)
info[:MerchantID] = @merchant_id
info[:MerchantOrderNo] = order.slug
info[:Amt] = order.amount
info[:ItemDesc] = order.name
info[:Email] = order.email
info[:TimeStamp] = Time.now.to_i
info[:RespondType] = "JSON"
info[:Version] = "1.6"
info[:ReturnURL] = "https://...."
info[:NotifyURL] = "https://...."
info[:LoginType] = 0
info[:CREDIT] = 1,
info[:VACC] = 1
end
end
end
2. 將 info
進行加密處理
這應該是最難的一關了,文件內其實沒寫得很清楚需要哪些步驟,很多關鍵還只藏在範例的程式碼內,對於沒接觸過 PHP 或 .net 的人來說就稍微頭痛了一點。因此整理了一份適合 Rails 的範本來使用。
1.將內容轉為 query string
def url_encoded_query_string
URI.encode_www_form(info)
end
# => "MerchantID=MS12345567&TimeStamp=1624388594&RespondType=JSON..."
2.trade_info 加密
利用 query 化的 info、hash key 與 hash iv 來進行 AES256 資料加密
def trade_info
aes_encode(url_encoded_query_string)
end
def aes_encode(string)
cipher = OpenSSL::Cipher::AES256.new(:CBC)
cipher.encrypt
cipher.key = @key
cipher.iv = @iv
cipher.padding = 0
padding_data = add_padding(string)
encrypted = cipher.update(padding_data) + cipher.final
encrypted.unpack('H*').first
end
def add_padding(data, block_size = 32)
pad = block_size - (data.length % block_size)
data + (pad.chr * pad)
end
3.trade_sha 加密
將 trade_info 加密後的結果再透過 SHA256 加密一次
def trade_sha
sha256_encode(@key, @iv, trade_info)
end
def sha256_encode(key, iv, trade_info)
encode_string = "HashKey=#{key}&#{trade_info}&HashIV=#{iv}"
Digest::SHA256.hexdigest(encode_string).upcase
end
這樣差不多就大功告成了,實作的完整程式碼如下:
module Newebpay
class Mpg
attr_accessor :info
def initialize(params)
@key = "hash key"
@iv = "hash iv"
@merchant_id = "merchant id"
@info = {} # 使用 attr_accessor 讓 info 方便存取
set_info(params)
end
def form_info
{
MerchantID: @merchant_id
TradeInfo: trade_info,
TradeSha: trade_sha,
Version: "1.6"
}
end
private
def trade_info
aes_encode(url_encoded_query_string)
end
def trade_sha
sha256_encode(@key, @iv, trade_info)
end
def set_info(order)
info[:MerchantID] = @merchant_id
info[:MerchantOrderNo] = order.slug
info[:Amt] = order.amount
info[:ItemDesc] = order.name
info[:Email] = order.email
info[:TimeStamp] = Time.now.to_i
info[:RespondType] = "JSON"
info[:Version] = "1.6"
info[:ReturnURL] = "https://...."
info[:NotifyURL] = "https://...."
info[:LoginType] = 0
info[:CREDIT] = 1,
info[:VACC] = 1
end
def url_encoded_query_string
URI.encode_www_form(info)
end
def aes_encode(string)
cipher = OpenSSL::Cipher::AES256.new(:CBC)
cipher.encrypt
cipher.key = @key
cipher.iv = @iv
cipher.padding = 0
padding_data = add_padding(string)
encrypted = cipher.update(padding_data) + cipher.final
encrypted.unpack('H*').first
end
def add_padding(data, block_size = 32)
pad = block_size - (data.length % block_size)
data + (pad.chr * pad)
end
def sha256_encode(key, iv, trade_info)
encode_string = "HashKey=#{key}&#{trade_info}&HashIV=#{iv}"
Digest::SHA256.hexdigest(encode_string).upcase
end
end
end
只要成功呼叫這個 service 就能夠產出符合 API 需求的 requset(如下圖)
至於要怎麼送 form post 出去這種簡單問題,就交給大家自己解決了(ゝ∀・)b
class PaymentsController < ApplicationController
def create
# 要記得附上該筆訂單的資訊,才有辦法建立付款喔!
order = Order.find(1)
@form_info = Newebpay::Mpg.new(order).form_info
end
end
如果沒碰到什麼問題,應該就能夠順利看到付款畫面了,可喜可賀!
付款完成後
付款完成錢錢進帳,就大功告ㄔㄥˊ...等等,還沒結束!
錢錢入帳的確是大事,但如果我們的系統都沒有紀錄,那麼訂單的管理可能會發生世紀大亂。
還記得我們當初有設定 ReturnURL
跟 NotifyURL
嗎?當付款完成後,藍新也會以 post 的形式回傳支付訊息給我們,但回傳回來的資訊也是經過加密的,所以還需要再處理回傳參數的解密。
解密的範例藍新也有提供 PHP 版本,而本篇同樣整理了 Rails 能夠使用的範例,至於回傳參數有哪些、商家會需要哪些資料,都可以在藍新手冊找到喔!
以下為實作範例:
module Newebpay
class MpgResponse
# 使用 attr_reader 可以更方便取用這些資訊
attr_reader :status, :message, :result, :order_no, :trans_no
def initialize(params)
@key = "your hash key"
@iv = "your hash iv"
response = decrypy(params)
@status = response['Status']
@message = response['Message']
@result = response['Result']
@order_no = @result["MerchantOrderNo"]
@trans_no = @result["TradeNo"]
# etc...
end
def success?
status === 'SUCCESS'
end
private
# AES 解密
def decrypy(encrypted_data)
encrypted_data = [encrypted_data].pack('H*')
decipher = OpenSSL::Cipher::AES256.new(:CBC)
decipher.decrypt
decipher.padding = 0
decipher.key = @key
decipher.iv = @iv
data = decipher.update(encrypted_data) + decipher.final
raw_data = strippadding(data)
JSON.parse(raw_data)
end
def strippadding(data)
slast = data[-1].ord
slastc = slast.chr
string_match = /#{slastc}{#{slast}}/ =~ data
if !string_match.nil?
data[0, string_match]
else
false
end
end
end
end
class PaymentsController < ApplicationController
def notify_response
response = Newebpay::MpgResponse.new(params[:TradeInfo])
end
end
同樣也是沒意外就能夠取得回傳資訊來更新訂單資訊了!
只要了解每個環節該做什麼事,用 Rails 串藍新金流沒那麼難,但因為篇幅問題沒辦法實際帶大家把所有的功能寫完,如果有興趣可以自己將內容補完並且跑起來試試看。但需要注意因為這是範例,實際使用時部分比較隱私的資訊最好使用 ENV 保護起來才是比較正確的喔。
我有將這次的實作內容放在 GitHub 上,或是對此篇文章有任何回饋都歡迎來信與我討論!
最後最後,如果對 Ruby on Rails 有興趣的話,可以參考近期即將開課的 Ruby on Rails 實戰課程