总体概述:
在项目中,首先是本地下单,接下来点击微信支付,我们希望能拉起微信H5支付页面完成支付。
首先得调用微信统一下单,得到拉起支付页面的链接,返回给前端跳转,页面拉起支付活动,支付完成回到支付完成页面,异步通知到回调接口。
具体流程如下:
1、用户在商户侧完成下单,使用微信支付进行支付
2、由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB
3、统一下单接口返回支付相关参数给商户后台,如支付跳转url(参数名“mweb_url”),商户通过mweb_url调起微信支付中间页
4、中间页进行H5权限的校验,安全性检查(此处常见错误请见下文)
5、如支付成功,商户后台会接收到微信侧的异步通知
6、用户在微信支付收银台完成支付或取消支付,返回商户页面(默认为返回支付发起页面)
7、商户在展示页面,引导用户主动发起支付结果的查询
8,9、商户后台判断是否接到收微信侧的支付结果通知,如没有,后台调用我们的订单查询接口确认订单状态
10、展示最终的订单支付结果给用户
准备工作
商户接入微信支付,调用API必须遵循以下规则:
传输方式 为保证交易安全性,采用HTTPS传输
提交方式 采用POST方法提交
数据格式 提交和返回数据都为XML格式,根节点名为xml
字符编码 统一采用UTF-8字符编码
签名算法 MD5/HMAC-SHA256
签名要求 请求和接收数据均需要校验签名,详细方法请参考安全规范-签名算法
证书要求 调用申请退款、撤销订单、红包接口等需要商户api证书,各api接口文档均有说明。
判断逻辑 先判断协议字段返回,再判断业务返回,最后判断交易状态
特别提示:
必须严格按照API的说明进行一单一支付,一单一红包,一单一付款,在未得到支付系统明确的回复之前不要换单,防止重复支付或者重复付款
在商户平台上图这个位置配置h5支付的域名(需要通过备案的域名,外网能直接访问)
申请入口:登录商户平台–>开发配置–>支付配置–>H5支付
微信统一下单接口
URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder
是否需要证书:否
请求参数不列举了,下面直接给代码,注意一下几点
- 接口中参数支付金额单位为【分】
- 交易类型trade_type
JSAPI–JSAPI支付(或小程序支付)、NATIVE–Native支付、APP–app支付,MWEB–H5支付,不同trade_type决定了调起支付的方式,请根据支付产品正确上传
MICROPAY–付款码支付,付款码支付有单独的支付接口,所以接口不需要上传,该字段在对账单中会出现
统一下单代码
appid:公众平台的appid
mch_id:公众平台的商户号
/**
* H5付款-统一下单
* @param bizOrder
* @return
*/
public Map<String, String> payH5() {
Map<String, String> resultMap = null;
try {
WXPayConfig config = new MyWXPayConfig();
WXPay wxPay = new WXPay(config);
Map<String, String> map = new HashMap<>();
map.put("appid", appid);//公众平台的appid
map.put("mch_id", mch_id);//公众平台的商户号
map.put("nonce_str", WXPayUtil.generateNonceStr());
map.put("body", "腾讯充值中心-QQ会员充值" );
map.put("out_trade_no",orderNo());//订单号
map.put("total_fee", orderFee());//订单金额
map.put("spbill_create_ip", ip);//当前设备的ip
map.put("notify_url", h5_notify_url);//异步回调地址
map.put("trade_type", "MWEB");//H5支付的交易类型为MWEB
map.put("sign_type", "HMAC-SHA256");
String xmlStr = WXPayUtil.generateSignedXml(map, key);
resultMap = wxPay.unifiedOrder(WXPayUtil.xmlToMap(xmlStr));
} catch (Exception e) {
e.printStackTrace();
}
return resultMap;
}
/**
* 微信H5支付
*
* @param bizOrder
* @return
*/
private CommonResponse getPayH5ByWx(BizOrder bizOrder,String ip) {
CommonResponse commonResponse = new CommonResponse();
Map<String, String> resultMap = payH5(bizOrder,ip);
if (resultMap.get("return_code").equals("SUCCESS") && "SUCCESS".equals(resultMap.get("result_code"))) {
//mweb_url为拉起微信支付收银台的中间页面,可通过访问该url来拉起微信客户端,完成支付,mweb_url的有效期为5分钟。
if (StringUtils.isNotEmpty(resultMap.get("mweb_url"))) {
System.out.println(resultMap.get("mweb_url"));
commonResponse.setSuccess(resultMap.get("mweb_url"));
} else {
commonResponse.setFailed("-2", resultMap.get("err_code_des"));
}
} else if (resultMap.get("return_code").equals("SUCCESS") && "ORDERPAID".equals(resultMap.get("err_code"))) {
commonResponse.setFailed("001", "订单已支付,请勿重复支付");
} else {
commonResponse.setFailed("-2", resultMap.get("err_code_des"));
}
return commonResponse;
}
统一下单成功以后我们可以得到 mweb_url,
这时前端js直接返回即可。
window.location.href = mweb_url;
这里用到的一些工具类,一一贴出来
package com.github.wxpay.sdk;
import java.io.InputStream;
public abstract class WXPayConfig {
/**
* 获取 App ID
*
* @return App ID
*/
public abstract String getAppID();
/**
* 获取 Mch ID
*
* @return Mch ID
*/
public abstract String getMchID();
/**
* 获取 API 密钥
*
* @return API密钥
*/
public abstract String getKey();
/**
* 获取商户证书内容
*
* @return 商户证书内容
*/
public abstract InputStream getCertStream();
/**
* HTTP(S) 连接超时时间,单位毫秒
*
* @return
*/
public int getHttpConnectTimeoutMs() {
return 6*1000;
}
/**
* HTTP(S) 读数据超时时间,单位毫秒
*
* @return
*/
public int getHttpReadTimeoutMs() {
return 8*1000;
}
/**
* 获取WXPayDomain, 用于多域名容灾自动切换
* @return
*/
public abstract IWXPayDomain getWXPayDomain();
/**
* 是否自动上报。
* 若要关闭自动上报,子类中实现该函数返回 false 即可。
*
* @return
*/
public boolean shouldAutoReport() {
return true;
}
/**
* 进行健康上报的线程的数量
*
* @return
*/
public int getReportWorkerNum() {
return 6;
}
/**
* 健康上报缓存消息的最大数量。会有线程去独立上报
* 粗略计算:加入一条消息200B,10000消息占用空间 2000 KB,约为2MB,可以接受
*
* @return
*/
public int getReportQueueMaxSize() {
return 10000;
}
/**
* 批量上报,一次最多上报多个数据
*
* @return
*/
public int getReportBatchSize() {
return 10;
}
}
package com.github.wxpay.sdk;
import com.github.wxpay.sdk.WXPayConstants.SignType;
import java.util.HashMap;
import java.util.Map;
public class WXPay {
private WXPayConfig config;
private SignType signType;
private boolean autoReport;
private boolean useSandbox;
private String notifyUrl;
private WXPayRequest wxPayRequest;
public WXPay(final WXPayConfig config) throws Exception {
this(config, null, true, false);
}
public WXPay(final WXPayConfig config, final boolean autoReport) throws Exception {
this(config, null, autoReport, false);
}
public WXPay(final WXPayConfig config, final boolean autoReport, final boolean useSandbox) throws Exception{
this(config, null, autoReport, useSandbox);
}
public WXPay(final WXPayConfig config, final String notifyUrl) throws Exception {
this(config, notifyUrl, true, false);
}
public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport) throws Exception {
this(config, notifyUrl, autoReport, false);
}
public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {
this.config = config;
this.notifyUrl = notifyUrl;
this.autoReport = autoReport;
this.useSandbox = useSandbox;
if (useSandbox) {
this.signType = SignType.MD5; // 沙箱环境
}
else {
this.signType = SignType.HMACSHA256;
}
this.wxPayRequest = new WXPayRequest(config);
}
private void checkWXPayConfig() throws Exception {
if (this.config == null) {
throw new Exception("config is null");
}
if (this.config.getAppID() == null || this.config.getAppID().trim().length() == 0) {
throw new Exception("appid in config is empty");
}
if (this.config.getMchID() == null || this.config.getMchID().trim().length() == 0) {
throw new Exception("appid in config is empty");
}
if (this.config.getCertStream() == null) {
throw new Exception("cert stream in config is empty");
}
if (this.config.getWXPayDomain() == null){
throw new Exception("config.getWXPayDomain() is null");
}
if (this.config.getHttpConnectTimeoutMs() < 10) {
throw new Exception("http connect timeout is too small");
}
if (this.config.getHttpReadTimeoutMs() < 10) {
throw new Exception("http read timeout is too small");
}
}
/**
* 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign <br>
* 该函数适用于商户适用于统一下单等接口,不适用于红包、代金券接口
*
* @param reqData
* @return
* @throws Exception
*/
public Map<String, String> fillRequestData(Map<String, String> reqData) throws Exception {
reqData.put("appid", config.getAppID());
reqData.put("mch_id", config.getMchID());
reqData.put("nonce_str", WXPayUtil.generateNonceStr());
if (SignType.MD5.equals(this.signType)) {
reqData.put("sign_type", WXPayConstants.MD5);
}
else if (SignType.HMACSHA256.equals(this.signType)) {
reqData.put("sign_type", WXPayConstants.HMACSHA256);
}
reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType));
return reqData;
}
/**
* 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
*
* @param reqData 向wxpay post的请求数据
* @return 签名是否有效
* @throws Exception
*/
public boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception {
// 返回数据的签名方式和请求中给定的签名方式是一致的
return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), this.signType);
}
/**
* 判断支付结果通知中的sign是否有效
*
* @param reqData 向wxpay post的请求数据
* @return 签名是否有效
* @throws Exception
*/
public boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception {
String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE);
SignType signType;
if (signTypeInData == null) {
signType = SignType.HMACSHA256;
}
else {
signTypeInData = signTypeInData.trim();
if (signTypeInData.length() == 0) {
signType = SignType.HMACSHA256;
}
else if (WXPayConstants.MD5.equals(signTypeInData)) {
signType = SignType.MD5;
}
else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) {
signType = SignType.HMACSHA256;
}
else {
throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData));
}
}
return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), signType);
}
/**
* 不需要证书的请求
* @param urlSuffix String
* @param reqData 向wxpay post的请求数据
* @param connectTimeoutMs 超时时间,单位是毫秒
* @param readTimeoutMs 超时时间,单位是毫秒
* @return API返回数据
* @throws Exception
*/
public String requestWithoutCert(String urlSuffix, Map<String, String> reqData,
int connectTimeoutMs, int readTimeoutMs) throws Exception {
String msgUUID = reqData.get("nonce_str");
String reqBody = WXPayUtil.mapToXml(reqData);
String resp = this.wxPayRequest.requestWithoutCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, autoReport);
return resp;
}
/**
* 需要证书的请求
* @param urlSuffix String
* @param reqData 向wxpay post的请求数据 Map
* @param connectTimeoutMs 超时时间,单位是毫秒
* @param readTimeoutMs 超时时间,单位是毫秒
* @return API返回数据
* @throws Exception
*/
public String requestWithCert(String urlSuffix, Map<String, String> reqData,
int connectTimeoutMs, int readTimeoutMs) throws Exception {
String msgUUID= reqData.get("nonce_str");
String reqBody = WXPayUtil.mapToXml(reqData);
String resp = this.wxPayRequest.requestWithCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, this.autoReport);
return resp;
}
/**
* 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
* @param xmlStr API返回的XML格式数据
* @return Map类型数据
* @throws Exception
*/
public Map<String, String> processResponseXml(String xmlStr) throws Exception {
String RETURN_CODE = "return_code";
String return_code;
Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
if (respData.containsKey(RETURN_CODE)) {
return_code = respData.get(RETURN_CODE);
}
else {
throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
}
if (return_code.equals(WXPayConstants.FAIL)) {
return respData;
}
else if (return_code.equals(WXPayConstants.SUCCESS)) {
if (this.isResponseSignatureValid(respData)) {
return respData;
}
else {
throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
}
}
else {
throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
}
}
/**
* 作用:统一下单<br>
* 场景:公共号支付、扫码支付、APP支付
* @param reqData 向wxpay post的请求数据
* @return API返回数据
* @throws Exception
*/
public Map<String, String> unifiedOrder(Map<String, String> reqData) throws Exception {
return this.unifiedOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
}
// end class
package com.github.wxpay.sdk;
import com.github.wxpay.sdk.WXPayConstants.SignType;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key: data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
return generateSignedXml(data, key, SignType.MD5);
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名类型
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key, SignType signType) throws Exception {
String sign = generateSignature(data, key, signType);
data.put(WXPayConstants.FIELD_SIGN, sign);
return mapToXml(data);
}
/**
* 判断签名是否正确
*
* @param xmlStr XML格式数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
Map<String, String> data = xmlToMap(xmlStr);
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key).equals(sign);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
*
* @param data Map类型数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
return isSignatureValid(data, key, SignType.MD5);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key, signType).equals(sign);
}
/**
* 生成签名
*
* @param data 待签名数据
* @param key API密钥
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key) throws Exception {
return generateSignature(data, key, SignType.MD5);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WXPayConstants.FIELD_SIGN)) {
continue;
}
if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("key=").append(key);
if (SignType.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
}
else if (SignType.HMACSHA256.equals(signType)) {
return HMACSHA256(sb.toString(), key);
}
else {
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 生成 HMACSHA256
* @param data 待处理数据
* @param key 密钥
* @return 加密结果
* @throws Exception
*/
public static String HMACSHA256(String data, String key) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 日志
* @return
*/
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
/**
* 获取当前时间戳,单位秒
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis()/1000;
}
/**
* 获取当前时间戳,单位毫秒
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
/**
* 从请求request中获取IP地址
*
* @param request
* @return IP
*/
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (ip != null && ip.toLowerCase().contains("x-forwarded-for")) {
String ip_temp = "";
if (ip.contains(":") && ip.split(":").length > 0 && !ip.endsWith(":")) {
ip_temp = ip.split(":")[1];
if (StringUtils.isBlank(ip_temp)) {
ip_temp = ip.substring(0, ip.lastIndexOf("X-Forwarded-For"));
}
} else {
ip_temp = ip.substring(0, ip.lastIndexOf("X-Forwarded-For"));
}
ip = ip_temp.replaceAll(" ", "");
}
if (ip != null && ip.indexOf(",") > -1) {
String ip_temp = ip.split(",")[0];
ip_temp = ip_temp.replaceAll(" ", "");
if (ip_temp.startsWith("10.") && ip.split(",").length > 1) {
ip = ip.split(",")[1];
ip = ip.replaceAll(" ", "");
} else {
ip = ip_temp;
}
}
return ip;
}
}
package cn.com.artlife.archiveCenterService.model;
/**
* 通用接口返回对象
*/
public class CommonResponse {
//默认成功状态
public static final String SUCCESS = "0";
//默认失败状态
public static final String ERROR = "-1";
//接口状态
private String status = ERROR;
//接口消息
private String msg = "";
//接口返回对象
private Object data = null;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
//失败
public void setFailed(String status,String msg){
setStatus(status);
setMsg(msg);
}
//带返回对象的成功
public void setSuccess(Object data){
setStatus(SUCCESS);
setData(data);
}
//没有返回对象的成功
public void setSuccess(){
setSuccess(null);
}
// //判断是否成功
// public boolean isSuccess(){
// return SUCCESS.equals(status);
// }
}
常见问题
- 网络环境未能通过安全验证,请稍后再试
商户侧统一下单传的终端IP(spbill_create_ip)与用户实际调起支付时微信侧检测到的终端IP不一致导致的,这个问题一般是商户在统一下单时没有传递正确的终端IP到spbill_create_ip导致
- 商家参数格式有误,请联系商家解决
当前调起H5支付的referer为空导致,一般是因为直接访问页面调起H5支付,js直接跳转就可以,不要复制链接再浏览器中打开,会报上面这个错。
3.商家存在未配置的参数,请联系商家解决
当前调起H5支付的域名(微信侧从referer中获取)与申请H5支付时提交的授权域名不一致,这里是H5的支付域名,不是公众号支付
- 支付请求已失效,请重新发起支付
统一下单返回的MWEB_URL生成后,有效期为5分钟,如超时请重新生成MWEB_URL后再发起支付