微信公众号JSAPI支付对接

微信公众号JSAPI支付对接事宜

相信各位程序员在开发过程中或多或少会有想关于支付对接的一些需求
本文主要总结在微信公众号对接过程中出现的一些注意事项:

公众号授权

公众号授权

代码块

例如:

/**
 * 获得引导关注者打开的页面地址
 * 
 * @param appid
 *            公众号的唯一标识
 * 
 * @param redirect_uri
 *            授权后重定向的回调链接地址
 * 
 * @param scope
 *            应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),
 * 
 *            snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、
 * 
 *            所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
 * 
 * @param state
 *            重定向后会带上state参数,开发者可以填写a-z A-Z 0-9的参数值
 * 
 */
public static String getLeadOAuthUrl(String appid, String redirect_uri, String scope, String state) {
    String do_redirect_uri = URLEncoder.encode(redirect_uri);
    String oauth_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid + "&redirect_uri="
            + do_redirect_uri + "&response_type=code&scope=" + scope + "&state=" + state + "#wechat_redirect";
    return oauth_url;
}


授权作用于scope:
snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid)
snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)。

第一步:获得引导关注者打开的页面地址

请求方法

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

若提示“该链接无法访问”,请检查参数是否填写错误,是否拥有scope参数对应的授权作用域权限。

尤其注意:由于授权操作安全等级较高,所以在发起授权请求时,微信会对授权链接做正则强匹配校验,如果链接的参数顺序不对,授权页面将无法正常访问
跳转回调redirect_uri,应当使用https链接来确保授权code的安全性。

参数说明:

参数是否必填说明
appid公众号的唯一标识
redirect_uri授权后重定向的回调链接地址,请使用urlEncode对链接进行处理
response_type返回类型,请填写code
scope应用授权作用域,
snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),
snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。
并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
state重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
#wechat_redirect无论直接打开还是做页面302重定向时候,必须带此参数


用户同意授权后

如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。

code说明 : code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。


第二步:通过code换取网页授权access_token
首先请注意,这里通过code换取的是一个特殊的网页授权access_token,与基础支持中的access_token(该access_token用于调用其他接口)不同。公众号可通过下述接口来获取网页授权access_token。如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。

尤其注意:由于公众号的secret和获取到的access_token安全级别都非常高,必须只保存在服务器,不允许传给客户端。后续刷新access_token、通过access_token获取用户信息等步骤,也必须从服务器发起。

请求方法

获取code后,请求以下链接获取access_token:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

参数说明

参数是否必填说明
appid公众号的唯一标识
secret公众号的appsecret
code填写第一步获取的code参数
grant_type填写为authorization_code


返回说明

正确时返回的JSON数据包如下:

{
“access_token”:”ACCESS_TOKEN”,
“expires_in”:7200,
“refresh_token”:”REFRESH_TOKEN”,
“openid”:”OPENID”,
“scope”:”SCOPE”
}

参数说明

参数描述
access_token网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
expires_inaccess_token接口调用凭证超时时间,单位(秒)
refresh_token用户刷新access_token
openid用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页,
也会产生一个用户和公众号唯一的OpenID
scope用户授权的作用域,使用逗号(,)分隔


参考:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842


UML 图:

Created with Raphaël 2.1.0 客户端 客户端 服务端 服务端 微信 微信 获得引导关注者打开的页面地址 返回页面地址 重定向URL 重定向到redirect_uri的网址上,并且带上code和state参数。 获取授权 返回openid

流程图:

Created with Raphaël 2.1.0 授权开始 引导关注者打开页面地址 确认授权? 授权结束 yes no

公众号支付


开发步骤


一、设置支付目录
请确保实际支付时的请求目录与后台配置的目录一致,否则将无法成功唤起微信支付。
在微信商户平台(pay.weixin.qq.com)设置您的公众号支付支付目录,设置路径:商户平台–>产品中心–>开发配置。公众号支付在请求支付的时候会校验请求来源是否有在商户平台做了配置,所以必须确保支付目录已经正确的被配置,否则将验证失败,请求支付不成功。

微信公众号支付-支付目录配置

规则:

1、所有使用公众号支付方式发起支付请求的链接地址,都必须在支付授权目录之下;
2、最多设置5个支付授权目录,且域名必须通过ICP备案;
3、头部要包含http或https,须细化到二级或三级目录,以左斜杠“/”结尾。

配置的具体规则是这样的:

1、

比如:调用以上JSSDK的页面地址为 http://a.b.com/pay/weixin/c.html

那么:授权目录配置为 http://a.b.com/pay/weixin/

2、

比如:调用以上JSSDK的页面地址为 http://a.b.com/pay/weixin

那么:授权目录配置为 http://a.b.com/pay/

3、

比如:调用以上JSSDK的页面地址为 http://a.b.com/pay

那么:授权目录配置为 http://a.b.com/

4、如果有QueryString,自动忽略

比如:调用以上JSSDK的页面地址为 http://a.b.com/pay/weixin/c.html?name=mango,

那么:授权目录配置为 http://a.b.com/pay/weixin/

