2017-07-19 13:19:00 fjnjxr 阅读数 12584
  • 微信支付开发-微信公众号开发12-微信开发php

    微信公众平台开发之微信支付开发是子恒老师《微信公众平台开发》视频教程的第12部。详细讲解了用php进行微信支付的开发。内容包含获取支付密钥,微信公众号支付开发,扫码支付,微信刷卡支付,异步处理支付结果等等。欢迎反馈,微信/QQ:68183131

    27766 人正在学习 去看看 秦子恒

微信开放平台网址:https://open.weixin.qq.com

申请微信开放平台的主要目的:实现APP或者网站的微信登录。

所以要实现APP或者网站的微信登录,必须通过微信开放平台开发者资质认证(一年需要交纳300元)才能获得接口权限。


现在很多企业都已经实现了三证合一,这主要记录下三证合一填写申请时候的一些注意事项。


1、组织机构代码证
目前仅支持中国大陆企业;
注: 若由于三证合一原因没有组织机构代码证时可填统一社会信用代码;
2、企业工商营业执照(副本)
中国大陆工商局或市场监督管理局颁发的工商营业执照必须在有效期内,且须是当前企业最新的工商营业执照。
注: 三证合一统一社会信用代码18位,可在营业执照注册号位置上填写“注册号”或“统一社会信用代码”;
3、申请公函
请下载申请认证公函(模版参考示例)
填写认证公函需要提供公众号的原始ID,去我的账户信息查看原始ID。
上传加盖企业公章的原件照片或扫描件
4、其他证明材料
可提交其他证明材料。
注:上传文件需加盖企业公章的原件照片或扫描件
支持.jpg .jpeg .bmp .gif .png格式照片,大小不超过2M。

2018-07-20 15:49:11 qq_34190023 阅读数 46926
  • 微信支付开发-微信公众号开发12-微信开发php

    微信公众平台开发之微信支付开发是子恒老师《微信公众平台开发》视频教程的第12部。详细讲解了用php进行微信支付的开发。内容包含获取支付密钥,微信公众号支付开发,扫码支付,微信刷卡支付,异步处理支付结果等等。欢迎反馈,微信/QQ:68183131

    27766 人正在学习 去看看 秦子恒

微信开放平台开发系列文章:

微信开放平台开发第三方授权登陆(一):开发前期准备

微信开放平台开发第三方授权登陆(二):PC网页端

微信开放平台开发第三方授权登陆(三):Android客户端

微信开放平台开发第三方授权登陆(四):微信公众号

微信开放平台开发第三方授权登陆(五):微信小程序

 

目录

一、前期准备流程

二、具体实现步骤

1、注册邮箱账号

2、注册微信开放平台账号、完善开发者资料

3、申请开发者资质认证

1)申请开发者资质认证

2)选定类型

3)填写“认证资料”

4)填写“管理员信息”

5)上传“企业基本信息”材料:

6)进入填写发票及支付费用

4、创建应用

1)创建网站应用

2)创建移动应用

5、申请微信登陆功能

三、注意事项

四.主要提供材料

五、特殊情况处理方案


据微信开放平台“开发资源”中网站应用微信登陆功能开发指南描述:进行授权登陆接入前,需要先注册一个开发者账号,并拥有一个已经审核通过的网站应用,并获取AppID和APPSecret,然后申请微信登陆并通过审核后,可以接入流程进行开发。

据开发者资质认证处介绍:开发者资质认证通过后,微信开放平台帐号下的应用,将获得微信登录、智能接口、第三方平台开发等高级能力。

所以在微信第三方授权登陆获取用户信息开发前,需要做一些前期准备。目的是获取到AppID以及AppSecret,并成功申请微信登陆功能

一、前期准备流程

1、注册邮箱账号。

2、根据邮箱账号注册微信开放平台账号,完善开发者资料。

3、申请开发者资质认证、填写相关资料、填写发票、支付认证金额。提交并等待认证结果

4、认证成功后,创建网站应用,填写基本信息、下载网站信息登记表填写并上传扫描件、填写授权回调域等。提交审核等待结果。

5、认证成功后,创建移动应用,至少选择安卓、IOS、WP8其中一种平台

6、创建应用成功后,申请微信登陆,等待审核结果,待审核通过后,可进行微信登陆的开发。

注:创建应用和开发者资质认证可同时进行

准备工作大致流程图

二、具体实现步骤

1、注册邮箱账号

支持网易邮箱、QQ邮箱等常规邮箱。此邮箱将成为日后登陆开放平台的账号

 

2、注册微信开放平台账号、完善开发者资料

1)填写邮箱进行注册、开放平台将发送邮件到填写邮箱中,点击邮件上的地址进行开发者资料的完善。(开放平台注册地址:https://open.weixin.qq.com/cgi-bin/readtemplate?t=regist/regist_tmpl&lang=zh_CN

2)开发者资料完善:主要填写注册人信息。如真实姓名、真实手机号码、联系地址、身份证号码。使用注册人的微信扫码绑定为管理员。提交信息。(邮件信息包含地址,点击后进行资料完善)

3)完善资料后,根据邮箱及密码进行登录

 

3、申请开发者资质认证

1)申请开发者资质认证

登录后,点击右上角邮箱号进入“基本资料”,点击“开发者资质认证”,显示未认证,点击“现在申请”。

认证成功后,这里将变成认证成功:

2)选定类型

同意“微信开放平台开发者资质认证服务协议”,点击下一步。选择类型为“企业”,点击确定。

注:类型包含了企业、网店商家、媒体(事业单位媒体、其他媒体)、政府及事业单位、其他组织(社会团体、民办非企业、其他组织)

3)填写“认证资料”

