APP提现之微信服务号红包

来源:互联网 发布:魔法精灵软件下载 编辑:程序博客网 时间:2024/05/02 13:30
现在很多APP都有提现功能,而提现大部分都是用微信提现,微信提现有两种,一是红包,二是企业付款,在这里结合一下开发,做一些微信服务号发送红包功能的介绍。
例如当前比较火的直播APP映客的提现就是通过微信公众号的红包功能实现的。它的流程是在APP内绑定微信号,然后需要绑定的微信号关注特定的公众号,关注之后就可以在APP内或者公众号提现,公众号通过红包的形式实现提现功能。这一切的前提是要关注特定
的公众号。由于微信红包是通过公众号发送的,这个时候就需要知道绑定的微信相对于这个公众号的openid,通过关注可以获得openid。


APP内部绑定微信号

1 客户端通过微信AUTH获得微信的code发送给服务器
2 服务器通过code获取用户access_token和openid,参照https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html
3 通过access_token和openid获得unionid,并记录该APP用户的unionid,以方便在关注公众号的时候获取对应的openid, 参照http://mp.weixin.qq.com/wiki/1/8a5ce6257f1d3b2afb20f83e72b72ce9.html


公众号配置

这个公众号是微信开放平台https://open.weixin.qq.com/里面所绑定的公众号,平台里面的移动应用应该包括了你需要实现提现功能的APP
1 公众号开启开发模式,如何开启请自行搜索(开启开发模式之后,原来公众号设置的自动回复,菜单等功能全部会失效,需要开发者自己实现)
2 填写开发模式的服务器配置 参照http://mp.weixin.qq.com/wiki/8/f9a0b8382e0b77d87b3bcc1ce6fbc104.html
我选择的是明文模式,配置之后,要确定这个服务器url能够正常访问。
3 验证服务器地址的有效性
我这边采用的是twisted框架用python实现的web开发,由于采用的是明文模式,验证代码只原样返回了'echostr'
4 获取微信公众号的access_token,参照https://mp.weixin.qq.com/wiki/11/0e4b294685f817b95cbed85ba5e82b8f.html,并使用redis做缓存处理,
5 依据接口文档实现业务逻辑
由于公众号的菜单功能已经失效,需要重新配置菜单,我是参照https://mp.weixin.qq.com/debug/cgi-bin/apiinfo?t=index&type=自定义菜单&form=自定义菜单创建接口%20/menu/create 创建菜单,其中的access_token通过公众号的APPID和APPSECRET获得,也>可以根据http://mp.weixin.qq.com/wiki/10/0234e39a2025342c17a7d23595c6b40a.html使用代码实现。
6 关注/取消关注事件,参照https://mp.weixin.qq.com/wiki/2/5baf56ce4947d35003b86a9805634b1e.html
在发生这类事件的时候,我们配置的服务器url会收到调用,参数如下
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
</xml>
'EVENT'是subscribe和unsubscribe分表表示关注和取消关注,'FromUserName'为微信用户的openid,我们可以根据公众号的access_token和这个openid获取用户的信息,参照https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html,这个里

面包含了unionid,这个unionid和通过APP内部绑定时获得unionid相同的时候就可以把APP用户和微信公众号的openid对应起来,之后我们就可以在APP申请提现,公众号发红包实现提现。

这部分的部分代码如下

class EventNotify(GreenResource):    isLeaf = True    def green_render_GET(self, request):        return arg_named(request, 'echostr')    def green_render_POST(self, request):        data = request.content.read()        root = ElementTree.fromstring(data)        response = {child.tag: child.text for child in root.getchildren()}        ret_xml = ''        event_type = response.get('Event')        openid = response.get('FromUserName')        if event_type == 'subscribe':            wx_user = WXClient.get_fuwuhao_user_info(opened)  #获取用户信息            if not wx_user or wx_user.get('errcode'):                logging.info(wx_user)                return            unionid = wx_user.get('unionid')            nickname = wx_user.get('nickname')            fwh = PWWeixinFuwuhao.find_or_new(openid)            fwh.unionid = unionid            fwh.nickname = nickname            fwh.save()            bind_query = WeixinBind.query(master=True, fetchone=False, unionid=unionid) #使用psql获取app用户            bind_all = [WeixinBind(**each) for each in bind_query]            for bind_one in bind_all:                bind_one.openid = openid                bind_one.save()            ret_fmt = '''<xml>                <ToUserName><![CDATA[%s]]></ToUserName>                <FromUserName><![CDATA[%s]]></FromUserName>                <CreateTime>%s</CreateTime>                <MsgType><![CDATA[text]]></MsgType>                <Content><![CDATA[%s]]></Content>                <FuncFlag>0</FuncFlag>                </xml>            '''            ret_parm = (                response.get('FromUserName'),                response.get('ToUserName'),                int(time.time()),                config.WX_FWH_AUTO_MSG,            )            ret_xml  = ret_fmt % ret_parm        elif event_type == 'unsubscribe':            result = WeixinFuwuhao.delete(openid=openid)            if not result:logging.info('fuwuhao unsubscribe is not exist %s' % (openid, ))        elif response.get('MsgId'):            ret_fmt = '''<xml>            <ToUserName><![CDATA[%s]]></ToUserName>            <FromUserName><![CDATA[%s]]></FromUserName>            <CreateTime>%s</CreateTime>            <MsgType><![CDATA[text]]></MsgType>            <Content><![CDATA[%s]]></Content>            <FuncFlag>0</FuncFlag>            </xml>            '''            ret_parm = (                response.get('FromUserName'),                response.get('ToUserName'),                int(time.time()),                config.WX_FWH_AUTO_MSG_2,            )            ret_xml  = ret_fmt % ret_parm        return ret_xml



