詳細記錄了當時使用 Flask 串接綠界金流 API 的過程,目前 Python 串接綠界支付的文章不多,希望此篇對您有幫助!
- 撰寫此篇時間為 2020-2-14,綠界 API 文件版本 V 5.1.38
- 資料庫使用 flask_sqlalchemy (2.4.0) – ORM
- 語言 Python (3.7.2) – flask (1.1.1)
此篇完整程式碼放置於 GitHub,歡迎 git clone 使用。
本篇參考的綠界支付官方文件如下:
- 官方文件:https://www.ecpay.com.tw//Service/API_Dwnld#
- 官方範例 Code:https://github.com/ECPay/ECPayAIO_Python/tree/master/sample
- 官方測試後台:https://vendor-stage.ecpay.com.tw/User/LogOn_Step1
文章目錄
大綱
本篇分成四個部分:
- 環境參數:將正式站和測試站的環境參數寫於此處
- 跳轉至綠界前:建立訂單後跳轉至 ECpay 頁面
- 綠界回傳:ReturnURL 綠界 Server 端回傳 (POST)
- 綠界回傳:OrderResultURL 綠界 Client 端 (POST)

第二部分對應到流程第 2 點,建立訂單
第三部分對應到流程第 13 點,背景接收付款結果 ReturnURL
第四部分對應到流程第 15 點,顯示結果畫面 OrderResultURL

