admin管理员组文章数量:1532440
文章目录
- 微信公众平台开发:微信网页授权和微信支付
- 1. 微信网页授权
- 1.1 微信网页授权和微信支付有什么关系?
- 1.2 微信授权流程
- 1.2.1 手工实现微信网页授权
- 1.2.2 利用第三方SDK开发
- 2. 微信支付
- 2.1 微信支付场景
- 2.2 JSAPI支付
- 2.3 微信支付业务流程
- 2.4 代码实现
- 2.4.1 统一下单接口
- 2.4.2 微信内H5调起支付
- 2.4.3 异步通知商户支付结果
微信公众平台开发:微信网页授权和微信支付
问题1:微信公众平台网页授权和微信开放平台网页授权什么关系?
① 微信公众平台
微信公众平台是微信公众账号申请入口和管理后台。商户可以在公众平台提交基本资料、业务资料、财务资料申请开通微信支付功能。
平台入口:http://mp.weixin.qq。
② 微信开放平台
微信开放平台是商户APP接入微信支付开放接口的申请入口,通过此平台可申请微信APP支付。
平台入口:http://open.weixin.qq。
问题2:微信支付和微信授权什么关系?
这篇文章,主要想聊一下微信公众平台的开发,下一篇聊微信开放平台授权
1. 微信网页授权
上一篇文章中已经详细的讲了微信网页授权的开发步骤,这里结合上篇文章,主要讲讲两种授权方式
1.1 微信网页授权和微信支付有什么关系?
① 如果用户第一次使用美团外卖的公众号,进入美团外卖公众号后选择一家小店的商品加入购物车,然后点击去结算
②点击结算后,出现一个要求用户授权的界面(在公众号进行微信支付功能,目的是获取用户的openid,只有拥有用户的openid才能进行后序的微信支付行为)
③ 当用户同意授权后,这时就会微信支付系统就会创建一个预付单
④ 点击去支付,完成微信支付
1.2 微信授权流程
授权流程主要涉及前面两个步骤:
关于微信授权的流程上一篇有具体详细的解释,这里梳理大致思路:https://developers.weixin.qq/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
① 用户同意授权,获取Code
-
引导用户访问:https://open.weixin.qq/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect (参数官方文档,很清楚)
-
当用户访问这个连接时就会跳转到 图② 微信网页授权页面,即美团外卖申请获取你的公开信息。
-
当用户点击
允许
后,代表同意授权,页面将跳转到:redirect_uri/?code=CODE&state=STATE。
② 通过code换取网页授权access_token(获取access_token的同时还会获取openid)
- 从
redirect_uri/?code=CODE&state=STATE
这个请求的请求参数中可以获得code
- 获取code后,请求以下链接获取access_token: https://api.weixin.qq/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
③ 通过access_token和openid拉取用户信息(要求scope=snsapi_userinfo)
- 从请求的返回结果中,便可以获得
access_token 和 openid
- 如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。
- 请求方法:https://api.weixin.qq/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
- 从请求方法的返回结果即可得到用户的信息
1.2.1 手工实现微信网页授权
@Controller
@RequestMapping("/weixin")
public class WiexinOathu {
// 1、当用户请求这个方法时,我们需要引导用户打开url这个地址,请求授权
@RequestMapping("/oauth")
public void autho(HttpServletResponse response) {
//redirect_uri
String path = "http://heng.nat300.top/sell/weixin/invoke";
try {
URLEncoder.encode(path,"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String url = "https://open.weixin.qq/connect/oauth2/authorize?" +
"appid=wxd0430ca3dde79d52" +
"&redirect_uri="+ path +
"&response_type=code" +
"&scope=snsapi_userinfo" +
"&state=STATE" +
"#wechat_redirect";
try {
response.sendRedirect(url);
} catch (IOException e) {
e.printStackTrace();
}
}
//用户同意授权后,页面回调接口路径:http://heng.nat300.top/sell/weixin/invoke
@RequestMapping("/invoke")
public void oauthInvoke(HttpServletRequest request){
//2、从回调地址中获取code
String code = request.getParameter("code");
String state = request.getParameter("state");
System.out.println("code="+code);
//3、认证服务器,获取code后,请求以下链接获取网页授权access_token
String url = "https://api.weixin.qq/sns/oauth2/access_token?" +
"appid=wxd0430ca3dde79d52" +
"&secret=46088f58a30ffb26b12d921735d26691" +
"&code="+code +
"&grant_type=authorization_code";
//认证服务器响应的json数据,获取access_token 和 openid
JSONObject jsonObject = WeiXinUtil.httpRequest(url,"POST",null);
String access_token = jsonObject.getString("access_token");
String openid = jsonObject.getString("openid");
System.out.println("access_token="+access_token);
System.out.println("openid="+openid);
//4、根据openid和access_token获取资源信息
String URLUserinfo = "https://api.weixin.qq/sns/userinfo?" +
"access_token=" + access_token +
"&openid="+openid+
"&lang=zh_CN";
JSONObject jsonobj = WeiXinUtil.httpRequest(URLUserinfo,"GET",null);
System.out.println(jsonobj);
}
}
在微信客户端访问:http://heng.nat300.top/sell/weixin/oauth
code=061Lgdll21xhn54RXCnl2EO7F33Lgdlg
---->httpRequest传回的信息是:{"access_token":"36_Sf82qk9BPk-3cQmqlmmqlQNtBKWcd6Qldslgj75fRzhw0rkzmlLWvd8NLUKodlmhavSq3CVaMyvOjjIsQufKUw","expires_in":7200,"refresh_token":"36_TOv7gFw840QLTNe_GmzAA6sDtDOo-2TlpkzuAM8t5GXVhu5SXSpOMhW83tYu9BqsJdxRb8w7p90oEK_1Uhz7FA","openid":"oaaZ7txEYH7kpwRVfBCc9hych-CE","scope":"snsapi_userinfo"}
access_token=36_Sf82qk9BPk-3cQmqlmmqlQNtBKWcd6Qldslgj75fRzhw0rkzmlLWvd8NLUKodlmhavSq3CVaMyvOjjIsQufKUw
openid=oaaZ7txEYH7kpwRVfBCc9hych-CE
---->httpRequest传回的信息是:{"openid":"oaaZ7txEYH7kpwRVfBCc9hych-CE","nickname":"别惹我","sex":2,"language":"zh_CN","city":"南京","province":"江苏","country":"中国","headimgurl":"http://thirdwx.qlogo/mmopen/vi_32/DYAIOgq83eoMGGJX7Etibc0he241duGKMSWickeibOUAGEC0OeaxuzMXaAOhbK3KSBViaNibf4ZbTBoABTqgSc0ia3bA/132","privilege":[]}
{"openid":"oaaZ7txEYH7kpwRVfBCc9hych-CE","nickname":"别惹我","sex":2,"language":"zh_CN","city":"南京","province":"江苏","country":"中国","headimgurl":"http://thirdwx.qlogo/mmopen/vi_32/DYAIOgq83eoMGGJX7Etibc0he241duGKMSWickeibOUAGEC0OeaxuzMXaAOhbK3KSBViaNibf4ZbTBoABTqgSc0ia3bA/132","privilege":[]}
1.2.2 利用第三方SDK开发
第三方SDKGithub地址:https://github/Wechat-Group/WxJava/wiki/MP_OAuth2%E7%BD%91%E9%A1%B5%E6%8E%88%E6%9D%83
@Controller
@RequestMapping("/wechat")
@Slf4j
public class WeChatController {
@Autowired
private WxMpService wxMpService;
@Autowired
private WxMpService wxOpenService;
//http://heng.nat300.top/sell/wechat/authorize
@GetMapping("/authorize")
public String authorize(){
//returnUrl就是用户授权同意后回调的地址
String returnUrl="http://heng.nat300.top/sell/wechat/userInfo";
//引导用户访问这个链接,进行授权
String url = wxMpService.oauth2buildAuthorizationUrl(returnUrl, WxConsts.OAUTH2_SCOPE_USER_INFO, URLEncoder.encode(returnUrl));
return "redirect:"+url;
}
//用户授权同意后回调的接口,从请求参数中获取code
@GetMapping("/userInfo")
public String userInfo(@RequestParam("code")String code,@RequestParam("state")String returnUrl){
WxMpOAuth2AccessToken wxMpOAuth2AccessToken = new WxMpOAuth2AccessToken();
try {
//通过code换取access_token
wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code);
} catch (WxErrorException e) {
log.error("【微信网页授权】{}",e);
throw new SellException(ResultEnum.WECHAT_MP_ERROR.getCode(),e.getError().getErrorMsg());
}
String openId = wxMpOAuth2AccessToken.getOpenId();
log.info("openid={}", openId);
//这个地址可有可无,反正只是为了拿到openid,但是如果没有会报400错误,为了好看随便返回一个百度的地址
returnUrl = "http://www.baidu";
return "redirect:" + returnUrl;
}
}
在微信客户端访问:http://heng.nat300.top/sell/wechat/authorize
控制台打印openid,同时网页最终返回至returnUrl = “http://www.baidu”;
2. 微信支付
开发JSAPI支付时,在统一下单接口中要求必传用户openid,在微信授权时我们已经获取了用户的openid,下面就可以进行微信支付的流程了。
2.1 微信支付场景
商户已有H5商城网站,用户通过消息或扫描二维码在微信
内打开网页时,可以调用微信支付
完成下单购买的流程。
① 商户通过自定义菜单吸引用户点击进入商户网页, 用户选择购买,完成选购流程,创建预支付订单;
② 调起微信支付控件,用户开始输入支付密码;
③ 密码验证通过,支付成功。商户后台得到支付成功的通知。
④ 返回商户页面,显示购买成功。该页面由商户自定义
⑤ 微信支付公众号下发支付凭证。
⑥ 商户公众号下发消息,提示发货成功。该步骤可选。
以下是支付场景的交互细节,请认真阅读,设计商户页面的逻辑:
① 用户打开商户网页选购商品,发起支付,在网页通过JavaScript调用getBrandWCPayRequest接口,发起微信支付请求,用户进入支付流程。
② 用户成功支付点击完成按钮后,商户的前端会收到JavaScript的返回值。商户可直接跳转到支付成功的静态页面进行展示。
③ 商户后台收到来自微信开放平台的支付成功回调通知,标志该笔订单支付成功。
2.2 JSAPI支付
JSAPI支付是指商户通过调用微信支付提供的JSAPI接口,在支付场景中调起微信支付模块完成收款。应用场景有:
线下场所:调用接口生成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付
公众号场景:用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
PC网站场景:在网站中展示二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付
如没有设置支付目录,无法调起支付接口:
2.3 微信支付业务流程
① 商户生成图文消息或二维码展示为用户,用户点击消息或扫描二维码在微信浏览器里面打开H5网页中通过JavaScript调用getBrandWCPayRequest接口,发起微信支付请求,请求生成支付订单。
https://pay.weixin.qq/wiki/doc/api/jsapi.php?chapter=7_7&index=6
② 商户后台生成商户订单(业务订单),并调用微信支付系统的统一下单接口API生成预支付单(支付订单),返回预支付单信息(商户根据公众号相关信息、用户相关信息、商品相关信息,去获取一个prepay_id和paySign)
https://pay.weixin.qq/wiki/doc/api/jsapi.php?chapter=9_1
③ 商户将获取到的前端所需的支付参数,传给前端用户,用户点击发起支付,微信支付系统验证参数合法性等信息后返回验证结果,要求用户支付授权(确认支付,输入密码)
④ 用户输入密码后,微信支付系统验证授权并异步通知商户支付结果(用户已经成功下单并支付了,你赶紧处理一下)
⑤ 商户处理完成后(将订单状态修改为已支付),需要将处理结果告知微信支付系统,否则微信支付系统会一直向商户发送异步通知
https://pay.weixin.qq/wiki/doc/api/jsapi.php?chapter=9_7
⑥ 微信支付公众号下发支付凭证,返回支付结果,展示消息给用户
可能会出现问题的地方:
问题1:当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知,怎么办?
支付完成后,微信会把相关支付结果及用户信息通过数据流的形式发送给商户,商户需要接收处理,并按文档规范返回应答。
1、同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
2、后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信会判定本次通知失败,重新发送通知,直到成功为止(在通知一直不成功的情况下,微信总共会发起多次通知),但微信不保证通知最终一定能成功。
3、在订单状态不明或者没有收到微信支付结果通知的情况下,建议商户主动调用微信支付【查询订单API】确认订单状态。
因此,由于网络异常或者系统的波动,可能会导致用户支付成功,但是商户侧未能成功接收到支付结果通知,进而显示订单未支付的情况。商户侧的订单状态更新不及时,容易造成用户投诉,甚至是重复支付的情况发生。
解决方案:
① 商户APP或者前端页面收到支付返回时,商户需要调用商户查单接口确认订单状态,并把查询结果展示给用户。
② 商户后台需要准确、高效地处理微信支付发送的异步支付结果通知,并按接口规范把处理结果返回给微信支付。
③ 商户后台未收到异步支付结果通知时,商户应该主动调用《微信支付查单接口》,同步订单状态。
④ 商户在T+1日从微信支付侧获取T日的交易账单,并与商户系统中的订单核对。如出现订单在微信支付侧成功,但是在商户侧未成功的情况,商户需要给用户补发货或者退款处理。
如果商户查单接口返回订单未支付,需要提醒用户“稍后进入订单管理页核实订单状态,不要重复发起支付”。商户后端需要及时获取、更新订单状态,实现逻辑参考【后端服务处理】。当用户再次进入订单管理页面,对未支付的订单再次发起支付时,商户应该使用原单号发起,不要更换支付单号,避免用户重复支付。
2.4 代码实现
2.4.1 统一下单接口
@Slf4j
@Service
public class PayServiceImpl implements PayService {
//封装了的方法,也是很关键的方法
@Autowired
private BestPayServiceImpl bestPayService;
private static final String ORDER_NAME="微信购物订单";
//创建一个支付订单,注意是支付订单,不是业务订单
@Override
public PayResponse create(OrderDTO orderDTO) {
//封装支付请求参数
PayRequest payRequest = new PayRequest();
//openid
payRequest.setOpenid(orderDTO.getBuyerOpenid());
//订单总金额OrderAmount
payRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue());
//订单id
payRequest.setOrderId(orderDTO.getOrderId());
//订单名称
payRequest.setOrderName(ORDER_NAME);
//支付类型,代表微信公众号支付
payRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5);
//调用支付接口发起支付
bestPayService.pay(payRequest);
log.info("【微信支付】request = {}", JsonUtil.toJson(payRequest));
//支付后的响应结果
PayResponse payResponse = bestPayService.pay(payRequest);
log.info("【微信支付】response】= {}",JsonUtil.toJson(payResponse));
return payResponse;
}
}
@Controller
@RequestMapping("/pay")
public class PayController {
//订单业务层接口
@Autowired
private OrderService orderService;
//支付业务层接口
@Autowired
private PayService payService;
//创建订单
@GetMapping("/create")
public ModelAndView create(@RequestParam("orderId")String orderId,
@RequestParam("returnUrl")String returnUrl,
Map<String,Object> map) throws UnsupportedEncodingException {
//1.根据订单id查询订单
OrderDTO orderDTO = orderService.findOnes(orderId);
if(orderDTO==null){
throw new SellException(ResultEnum.ORDER_NOT_EXIST);
}
//2. 发起支付
PayResponse payResponse = payService.create(orderDTO);
map.put("payResponse",payResponse);
map.put("returnUrl",returnUrl);
//跳转到模板引擎页面create.ftlh
return new ModelAndView("pay/create",map);
}
}
2.4.2 微信内H5调起支付
<script>
function onBridgeReady(){
//动态的从ModelAndView中提取
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
//公众号名称,由商户传入
"appId": "${payResponse.appId}",
//时间戳,自1970年以来的秒数
"timeStamp": "${payResponse.timeStamp}",
//随机串
"nonceStr": "${payResponse.nonceStr}",
"package":"${payResponse.packAge}",
//微信签名方式
"signType": "MD5",
//微信签名
"paySign": "${payResponse.paySign}"
},
function(res){
// 在用户支付成功后跳转的通知地址
location.href="${returnUrl}";
});
}
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
</script>
2.4.3 异步通知商户支付结果
@Service
@Slf4j
public class PayServiceImpl implements PayService {
@Autowired
private BestPayServiceImpl bestPayService;
@Autowired
private OrderService orderService;
//异步通知
@Override
public PayResponse notify(String notifyData) {
//异步通知响应结果
PayResponse payResponse = bestPayService.asyncNotify(notifyData);
log.info("【微信支付】异步通知, payResponse={}", JsonUtil.toJson(payResponse));
//查询订单
OrderDTO orderDTO = orderService.findOne(payResponse.getOrderId());
//判断订单是否存在
if (orderDTO == null) {
log.error("【微信支付】异步通知, 订单不存在, orderId={}", payResponse.getOrderId());
throw new SellException(ResultEnum.ORDER_NOT_EXIST);
}
//判断金额是否一致(0.10 0.1)
if (!MathUtil.equals(payResponse.getOrderAmount(), orderDTO.getOrderAmount().doubleValue())) {
log.error("【微信支付】异步通知, 订单金额不一致, orderId={}, 微信通知金额={}, 系统金额={}",
payResponse.getOrderId(),
payResponse.getOrderAmount(),
orderDTO.getOrderAmount());
throw new SellException(ResultEnum.WXPAY_NOTIFY_MONEY_VERIFY_ERROR);
}
//修改订单的支付状态
orderService.paid(orderDTO);
return payResponse;
}
}
@Controller
@RequestMapping("/pay")
public class PayController {
@Autowired
private OrderService orderService;
@Autowired
private PayService payService;
// 微信异步通知
@PostMapping("/notify")
public ModelAndView notify(@RequestBody String notifyData) {
payService.notify(notifyData);
//商户返回给微信支付系统处理结果
return new ModelAndView("pay/success");
}
}
版权声明:本文标题:微信公众平台:微信网页授权和微信支付 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/xitong/1725776271a1041891.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论