包括企业全称、组织机构代码、工商执照注册号、法定代表人/企业负责人姓名、经营范围(一般经营范围)、企业规模(选填)、企业开户名称、企业开户银行、企业银行账号。

4)填写“管理员信息”

包括账号管理员姓名、账号管理员部门与职位、账号管理员手机号、管理员座机、管理员电子邮件、身份证号。最后需要使用管理员的微信进行扫码。

5)上传“企业基本信息”材料:

需要准备的材料及要求如下

I.组织机构代码证

组织机构代码证必须在有效期范围内。必须是原件照片、扫描件或复印件加盖企业公章后的扫描件。图片不超2M

II.企业工商营业执照

中国大陆工商局或市场监督管理局颁发的工商营业执照,且必须在有效期内。要求原件照片、扫描件或者复印件加盖企业公章后的扫描件

III.申请公函下载打印

如果是一般企业需要先下载“一般企业申请认证公函”,个体下载“个体工商户申请认证公函”(见附件中“一般企业申请认证公函.doc”及“个体工商户申请认证公函.doc”或于网站中下载)。对下载的申请公函进行打印。

IV.填写申请公函信息

填写内容主要有(根据不同企业类型进行填写,只需要填写其中一个就可以):

A.一般企业申请认证公函:

a.企业信息:申请企业全称、官网(可缺省)、办公电话、办公地址、企业规模、企业简介(要说明企业经营范围,重要历程,主要产品和服务及覆盖区域等事项)、

b.法定代表信息:法定代表人姓名、身份证号码、应用的Appid(这时还没有Appid,此项不填)、登陆Email、

c.申请相关信息:申请认证理由、提供的服务、

d.账号运营者信息 : 账号运营者姓名、部门及职位、身份证号、手机号、办公联系电话、电子邮箱

e.填写认证申请公函:最后运营者签字并加盖企业章

B.个体工商户申请认证公函:

a.个体工商户信息:个体工商户全称、官网(可缺省)、办公电话、办公地址、规模、简介(主要说明经营范围,重要历程,主要产品和服务及覆盖区域等事项)

b.经营者信息:经营者姓名、身份证号码、应用的Appid(这时还没有Appid,此项不填)、登陆Email

c.申请相关信息:申请认证理由、提供的服务、

d.账号运营者信息:账号运营者姓名、部门及职位、身份证号、手机号、办公联系电话、电子邮箱

e.填写认证申请公函:最后运营者签字并加盖企业章

V.对填写好的申请公函进行上传图片:

上传加盖企业公章的原件照片或扫描件

点击下一步。

6)进入填写发票及支付费用

等待认证结果。

注:开发者认证费用为300.有效期一年,可在最后三个月申请年审续期

 

4、创建应用

1)创建网站应用

进入开放平台首页(https://open.weixin.qq.com),点击上方“管理中心”,点击“网站应用”,点击“创建网站应用

I.填写基本信息

包括网站应用名称、英文名(选填)、应用简介、英文简介(选填)、应用官网。

II.上传网站信息登记表扫描件及网站应用图片

       A. 网站信息登记表

先下载《微信开放平台网站信息登记表》(见附件”微信开放平台网站信息登记表.doc”).然后进行填写打印。最后盖章,上传扫描件。图片不超过2M

网站开放平台信息登记表填写内容包括:

a. 网站信息:

1)网站网址:要创建第三方准备使用的网址。

2)网站备案号/许可证号

3)主板单位名称/个人姓名

4)单位/个人官网网址

5)单位/个人所在地: 

6)单位/个人简介:

b. 开发者账户信息

1)邮箱:注册微信开放平台的注册邮箱

2)网站应用名称:与开放平台填写的网站应用名称一致

c. 负责人信息

1)  姓名

2)  职务

3)  联系人手机

4)  责任人个人邮箱

然后填写责任人签名。职务。以及填写日期,最后盖上申请公司的章

       B. 网站应用图片

上传网站应用水印招聘。28*28像素以及108*108像素的png图片,大小不超过300k。

上传完成后,点击下一步

III.填写授权回调域

填写合法的域名。第三方授权登陆后,只能回调到该域名下的页面。(即,开发的网站上线后的域名)

审核时间大概7个工作日内(审核资料完整正确的话,大概3天左右能审核通过)

审核不通过的话,会显示驳回,并且告知哪个环节审核不通过,可以重新修改再审,

审核成功页面:

2)创建移动应用

进入开放平台首页(https://open.weixin.qq.com),点击上方“管理中心”,点击“移动应用”,点击“创建移动应用

I.填写基本信息

包括移动应用名称、英文名(选填)、应用简介、英文简介(选填)、应用官网。

II.上传移动应用图片

    上传移动应用水印图片28*28像素的png图片,以及移动应用高清图片108*108像素的png图片。大小均不超过300k

III.填写平台信息

     选择平台,一共有三种平台:ISO应用、Android应用和WP8应用

     至少选择一个平台,可以多选。

A. IOS应用

       细分为iPhone和iPad,可多选

       这两种设备类型均需要提供“Bundle ID”(ISO应用唯一标识)、“测试版本Bundle ID”(ISO应用测试版本的BundleID)、“AppStore下载地址”(选填)

B.Android应用

        需要提供 “应用签名” 和 “应用包名”,以及“应用下载地址(选填)”

     应用签名可以使用签名生成工具直接从安装当前应用的手机获取,应用签名由开发者签名该应用的keystore文件决定

     应用包名在manifest文件里声明,要和正式发布应用的包名一致。

C.WP8 应用

        WP8应用只需要填写“应用下载地址(选填)”就可以了。

注意:应用若还没有提交到应用市场,下载地址等可以暂时为空,提交到应用市场后,可修改下载地址。

 

注:

1.审核将在三个工作日内完成,目前只审核开发者的资质和真实性,不涉及应用内容本部,不需要开发者提交ipa文件或将含微信SDK的版本在AppStore上线后再审核,但要求开发者提供该应用已经上线的版本的下载地址

2.同一应用在不同平台的版本应共用一个AppID

审核成功后的页面(Android)

5、申请微信登陆功能

成功创建应用并审核通过后,点击应用右方“查看”可以查看应用详情。

在“接口信息”一栏中,查看当前应用拥有的接口权限。

选中“微信登陆”接口,点击右方申请开通,开通微信登陆功能。

 

三、注意事项

1、开发者资质认证结果只有成功或失败两种情况。审核费用与最终是否审核通过无关。每申请一次,就需要支付一次审核服务费用

2、我方在完成在线申请及资料提交流程,并完成审核服务费用支付后,腾讯会在15个工作日内展开认证工作,我方应积极配合腾讯及第三方审核公司的认证需求,并有权随时了解、查询认证进度

3.认证失败原因可能情况:

1)由于我方原因,经腾讯三次系统通知用户调整申请内容,我方仍未能满足开发者资质认证要求的;

