詳細記錄了當時使用 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
Table
大綱
本篇分成四個部分:
- 環境參數:將正式站和測試站的環境參數寫於此處
- 跳轉至綠界前:建立訂單後跳轉至 ECpay 頁面
- 綠界回傳:ReturnURL 綠界 Server 端回傳 (POST)
- 綠界回傳:OrderResultURL 綠界 Client 端 (POST)

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

第一部分:環境參數設定
方便用於正式和測試站的 Hashkey 參數轉換,以及檢查碼 [CheckMacValue] 的驗證計算
關於綠界檢查碼 [CheckMacValue] 的驗證方式:
- 將傳遞參數依照字母排序
- 參數最前面加上 HashKey、最後面加上 HashIV
- 進行 URL encode
- 轉為小寫
- 以 SHA256 加密方式來產生雜凑值
- 再轉大寫產生 CheckMacValue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# 環境參數 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,跳轉至綠界
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# 建立訂單後跳轉至 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’
1 2 3 4 5 6 7 8 9 10 11 12 |
# 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 是否為正確
- 判斷回傳資訊,交易成功則修改修資料庫並且跳轉至交易成功頁面
- 判斷回傳資訊,失敗則跳轉至失敗頁面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# 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粉絲專頁,如果想看最新更新,還請您按讚或是追蹤唷!
在〈【Flask 教學】Python 綠界金流 API 信用卡串接〉中有 1 則留言
好想看串LinePay的教學
留言功能已關閉。