需求
想在微信浏览器里面实现微信支付. 查看微信支付文档, 发现要用微信JSAPI公众号支付, 微信H5支付是不能实现的.
自己在实现的时候踩了许多坑, 花费了很多时间. 特此记录, 希望能帮助到看到该文章的开发者.
开发环境
后端SpringBoot, 前端VUE
准备工作
1.首先我们要在微信公众号平台和微信商户平台 注册账号
2.准备JSAPI支付, 和调用微信统一下单的时候, 所需要的信息.
appid: 这个appid是公众号的appId.
重要的一步. 这里appid如果想使用, 必需要在微信商户号那里关联并授权. 可根据 查看指引 这一步如何操作
appsecret: 开发者密码. 可在微信公众号平台, 自己设置. 切记,设置后保存一下, 因为没有地方可以回显.
mer_id: 微信商户号id
key: 微信商户号API秘钥. 配置完之后也需要保存, 因为没有地方可以回显
body: 订单备注
nonce_str: 即用来标识一笔单, 是个随机数, 可自行实现.
openid: 这个重中之重, 需要前后端一起配合, 后面讲解如何获取.
out_trade_no: 订单编号
spbill_create_ip: ip地址,可以获取当前请求的ip地址, 实现方式有很多, 可自行百度实现.
total_fee: 支付金额, 需要注意这个金额的单位是分, 所以在使用的时候, 要自己订单金额 *100, 可能有时候失败, 也是因为这个原因.
sign_type: 加签方式, 也就是生成 sign 的方式, sign后面会提到, 这里默认是MD5, 建议也写上MD5, 方便调起JSAPI支付的时候, 做统一.
trade_type: 调用的支付方式, 因为这里是JSAPI支付, 所以值为 JSAPI
notify_url: 微信支付的回调, 用于支付成功后处理的业务逻辑. 这个地址必须是外网可以访问的地址, 如果支付成功没有进入回调, 可能就是这里的原因.
sign: 签名, 这个需要程序自己生成, 是根据上面的信息生成, 具体方式可看下面的代码.
其中openid还没有获取到, 最费劲的也就是它了. 下面给出获取openid的步骤, 需要前后端一起配合. 具体实现流程, 可根据自身情况.
获取openid
用户同意授权, 获取code, 这里获取code的方式, 是我们给前端实现了.
https
://open.weixin
.qq
.com
/connect
/oauth2
/authorize
?appid
=appid
&redirect_uri
=redirect_uri
&response_type
=code
&scope
=SCOPE
&state
=STATE#wechat_redirect
根据code获取openid
有一点需要注意. 这里的code有效时间是5分钟, 且只能用一次, 如果获取openid失败了, 看一下这个code是不是失效了,或者重复使用了, 我们之前前端获取openid失败, 就是因为没有刷新, code重复使用了.
@ApiOperation("根据code获取openId")@GetMapping("openid")public Result getOpenIdByCode(@RequestParam String code
){log
.info("根据code:{}获取openId", code
);String getopenid_url
= "https://api.weixin.qq.com/sns/oauth2/access_token";String param
= "appid="+appid
+"&secret="+secret
+"&code="+code
+"&grant_type=authorization_code";String openIdStr
= HttpUtils.sendGet(getopenid_url
, param
);JSONObject json
= JSONObject.parseObject(openIdStr
);log
.info("查询结果: "+json
.toString());String openId
= json
.getString("openid");return Result.success(ResultEnum.SELECT_SUCCESS
, openId
);}
目前为止, 我们已经获取到了微信JSAPI支付所需要的全部数据, 如果您能跟着实现到了这里, 就已经成功了百分之八十.
下面开始代码实现
后端接口准备. 编写支付接口中的JSAPI. 这里主要是为了给前端调起JSAPI支付所需要的数据. 也就是后面返回的这几个值
appId: 也就是这里一直用的appid
timeStamp: 注意,这里是时间戳, 我们之前调起失败, 也是因为这里没用时间错
nonceStr: 随机字符
signType: 签名方式, 和我们调用统一下单生成的签名方式一样. 也要设置为MD5
package: 包名. 注意: 不能写 package, 因为是关键字
paySign: 签名
String requestIp
= CommonUtils.getIpAddr(request
);SortedMap<String,String> params
= new TreeMap<>();params
.put("appid", appid
);params
.put("body", remark
);params
.put("mch_id", merId
);params
.put("nonce_str", CommonUtils.generateUUID());params
.put("openid", openId
);params
.put("out_trade_no", out_trade_no
);params
.put("spbill_create_ip", requestIp
);params
.put("total_fee","100");params
.put("sign_type", "MD5");params
.put("trade_type", "JSAPI");params
.put("notify_url", weChatConfig
.notify_url
);String sign
= WXPayUtil.createSign(params
, weChatConfig
.key
);params
.put("sign", sign
);String payXml
;try {payXml
= WXPayUtil.mapToXml(params
);log
.info("payXml: "+payXml
);String orderStr
= HttpUtils.doPost("https://api.mch.weixin.qq.com/pay/unifiedorder",payXml
,4000);log
.info("统一下单返回结果: "+orderStr
);if(StringUtils.isEmpty(orderStr
)){log
.error("微信支付失败.原因: 调用微信统一下单接口失败");throw new TPlusException(ErrorEnum.WECHAT_PAY_ERROR
);}String prepay_id
= "";if (orderStr
.indexOf("SUCCESS") != -1) {Map<String, String> map
= WXPayUtil.xmlToMap(orderStr
);prepay_id
= map
.get("prepay_id");}SortedMap<String,String> payMap
= new TreeMap<>();payMap
.put("appId", appid
);payMap
.put("timeStamp", String.valueOf(new Date().getTime()/1000));payMap
.put("nonceStr", CommonUtils.generateUUID());payMap
.put("signType", "MD5");payMap
.put("package", "prepay_id="+prepay_id
);String paySign
= WXPayUtil.createSign(payMap
, key
);payMap
.put("paySign", paySign
);return payMap
;} catch (Exception e
) {log
.error("微信支付失败.原因: map参数转xml失败({})", e
.getMessage());throw new TPlusException(ErrorEnum.WECHAT_PAY_ERROR
);}
得到需要的数据之后, 前端来说就很简单了, 直接可以根据返回的数据调起微信支付.
也可参考: JSAPI调起支付文档
function
onBridgeReady(){WeixinJSBridge.invoke('getBrandWCPayRequest', {"appId":appId
, "timeStamp":timeStamp
, "nonceStr":nonceStr
, "package":package, "signType":signType
, "paySign":paySign
},function(res
){if(res
.err_msg
== "get_brand_wcpay_request:ok" ){} });
}
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();
}
最后一个需要注意的问题是, 前端在调起微信JSAPI支付的时候, 通过本地是无法调用成功的, 必须发布到线上, 而且要在微信商户平台, 配置线上地址的授权域名.
特别注意: 该域名必须和前端访问的域名一致, 不可以配置主域名. 我们一直无法调起支付, 其中有一个坑也是这个原因.
上面Java调用统一下单时所需要的工具类
CommonUtils
public class CommonUtils {public static String generateUUID(){String uuid
= UUID
.randomUUID().toString().replaceAll("-","").substring(0,32);return uuid
;}public static String getIpAddr(HttpServletRequest request
) {String ipAddress
= request
.getHeader("x-forwarded-for");if (ipAddress
== null || ipAddress
.length() == 0 || "unknown".equalsIgnoreCase(ipAddress
)) {ipAddress
= request
.getHeader("Proxy-Client-IP");}if (ipAddress
== null || ipAddress
.length() == 0 || "unknown".equalsIgnoreCase(ipAddress
)) {ipAddress
= request
.getHeader("WL-Proxy-Client-IP");}if (ipAddress
== null || ipAddress
.length() == 0 || "unknown".equalsIgnoreCase(ipAddress
)) {ipAddress
= request
.getRemoteAddr();if (ipAddress
.equals("127.0.0.1") || ipAddress
.equals("0:0:0:0:0:0:0:1")) {InetAddress inet
= null;try {inet
= InetAddress.getLocalHost();} catch (UnknownHostException e
) {e
.printStackTrace();}ipAddress
= inet
.getHostAddress();}}if (ipAddress
!= null && ipAddress
.length() > 15) { if (ipAddress
.indexOf(",") > 0) {ipAddress
= ipAddress
.substring(0, ipAddress
.indexOf(","));}}return ipAddress
;}
到此为止, 就已经完成了微信JSAPI支付, 以上纯手写, 如有错误的地方, 欢迎指教. 谢谢!
总结
以上是生活随笔为你收集整理的手把手教你实现Java微信JSAPI支付的全部内容,希望文章能够帮你解决所遇到的问题。
如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。