2)由于我方原因:自用户付费之日起三十日,我方仍未能满足开发者资质认证要求之一;

3)因其他原因而不能认证成功的情形

4、网站应用和移动应用一个开发者最多只能登记10个。

 

四.主要提供材料

主要提供材料,详情见“具体实现步骤

  1. 微信开放平台登陆邮箱
  2. 开发者个人详细信息
  3. 企业信息及法人(负责人)信息(包括官网等)
  4. 企业账号管理员信息(包括微信扫码)
  5. 企业章
  6. 应用官网及备案号/许可证。 应用图标。

 

五、特殊情况处理方案

1.若我方向腾讯或者第三方审核机构提供的资料和信息如有变更的,应当及时采取以下措施:

1)如处于认证过程中的资料和信息发生变更,用户应立即通知腾讯或负责认证订单的第三方审核机构更新有关资料及信息;

2)如认证成功后资料和信息发生变更,用户应及时申请补充订单变更有关资料及信息;

3)如认证成功后腾讯发现资料和信息存在错误,用户应及时申请补充订单更正有关资料及信息。

2018-03-03 14:25:53 ChenMMo 阅读数 2780
  • 微信支付开发-微信公众号开发12-微信开发php

    微信公众平台开发之微信支付开发是子恒老师《微信公众平台开发》视频教程的第12部。详细讲解了用php进行微信支付的开发。内容包含获取支付密钥,微信公众号支付开发,扫码支付,微信刷卡支付,异步处理支付结果等等。欢迎反馈,微信/QQ:68183131

    27766 人正在学习 去看看 秦子恒

什么是第三方开放平台

来波官方解释:
我才是官方文档

第三方平台的开放,让公众号或小程序运营者在面向垂直行业需求时,可以通过一键登录授权给第三方开发者,来完成相关能力。

简单的说,就是让公众号授权给第三个开放平台,根据授权不同,第三开放平台可以获取到该公众号的接口权限,从而直接调用微信api,进行公众号开发;

开通创建流程
这里写图片描述

开发者资质审核通过后,就可以创建第三方开放平台了,由于我们第三方开放平台已经建立了。就不演示创建平台的过程了,下面解释下填写的资料

  1. 第一步基本资料填写,不做多余说明,填写基本资料即可
  2. 第二步选择权限集,大概意思就是 选择下 你这个第三方开放平台 要代替公众号实现那些业务 获取公众号的那些接口权限,需要注意的是首先要确保该公众号已经有了这个权限 我是官方文档
  3. 第三步是填写开发资料,基本上就是域名,白名单一些的配置了 这里官方文档写的也比较清楚 不懂看这里

开始开发
首先要做的 是授权事件接收URL的处理,用于接收取消授权通知、授权成功通知、授权更新通知,也用于接收ticket,ticket是验证平台方的重要凭据,服务方在获取component_access_token时需要提供最新推送的ticket以供验证身份合法性。此ticket作为验证服务方的重要凭据,请妥善保存。

权限变更这些可以先不考虑,先考虑的是 接受这个ticket,只有拥有了ticket才能去换取第三方平台的token
下面是主要代码。实体类这些不提供了,根据需求可以自行创建

 package com.ysh.wxtest.controller;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.websocket.Session;

import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import com.qq.weixin.mp.aes.AesException;
import com.qq.weixin.mp.aes.WXBizMsgCrypt;
import com.ysh.wxtest.model.Users;
import com.ysh.wxtest.model.Wxauthinfo;
import com.ysh.wxtest.model.Wxauthorizerinfo;
import com.ysh.wxtest.model.Wxcomtoken;
import com.ysh.wxtest.model.Wxticket;
import com.ysh.wxtest.service.WxauthinfoService;
import com.ysh.wxtest.service.WxauthorizerinfoService;
import com.ysh.wxtest.service.WxcomtokenService;
import com.ysh.wxtest.service.WxticketService;
import com.ysh.wxtest.util.AjaxMessage;

import common.Logger;
import weixin.popular.bean.component.*;
import weixin.popular.util.XMLConverUtil;


/**
 * 微信 第三方开放平台  controller层
 * @author YaoShiHang
 *
 */
@Controller
public class WeixinAccreditController {

    @Autowired
    private WxticketService  wxticketService;   //微信推送 ticket服务,用于保存 ticket
    @Autowired
    private WxcomtokenService wxcomtokenService; //微信第三方平台  token服务 ,有效期2小时。获取次数有限 注意缓存
    @Autowired
    private WxauthinfoService wxauthinfoService; //微信授权信息 服务   
    @Autowired
    private WxauthorizerinfoService WxinfoService; 