比如:调用以上JSSDK的页面地址为 http://a.b.com/#/pay/weixin/c.html?name=mango,
那么:授权目录配置为 http://a.b.com/


二、设置授权域名

开发公众号支付时,在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。


业务流程

UML图

Created with Raphaël 2.1.0 客户端 客户端 商户后台系统 商户后台系统 微信支付系统 微信支付系统 用户发起下单 调用商户订单生成接口 生成商户订单 调用统一下单API 生成预支付订单 返回预支付订单信息(prepay_id) 生成JSAPI页面调用的支付参数并签名 返回支付参数 用户发起支付 JSAPI请求支付接口 校验参数的合法性 异步通知商户支付结果 告知微信通知处理结果 返回客户端支付结果,并发送微信消息提示 微信跳转回商户的H5页面 查询商户后台支付结果 调用查询API,查询支付结果 返回支付结果 返回支付结果 个性化页面提示

商户系统和微信支付系统主要交互:

1、商户server调用统一下单接口请求订单,api参见公共api【统一下单API】

2、商户server接收支付通知,api参见公共api【支付结果通知API】

3、商户server查询支付结果,api参见公共api【查询订单API】

参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_4


API列表

统一下单

参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
注意事项:trade_type=JSAPI时(即公众号支付),openid必填,openid获取即关注着关注公众号可以获取openid

微信内H5调起支付

参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
注意事项:签名生成方式

查询订单

参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2
注意事项:签名生成方式

代码:

package cn.com.anne.bqj.trade.web.action.wechat.oauth;

import java.net.URLEncoder;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import cn.com.anne.bqj.trade.web.action.wechat.common.HttpClientConnectionManager;
import cn.com.anne.bqj.trade.web.action.wechat.oauth.vo.OauthAccessToken;
import cn.com.anne.bqj.trade.web.action.wechat.oauth.vo.OauthUser;

/**
 * 微信网页授权管理类
 * 
 * @className: OAuthManage 
 * @date: 2017-11-10 10:07
 */
@SuppressWarnings("deprecation")
public class OAuthManage {

    // http客户端

    public static DefaultHttpClient httpclient;

    static {
        httpclient = new DefaultHttpClient();
        httpclient = (DefaultHttpClient) HttpClientConnectionManager.getSSLInstance(httpclient); // 接受任何证书的浏览器客户端

    }

    /**
     * 获得引导关注者打开的页面地址
     * 
     * @param appid
     *            公众号的唯一标识
     * 
     * @param redirect_uri
     *            授权后重定向的回调链接地址
     * 
     * @param scope
     *            应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),
     * 
     *            snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、
     * 
     *            所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
     * 
     * @param state
     *            重定向后会带上state参数,开发者可以填写a-z A-Z 0-9的参数值
     * 
     */
    public static String getLeadOAuthUrl(String appid, String redirect_uri, String scope, String state) {
        String do_redirect_uri = URLEncoder.encode(redirect_uri);
        String oauth_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid + "&redirect_uri="
                + do_redirect_uri + "&response_type=code&scope=" + scope + "&state=" + state + "#wechat_redirect";
        return oauth_url;
    }

    /**
     * 
     * 通过code换取网页授权access_token ,code是用户同意授权后获取的code,
     * 
     * 这里通过code换取的网页授权access_token,与基础支持中的access_token不同。
     * 
     * @param appid
     *            公众号的唯一标识
     * 
     * @param secret
     *            公众号的appsecret
     * 
     * @param code
     *            填写第一步获取的code参数
     * 
     */
    public static OauthAccessToken get_oauth_access_token_by_code(String appid, String secret, String code)
            throws Exception {
        HttpGet get = HttpClientConnectionManager
                .getGetMethod("https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + secret
                        + "&code=" + code + "&grant_type=authorization_code");
        HttpResponse response = httpclient.execute(get);
        String jsonStr = EntityUtils.toString(response.getEntity(), "utf-8");
        JSONObject object = JSON.parseObject(jsonStr);
        String access_token = object.getString("access_token");
        int expires_in = object.getIntValue("expires_in");
        String refresh_token = object.getString("refresh_token");
        String openid = object.getString("openid");
        String scope = object.getString("scope");
        OauthAccessToken oauthAccessToken = new OauthAccessToken(access_token, expires_in, refresh_token, openid,
                scope);
        return oauthAccessToken;
    }

    /**
     * 
     * 刷新access_token(如果需要)
     * 
     * @param appid
     *            公众号的唯一标识
     * 
     * @param refresh_token
     *            填写通过access_token获取到的refresh_token参数,
     * 
     *            refresh_token拥有较长的有效期(7天、30天、60天、90天),当refresh_token失效的后,需要用户重新授权。
     * 
     */
    public static OauthAccessToken get_oauth_access_token_by_refresh_token(String appid, String refresh_token)
            throws Exception {
        HttpGet get = HttpClientConnectionManager
                .getGetMethod("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=" + appid
                        + "&grant_type=refresh_token&refresh_token=" + refresh_token);
        HttpResponse response = httpclient.execute(get);
        String jsonStr = EntityUtils.toString(response.getEntity(), "utf-8");
        JSONObject object = JSON.parseObject(jsonStr);
        String access_token = object.getString("access_token");
        int expires_in = object.getIntValue("expires_in");
        String new_refresh_token = object.getString("refresh_token");
        String openid = object.getString("openid");
        String scope = object.getString("scope");
        OauthAccessToken oauthAccessToken = new OauthAccessToken(access_token, expires_in, new_refresh_token, openid,
                scope);
        return oauthAccessToken;
    }

