因工作需要研究了支付寶即時到帳介面,併成功應用到網站上,把過程拿出來分享。
即時到帳只是支付寶眾多商家服務中的一個,表示客戶付款,客戶用支付寶付款,支付寶收到款項後,馬上通知你,並且此筆款項與交易脫離關係,商家可以馬上使用。
即時到帳只對企業客戶服務,註冊成功企業賬號以後,申請簽約即時到帳產品,大約3-5個工作日後,簽約成功,可以馬上進入整合產品階段。
這個是支付寶提供的介面,有asp,c#,java,php四種語言的,每種語言提供GBK和UTF-8兩種方案。另帶一份支付寶的文件,這份文件我感覺本來簡單的事情越說越麻煩了。
http://download.alipay.com/public/api/base/alipaydirect.zip
網上搜了一下,發現python介面有幾個現成的方案。
https://github.com/fengli/alipay_python
這兩個是一個,程式碼我還沒看,寫文件的時候發現的。
https://github.com/lxneng/alipay
https://pypi.python.org/pypi/alipay/0.2.2
支付寶即時到帳交易過程。
商家:是指支付寶的企業客戶。也就是你整合服務單位。
終端消費者:是指在網上購物的消費者,你整合服務單位的客戶。
1、終端消費者在商家網站選擇商品,下訂單。
2、商家把支付資訊,get到支付寶指定的連結。
3、終端消費者在支付寶的網站上操作付款。
4、付款成功後,支付寶post付款成功的資訊到商家預先提供的地址。
5、支付寶在終端消費者付款成功後三秒後,通過get跳回商家指定的連結。
第4步跟終端消費者操作付款的跳轉無關,所以被稱為非同步通知。這一步,支付寶期待你返回’success’,如果你不返回’success’,支付寶會於4分鐘後再次post付款成功的資訊,此後每10分鐘post一次,至少30分鐘內如此。如果終端消費者付款失敗,非同步通知不會發生。
通過整合,我知道為什麼終端消費者付款成功後要等3秒後跳轉回商家頁面了,因為它要等非同步通知的資訊先到達,先處理訂單,再帶終端消費者回到客戶的介面,這樣就可以看到支付成功的頁面了。當然付款失敗,非同步通知不發生,訂單狀態沒有改變,終端消費者就只能看到付款失敗的資訊。
瞭解了支付過程,開始設計程式。
1、生成商品訂單。終端消費者選擇商品生成商品訂單。ID號要唯一,這個唯一不是要你採用UUID,而是跟支付寶往來過程唯一即可,從1遞增也可以,只是終端消費者能看到這個id,所以最好採用固定長度的字串,終端消費者如果知道自己是第一個客戶,會不會心裡發怵?後面會談到,為了安全不僅不能用遞增,而且至少要6位隨機碼以上。
2、選擇支付方式。因為我們僅僅整合了即時到帳,所以只有支付寶付款一個選項。把支付方式加入訂單資訊,同時把訂單id post到下一步地址,這一步post,最好採用新開頁面,本頁面彈出對話方塊,讓客戶自己選擇支付成功或者支付失敗。
3、傳送支付資訊。根據post過來的訂單號組合資訊,get方式傳送資料給支付寶,同時帶動終端消費者頁面跳轉到支付寶支付頁面。
4、接受非同步通知。
5、支付成功後支付寶跳轉回來的程式。因為回來時get方式,為避免終端消費者看到更多敏感資訊,這一步並沒有渲染頁面,而是處理資訊,跳轉到另外一個頁面。
6、顯示支付結果。這個頁面和第2步讓客戶點選支付成功跳轉的是同一個。支付失敗,就跳轉到一個說明吧。
alipay.py
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 |
import types from urllib import urlencode, urlopen from hashcompat import md5_constructor as md5 #見hashcompact.py from config import settings #見config.py #字串編解碼處理 def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'): if strings_only and isinstance(s, (types.NoneType, int)): return s if not isinstance(s, basestring): try: return str(s) except UnicodeEncodeError: if isinstance(s, Exception): return ' '.join([smart_str(arg, encoding, strings_only, errors) for arg in s]) return unicode(s).encode(encoding, errors) elif isinstance(s, unicode): return s.encode(encoding, errors) elif s and encoding != 'utf-8': return s.decode('utf-8', errors).encode(encoding, errors) else: return s # 閘道器地址 _GATEWAY = 'https://mapi.alipay.com/gateway.do?' # 對陣列排序併除去陣列中的空值和簽名引數 # 返回陣列和連結串 def params_filter(params): ks = params.keys() ks.sort() newparams = {} prestr = '' for k in ks: v = params[k] k = smart_str(k, settings.ALIPAY_INPUT_CHARSET) if k not in ('sign','sign_type') and v != '': newparams[k] = smart_str(v, settings.ALIPAY_INPUT_CHARSET) prestr += '%s=%s&' % (k, newparams[k]) prestr = prestr[:-1] return newparams, prestr # 生成簽名結果 def build_mysign(prestr, key, sign_type = 'MD5'): if sign_type == 'MD5': return md5(prestr + key).hexdigest() return '' # 即時到賬交易介面 def create_direct_pay_by_user(tn, subject, body, bank, total_fee): params = {} params['service'] = 'create_direct_pay_by_user' params['payment_type'] = '1' #商品購買,只能選這個 # 獲取配置檔案 params['partner'] = settings.ALIPAY_PARTNER params['seller_id'] = settings.ALIPAY_PARTNER params['seller_email'] = settings.ALIPAY_SELLER_EMAIL params['return_url'] = settings.ALIPAY_RETURN_URL params['notify_url'] = settings.ALIPAY_NOTIFY_URL params['_input_charset'] = settings.ALIPAY_INPUT_CHARSET params['show_url'] = settings.ALIPAY_SHOW_URL # 從訂單資料中動態獲取到的必填引數 params['out_trade_no'] = tn # 請與貴網站訂單系統中的唯一訂單號匹配 params['subject'] = subject # 訂單名稱,顯示在支付寶收銀臺裡的“商品名稱”裡,顯示在支付寶的交易管理的“商品名稱”的列表裡。 params['body'] = body # 訂單描述、訂單詳細、訂單備註,顯示在支付寶收銀臺裡的“商品描述”裡,可以為空 params['total_fee'] = total_fee # 訂單總金額,顯示在支付寶收銀臺裡的“應付總額”裡,精確到小數點後兩位 # 擴充套件功能引數——網銀提前 if bank=='alipay' or bank=='': params['paymethod'] = 'directPay' # 支付方式,四個值可選:bankPay(網銀); cartoon(卡通); directPay(餘額); CASH(網點支付) params['defaultbank'] = '' # 支付寶支付,這個為空 else: params['paymethod'] = 'bankPay' # 預設支付方式,四個值可選:bankPay(網銀); cartoon(卡通); directPay(餘額); CASH(網點支付) params['defaultbank'] = bank # 預設網銀代號,代號列表見http://club.alipay.com/read.php?tid=8681379 params,prestr = params_filter(params) params['sign'] = build_mysign(prestr, settings.ALIPAY_KEY, settings.ALIPAY_SIGN_TYPE) params['sign_type'] = settings.ALIPAY_SIGN_TYPE return _GATEWAY + urlencode(params) def notify_verify(post): # 初級驗證--簽名 _,prestr = params_filter(post) mysign = build_mysign(prestr, settings.ALIPAY_KEY, settings.ALIPAY_SIGN_TYPE) if mysign != post.get('sign'): return False # 二級驗證--查詢支付寶伺服器此條資訊是否有效 params = {} params['partner'] = settings.ALIPAY_PARTNER params['notify_id'] = post.get('notify_id') gateway = 'https://mapi.alipay.com/gateway.do?service=notify_verify&' verify_result = urlopen(gateway, urlencode(params)).read() if verify_result.lower().strip() == 'true': return True return False |
hashcompact.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
""" The md5 and sha modules are deprecated since Python 2.5, replaced by the hashlib module containing both hash algorithms. Here, we provide a common interface to the md5 and sha constructors, preferring the hashlib module when available. """ try: import hashlib md5_constructor = hashlib.md5 md5_hmac = md5_constructor sha_constructor = hashlib.sha1 sha_hmac = sha_constructor except ImportError: import md5 md5_constructor = md5.new md5_hmac = md5 import sha sha_constructor = sha.new sha_hmac = sha |
config.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#-*- coding:utf-8 -*- class settings: # 安全檢驗碼,以數字和字母組成的32位字元 ALIPAY_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ALIPAY_INPUT_CHARSET = 'utf-8' # 合作身份者ID,以2088開頭的16位純數字 ALIPAY_PARTNER = 'xxxxxxxxxxxxxxxx' # 簽約支付寶賬號或賣家支付寶帳戶 ALIPAY_SELLER_EMAIL = 'ls@abc.com' ALIPAY_SIGN_TYPE = 'MD5' # 付完款後跳轉的頁面(同步通知) 要用 http://格式的完整路徑,不允許加?id=123這類自定義引數 ALIPAY_RETURN_URL='http://www.xxx.com/alipay/return/' # 交易過程中伺服器非同步通知的頁面 要用 http://格式的完整路徑,不允許加?id=123這類自定義引數 ALIPAY_NOTIFY_URL='http://www.xxx.com/alipay/notify/' |
view.py
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 |
#確認支付 def pay(request): cbid=request.POST.get('id') try: cb=cBill.objects.get(id=cbid) except ObjectDoesNotExist: return HttpResponseRedirect("/err/no_object") #如果閘道器是支付寶 if cb.cbank.gateway=='alipay': tn=cb.id subject='' body='' bank=cb.cbank.id tf='%.2f' % cb.amount url=create_direct_pay_by_user (tn,subject,body,bank,tf) #如果閘道器是財付通 elif cb.cbank.gateway=='tenpay': pass #去支付頁面 return HttpResponseRedirect (url) #alipay非同步通知 @csrf_exempt def alipay_notify_url (request): if request.method == 'POST': if notify_verify (request.POST): #商戶網站訂單號 tn = request.POST.get('out_trade_no') #支付寶單號 trade_no=request.POST.get('trade_no') #返回支付狀態 trade_status = request.POST.get('trade_status') cb = cBill.objects.get(pk=tn) if trade_status == 'TRADE_SUCCESS': cb.exe() log=Log(operation='notify1_'+trade_status+'_'+trade_no) log.save() return HttpResponse("success") else: #寫入日誌 log=Log(operation='notify2_'+trade_status+'_'+trade_no) log.save() return HttpResponse ("success") else: #黑客攻擊 log=Log(operation='hack_notify_'+trade_status+'_'+trade_no+'_'+'out_trade_no') log.save() return HttpResponse ("fail") #同步通知 def alipay_return_url (request): if notify_verify (request.GET): tn = request.GET.get('out_trade_no') trade_no = request.GET.get('trade_no') trade_status = request.GET.get('trade_status') cb = cBill.objects.get(pk=tn) log=Log(operation='return_'+trade_status+'_'+trade_no) log.save() return HttpResponseRedirect ("/public/verify/"+tn) else: #錯誤或者黑客攻擊 log=Log(operation='err_return_'+trade_status+'_'+trade_no) log.save() return HttpResponseRedirect ("/") #外部跳轉回來的連結session可能丟失,無法再進入系統。 #客戶可能通過xxx.com操作,但是支付寶只能返回www.xxx.com,域名不同,session丟失。 def verify(request,cbid): try: cb=cBill.objects.get(id=cbid) #如果訂單時間距現在超過1天,跳轉到錯誤頁面! #避免網站資訊流失 return render_to_response('public_verify.html',{'cb':cb},RequestContext(request)) except ObjectDoesNotExist: return HttpResponseRedirect("/err/no_object") |
view.py中的程式碼僅供參考!