关于微信网页支付,分为微信域内浏览器支付 + 外部浏览器支付,两者还是稍微有点点区别的,内部浏览器即在微信内打开网页,进行支付,支付调用是需要开通JSAPI支付方式;而外部浏览器「比如浏览器等」则需要开通 H5支付方式。
准备工作
接入说明
开通H5微信支付-V3,用于移动端H5网页调起微信支付进行付款功能。
微信支付需准备商户号、微信社交载体(公众号/小程序/开放平台)、网站备案域名
公众号平台申请-注册服务号(主体信息与商户平台、网站域名备案主体一致)
微信公众平台
商户平台(需备案,与公众号平台、网站域名备案主体一致)
接入微信支付 - 微信商户平台
API参数获取说明
微信社交载体-AppId
appId是微信社交载体身份的唯一凭证,所以需要申请公众号/小程序/开放平台后,获取对应的appId
微信社交载体-appSecret
appSecret是APPID对应的接口密码,用于获取接口调用凭证-accessToken时使用(微信支付已将获取accessToken部分功能封装,无需自己开发),可登录微信社交载体获取AppSecret。
商户平台-mchId(商户号)
mchId是微信商户平台的身份凭证,且每个mchId只能对应一个结算币种,若要使用多个结算币种,则需要申请相应数量的mchId。注册并登录商户平台后,查看账号信息,以获取对应的mchId
商户平台-APIKey
登录微信商户平台,进入【账户中心 > API安全】目录,设置APIV3密钥。内容为32位字符,包括数字及大小写字母,此时需要输入操作密码及管理员手机验证码。
商户平台-商户证书密钥-privateKey
登录微信商户平台,进入【账户中心 > API安全】目录,申请API证书,在弹出窗口内点击点击“下载证书工具”按钮下载证书工具,该工具为.exe格式,需要在windows环境下运行安装。根据证书工具的提示操作,直至生成证书文件至电脑。
打开生成的证书文件夹,其中apiclient_key.pem便是我们需要的商户证书密钥文件,使用文本编辑器打开,复制内容即可。
商户平台-商户证书序列号-mchSerialNo
同证书密钥-登录微信商户平台,进入【账户中心 > API安全 】目录,申请API证书后,在API证书一栏 会 显示该序列号。
java配置样例:
wx: pay: appId: #公众号或者小程序appid mchId: #商户id apiV3Key: #V3密钥 certSerialNo: #证书序列号 privateKeyPath: #证书key路径 privateCertPath: #证书路径
H5支付(域外)
整体流程
官方文档: 开发指引-H5支付 | 微信支付商户平台文档中心
1、用户在商户侧完成下单,使用微信支付进行支付
2、由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB
3、统一下单接口返回支付相关参数给商户后台,如支付跳转url(参数名“mweb_url”),商户通过mweb_url 调起微信支付中间页
4、中间页进行H5权限的校验,安全性检查
5、如支付成功,商户后台会接收到微信侧的异步通知
6、用户在微信支付收银台完成支付或取消支付,返回商户页面(默认为返回支付发起页面)
7、商户在展示页面,引导用户主动发起支付结果的查询
遇到的问题
- java调用V3接口统一下单时,会报 Illegal key size 报错问题,你的代码是没错的,jdk的问题
javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1029) javax.crypto.Cipher.implInit(Cipher.java:795) javax.crypto.Cipher.chooseProvider(Cipher.java:854) javax.crypto.Cipher.init(Cipher.java:1374) javax.crypto.Cipher.init(Cipher.java:1308) com.focustech.common.codec.encrypter.AES256Encrypter.cipher(AES256Encrypter.java:37) com.focustech.common.codec.encrypter.AES256Encrypter.encrypt(AES256Encrypter.java:45)
原因是:如果密钥大于128, 会抛出java.security.InvalidKeyException: Illegal key size 异常. 因为密钥长度是受限制的, java运行时环境读到的是受限的policy文件. 文件位于jdk/jre/lib/security,
解决办法:
替换jdk 与jre下两个jar包:local_policy.jar和 US_export_policy.jar即可。见附件
📎UnlimitedJCEPolicyJDK8.rar
- 你的请求必须是从添加的H5域名相同,否则下单链接可以获取,但是支付不能成功
- 链接不能通过浏览器直接打开,会报下面的错误。你必须在app或者H5里面使用代码打开。
当前调用H5支付的referer为空导致,你需要在app或者H5里面使用代码打开,或者发起支付是,手动设置referer。 如果是webview中,手动设置referer., referer必须是申请H5提交时的授权域名。
JSAPI支付(域内)
整体流程
微信内部浏览器支付,支付时会直接调起微信支付,不同于外部浏览器支付,内部浏览器支付首先需要获得当前支付用户对该公众号的唯一标识 openId,拿到 openId 后,结合后端其他参数调用微信预支付接口,获得预支付id,然后交由前端发起微信支付,支付成功后回调后端接口。
官方文档:开发指引-JSAPI支付 | 微信支付商户平台文档中心
- 【服务端】JSAPI下单
重要入参说明:
• package:JSAPI下单接口返回的prepay_id参数值,提交格式如:prepay_id=***
• signType:该接口V3版本仅支持RSA
• paySign:签名
paySign生成规则、响应详情及错误码请参见 JSAPI调起支付接口文档
- 【客户端】JSAPI调起支付
- 【服务端】接收支付结果通知
- 【服务端】查询订单
遇到的问题
- 配置token , 报token失败?
需要公网下,接受微信的验证消息,并且返回正确值, java代码示例
@GetMapping(value = "/mp/portal",produces = "text/plain;charset=utf-8") public String authGet( @RequestParam(name = "signature", required = false) String signature, @RequestParam(name = "timestamp", required = false) String timestamp, @RequestParam(name = "nonce", required = false) String nonce, @RequestParam(name = "echostr", required = false) String echostr) { LOGGER.info("\n接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature, timestamp, nonce, echostr); if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) { throw new IllegalArgumentException("请求参数非法,请核实!"); } if (wxMpService.checkSignature(timestamp, nonce, signature)) { return echostr; } return "非法请求"; }
- JSAPI微信支付必须要openid,怎么获取openid?
官方文档: 网页授权 | 微信开放文档
第一步,用户同意授权,获取 code
code 是用来取得 openid 的钥匙,先访问微信指定的api取得 code,再拿 code 去换取 openid 是第一步要做的事情。
获取 code 只需按照微信给出的固定访问方式,拼接参数,请求指定接口,如无错误即可获得,详见下
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
第二步,AppID + AppSecret + code 获取 openid
首先,通过公众号的 微信公众平台 开发 基本配置 开发者ID 开发者密码(AppSecret) IP白名单 ,获取 AppID 和 AppSecret 两个关键信息(APPID第一步已经获得),需要注意的是,虽然微信的官方Wiki教程中并没有提到设置IP白名单,但在设置 AppSecret 时是被提示需要一并设置的。
按开发文档要求拼接参数,请求以下链接获取access_token, 同时可以获取到openid
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
- 前端如何唤起支付?
function onBridgeReady() { WeixinJSBridge.invoke('getBrandWCPayRequest', { "appId": "wx2421b1c4370ec43b", //公众号ID,由商户传入 "timeStamp": "1395712654", //时间戳,自1970年以来的秒数 "nonceStr": "e61463f8efa94090b1f366cccfbbb444", //随机串 "package": "prepay_id=up_wx21201855730335ac86f8c43d1889123400", "signType": "RSA", //微信签名方式: "paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==" //微信签名 }, function(res) { if (res.err_msg == "get_brand_wcpay_request:ok") { // 使用以上方式判断前端返回,微信团队郑重提示: //res.err_msg将在用户支付成功后返回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(); }
支付回调
官方指引文档: 微信支付-开发者文档
由于微信支付回调的参数数据都是加密的,需要解密后,再处理逻辑。
验签
验签的目的是为了确定回调请求来自于微信官方,而非其他第三方。
解密
解密是解密出微信官方回调后resource字段里的 ciphertext 字段。从而实现本身业务
问题
- 通知返回给微信,微信重试机制?
接收成功:HTTP应答状态码需返回200或204,无需返回应答报文。
接收失败:HTTP应答状态码需返回5XX或4XX,同时需返回应答报文,格式如下:
如果微信手动商户的应答不是成功或者超时时,微信会认为通知失败,微信会通过一定的策略(如:30分钟共8次)定期重新发起通知,尽可能提高成功的通过率,但是微信不保证通知最终能成功