    /**
     * 
     * 拉取用户信息(需scope为 snsapi_userinfo)
     * 
     * @param access_token
     *            网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     * 
     * @param openid
     *            用户的唯一标识
     * 
     */
    public static OauthUser get_oauth_user_info(String access_token, String openid) throws Exception {
        HttpGet get = HttpClientConnectionManager.getGetMethod("https://api.weixin.qq.com/sns/userinfo?access_token="
                + access_token + "&openid=" + openid + "&lang=zh_CN");
        HttpResponse response = httpclient.execute(get);
        String jsonStr = EntityUtils.toString(response.getEntity(), "utf-8");
        JSONObject object = JSON.parseObject(jsonStr);
        ;
        String new_openid = object.getString("openid");
        String nickname = object.getString("nickname");
        String sex = object.getString("sex");
        if ("1".equals(sex)) {
            sex = "男";
        } else if ("2".equals(sex)) {
            sex = "女";
        } else if ("0".equals(sex)) {
            sex = "未知";
        }
        String province = object.getString("province");
        String city = object.getString("city");
        String country = object.getString("country");
        String headimgurl = object.getString("headimgurl");
        JSONArray privilege = object.getJSONArray("privilege");
        OauthUser oauthUser = new OauthUser(new_openid, nickname, sex, city, country, province, headimgurl, privilege);
        return oauthUser;
    }
}
package cn.com.anne.bqj.trade.web.action.wechat.oauth.vo;

import com.alibaba.fastjson.JSONArray;

/**
 * 授权者基本信息
 * @className: OauthUser 
 * @date: 2017-11-10 10:06
 */
public class OauthUser {

    private String openid;//用户的标识,对当前公众号唯一

    private String nickname;//用户的昵称

    private String sex;//用户的性别,值为1时是男性,值为2时是女性,值为0时是未知

    private String city;//用户所在城市

    private String country;//用户所在国家

    private String province;//用户所在省份

    private String headimgurl;//用户头像,最后一个数值代表正方形头像大小

                               //(有0、46、64、96、132数值可选,0代表640*640正方形头像),

                               //用户没有头像时该项为空

    private JSONArray privilege;//用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)




    public OauthUser(String openid, String nickname,
            String sex, String city, String country,
            String province, String headimgurl,
            JSONArray privilege) {
        this.openid = openid;
        this.nickname = nickname;
        this.sex = sex;
        this.city = city;
        this.country = country;
        this.province = province;
        this.headimgurl = headimgurl;
        this.privilege = privilege;
    }
    public String getOpenid() {
        return openid;
    }
    public void setOpenid(String openid) {
        this.openid = openid;
    }
    public String getNickname() {
        return nickname;
    }
    public void setNickname(String nickname) {
        this.nickname = nickname;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public String getCountry() {
        return country;
    }
    public void setCountry(String country) {
        this.country = country;
    }
    public String getProvince() {
        return province;
    }
    public void setProvince(String province) {
        this.province = province;
    }
    public String getHeadimgurl() {
        return headimgurl;
    }
    public void setHeadimgurl(String headimgurl) {
        this.headimgurl = headimgurl;
    }
    public JSONArray getPrivilege() {
        return privilege;
    }
    public void setPrivilege(JSONArray privilege) {
        this.privilege = privilege;
    }
}
package cn.com.anne.bqj.trade.web.action.wechat.oauth.vo;

/**
 * 通过code换取网页授权access_token信息
 * @className: OauthAccessToken 
 * @date: 2017-11-10 10:06
 */
public class OauthAccessToken {

    private String access_token;//网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同

    private int expires_in;//access_token接口调用凭证超时时间,单位(秒)

    private String refresh_token;//用户刷新access_token

    private String openid;//用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的OpenID

    private String scope;//用户授权的作用域,使用逗号(,)分隔

    public OauthAccessToken(String accessToken, int expiresIn,
            String refreshToken, String openid, String scope) {
        this.access_token = accessToken;
        this.expires_in = expiresIn;
        this.refresh_token = refreshToken;
        this.openid = openid;
        this.scope = scope;
    }
    public String getAccess_token() {
        return access_token;
    }
    public void setAccess_token(String accessToken) {
        access_token = accessToken;
    }
    public int getExpires_in() {
        return expires_in;
    }
    public void setExpires_in(int expiresIn) {
        expires_in = expiresIn;
    }
    public String getRefresh_token() {
        return refresh_token;
    }
    public void setRefresh_token(String refreshToken) {
        refresh_token = refreshToken;
    }
    public String getOpenid() {
        return openid;
    }
    public void setOpenid(String openid) {
        this.openid = openid;
    }
    public String getScope() {
        return scope;
    }
    public void setScope(String scope) {
        this.scope = scope;
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值