微信v3版本native支付java踩过的坑和代码,这个东西真的是麻烦死了,文档上的东西太难找了花了两天终于解决
首先是要在微信的商户平台上弄一些调接口的相关参数例如appid,mchid等等,下图为相关配置。
v3.appid=
#native支付统一下单接口地址
v3.payUrl=https://api.mch.weixin.qq.com/v3/pay/transactions/native
#native支付查询接口地址
v3.findUrl=https://api.mch.weixin.qq.com/v3/pay/transactions
#秘钥
v3.keyPath=apiclient_key.pem的地址(从商户平台下载弄进项目中)
#CA证书 格式.pem
v3.certPath=apiclient_cert.pem的地址(从商户平台下载弄进项目中)
#CA证书 格式.p12
v3.certP12Path==apiclient_cert.p12的地址(从商户平台下载弄进项目中)
#证书序列号
v3.certSerialNo=(在商户平台中即可看见)
#服务商id/商户id
v3.mchid=
# apiv3 秘钥
v3.apiKey3=(商户平台中设置的)
接下来是获取签名的类
package net.bx.util;
import net.bx.controller.AttachmentController;
import okhttp3.HttpUrl;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Date;
public class V3Util {
private static final Logger logger = LoggerFactory.getLogger(AttachmentController.class);
//method(请求类型GET、POST url(请求url) body(请求body,GET请求时body传"",POST请求时body为请求参数的json串) merchantId(商户号) certSerialNo(API证书序列号) keyPath(API证书路径)
public static String getToken(String method,String url, String body,String merchantId,String certSerialNo,String keyPath) throws Exception {
String signStr = "";
HttpUrl httpurl = HttpUrl.parse(url);
String nonceStr = MD5Utils.md5(new Date().getTime()+"");
long timestamp = System.currentTimeMillis() / 1000;
if(StringUtils.isEmpty(body)){
body = "";
}
String message = buildMessage(method, httpurl, timestamp, nonceStr, body);
byte[] by;
by = message.getBytes("ISO-8859-1");
String signature = sign(by,keyPath);
int length = 0;
for (int i = 0; i < signature.length(); i++) {
int ascii = Character.codePointAt(signature, i);
if (ascii >= 0 && ascii <= 255) {
length++;
} else {
length += 2;
}
}
System.out.println(length);
signStr = "mchid=\"" + merchantId
+ "\",nonce_str=\"" + nonceStr
+ "\",signature=\"" + signature
+ "\",timestamp=\"" + timestamp
+ "\",serial_no=\"" + certSerialNo+ "\"";
logger.info("Authorization Token:" +signStr);
return signStr;
}
public static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
String canonicalUrl = url.encodedPath();
if (url.encodedQuery() != null) {
canonicalUrl += "?" + url.encodedQuery();
}
return method + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
}
public static String sign(byte[] message,String keyPath) throws Exception {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(getPrivateKey(keyPath));
sign.update(message);
return Base64.encodeBase64String(sign.sign());
}
/**
* 获取私钥。
*
* @param filename 私钥文件路径 (required)
* @return 私钥对象
*/
public static PrivateKey getPrivateKey(String filename) throws IOException {
String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
logger.info("File content:"+content);
try {
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
logger.info("privateKey:"+privateKey);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
logger.info("异常:"+e);
throw new RuntimeException("无效的密钥格式");
}
}
}
接下来是controller层,为了方便你们看业务我将所有逻辑都写在controller中了,上代码:
package net.bx.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import net.bx.entity.CourseOrder;
import net.bx.po.CourseOrderParam;
import net.bx.repository.CourseOrderRepository;
import net.bx.util.BaseResult;
import net.bx.util.V3Util;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping(value = "/weXin")
public class WeXinPayController {
private static final Logger logger = LoggerFactory.getLogger(WeXinPayController.class);
@Value("${v3.appid}")
private String appid;
@Value("${v3.mchid}")
private String mchid;
@Value("${v3.payUrl}")
private String payUrl;
@Value("${v3.keyPath}")
private String keyPath;
@Value("${v3.certPath}")
private String certPath;
@Value("${v3.certSerialNo}")
private String certSerialNo;
@Value("${v3.findUrl}")
private String findUrl;
@Autowired
CourseOrderRepository courseOrderRepository;
/**
* 生成微信支付二维码
*
* @param outTradeNo 订单号
* @param totalFee 金额(分)
*/
@RequestMapping(value = "/createNative")
public BaseResult createNative(@RequestParam("outTradeNo") String outTradeNo,
@RequestParam("totalFee") int totalFee,
@RequestParam("description") String description,
HttpServletRequest request, HttpServletResponse response) throws Exception {
CourseOrder byCourseOrderCode = courseOrderRepository.findByCourseOrderCode(outTradeNo);
//判断该订单是否存在
if (byCourseOrderCode != null) {
return new BaseResult<Object>(true, "000002", "该订单号已存在,不支持再次下单");
}
HttpPost httpPost = new HttpPost(payUrl);
Map req = new HashMap();
Map map = new HashMap();
req.put("appid", appid); //公众号
req.put("mchid", mchid); // 商户号
req.put("description", description); // 商品描述
req.put("out_trade_no", outTradeNo); // 商户订单号
map.put("total", totalFee); //金额
map.put("currency", "CNY"); //金额类型
req.put("amount", map);
req.put("notify_url", "https://weixin.qq.com/"); // 回调地址
String body = JSONObject.toJSONString(req);
logger.info("调用v3微信native支付统一下单接口入参{}:" + body);
String sign = V3Util.getToken("POST", payUrl, body, mchid, certSerialNo, keyPath);
//请求体
httpPost.setEntity(new StringEntity(body));
//请求头
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("Authorization", "WECHATPAY2-SHA256-RSA2048" + " " + sign);
CloseableHttpClient httpClient = HttpClients.createDefault();
//完成签名并执行请求
CloseableHttpResponse resp = httpClient.execute(httpPost);
try {
int statusCode = resp.getStatusLine().getStatusCode();
Map maps = (Map) JSON.parse(EntityUtils.toString(resp.getEntity()));
logger.info("调用v3微信native支付统一下单接口出参{}:" + JSON.toJSONString(maps));
if (statusCode == 200) { //处理成功
return new BaseResult<Object>(true, maps, "000000", "操作成功");
} else if (statusCode == 204) { //处理成功,无返回Body
return new BaseResult<Object>(true, maps, "000000", "操作成功");
} else {
return new BaseResult<Object>(true, maps, "000001", "操作失败");
}
} finally {
resp.close();
}
}
}
到这一个简单的native统一下单接口就实现了,微信那边会返回一个url,返给前端即可生成二维码。
**注意**:返回错误的签名,验签失败这种情况不要慌,我也是这么过来的。
问题主要出现在一下几个方面:
1.参数是否有误,请求方式是否有问题
2.传入汉字了试试改成英文,看看是不是编码的问题
3.就是时间戳和随机串,加请求头Authorization的时候是否和签名时一致
4。商户证书序列号就是从微信商户后台就能看到,不用调接口去获取,调接口的那个是平台证书。
这几点检查没问题那就应该没问题了,相信大家的问题都能迎刃而解!!!!