微信JSAPI支付 模式
JSAPI支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付。应用场景有:
◆ 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
◆ 用户的好友在朋友圈、聊天窗口等分享商家页面连接,用户点击链接打开商家页面,完成支付
◆ 将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付
开发文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
微信公众号
做微信的JSAPI支付,首先要有一个公众号,而且需要是服务号,订阅号的话有很多接口是不支持的。但是对于个人开发者而言,申请又不能申请服务号,那么这个时候就可以使用微信的沙盒环境(类似于支付宝的沙箱环境),使用该环境可以进行申请到一个公众服务号。
地址:http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
我们在进行公众号网页开发的时候,想要获取用户的基本信息,首先得获取到access_token,从access_token里我们要拿出用户的openid来作为用户在我们系统中的唯一标识,以及通过openid可以保证该用户的只能访问到与其openid相对应的数据,防止越权漏洞。因此,我们需要对网页进行授权,否则是无法在获取到用户的openid的。
为了能够与微信进行联调,所以我们需要使用到内网穿透工具,让外网能够访问到我们内网的接口地址。我之前写了一篇关于如何使用natapp进行内网穿透的文章,这里就不再过多赘述这些基本的工具使用了。
在本文中会介绍两种获取openid的方式:自己根据文档接口手写代码获取、使用第三方已经封装好的SDK来获取。第三方SDK的GitHub地址:
说明与注意:
(1)网页授权分为两种:
- 一种为只获取openid (基本授权 snsapi_base)
- 一种为获取用户全部信息,仅限账户、昵称以及头像等信息 (高级授权 snsapi_userinfo)
(2)你的公众号必须为认证的订阅号或者认证的服务号或申请官方提供的测试号,否则没有此接口权限。
(3)你要配置好回调域名:即用户点击网址获取用户信息后打开哪个域名。
测试号申请及设置外网域名
我们先来申请一个官方的测试号,微信测试账号申请地址如下:
扫码登陆登录成功后,即可获取测试号信息:
下拉页面找到测试号二维码那一栏,然后使用微信扫描二维码关这个注测试公众号。关注成功后,稍等一会就会显示出用户列表。如下:
配置完测试号后,继续下拉页面找到网页帐号的设置,点击修改:
然后将我们的在natapp里注册外网域名配置进去:
注:由于是测试号,这一块不会严格去检测这个域名,如果是使用真实的公众账号进行配置时,会对配置的域名进行检测。但是即便是测试,也要保证这个域名是可用的,不然就无法进行联调了。
手动获取openid
(1)第一步,用户同意授权,获取code参数:
完成以上测试账号的配置及微信网页授权后,创建一个SpringBoot的Web工程,并打开natapp的客户端。关于获取code这一步要仔细查看这部分文档,我就不过多解释了:
从文档中可以看到,需要让用户在访问页面的过程中打开一个特定的链接,然后用户授权成功后会跳转回调redirect_uri参数里指定的链接,这样我们就能获取code参数了。so,在工程中创建controller包,在该包里新建一个 WeixinController 控制器,我们来写一个这样的跳转回调接口给微信进行回调,看看能否获取到回调时传递的code参数:
/**
* @author Declan
* @date 2019/05/29 14:17
*/
@RestController
@RequestMapping("/weixin")
@Slf4j
public class WeixinController {
@RequestMapping(value = "/auth", method = RequestMethod.GET)
public void auth(@RequestParam("code") String code){
log.info("进入auth方法...");
log.info("code = {}", code);
}
}
编写完接口代码之后,我们来基于文档中给出的链接进行修改,我这里填写了appid、redirect_uri以及scope三个参数,其余的参数我保持了默认。所以填写完成后的连接如下:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx58688xxxxa56&redirect_uri=https://declan.mynatapp.cc/weixin/auth&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect
如果需要获取用户信息的话,则需要把scope参数改为snsapi_userinfo,用户访问这个连接时就会提示用户登录。但是如果是使用的测试账号的appid,则不会弹出登录界面。使用真实的公众号账号的appid才会弹出登录界面,我们使用的是测试账户所以是不会弹出登录界面的:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx58688xxxxa56&redirect_uri=https://declan.mynatapp.cc/weixin/auth&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect
然后复制该链接到微信中打开,一定要在微信中打开,使用pc端的浏览器或手机端的浏览器是无法打开该连接的,打开后也会提示你在微信中打开。在微信访问该地址成功后,控制台会输出如下内容,其中code参数获取成功就代表这一步测试是成功的:
2019-05-29 14:45:18.476 - 进入auth方法...
2019-05-29 14:45:18.476 - code = 0219vS7617imVS1dqe861eXP7619vS7j
但是code是一次性的,不能直接使用,所以接下来的第二步我们需要拿这个code去换取access_token
(2)第二步使用code来换取access_token
关于使用code换取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以及code。由于code参数我们需要在auth接口被微信调用时获取,所以修改auth接口的代码如下:
/**
* @author Declan
* @date 2019/05/29 14:17
*/
@RestController
@RequestMapping("/weixin")
@Slf4j
public class WeixinController {
@RequestMapping(value = "/auth", method = RequestMethod.GET)
public void auth(@RequestParam("code") String code){
log.info("进入auth方法...");
log.info("code = {}", code);
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx3761xxxxx1ac6c76&secret=f12caef4d31axxxxx819c4c405541f4&code=" + code + "&grant_type=authorization_code";
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject(url, String.class);
log.info("response = {}", response);
}
}
重新启动SpringBoot,然后再次到微信里访问之前那个获取code参数的链接,访问成功后,微信会进行会回调,我们的接口就会接收到一个json格式的返回信息,其中就有我们需要的openid。我这里是访问成功的,所以获取到了这个返回信息,将其格式化后如下:
{
"access_token": "9_lZjiw19HhAga4cZ9C4rmtN50AkLzCwLVnlp8msI9xvCCWMIR88CmZAtB-IN1SGOwR_nh76V100vaTF8qihmBnfDx7XYynqHurb4UiqSZTDo",
"expires_in": 7200,
"refresh_token": "9_oMBSt0Bu-UUbVajpfo4OdyPwIfYbN1lut8KHjT4JEMTTbITtPnBFcQopP-SIzYvWHQuEdeQRcVHt1pRJHwvDhecDrd5pGz5HPckOCeHvmxg",
"openid": "ok_HP0txxYBxxxxxxd1kZGz6A",
"scope": "snsapi_base"
}
从回调的数据中可以看到,openid包含在了这个数据里,这样我们就获取到了用户的openid,到此为止,我们手工获取OpenID的方式就演示完成了。接下来我们当然就是上工具了,有一个很优秀的第三方SDK,在文章的开头也给出了GitHub的链接,接下来使用该SDK来对接微信网页授权。
使用第三方SDK获取openid
首先需要将SDK集成到我们的项目中,在pom.xml文件中加入如下依赖配置:
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>3.4.0</version>
</dependency>
关于该SDK的一些文档地址:
在application.yml中配置微信的相关参数:
wechat:
mpAppId: wx586xxxxxxxc6a56
mpAppSecret: ee72cdexxxxxxxxxxc9089b8b278
在工程中新建config包,用于存放一些配置类,在该包下新建 WechatAccountConfig 类,用于加载我们的配置在.yml文件里的AppId以及AppSecret属性。代码如下:
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author Declan
* @date 2019/05/29 19:32
*/
@Component
// 载入配置文件里前缀为wechat的配置信息
@ConfigurationProperties(prefix = "wechat")
@Data
public class WechatAccountConfig {
private String mpAppId;
private String mpAppSecret;
}
接着再创建一个 WechatMpConfig 类,配置微信网页授权时所需的信息。代码如下:
import me.chanjar.weixin.mp.api.WxMpConfigStorage;
import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* 微信网页授权信息配置类
* @author Declan
* @date 2019/05/29 19:35
*/
@Component
public class WechatMpConfig {
@Autowired
private WechatAccountConfig wechatAccountConfig;
/**
* 配置WxMpService所需信息
* @return
*/
@Bean
public WxMpService wxMpService(){
WxMpService wxMpService = new WxMpServiceImpl();
// 设置配置信息的存储位置
wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
return wxMpService;
}
/**
* 配置appID和appsecret
* @return
*/
@Bean
public WxMpConfigStorage wxMpConfigStorage(){
// 使用这个实现类则表示将配置信息存储在内存中
WxMpInMemoryConfigStorage wxMpInMemoryConfigStorage = new WxMpInMemoryConfigStorage();
wxMpInMemoryConfigStorage.setAppId(wechatAccountConfig.getMpAppId());
wxMpInMemoryConfigStorage.setSecret(wechatAccountConfig.getMpAppSecret());
return wxMpInMemoryConfigStorage;
}
}
在controller包下,新建一个 WechatController 控制器类,该类的接口用于对接微信网页授权,授权完成后,需跳转到我们指定的网页中。代码如下:
import com.imooc.enums.ResultEnum;
import com.imooc.exception.SellException;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.net.URLEncoder;
/**
* @author Declan
* @date 2019/05/29 19:44
*/
@Controller
@RequestMapping("/wechat")
@Slf4j
public class WechatController {
@Autowired
private WxMpService wxMpService;
/**
* 获取code参数
*
* @param returnUrl 需要跳转的url
* @return
*/
@GetMapping("/authorize")
public String authorize(@RequestParam("returnUrl") String returnUrl) {
// 暂时将我们的回调地址硬编码在这里,方便一会调试
String url = "https://declan.mynatapp.cc/wechat/userInfo";
// 获取微信返回的重定向url
String redirectUrl = wxMpService.oauth2buildAuthorizationUrl(url, WxConsts.OAuth2Scope.SNSAPI_BASE, URLEncoder.encode(returnUrl));
log.info("【微信网页授权】获取code,redirectUrl = {}", redirectUrl);
return "redirect:" + redirectUrl;
}
/**
* 使用code参数换取access_token信息,并获取到openid
*
* @param code
* @param returnUrl
* @return
*/
@GetMapping("/userInfo")
public String userInfo(@RequestParam("code") String code, @RequestParam("state") String returnUrl) {
WxMpOAuth2AccessToken wxMpOAuth2AccessToken;
try {
// 使用code换取access_token信息
wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code);
} catch (WxErrorException e) {
log.error("【微信网页授权】异常,{}", e);
throw new SellException(ResultEnum.WECHAT_MP_ERROR, e.getError().getErrorMsg());
}
// 从access_token信息中获取到用户的openid
String openId = wxMpOAuth2AccessToken.getOpenId();
log.info("【微信网页授权】获取openId,openId = {}", openId);
// 重定向到我们要跳转的页面
return "redirect:" + returnUrl + "?openid=" + openId;
}
}
完成以上代码的编写后,重新启动SpringBoot,启动完成后,在微信里访问如下链接,我这里随便指定了一个需要跳转的url,是我的OSC地址:
https://declan.mynatapp.cc/wechat/authorize?returnUrl=https://my.oschina.net/Declan
我这里在微信访问该连接后是跳转成功的,跳转到了我们在链接里指定的OSC地址:
控制台输出的日志内容如下:
2019-05-29 19:58:52.791 INFO 11900 --- [0.1-8080-exec-6] com.declan.controller.WechatController : 【微信网页授权】获取code,redirectUrl = https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx5868xxxxx6a56&redirect_uri=http%3A%2F%2declan.mynatapp.cc%2Fwechat%2FuserInfo&response_type=code&scope=snsapi_base&state=https%3A%2F%2Fmy.oschina.net%2FDeclan&connect_redirect=1#wechat_redirect
2019-05-29 19:58:54.229 INFO 11900 --- [0.1-8080-exec-8] com.declan.controller.WechatController : 【微信网页授权】获取openId,openId = oLPQF1YH6M-7xxxxxxx6f9KwYp1g
我们可以将手机上的url复制出来,如下:
https://my.oschina.net/Declan?openid=oLPQF1YH6M-xxxxxx6f9KwYp1g
可以看到,openid成功获取,并且页面也成功跳转了,我们这一步的测试也就通过了,以上就是如何使用这个第三方的SDK获取openid。到此为止,两种获取用户openid的方式都介绍了,至于在实际项目里使用哪一种方式就看自己的实际情况了,我这里是使用第三方的SDK,毕竟真实的项目模块比较多,涉及的微信特性也比较多,如果没有特殊要求的话也没必要重复去造轮子。