第一部分:環境參數設定
方便用於正式和測試站的 Hashkey 參數轉換,以及檢查碼 [CheckMacValue] 的驗證計算
關於綠界檢查碼 [CheckMacValue] 的驗證方式:
- 將傳遞參數依照字母排序
- 參數最前面加上 HashKey、最後面加上 HashIV
- 進行 URL encode
- 轉為小寫
- 以 SHA256 加密方式來產生雜凑值
- 再轉大寫產生 CheckMacValue
# 環境參數
class Params:
def __init__(self):
web_type = ‘test’
if web_type == ‘offical’:
# 正式環境
self.params = {
'MerchantID': 'ID隱藏',
'HashKey': 'Key 隱藏',
'HashIV': 'IV 隱藏',
'action_url':
'https://payment.ecpay.com.tw/Cashier/AioCheckOut/V5'
}
else:
# 測試環境
self.params = {
'MerchantID':
'2000132',
'HashKey':
'5294y06JbISpM5x9',
'HashIV':
'v77hoKGq4kWxNNIS',
'action_url':
'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5'
}
@classmethod
def get_params(cls):
return cls().params
# 驗證綠界傳送的檢查碼 check_mac_value 值是否正確
@classmethod
def get_mac_value(cls, get_request_form):
params = dict(get_request_form)
if params.get(‘CheckMacValue’):
params.pop(‘CheckMacValue’)
ordered_params = collections.OrderedDict(
sorted(params.items(), key=lambda k: k[0].lower()))
HahKy = cls().params['HashKey']
HashIV = cls().params['HashIV']
encoding_lst = []
encoding_lst.append('HashKey=%s&' % HahKy)
encoding_lst.append(''.join([
'{}={}&'.format(key, value)
for key, value in ordered_params.items()
]))
encoding_lst.append('HashIV=%s' % HashIV)
safe_characters = '-_.!*()'
encoding_str = ''.join(encoding_lst)
encoding_str = quote_plus(str(encoding_str),
safe=safe_characters).lower()
check_mac_value = ''
check_mac_value = hashlib.sha256(
encoding_str.encode('utf-8')).hexdigest().upper()
return check_mac_value
第二部分:跳轉至綠界前
- 先從 session 取得使用者id (uid),以及購物車頁面 POST 的收件人資訊
- 建立訂單資訊
- 將交易商品 & 金額寫進參數,並呼叫綠界 SDK,跳轉至綠界
# 建立訂單後跳轉至 ECpay 頁面
@payment.route(‘/to_ecpay’, methods=[‘POST’])
def ecpay():
# 從 session 中取得 uid
uid = session.get(‘uid’)
host_name = request.host_url
# 取得 POST 的收件人資訊
trade_name = request.values[‘name’]
trade_phone = request.values[‘phone’]
county = request.values[‘county’]
district = request.values[‘district’]
zipcode = request.values[‘zipcode’]
address = request.values[‘address’]
# 利用 uid 查詢資料庫,購物車商品 & 價錢
carts = sql.AddToCar.query.filter_by(uid=uid, state='Y')
total_product_price = 0
total_product_name = ''
for cart in carts:
price = cart.product.price
quan = cart.quantity
product_name = cart.product.name
total_product_price += price * quan
total_product_name += product_name + '#'
# 建立交易編號 tid
date = time.time()
tid = str(date) + 'Uid' + str(uid)
status = '未刷卡'
# 新增 Transaction 訂單資料
T = sql.Transaction(uid, tid, trade_name, trade_phone, address,
total_product_price, status, county, district, zipcode)
db.session.add(T)
db.session.commit()
params = Params.get_params()
# 設定傳送給綠界參數
order_params = {
'MerchantTradeNo': datetime.now().strftime("NO%Y%m%d%H%M%S"),
'StoreID': '',
'MerchantTradeDate': datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
'PaymentType': 'aio',
'TotalAmount': total_product_price,
‘TradeDesc’: ‘ToolsFactory’,
‘ItemName’: total_product_name,
‘ReturnURL’: host_name + ‘payment/receive_result’,
‘ChoosePayment’: ‘Credit’,
‘ClientBackURL’: host_name + ‘payment/trad_result’,
‘Remark’: ‘交易備註’,
‘ChooseSubPayment’: ‘’,
‘OrderResultURL’: host_name + ‘payment/trad_result’,
‘NeedExtraPaidInfo’: ‘Y’,
‘DeviceSource’: ‘’,
‘IgnorePayment’: ‘’,
‘PlatformID’: ‘’,
‘InvoiceMark’: ’N’,
'CustomField1': str(tid),
'CustomField2': '',
'CustomField3': '',
'CustomField4': '',
'EncryptType': 1,
}
extend_params_1 = {
'BindingCard': 0,
'MerchantMemberID': '',
}
extend_params_2 = {
'Redeem': 'N',
'UnionPay': 0,
}
inv_params = {
# 'RelateNumber': 'Tea0001', # 特店自訂編號
# 'CustomerID': 'TEA_0000001', # 客戶編號
# 'CustomerIdentifier': '53348111', # 統一編號
# 'CustomerName': '客戶名稱',
# 'CustomerAddr': '客戶地址',
# 'CustomerPhone': '0912345678', # 客戶手機號碼
# 'CustomerEmail': '[email protected]',
# 'ClearanceMark': '2', # 通關方式
# 'TaxType': '1', # 課稅類別
# 'CarruerType': '', # 載具類別
# 'CarruerNum': '', # 載具編號
# 'Donation': '1', # 捐贈註記
# 'LoveCode': '168001', # 捐贈碼
# 'Print': '1',
# 'InvoiceItemName': '測試商品1|測試商品2',
# 'InvoiceItemCount': '2|3',
# 'InvoiceItemWord': '個|包',
# 'InvoiceItemPrice': '35|10',
# 'InvoiceItemTaxType': '1|1',
# 'InvoiceRemark': '測試商品1的說明|測試商品2的說明',
# 'DelayDay': '0', # 延遲天數
# 'InvType': '07', # 字軌類別
}
ecpay_payment_sdk = module.ECPayPaymentSdk(MerchantID=params['MerchantID'],
HashKey=params['HashKey'],
HashIV=params['HashIV'])
# 合併延伸參數
order_params.update(extend_params_1)
order_params.update(extend_params_2)
# 合併發票參數
order_params.update(inv_params)
try:
# 產生綠界訂單所需參數
final_order_params = ecpay_payment_sdk.create_order(order_params)
# 產生 html 的 form 格式
action_url = params['action_url']
html = ecpay_payment_sdk.gen_html_post_form(action_url,final_order_params)
return html
except Exception as error:
print(‘An exception happened: ‘ + str(error))
第三部分:綠界回傳交易資訊
ECpay 回傳結果有三種:
- ReturnURL : 綠界 Server端 POST 付款完成通知至指定網址
- 因資安關係限制 443 port & 合法的 domain 才能正確接收成功
- OrderResultURL: 綠界 Client端 POST,回傳付款結果至指定網址
- ClientBackURL : 綠界 Client端 GET,使用者點選按鈕後返回連結
- 如果有同時使用 OrderResultURL,使用者在付款成功後,會直接跳轉到 OrderResultURL 指定頁面 (POST)
- 如果只有使用 ClientBackURL,使用者在付款成功後,會停留在綠界支付的交易成功頁面 ( 適用於方便 debug )
一. 設定 ReturnURL 接收頁面
- 限制於 443 port & 合法的 domain 才能正確接收成功,如果是測試 locahost 並沒有 https 的話,建議可以使用 ngrox 來獲得 https 進行測試
- 如有接收成功,官方要求回應 ‘1|OK’
# ReturnURL: 綠界 Server 端回傳 (POST)
@csrf.exempt
@payment.route(‘/receive_result’, methods=[‘POST’])
def end_return():
result = request.form[‘RtnMsg’]
tid = request.form[‘CustomField1’]
trade_detail = sql.Transaction.query.filter_by(tid=tid).first()
trade_detail.status = ‘交易成功 sever post’
db.session.add(trade_detail)
db.session.commit()
return ‘1|OK’
二. 設定 OrderResultURL 接收頁面
- 先檢驗綠界傳送的 check_mac_value 是否為正確
- 判斷回傳資訊,交易成功則修改修資料庫並且跳轉至交易成功頁面
- 判斷回傳資訊,失敗則跳轉至失敗頁面
# OrderResultURL: 綠界 Client 端 (POST)
@csrf.exempt
@payment.route(‘/trad_result’, methods=[‘GET’, ‘POST’])
def end_page():
if request.method == ‘GET’:
return redirect(url_for(‘index’))
if request.method == ‘POST’:
check_mac_value = Params.get_mac_value(request.form)
if request.form[‘CheckMacValue’] != check_mac_value:
Return ‘請聯繫管理員’
# 接收 ECpay 刷卡回傳資訊
result = request.form[‘RtnMsg’]
tid = request.form['CustomField1']
trade_detail = sql.Transaction.query.filter_by(tid=tid).first()
# 取得交易使用者資訊
uid = trade_detail.uid
trade_client_detail = {
'name': trade_detail.trade_name,
'phone': trade_detail.trade_phone,
‘county’: trade_detail.trade_county,
‘district’: trade_detail.trade_district,
‘zipcode’: trade_detail.trade_zipcode,
‘trade_address’: trade_detail.trade_address
}
# 判斷成功
if result == ‘Succeeded’:
trade_detail.status = ‘待處理’
commit_list = []
# 移除 AddToCar (狀態:Y 修改成 N)
carts = sql.AddToCar.query.filter_by(uid=uid, state=‘Y’)
for cart in carts:
price = cart.product.price
quan = cart.quantity
cart.state = 'N'
# 新增 Transaction_detail 訂單細項資料
Td = sql.Transaction_detail(tid, cart.product.pid, quan, price)
commit_list.append(Td)
commit_list.append(cart)
db.session.add_all(commit_list)
db.session.commit()
# 讀取訂單細項資料
trade_detail_items = sql.Transaction_detail.query.filter_by(
tid=tid)
return render_template('/payment/trade_success.html',
shopping_list=trade_detail_items,
total=trade_detail.total_value)
# 判斷失敗
else:
carts = sql.AddToCar.query.filter_by(uid=uid, state='Y')
trade_detail = sql.Transaction.query.filter_by(tid=tid).first()
return render_template('/payment/trade_fail.html',
shopping_list=carts,
total=trade_detail.total_value,
trade_client_detail=trade_client_detail)
最後:
此篇完整程式碼放置於 GitHub,歡迎 git clone 使用。
關於更多 Python Flask 教學的延伸閱讀:
▍關於 Flask 教學系列目錄:
▍其他 Flask 相關教學:
- 【Flask教學系列】Flask 為甚麼需要 WSGI 與 Nginx
- 【Flask教學系列】Flask-JWT-Extended 實作
- 【Flask教學系列】實作 Flask CORS
- 【Flask教學系列】實作 Flask CSRF Protection
- 【Flask教學系列】實作 Dockerfile + nginx + ssl + Flask 教學 (附GitHub完整程式)
有關Max行銷誌的最新文章,都會發佈在Max的Facebook粉絲專頁,如果想看最新更新,還請您按讚或是追蹤唷!