python采用django框架实现支付宝即时到帐接口
admin
2023-08-02 13:39:29
0

因工作需要研究了支付宝即时到帐接口,并成功应用到网站上,把过程拿出来分享。

即时到帐只是支付宝众多商家服务中的一个,表示客户付款,客户用支付宝付款,支付宝收到款项后,马上通知你,并且此笔款项与交易脱离关系,商家可以马上使用。

即时到帐只对企业客户服务,注册成功企业账号以后,申请签约即时到帐产品,大约3-5个工作日后,签约成功,可以马上进入集成产品阶段。

这个是支付宝提供的接口,有asp,c#,java,php四种语言的,每种语言提供GBK和UTF-8两种方案。另带一份支付宝的文档,这份文档我感觉本来简单的事情越说越麻烦了。

网上搜了一下,发现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

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

\"\"\" 
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

#-*- 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

#确认支付 
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中的代码仅供参考!

以上就是本文的全部内容,希望对大家的学习有所帮助。

相关内容

热门资讯

Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...
python清除字符串里非数字... 本文实例讲述了python清除字符串里非数字字符的方法。分享给大家供大家参考。具体如下: impor...