公众号发送红包

1 发送红包,微信红包针对所有用户、单个用户都有发送频率的限制,金额也有限制,如果提现金额大于红包最大金额,需要分批发送。参照https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_5
2 由于红包需要微信用户去领取,所以APP服务器需要不断查询红包状态,查询参照https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_7&index=6

  每个红包是 SENDING:发放中 SENT:已发放待领取 FAILED:发放失败 RECEIVED:已领取 REFUND:已退款 中的任何一种状态,只有 为FAILED RECEIVED REFUND状态的时候表示这个红包的生命周期已经结束

部分代码如下

class WeixinWithdrawOrder(object):    def __init__(self, withhdraw_id, openid, mch_billno, total_amount):        self.wid = withhdraw_id        self.pw_tid = 0        self.nonce_str = hashlib.md5('%s-%s' % (self.wid, time.time())).hexdigest()        self.mch_id = config.WX_FWH_MCH_ID        self.mch_billno = mch_billno        self.wxappid = config.WX_FWH_APPID        self.send_name = config.WX_WITHDRAW_SEND_NAME        self.re_openid = openid        self.total_amount = 100 if config.ENV_TEST else total_amount        self.total_num = 1        self.act_name = config.WX_WITHDRAW_ACT_NAME        self.wishing = config.WX_WITHDRAW_WISHING        self.client_ip = config.WX_PAY_IP_ADDR        self.remark = config.WX_WITHDRAW_REMARK    @classmethod    def sign(cls, data):        weixin_params = []        for key, value in data.iteritems():            if key not in ('wid', 'sign', 'pw_tid', ):                weixin_params.append([str(key), str(value)])        weixin_params.sort(key=lambda x:x[0])        sign_src = '%s&key=%s' % ('&'.join(['%s=%s'%(x[0], x[1]) for x in weixin_params]), config.WX_FWH_MCH_KEY)        logging.info(sign_src)        return hashlib.md5(sign_src).hexdigest().upper()    def order(self):        return '''<xml>   <nonce_str>%s</nonce_str>   <sign>%s</sign>   <mch_billno>%s</mch_billno>   <mch_id>%s</mch_id>   <wxappid>%s</wxappid>   <send_name>%s</send_name>   <re_openid>%s</re_openid>   <total_amount>%s</total_amount>   <total_num>%s</total_num>   <wishing>%s</wishing>   <act_name>%s</act_name>   <client_ip>%s</client_ip>   <remark>%s</remark></xml>''' % (            self.nonce_str, WeixinWithdrawOrder.sign(self.__dict__), self.mch_billno,            self.mch_id, self.wxappid, self.send_name,            self.re_openid.encode('utf8'), self.total_amount, self.total_num, self.wishing,            self.act_name, self.client_ip, self.remark,        )    def run_withdraw(self):        try:            order = self.order()            logging.info(order)            url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack'            cert = ('data/weixin/apiclient_cert.pem', 'data/weixin/apiclient_key.pem') #微信商户号的key,通过商户平台下载            data = requests.post(url, data=order, cert=cert).text  #使用第三方库request            root = ElementTree.fromstring(data)            response = {child.tag : child.text for child in root.getchildren()}            logging.info(response)            if response.get('return_code') == 'SUCCESS':                if response.get('result_code') == 'SUCCESS':                    return True, response.get('mch_billno'), response.get('err_code'), response.get('return_msg')                else:                    logging.error(data)                    return False, self.mch_billno, response.get('err_code'), response.get('return_msg')            else:                logging.error(data)                return False, self.mch_billno, response.get('err_code'), response.get('return_msg')        except:            traceback.print_exc()            return False, self.mch_billno, 'url not connect', 'url not connect'



0 0