美文网首页
微信支付浅尝

微信支付浅尝

作者: 飞行员suke | 来源:发表于2017-02-04 10:26 被阅读0次

    微信支付浅尝

    一. 微信支付方式概览

    支付方式

    1. 刷卡支付

    刷卡支付是用户展示微信钱包内的“刷卡条码/二维码”给商户系统扫描后直接完成支付的模式。主要应用线下面对面收银的场景

    2. 公众号支付

    公众号支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付。应用场景有:
    ◆ 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
    ◆ 用户的好友在朋友圈、聊天窗口等分享商家页面连接,用户点击链接打开商家页面,完成支付
    ◆ 将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付

    3. 扫码支付

    扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。

    4. APP支付

    APP支付又称移动端支付,是商户通过在移动端应用APP中集成开放SDK调起微信支付模块完成支付的模式。

    二. 刷卡支付场景及流程 (被扫)

    1. 刷卡支付场景

    2. 刷卡支付流程

    A:免密支付


    刷卡支付免密

    B:验密支付


    刷卡支付验密.png

    三. 公众号支付场景及流程(H5支付,JSSDK)

    1. 公众号支付场景

    2. 公众号支付流程

    公众号支付

    四. 扫码支付流程(主动扫)

    1. 扫码支付场景

    2. 扫码支付流程

    A. 模式一(依赖回调)
    商户后台系统根据微信支付规则链接生成二维码,链接中带固定参数productid(可定义为产品标识),
    用户扫码后,微信支付系统将productid和用户唯一标识(openid)回调商户后台系统(需要设置支付回调URL),
    商户后台系统根据productid生成支付交易,最后微信支付系统发起用户支付流程
    适合应用场景:线下。因为一张二维码可重复应用于多人,不会过期

    扫码支付模式一

    B. 模式二(不依赖回调)
    商户后台系统调用微信支付【统一下单API】生成预付交易,
    将接口返回的链接生成二维码,用户扫码后输入密码完成支付交易。注意:该模式的预付单有效期为2小时,过期后无法支付。
    适合应用场景:线上。(线下不建议使用,因为每张二维码只能扫一次,而且有时间限制)

    扫码支付模式二

    3. 商户后台开发

    A:开发前提
    微信支付服务商.png

    微信支付的开发需要有一个公众号或服务号,并且开通微信支付功能。
    要是想作为微信支付服务商,则需提交服务商申请。
    服务商可为所拓展的特约商户完成支付申请、技术接入、活动营销等全生态服务。

    申请完成后便会得到重要的账户参数和接口API参数


    重要参数.png
    B:协议规则
    协议规则.png
    生成二维码规则
    二维码中的内容为链接,形式为:
    weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX
    其中XXXXX为商户需要填写的内容,商户将该链接生成二维码,如需要打印发布二维码,需要采用此格式。商户可调用第三方库生成二维码图片 二维码生成与解析工具
    C:安全规范 详细说明

    签名算法
    生成随机数算法
    商户证书
    商户回调API安全

    D:Develop实践

    xx通接入实践

    微信支付API列表

    scanPay.py

    import requests
    import json
    import tornado.web
    from core.weixin_sdk.utils import Util
    from core.weixin_sdk.utils import HttpUtil
    from core.logger_helper import logger
    try:
        import xml.etree.cElementTree as ET
    except ImportError:
        import xml.etree.ElementTree as ET
    
    """ 微信扫码支付 """
    class WeiXinScanPayHandler(tornado.web.RequestHandler):
        def post(self):
            print("web2shopRequest=",self.request.body)
            params = {}
            params['service'] = 'pay.weixin.native'
            # params['version'] = '2.0'
            # params['charset'] = 'UTF-8'
            # params['sign_type'] = 'MD5'
            params['mch_id'] = '7551000001'
            params['out_trade_no'] = self.get_argument('out_trade_no')
            # params['device_info'] = '127.0.0.1'
            params['body'] = self.get_argument('body')
            params['attach'] = self.get_argument('attach')
            params['total_fee'] = self.get_argument('total_fee')
            params['mch_create_ip'] = self.get_argument('mch_create_ip')
            params['notify_url'] = 'https://weixin.g-pay.cn/scanPaied'  #通知地址
            params['time_start'] = self.get_argument('time_start')
            params['time_expire'] = self.get_argument('time_expire')
            # params['op_user_id'] = '7551000001' #操作员账号,默认为商户号
            # params['goods_tag'] = '商品标记,用于优惠券或者满减使用'
            # params['product_id'] = '12345678'  #商品ID
            params['nonce_str'] =  Util.generate_nonce()
            sign = Util.get_sign(params,"9d101c97133837e13dde2d32a5054abb");
            params['sign'] = sign
            data = Util.dict_to_xml(params)
            print('shop2weifutong=',data)
            
            BASE_URL = 'https://pay.swiftpass.cn/pay/gateway'
            r = requests.post(BASE_URL,data.encode('utf-8'))
            if r.status_code == 200:
                print('weifutongResponse==',r.text)
                dic = Util.xml_to_dict(r.text)
                if('0' == dic['status'] and '0' == dic['result_code']):
                    self.render("template.html",url=dic['code_img_url'],mch_id=dic['mch_id'])
                else:
                    self.render('orderQueryResult.html',title='请求生成二维码失败',dic=dic)
    
    """支付宝扫码支付 """
    class AliScanPayHandler(tornado.web.RequestHandler):
        def post(self):
            print("data=",self.request.body)
            params = {}
            params['service'] = 'pay.alipay.native'
            # params['version'] = '2.0'
            # params['charset'] = 'UTF-8'
            # params['sign_type'] = 'MD5'
            params['mch_id'] = '7551000001'
            params['out_trade_no'] = self.get_argument('out_trade_no')
            # params['device_info'] = '127.0.0.1'
            params['body'] = self.get_argument('body')
            params['attach'] = self.get_argument('attach')
            params['total_fee'] = self.get_argument('total_fee')
            params['mch_create_ip'] = self.get_argument('mch_create_ip')
            params['notify_url'] = 'https://weixin.g-pay.cn/scanPaied'  #支付后的异步通知地址
            # params['time_start'] = self.get_argument('time_start')
            # params['time_expire'] = self.get_argument('time_expire')
            # params['op_user_id'] = '101520000465' #操作员账号,默认为商户号
            # params['goods_tag'] = '商品标记,用于优惠券或者满减使用'
            params['product_id'] = '12345678'  #商品ID
            params['nonce_str'] =  Util.generate_nonce()
            sign = Util.get_sign(params,"9d101c97133837e13dde2d32a5054abb");
            params['sign'] = sign
            data = Util.dict_to_xml(params)
            print(data)
            
            BASE_URL = 'https://pay.swiftpass.cn/pay/gateway'
            r = requests.post(BASE_URL,data.encode('utf-8'))
            if r.status_code == 200:
                print('res==',r.text)
                dic = Util.xml_to_dict(r.text)
                if('0' == dic['status'] and '0' == dic['result_code']):
                    self.render("template.html",url=dic['code_img_url'],mch_id=dic['mch_id'])
                else:
                    self.render('orderQueryResult.html',title='请求生成二维码失败',dic=dic)
    
    """ 支付后接收异步通知,并做相应处理"""
    class PaiedHandler(tornado.web.RequestHandler):
        def post(self):
            print("return=",self.request.body)
            logger.debug('paidHandler:{}'.format(self.request.body))
            self.write("success")
    
    
    """ 订单查询 """
    class OrderQueryHandler(tornado.web.RequestHandler):
        def post(self):
            print("data=",self.request.body)
            params = {}
            params['service'] = 'unified.trade.query'  #订单查询接口 必填
            # params['mch_id'] = '101520000465'            #商户号  必填
            params['mch_id'] = '7551000001'
            params['nonce_str'] =  Util.generate_nonce() #随机串 必填
    
            out_trade_no = self.get_argument('out_trade_no')
            if(out_trade_no): 
                params['out_trade_no'] = out_trade_no  #商户订单号
    
            transaction_id = self.get_argument('transaction_id')
            if(transaction_id):
                params['transaction_id'] = transaction_id  #威富通订单号(与商户订单号必填1个)
            
            # params['version'] = '2.0'     #版本号 默认值2.0 选填
            # params['charset'] = 'UTF-8' #字符集,默认值UTF-8  选填
            # params['sign_type'] = 'MD5' #签名方式,默认MD5, 选填
            # params['sign_agentno'] = ''   #授权渠道编号 如果不为空,则用授权渠道的秘钥进行签名         
            # sign = Util.get_sign(params,"58bb7db599afc86ea7f7b262c32ff42f");
            sign = Util.get_sign(params,'9d101c97133837e13dde2d32a5054abb')
            params['sign'] = sign
            data = Util.dict_to_xml(params)
            print(data)
            BASE_URL = 'https://pay.swiftpass.cn/pay/gateway'
            r = requests.post(BASE_URL,data)
            if r.status_code == 200:
                res = r.text
                print('res==',res)
                dic = Util.xml_to_dict(res)
                if('0' != dic['status']):
                    self.write(dic['message'])
                else:
                    self.render('orderQueryResult.html',title='订单查询结果',dic=dic)
    
    """ 退款 """
    class TradeRefundHandler(tornado.web.RequestHandler):
        def post(self):
            print("data=",self.request.body)
            params = {}
            params['service'] = 'unified.trade.refund' #订单查询接口 必填
            params['mch_id'] = '7551000001'            #商户号  必填
            params['op_user_id'] = '7551000001'        #操作员
            params['nonce_str'] =  Util.generate_nonce() #随机串 必填
    
            out_trade_no = self.get_argument('out_trade_no')
            if(out_trade_no): 
                params['out_trade_no'] = out_trade_no  #商户订单号
    
            transaction_id = self.get_argument('out_transaction_id')
            if(transaction_id):
                params['transaction_id'] = transaction_id  #微信订单号(与商户订单号必填1个)   
            
            params['out_refund_no'] = self.get_argument('out_refund_no') #商户退款单号 必填
            params['total_fee'] = self.get_argument('total_fee')
            params['refund_fee'] = self.get_argument('refund_fee')
            sign = Util.get_sign(params,"9d101c97133837e13dde2d32a5054abb");
            params['sign'] = sign
            data = Util.dict_to_xml(params)
    
            print(data)
            BASE_URL = 'https://pay.swiftpass.cn/pay/gateway'
            r = requests.post(BASE_URL,data)
            if r.status_code == 200:
                res = r.text
                print('res==',res)
                dic = Util.xml_to_dict(res)
                if('0' != dic['status']):
                    self.write(dic['message'])
                else:
                    self.render('orderQueryResult.html',title='退款结果页面',dic=dic)
    
    """ 退款查询 """
    class RefundQueryHandler(tornado.web.RequestHandler):
        def post(self):
            print("data=",self.request.body)
            params = {}
            params['service'] = 'unified.trade.refundquery'  #订单查询接口 必填
            params['mch_id'] = '7551000001'            #商户号  必填
            params['nonce_str'] =  Util.generate_nonce() #随机串 必填
    
            out_trade_no = self.get_argument('out_trade_no')
            if(out_trade_no): 
                params['out_trade_no'] = out_trade_no  #商户订单号
    
            transaction_id = self.get_argument('out_transaction_id')
            if(transaction_id):
                params['transaction_id'] = transaction_id  #微信订单号
    
            out_refund_no = self.get_argument('out_refund_no')
            if(out_refund_no):
                params['out_refund_no'] = out_refund_no #商户退款单号
    
            refund_id = self.get_argument('refund_id')  
            if(refund_id):
                params['refund_id'] = refund_id   #微信退款单号
            
            # params['version'] = '2.0'     #版本号 默认值2.0 选填
            # params['charset'] = 'UTF-8' #字符集,默认值UTF-8  选填
            # params['sign_type'] = 'MD5' #签名方式,默认MD5, 选填
            # params['sign_agentno'] = ''   #授权渠道编号 如果不为空,则用授权渠道的秘钥进行签名         
            sign = Util.get_sign(params,"9d101c97133837e13dde2d32a5054abb");
            params['sign'] = sign
            data = Util.dict_to_xml(params)
            print(data)
            
            BASE_URL = 'https://pay.swiftpass.cn/pay/gateway'
            r = requests.post(BASE_URL,data)
            if r.status_code == 200:
                res = r.text
                print('res==',res)
                dic = Util.xml_to_dict(res)
                if('0' != dic['status']):
                    self.write(dic['message'])
                else:
                    self.render('orderQueryResult.html',title='退款查询结果',dic=dic)
    
    
    """ 订单关闭 """
    class OrderCloseHandler(tornado.web.RequestHandler):
        def post(self):
            print("data=",self.request.body)
            params = {}
            params['service'] = 'unified.trade.close'  #订单查询接口 必填
            params['mch_id'] = '7551000001'            #商户号  必填
            params['nonce_str'] =  Util.generate_nonce() #随机串 必填
            params['out_trade_no'] = self.get_argument('out_trade_no')  #商户订单号
            # params['version'] = '2.0'     #版本号 默认值2.0 选填
            # params['charset'] = 'UTF-8' #字符集,默认值UTF-8  选填
            # params['sign_type'] = 'MD5' #签名方式,默认MD5, 选填       
            sign = Util.get_sign(params,"9d101c97133837e13dde2d32a5054abb");
            params['sign'] = sign
            data = Util.dict_to_xml(params)
            print(data)
            BASE_URL = 'https://pay.swiftpass.cn/pay/gateway'
            r = requests.post(BASE_URL,data)
            if r.status_code == 200:
                res = r.text
                print('res==',res)
                dic = Util.xml_to_dict(res)
                if('0' != dic['status']):
                    self.write(dic['message'])
                else:
                    self.render('orderQueryResult.html',title='订单关闭结果',dic=dic)
    
    
    
    class TEST(object):
        """docstring for TEST"""
        def qcodeRequest(self):
            params = {}
            params['service'] = 'pay.weixin.native'
            params['mch_id'] = '7551000001'
            params['notify_url'] = 'http://www.qq.com'
            params['out_trade_no'] = "123abc457"
            params['body'] = u'测试商品'.encode('utf-8')
            params['total_fee'] = 1
            params['mch_create_ip'] = '127.0.0.1'
            params['nonce_str'] =  Util.generate_nonce()
            sign = Util.get_sign(params,"9d101c97133837e13dde2d32a5054abb");
            params['sign'] = sign
            data = Util.dict_to_xml(params)
            BASE_URL = 'https://pay.swiftpass.cn/pay/gateway'
            print(data)
            r = requests.post(BASE_URL,data)
            if r.status_code == 200:
                res = r.text
                print("{}".format(res))
    
    if __name__ == '__main__':
        test = TEST()
        test.qcodeRequest()
    

    utils.py

    # -*- coding: utf-8 -*-
    
    import time
    import string
    import random
    import json
    import hashlib
    import urllib
    import requests
    from xml.etree import ElementTree
    
    class HttpUtil:
    
        def __init__(self):
            pass
    
        @staticmethod
        def get(url, params=None):
            response = requests.get(url, params=params)
            return json.loads(response.content)
    
        @staticmethod
        def post(url, params, ctype='json', **kwargs):
            """post请求,传入dict,返回dict,内部处理json或xml"""
            if ctype == 'json':
                data = json.dumps(params, ensure_ascii=False)
                data = data.encode('utf8')
                response = requests.post(url, data, **kwargs)
                return json.loads(response.content)
            elif ctype == 'xml':
                data = Util.encode_data(params)
                data = Util.dict_to_xml(data)
                response = requests.post(url, data, **kwargs)
                return Util.xml_to_dict(response.content)
            else:
                data = params
                response = requests.post(url, data, **kwargs)
                return response.json()
    
        @staticmethod
        def url_update_query(url, **kwargs):
            url_parts = list(urllib.parse(url))
            query = dict(urlparse.parse(url_parts[4]))
            query.update(**kwargs)
            url_parts[4] = urllib.urlencode(query)
            final_url = urllib.unparse(url_parts)
            return final_url
    
    
    class Util:
    
        @staticmethod
        def xml_to_dict(xml_data):
            """xml -> dict"""
            xml_data = Util.encode_data(xml_data)
            data = {}
            for child in ElementTree.fromstring(xml_data):
                data[child.tag] = child.text
            return data
    
        @staticmethod
        def dict_to_xml(dict_data):
            xml_str = '<xml>'
            for key, value in dict_data.items():
                xml_str += '<%s><![CDATA[%s]]></%s>' % (key, value, key)
            xml_str += '</xml>'
            return xml_str
    
        @staticmethod
        def timestamp():
            return int(time.time())
    
        @staticmethod
        def generate_nonce(length=6):
            """生成随机字符串"""
            return ''.join([random.choice(string.digits + string.ascii_letters) for i in range(length)])
    
        @staticmethod
        def get_local_ip():
            """获取本机ip地址"""
            import socket
            return socket.gethostbyname(socket.gethostname())
    
        @staticmethod
        def get_sign(dic,key):
            """获取sign"""
            string1 = ""
            lis = sorted(dic)
            for k in lis:
                string1 += ('{0}={1}&'.format(k,dic[k]))
            string1 += 'key={}'.format(key)
            return hashlib.md5(string1.encode('utf-8')).hexdigest().upper()
    
        @staticmethod
        def camel_to_underline(camel_format):
            """驼峰命名格式转下划线命名格式"""
            underline_format=''
            if isinstance(camel_format, str):
                for _s_ in camel_format:
                    underline_format += _s_ if _s_.islower() else '_'+_s_.lower()
            return underline_format.strip('_')
    
        @staticmethod
        def underline_to_camel(underline_format):
            """
            下划线命名格式驼峰命名格式
           """
            camel_format = ''
            if isinstance(underline_format, str):
                for _s_ in underline_format.split('_'):
                    camel_format += _s_.capitalize()
            return camel_format
    
        @staticmethod
        def cap_lower(origin_str):
            """首字母小写"""
            if origin_str:
                return origin_str[0].lower() + origin_str[1:]
            return origin_str
    
        @staticmethod
        def md5(origin_str):
            return hashlib.md5(origin_str).hexdigest()
    
        @staticmethod
        def sha1(origin_str):
            return hashlib.sha1(origin_str).hexdigest()
    
        @staticmethod
        def encode_data(data):
            """对dict, list, unicode-str对象编码为utf-8格式"""
            if not data:
                return data
    
            if isinstance(data, str):
                result = data.encode('utf-8')
    
            elif isinstance(data, dict):
                result = {}
                for k,v in data.items():
                    k = Util.encode_data(k)
                    v = Util.encode_data(v)
                    result.update({k:v})
                return result
    
            elif isinstance(data, list):
                result = []
                for item in data:
                    result.append(Util.encode_data(item))
                return result
    
            else:
                result = data
            return result
    
    
    class ObjectDict(dict):
        """
        Makes a dictionary behave like an object, with attribute-style access.
        """
        def __getattr__(self, name):
            try:
                return self[name]
            except KeyError:
                raise AttributeError(name)
    
        def __setattr__(self, name, value):
            self[name] = value
    
    
    class WxError(Exception):
        pass
    
    
    if __name__ == '__main__':
        pass
    

    五. APP支付(SDK)

    1. APP支付场景

    2. APP支付流程

    APP支付.png
    APP支付尾部

    相关文章

      网友评论

          本文标题:微信支付浅尝

          本文链接:https://www.haomeiwen.com/subject/jfunpttx.html