    private final String APPID = "???";

    private Logger log= Logger.getLogger(getClass());

    /**
     * 微信全网测试账号
     */
    private final static String COMPONENT_APPID = "XXXXXXXXXXXX"; //第三方平台 APPID
    private final String COMPONENT_APPSECRET = "XXXXXXXXXXXX";    //第三方平台 秘钥
    private final static String COMPONENT_ENCODINGAESKEY = "XXXXXXXXXXXX";  //开发者 设置的 key
    private final static String COMPONENT_TOKEN = "XXXXXXXXXXXX";   //开发者 设置的 token 

    // 授权事件接受url 每隔10分钟 获取微信服务器推送ticket 接收后需要解密 接收到后 必须返回字符串success
    @RequestMapping("/openwx/getticket")
    public void getTicket(HttpServletRequest request, HttpServletResponse response)
            throws IOException, DocumentException, AesException {
        processAuthorizeEvent(request);
        output(response, "success"); // 输出响应的内容。
    }

    /**
     *  授权事件处理 
     * @param request
     * @throws IOException
     * @throws DocumentException
     * @throws AesException
     */
    public void processAuthorizeEvent(HttpServletRequest request) throws IOException, DocumentException, AesException {
        String nonce = request.getParameter("nonce");
        String timestamp = request.getParameter("timestamp");
        String signature = request.getParameter("signature");
        String msgSignature = request.getParameter("msg_signature");
        HttpSession session  = request.getSession();
        if (!StringUtils.isNotBlank(msgSignature)){ //判断消息是否空
            return;// 微信推送给第三方开放平台的消息一定是加过密的,无消息加密无法解密消息
        }
        boolean isValid = checkSignature(COMPONENT_TOKEN, signature, timestamp, nonce);
        if (isValid) {
            StringBuilder sb = new StringBuilder();
            BufferedReader in = request.getReader();
            String line;
            while ((line = in.readLine()) != null) {
                sb.append(line);
            }
            String xml = sb.toString();
              log.info("第三方平台全网发布-----------------------原始 Xml="+xml);
            String encodingAesKey = COMPONENT_ENCODINGAESKEY;// 第三方平台组件加密密钥
            String appId =  (xml);// 此时加密的xml数据中ToUserName是非加密的,解析xml获取即可
               log.info("第三方平台全网发布-------------appid----------getAuthorizerAppidFromXml(xml)-----------appId="+appId);
            WXBizMsgCrypt pc = new WXBizMsgCrypt(COMPONENT_TOKEN, encodingAesKey, COMPONENT_APPID);
            xml = pc.decryptMsg(msgSignature, timestamp, nonce, xml);
               log.info("第三方平台全网发布-----------------------解密后 Xml="+xml);
            ComponentReceiveXML com = XMLConverUtil.convertToObject(ComponentReceiveXML.class, xml);
            session.setAttribute("com",com);
            processAuthorizationEvent(xml);
        }
    }
    /**
     * 保存Ticket
     * @param xml
     */
    void processAuthorizationEvent(String xml) {
        Document doc;

        try {
            doc = DocumentHelper.parseText(xml);
            Element rootElt = doc.getRootElement();
            String ticket = rootElt.elementText("ComponentVerifyTicket");
            if(ticket!=null){
                Wxticket wxticket = new Wxticket();
                wxticket.setAppid(APPID);
                wxticket.setAddtime(new Date());;
                wxticket.setId(1l);
                wxticket.setTicket(ticket);
                wxticketService.updateNotNull(wxticket);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    String getAuthorizerAppidFromXml(String xml) {
        Document doc;
        try {
            doc = DocumentHelper.parseText(xml);
            Element rootElt = doc.getRootElement();
            String toUserName = rootElt.elementText("ToUserName");
            return toUserName;
        } catch (DocumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 判断消息是否加密
     * @param token
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public static boolean checkSignature(String token, String signature, String timestamp, String nonce) {
        System.out.println(
                "###token:" + token + ";signature:" + signature + ";timestamp:" + timestamp + "nonce:" + nonce);
        boolean flag = false;
        if (signature != null && !signature.equals("") && timestamp != null && !timestamp.equals("") && nonce != null
                && !nonce.equals("")) {
            String sha1 = "";
            String[] ss = new String[] { token, timestamp, nonce };
            Arrays.sort(ss);
            for (String s : ss) {
                sha1 += s;
            }
            sha1 = AddSHA1.SHA1(sha1);
            if (sha1.equals(signature)) {
                flag = true;
            }
        }
        return flag;
    }
    /**
     * 工具类:回复微信服务器"文本消息"
     * @param response
     * @param returnvaleue
     */
    public void output(HttpServletResponse response, String returnvaleue) {
        try {
            PrintWriter pw = response.getWriter();
            pw.write(returnvaleue);
            System.out.println("****************returnvaleue***************="+returnvaleue);
            pw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

class AddSHA1 {
    public static String SHA1(String inStr) {
        MessageDigest md = null;
        String outStr = null;
        try {
            md = MessageDigest.getInstance("SHA-1"); // 选择SHA-1,也可以选择MD5
            byte[] digest = md.digest(inStr.getBytes()); // 返回的是byet[],要转化为String存储比较方便
            outStr = bytetoString(digest);
        } catch (NoSuchAlgorithmException nsae) {
            nsae.printStackTrace();
        }
        return outStr;
    }
    public static String bytetoString(byte[] digest) {
        String str = "";
        String tempStr = "";
        for (int i = 0; i < digest.length; i++) {
            tempStr = (Integer.toHexString(digest[i] & 0xff));
            if (tempStr.length() == 1) {
                str = str + "0" + tempStr;
            } else {
                str = str + tempStr;
            }
        }
        return str.toLowerCase();
    }
}

提供一个xml 解析工具类


import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.sun.xml.bind.marshaller.CharacterEscapeHandler;

/**
 * XML 数据接收对象转换工具类
 * @author LiYi
 *
 */
public class XMLConverUtil{

    private static final ThreadLocal<Map<Class<?>,Marshaller>> mMapLocal = new ThreadLocal<Map<Class<?>,Marshaller>>() {
        @Override
        protected Map<Class<?>, Marshaller> initialValue() {
            return new HashMap<Class<?>, Marshaller>();
        }
    };

    private static final ThreadLocal<Map<Class<?>,Unmarshaller>> uMapLocal = new ThreadLocal<Map<Class<?>,Unmarshaller>>(){
        @Override
        protected Map<Class<?>, Unmarshaller> initialValue() {
            return new HashMap<Class<?>, Unmarshaller>();
        }
    };

    /**
     * XML to Object
     * @param <T> T
     * @param clazz clazz
     * @param xml xml
     * @return T
     */
    public static <T> T convertToObject(Class<T> clazz,String xml){
        return convertToObject(clazz,new StringReader(xml));
    }

    /**
     * XML to Object
     * @param <T> T
     * @param clazz clazz
     * @param inputStream  inputStream
     * @return T
     */
    public static <T> T convertToObject(Class<T> clazz,InputStream inputStream){
        return convertToObject(clazz,new InputStreamReader(inputStream));
    }

    /**
     * XML to Object
     * @param <T> T
     * @param clazz clazz
     * @param reader reader
     * @return T
     */
    @SuppressWarnings("unchecked")
    public static <T> T convertToObject(Class<T> clazz,Reader reader){
        try {
            Map<Class<?>, Unmarshaller> uMap = uMapLocal.get();
            if(!uMap.containsKey(clazz)){
                JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
                Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
                uMap.put(clazz, unmarshaller);
            }
            return (T) uMap.get(clazz).unmarshal(reader);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Object to XML
     * @param object object
     * @return xml
     */
    public static String convertToXML(Object object){
        try {
            Map<Class<?>, Marshaller> mMap = mMapLocal.get();
            if(!mMap.containsKey(object.getClass())){
                JAXBContext jaxbContext = JAXBContext.newInstance(object.getClass());
                Marshaller marshaller = jaxbContext.createMarshaller();
                marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
                //设置CDATA输出字符
                marshaller.setProperty(CharacterEscapeHandler.class.getName(), new CharacterEscapeHandler() {
                    public void escape(char[] ac, int i, int j, boolean flag, Writer writer) throws IOException {
                        writer.write(ac, i, j);
                    }
                });
                mMap.put(object.getClass(), marshaller);
            }
            StringWriter stringWriter = new StringWriter();
            mMap.get(object.getClass()).marshal(object,stringWriter);
            return stringWriter.getBuffer().toString();
        } catch (JAXBException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 转换简单的xml to map
     * @param xml xml
     * @return map
     */
    public static Map<String,String> convertToMap(String xml){
        Map<String, String> map = new LinkedHashMap<String,String>();
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            StringReader sr = new StringReader(xml);
            InputSource is = new InputSource(sr);
            Document document = db.parse(is);

            Element root = document.getDocumentElement();
            if(root != null){
                NodeList childNodes = root.getChildNodes();
                if(childNodes != null && childNodes.getLength()>0){
                    for(int i = 0;i < childNodes.getLength();i++){
                        Node node = childNodes.item(i); 
                        if( node != null && node.getNodeType() == Node.ELEMENT_NODE){
                            map.put(node.getNodeName(), node.getTextContent());
                        }
                    }
                }
            }
        } catch (DOMException e) {
            e.printStackTrace();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return map;
    }
}

这一步结束后 第三方开放平台就可以审核通过了。然后就是进行代公众号实现业务了,相对来说简单了很多,微信提供的都有相关文档。直接调用接口就行了。需要注意的是在首先要确保公众号获取了这个权限,其次确保公众号把这个权限授权给了第三方平台。在开发中代公众号实现网页授权的接口 假如使用第三方开放平台的话,接口地址是有所改变的,参考第三方开放平台开发文档,其他的接口 只需将原来公众号的token 改变为 通过第三方开放平台token 获取的公众号授权给第三方开放平台的token 两个token是不一样的。有效期都是2小时 需要缓存。最后上一波成功图片

这里写图片描述

这里写图片描述

2019-11-25 18:02:47 YuanZhea 阅读数 80
  • 微信支付开发-微信公众号开发12-微信开发php

    微信公众平台开发之微信支付开发是子恒老师《微信公众平台开发》视频教程的第12部。详细讲解了用php进行微信支付的开发。内容包含获取支付密钥,微信公众号支付开发,扫码支付,微信刷卡支付,异步处理支付结果等等。欢迎反馈,微信/QQ:68183131

    27766 人正在学习 去看看 秦子恒

概念

微信公众平台-第三方平台(简称第三方平台)开放给所有通过开发者资质认证后的开发者使用。在得到公众号或小程序运营者(简称运营者)授权后,第三方平台开发者可以通过调用微信开放平台的接口能力,为公众号或小程序的运营者提供账号申请、小程序创建、技术开发、行业方案、活动营销、插件能力等全方位服务。同一个账号的运营者可以选择多家适合自己的第三方为其提供产品能力或委托运营。

写此篇博客的缘由

由于腾讯文档的极其简陋,导致很多开发者(我)多走了许多弯路。所以我立下誓言如果我这边开发完成,我一定搞篇博客,给后来者指条路,因为腾讯实在太坑了,废话就不啰嗦了,直接开讲。

第一步:创建第三方平台

1).在微信开放平台-管理中心-第三方平台中创建第三方平台账号。创建第三方平台

2).选择“平台型服务商类型”创建第三方平台。填写第三方平台的基本信息. 填写基本信息

3).设置相关权限信息,具体权限集信息可参考:第三方平台权限说明
4).填写开发资料的信息说明, 可参考:第三方平台申请资料说明

第二步:调试开发

      接收第三方验证票据(component_verity_ticket

在第三方平台创建审核通过后,微信服务器会向其“授权事件接收URL”每隔10分钟定时推送component_verify_ticket。第三方平台方在收到ticket推送后也需进行解密,接收到后必须直接返回字符串success。

/**
     * 用于接受微信传来的ticket
     *
     * @return string
     */
    public function authCallback(){

        $xml_msg=file_get_contents('php://input');

        $msg=array(
            "timeStamp"  => empty($_GET['timestamp'])     ? ""    : trim($_GET['timestamp']) ,
            "nonce"     => empty($_GET['nonce'])     ? ""    : trim($_GET['nonce']) ,
            "msg_sign"   => empty($_GET['msg_signature']) ? ""    : trim($_GET['msg_signature'])
        );
       //第三方平台的配置信息
        $wx_settins=[
   
               'app_id'  => env('WECHAT_OPEN_PLATFORM_APPID', ''),
               'secret'  => env('WECHAT_OPEN_PLATFORM_SECRET', ''),
               'token'   => env('WECHAT_OPEN_PLATFORM_TOKEN', ''),
               'aes_key' => env('WECHAT_OPEN_PLATFORM_AES_KEY', ''),
       ];

        $result = $this->component_decode($xml_msg,$msg,$wx_settins);
        $ticket = WechatTicket::where('ticket', $result)->get();
        if(!count($ticket)){
            $Data=[
                'appid'=>open_platform_app_id,
                'ticket'=>$result,
                'created_at'=>date('Y-m-d H:i:s')
            ];
            WechatTicket::insert($Data);
        }
        return 'success';
    }

注意1:component_verity_ticket建议每次接受都进行写入缓存/数据库/文件

注意2:微信发送的请求中总共有5个参数,具体如下:

时间戳 timestamp,随机数nonce , encrypt_type(加密类型,为aes)和msg_signature(消息体签名,用于验证消息体的正确性)以及xml内容 

XML内容

<xml>

<AppId></AppId>

<CreateTime>1413192605 </CreateTime>

<InfoType> </InfoType>

<ComponentVerifyTicket> </ComponentVerifyTicket>

</xml>

注意3:对上述XML内容解密

解密/加密方式都是aes,

需要在创建第三方平台时填写开发资料时填写的:消息验证token,消息加解密key,appid

和微信请求来的参数:msg_signature(签名),timestamp(时间戳),nonce(随机数),postDataStr(post来的数据字符串),进行校验和解密成明文内容。然后提取出ComponentVerifyTicket进行写入缓存/数据库/文件(后续所有的操作都需要用到)

       获取令牌(component_access_token

第三方平台component_access_token是第三方平台的下文中接口的调用凭据,也叫做令牌(component_access_token)。每个令牌是存在有效期(2小时)的,且令牌的调用不是无限制的,请第三方平台做好令牌的管理,在令牌快过期时(比如1小时30分)再进行刷新.

  /**
     * 获取component_access_token
     *
     * @return bool|string
     */
    public function getComponentAccessToken()
    {
        $componentAccessToken=Redis::get('component_access_token');
        if(!$componentAccessToken){
            // 获取最新的ticket
            $ticket = WechatTicket::where('appid', $this->open_platform['open_platform']['app_id'])
                ->orderBy('created_at', 'DESC')
                ->limit(1)
                ->select(['ticket'])
                ->first();
            $this->componentVerifyTicket = $ticket->ticket;
            $url = "https://api.weixin.qq.com/cgi-bin/component/api_component_token";
            $postData = array(
                "component_appid" =>open_platform_app_id,
                "component_appsecret" => open_platform_secret,
                "component_verify_ticket" => $this->componentVerifyTicket
            );
            $componentAccessTokenReq = $this->http($url, $postData, 'POST');
            $componentAccessTokenArr = json_decode($componentAccessTokenReq, true);
            $componentAccessToken = $componentAccessTokenArr['component_access_token'];
            Redis::setex('component_access_token', 5400, $componentAccessToken); //有效时间2个小时,缓存一定要小于两个小时
        }
        return $componentAccessToken;
    }
  1. 判断token未过期直接返回(不要频繁的请求token,微信有请求次数限制)
  2. 用最新的ticket
  3. 拼装请求数据
  4. 发送Https请求并获取结果
  5. 解析结果并缓存token

       获取预授权码(pre_auth_code

该API用于获取预授权码。预授权码用于公众号或小程序授权时的第三方平台方安全验证

/**
     * 获取预授权码
     *
     * @param string $componentAccessToken
     * @return bool|string
     */
    public function getPreAuthCode(string $componentAccessToken)
    {
        $url = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=" . $componentAccessToken;
        $postData = array(
            "component_appid" => $open_platform_app_id, //第三方平台的APPID
        );

        return OpenWechat::newInstance()->http($url, $postData, 'POST');//发送post请求
    }

重定向到授权页,引入用户进入授权页

第三方平台方可以在自己的网站:中放置“微信公众号授权”的入口,引导公众号进入授权页。授权页网址为

https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=xxxx&pre_auth_code=xxxxx&redirect_uri=xxxx

该网址中第三方平台方需要提供第三方平台方appid、预授权码和回调URI(授权成功后直接跳转的页面),

步骤:

  • 在第三方平台指定的域名下做一个简单的页面作为授权页的入口
  • 获取pre_auth_code
  • 替换重定向的url的参数

   

 /**
     * 公众号授权
     *
     * @param Request $request
     * @return string
     */
    public function openOauth(Request $request){
        // 1、获取component_access_token
        $componentAccessToken= OpenWechat::getComponentAccessToken();
        if($request->has('auth_code')){
            // 存在auth_code 说明前两个接口已请求过
            // 4、使用授权码换取公众号或小程序的接口调用凭据和授权信息
            $authorization = $this->getAuthToken($componentAccessToken,$request->input('auth_code'));
            $authorization_info = json_decode($authorization,true);
            $whereData=['authorizer_appid'=>$authorization_info['authorization_info']['authorizer_appid']];
            $updataData=[
                'authorizer_access_token'=>$authorization_info['authorization_info']['authorizer_access_token'],
                'authorizer_refresh_token'=>$authorization_info['authorization_info']['authorizer_refresh_token']
            ];
            WechatAccount::updateOrCreate($whereData,$updataData);
        }else{
            // 2、获取预授权码
            $preAuthCodeReq = $this->getPreAuthCode($componentAccessToken);
            $preAuthCodeArr = json_decode($preAuthCodeReq,true);
            $preAuthCode = $preAuthCodeArr['pre_auth_code'];

            // 3、引导进入授权页面
            $url = "https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=". $this->open_platform['open_platform']['app_id'] ."&pre_auth_code=". $preAuthCode ."&redirect_uri=".$request->fullUrl();
            return view('wechat.auth', [
                'url' => $url,
            ]);
        }

        return 'success';
    }

注意:接收授权方授权请求并一定要保存授权方信息

在第四步后重定向到授权页后,在授权页上会有一个二维码,微信公众号管理员(开发者的权限不行)通过的使用本人微信扫描二维码对第三方平台进行授权,在公众号管理员扫描二维码后,第三方平台后台给定的回调地址(redirect_uri将会收到一条请求,请求中包含了授权方的authorization_code和authorization_code的有效期 ;

  1. 使用authorization_code换取授权方的authorizer_access_token和authorizer_refresh_token。
​
/**
     * 获取公众号信息包括token refresh_token
     *
     * @param string $componentAccessToken
     * @param string $authCode
     * @return bool|string
     */
    private function getAuthToken(string $componentAccessToken, string $authCode)
    {
        $url = "https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=" . $componentAccessToken;
        $postData = array(
            'component_appid' => , //第三方app_id
            "authorization_code" => $authCode
        );
//        return $this->http($url, $postData, 'POST');
        return OpenWechat::newInstance()->http($url, $postData, 'POST');
    }

​

步骤:

  • 获取第三方平台的token
  • 获取授权方授权信息
  • 获取授权方账号信息进行保存

获取/刷新接口调用令牌

在公众号/小程序接口调用令牌(authorizer_access_token)失效时,可以使用刷新令牌(authorizer_refresh_token)获取新的接口调用令牌。

注意: authorizer_access_token 有效期为 2 小时,开发者需要缓存 authorizer_access_token,避免获取/刷新接口调用令牌的 API 调用触发每日限额

   /**
     * 更新authorizer_access_token
     *
     * @return bool|string
     */
    public function UpdateAuthorizerAccessToken($authorizer_appid)
    {
        $component_access_token=$this->getComponentAccessToken();
        $old_authorizer_refresh_token= WechatAccount::where("authorizer_appid",$authorizer_appid)->first(['authorizer_refresh_token']);
        $url="https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=$component_access_token";
        $postData = array(
            "component_appid" => $this->open_platform['open_platform']['app_id'],
            "authorizer_appid" => $authorizer_appid,
            "authorizer_refresh_token" => $old_authorizer_refresh_token->authorizer_refresh_token
        );
        $authorizer_refresh_token=$this->http($url, $postData, 'POST');
        $new_authorizer_refresh_token=json_decode($authorizer_refresh_token, true);
        $whereData=['authorizer_appid'=>$authorizer_appid];
        $updataData=[
            'authorizer_access_token'=>$new_authorizer_refresh_token['authorizer_access_token'],
            'authorizer_refresh_token'=>$new_authorizer_refresh_token['authorizer_refresh_token']
        ];
        WechatAccount::updateOrCreate($whereData,$updataData);
    }

公用的http请求方法

/**
     * post 请求
     *
     * @param string $url
     * @param array $data
     * @param string $method
     * @return bool|string
     */
    public function http(string $url, array $data, string $method)
    {
        $data = json_encode($data);
        $curl = curl_init(); // 启动一个CURL会话
        curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 对认证证书来源的检查
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
        curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
        curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
        if ($method == 'POST') {
            curl_setopt($curl, CURLOPT_POST, 1); // 发送一个常规的Post请求
            if ($data != '') {
                curl_setopt($curl, CURLOPT_POSTFIELDS, $data); // Post提交的数据包
            }
        }
        curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
        curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回
        $tmpInfo = curl_exec($curl); // 执行操作
        curl_close($curl); // 关闭CURL会话
        return $tmpInfo; // 返回数据
    }

第三步:代公众号调用接口

 当微信公众号授权给第三方开发平台后,第三方开发平台执行某些操作时,如查询用户信息、发送模板消息等等,这就需要使用authorizer_access_token。这个token从获得开始,2小时内有效,如果需要继续使用授权,就需要在有效期内主动刷新token。但是当某些原因导致刷新token失败时,仍然可以使用authorizer_refresh_token(自获得起30天内有效)重新获得authorizer_access_token。这里的authorizer_access_token就可以代替普通的access_token去调你授权的接口了。

 

2018-09-17 11:55:45 Donatello_Jiang 阅读数 514
  • 微信支付开发-微信公众号开发12-微信开发php

    微信公众平台开发之微信支付开发是子恒老师《微信公众平台开发》视频教程的第12部。详细讲解了用php进行微信支付的开发。内容包含获取支付密钥,微信公众号支付开发,扫码支付,微信刷卡支付,异步处理支付结果等等。欢迎反馈,微信/QQ:68183131

    27766 人正在学习 去看看 秦子恒

这一段时间集成了微信第三方平台,坑还是蛮多的,记录一下集成过程和遇到的坑

授权流程

这里写图片描述
首先说一下过程,这个过程再微信开放平台上有。开放平台链接戳这
授权给第三方平台首先要获取预授权码,然后用预授权码引导用户授权,最后成功授权返回授权码。
这地方分三部分:
第一部分,如何获取预授权码。
第二部分,通过预授权码引导用户授权。
第三部分,返回授权码获取公众号信息。

一、如何获取预授权码

1、在第三方平台创建审核通过后,微信服务器会向其“授权事件接收URL”每隔10分钟定时推送component_verify_ticket。第三方平台方在收到ticket推送后也需进行解密(详细请见【消息加解密接入指引】),接收到后必须直接返回字符串success。

首先需要了解一个加解密,微信平台会有一个官方demo。接下来就是我遇到的第一个坑了。传过来的xml解析失败。
官网上解析的结果是这样的:
这里写图片描述
想要解析,你得获取微信服务器传过来的xml(传来的timestamp,nonce,encrypt_type,msg_signature可以直接用request.getParameter获取,但是xml需要读取request的body部分)。然后按照demo上那样解析。下面是我的方法:

/**
     * 微信第三方解码
     * @param msgMap
     * @param token
     * @param encodingAesKey
     * @param appId
     * @param timestamp
     * @param nonce
     * @return
     */
    public static String DecryptMsg(Map<String,Object> msgMap, String token, String encodingAesKey, String appId, String timestamp, String nonce){
        String result = null;
        try{
            WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appId);
            String msgSignature = (String)msgMap.get("msg_signature");
            String encrypt = (String)msgMap.get("encrypt_type");
            String postXml = (String)msgMap.get("post_xml");
            String format = postXml;
            String fromXML = String.format(format, encrypt);
            result = pc.decryptMsg(msgSignature, timestamp, nonce, fromXML);
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }

接收到的xml:

<xml>
    <AppId><![CDATA[xxxxxxxxxxxxxx]]></AppId>
    <Encrypt><![CDATA[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]]></Encrypt>
</xml>

解析出来之后:

<xml><AppId><![CDATA[xxxxxxxxxxxxxx]]></AppId>
<CreateTime>1537153589</CreateTime>
<InfoType><![CDATA[component_verify_ticket]]></InfoType>
<ComponentVerifyTicket><![CDATA[xxxxxxxxxxxxxxxxxxxxxxxxxxx]]></ComponentVerifyTicket>
</xml>

然后一直报一个空指针的错误,我发现下面的代码解析失败,因为XMLParse那个类找不到ToUserName的标签。
这里写图片描述
这里写图片描述

就没有<ToUserName>这个页签,所以最后我改成了<AppId>

2、第三方平台component_access_token是第三方平台的下文中接口的调用凭据,也叫做令牌(component_access_token)。每个令牌是存在有效期(2小时)的,且令牌的调用不是无限制的,请第三方平台做好令牌的管理,在令牌快过期时(比如1小时50分)再进行刷新。

这里没啥说的,就是需要做的就是维护好令牌(持久化,启动加载等)。到这就可以获取到预授权码(pre_auth_code)。

二、引导用户授权

这里写图片描述
走到这,你会忽然发现,你跳转的页面完全不能授权,一直提示你的域名不对。因为这就是我遇见的第二个坑。
这边的操作应该是这样的:你需要写一个页面来授权,让用户去点击那个授权按钮,按钮的链接就是你拼接的那个url,废话不多说,代码伺候:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<div>
    <input id="tolink" type="button" name="" value="授权跳转" /></div>
<script>
    var btn=document.getElementById("tolink");
    btn.onclick=function(){
        var ajax = new XMLHttpRequest();
        ajax.open('get','http://abcd.com/authorizedurl');
        ajax.send();
        ajax.onreadystatechange = function () {
            if (ajax.status==200) {
                window.location.href=ajax.responseText
            }
        }
    }
</script>
</body>
</html>

这个url:http://abcd.com/authorizedurl是用来获取拼接后的url的
这里写图片描述
方式二:点击移动端链接快速授权
第三方平台方可以生成授权链接,将链接通过移动端直接发给授权管理员,管理员确认后即授权成功。
这里写图片描述
然后点击页面的授权按钮,就可以正常进行授权了,授权的必须是公众的管理员(绑定公众号的那个),然后就可以授权了。

三、授权码获取公众号信息

该API用于获取授权方的基本信息,包括头像、昵称、帐号类型、认证类型、微信号、原始ID和二维码图片URL。

需要特别记录授权方的帐号类型,在消息及事件推送时,对于不具备客服接口的公众号,需要在5秒内立即响应;而若有客服接口,则可以选择暂时不响应,而选择后续通过客服接口来发送消息触达粉丝。
这里写图片描述
这里写图片描述
这里写图片描述

没有更多推荐了,返回首页