美文网首页
微信支付浅尝

微信支付浅尝

作者: 飞行员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