前言
新开发的小程序要调起微信支付,关于微信支付API V3对接(Java)的资料不是很多,研究了很久文档和SDK里的代码,也踩了很多坑,特此记录。 本文包括JSAPI下单、查询订单、关闭订单、小程序调起支付、支付结果通知、申请退款、退款结果通知。
提示:以下是本篇文章正文内容,下面案例可供参考
一、微信支付商户平台
微信支付商户平台—API安全 在【API安全】里需要申请API证书(后需要用的私钥就是这个红框里的)、设置APIv3密钥
微信支付商户平台—开发配置、AppID账号管理 在【开发配置】里需要配置合法域名 在【AppID账号管理】里需要绑定小程序的AppID
二、代码
1.小程序调起支付
timeStamp:时间戳(只需要到秒,如果获取的是毫秒级需要除以1000) nonceStr:随机字符串(不长于32位) package:订单详情扩展字符串(prepay_id=……) signType:签名方式(仅支持RSA) paySign:签名(使用字段appId、timeStamp、nonceStr、package计算得出的签名值)
wx.requestPayment({
timeStamp: '',
nonceStr: '',
package: '',
signType: '',
paySign: '',
success: (result) => {},
fail: () => {},
complete: () => {}
});
2.后端代码
Maven依赖:
<dependencies>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.12.5</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
PayUtil支付工具类:
package util;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.ParseException;
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.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import cn.hutool.core.util.RandomUtil;
public class PayUtil {
public static String createOrder(String openid, String description, int money) {
Connection conn = DBUtil.getConnection();
try {
String outTradeNo = stringToMD5(System.currentTimeMillis() + "" + RandomUtil.randomString(32));
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream("私钥文件路径");
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(PayConstants.MCH_ID,
new WechatPay2Credentials(PayConstants.MCH_ID,
new PrivateKeySigner(PayConstants.MCH_SERIAL_NO, merchantPrivateKey)),
PayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(PayConstants.MCH_ID, PayConstants.MCH_SERIAL_NO, merchantPrivateKey)
.withValidator(new WechatPay2Validator(certificatesManager.getVerifier(PayConstants.MCH_ID)))
.build();
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("mchid", PayConstants.MCH_ID)
.put("appid", PayConstants.APP_ID)
.put("description", description)
.put("notify_url", PayConstants.NOTIFY_URL)
.put("out_trade_no", outTradeNo);
rootNode.putObject("amount")
.put("total", money);
rootNode.putObject("payer")
.put("openid", openid);
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
String bodyAsString = EntityUtils.toString(response.getEntity());
String timestamp = (System.currentTimeMillis() / 1000) + "";
String nonce = RandomUtil.randomString(32).toUpperCase();
StringBuilder builder = new StringBuilder();
builder.append(PayConstants.APP_ID).append("\n");
builder.append(timestamp).append("\n");
builder.append(nonce).append("\n");
JsonNode node = objectMapper.readTree(bodyAsString);
builder.append("prepay_id="
+ node.get("prepay_id").toString().substring(1, node.get("prepay_id").toString().length() - 1))
.append("\n");
String ciphertext = sign(builder.toString().getBytes(StandardCharsets.UTF_8));
Map<String, String> map = new HashMap<String, String>();
map.put("nonceStr", nonce);
map.put("package", "prepay_id="
+ node.get("prepay_id").toString().substring(1, node.get("prepay_id").toString().length() - 1));
map.put("timeStamp", timestamp);
map.put("paySign", ciphertext);
map.put("signType", "RSA");
map.put("outTradeNo", outTradeNo);
String json = JSON.toJSONString(map);
return "{\"pay\":" + json + "}";
} catch (UnsupportedCharsetException | HttpCodeException | NotFoundException | ParseException | IOException
| GeneralSecurityException | SQLException e) {
e.printStackTrace();
}
return "创建订单失败";
}
public static boolean signVerify(String serialNumber, String message, String signature) {
try {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream("私钥文件路径");
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(PayConstants.MCH_ID,
new WechatPay2Credentials(PayConstants.MCH_ID,
new PrivateKeySigner(PayConstants.MCH_SERIAL_NO, merchantPrivateKey)),
PayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
Verifier verifier = certificatesManager.getVerifier(PayConstants.MCH_ID);
return verifier.verify(serialNumber, message.getBytes(StandardCharsets.UTF_8), signature);
} catch (HttpCodeException | NotFoundException | IOException | GeneralSecurityException e) {
e.printStackTrace();
}
return false;
}
public static String decryptOrder(String body) {
try {
AesUtil util = new AesUtil(PayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
ObjectMapper objectMapper = new ObjectMapper();
JsonNode node = objectMapper.readTree(body);
JsonNode resource = node.get("resource");
String ciphertext = resource.get("ciphertext").textValue();
String associatedData = resource.get("associated_data").textValue();
String nonce = resource.get("nonce").textValue();
return util.decryptToString(associatedData.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext);
} catch (JsonProcessingException | UnsupportedEncodingException | GeneralSecurityException e) {
e.printStackTrace();
}
return null;
}
public static void closeOrder(String outTradeNo) {
try {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream("私钥文件路径");
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(PayConstants.MCH_ID,
new WechatPay2Credentials(PayConstants.MCH_ID,
new PrivateKeySigner(PayConstants.MCH_SERIAL_NO, merchantPrivateKey)),
PayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(PayConstants.MCH_ID, PayConstants.MCH_SERIAL_NO, merchantPrivateKey)
.withValidator(new WechatPay2Validator(certificatesManager.getVerifier(PayConstants.MCH_ID)))
.build();
HttpPost httpPost = new HttpPost(
"https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo + "/close");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("mchid", PayConstants.MCH_ID);
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
System.out.println(response.getStatusLine().getStatusCode());
} catch (UnsupportedCharsetException | HttpCodeException | NotFoundException | ParseException | IOException
| GeneralSecurityException e) {
e.printStackTrace();
}
}
public static String queryOrder(String outTradeNo) {
try {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream("私钥文件路径");
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(PayConstants.MCH_ID,
new WechatPay2Credentials(PayConstants.MCH_ID,
new PrivateKeySigner(PayConstants.MCH_SERIAL_NO, merchantPrivateKey)),
PayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(PayConstants.MCH_ID, PayConstants.MCH_SERIAL_NO, merchantPrivateKey)
.withValidator(new WechatPay2Validator(certificatesManager.getVerifier(PayConstants.MCH_ID)))
.build();
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"
+ outTradeNo + "?mchid=" + PayConstants.MCH_ID);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);
String bodyAsString = EntityUtils.toString(response.getEntity());
System.out.println(bodyAsString);
} catch (HttpCodeException | NotFoundException | ParseException | IOException | GeneralSecurityException
| URISyntaxException e) {
e.printStackTrace();
}
return null;
}
public static String createRefundOrder(String outTradeNo, int total, int refund) {
try {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream("私钥文件路径");
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(PayConstants.MCH_ID,
new WechatPay2Credentials(PayConstants.MCH_ID,
new PrivateKeySigner(PayConstants.MCH_SERIAL_NO, merchantPrivateKey)),
PayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(PayConstants.MCH_ID, PayConstants.MCH_SERIAL_NO, merchantPrivateKey)
.withValidator(new WechatPay2Validator(certificatesManager.getVerifier(PayConstants.MCH_ID)))
.build();
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
String outRefundNo = stringToMD5("refund"+outTradeNo);
rootNode.put("out_refund_no", outRefundNo)
.put("notify_url", PayConstants.REFUND_NOTIFY_URL)
.put("out_trade_no", outTradeNo);
rootNode.putObject("amount")
.put("total", total)
.put("refund", refund)
.put("currency", "CNY");
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
String bodyAsString = EntityUtils.toString(response.getEntity());
System.out.println(bodyAsString);
} catch (UnsupportedCharsetException | HttpCodeException | NotFoundException | ParseException | IOException
| GeneralSecurityException e) {
e.printStackTrace();
}
return null;
}
static String sign(byte[] message) {
try {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(
PayUtil.class.getClassLoader().getResource("util/apiclient_key.pem").getPath()));
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(merchantPrivateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
}
return null;
}
public static String stringToMD5(String plainText) {
byte[] secretBytes = null;
try {
secretBytes = MessageDigest.getInstance("md5").digest(plainText.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Secret Failed");
}
String md5code = new BigInteger(1, secretBytes).toString(16);
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = "0" + md5code;
}
return md5code;
}
}
3.异步通知【回调】(以支付回调为例)
这部分是前端付款成功后微信主动访问你的服务器回调地址给你一个通知
package web;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import util.PayUtil;
@WebServlet("/PayCallback")
public class PayCallback extends HttpServlet {
private static final long serialVersionUID = 1L;
public PayCallback() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
try {
System.out.println("Wechatpay-Timestamp:" + request.getHeader("Wechatpay-Timestamp"));
System.out.println("Wechatpay-Nonce:" + request.getHeader("Wechatpay-Nonce"));
System.out.println("Wechatpay-Signature:" + request.getHeader("Wechatpay-Signature"));
System.out.println("Wechatpay-Serial:" + request.getHeader("Wechatpay-Serial"));
Map<String, String> result = new HashMap<String, String>();
result.put("code", "FAIL");
StringBuilder signStr = new StringBuilder();
signStr.append(request.getHeader("Wechatpay-Timestamp")).append("\n");
signStr.append(request.getHeader("Wechatpay-Nonce")).append("\n");
BufferedReader br = request.getReader();
String str = null;
StringBuilder builder = new StringBuilder();
while ((str = br.readLine()) != null) {
builder.append(str);
}
System.out.println(builder.toString());
signStr.append(builder.toString()).append("\n");
if (!PayUtil.signVerify(request.getHeader("Wechatpay-Serial"), signStr.toString(), request.getHeader("Wechatpay-Signature"))) {
System.out.println("PayCallback==>>sign error");
result.put("message", "sign error");
String json = JSON.toJSONString(result);
PrintWriter out = response.getWriter();
out.write(json);
return;
}
System.out.println("PayCallback==>>sign success");
String info = PayUtil.decryptOrder(builder.toString());
System.out.println(info);
ObjectMapper objectMapper = new ObjectMapper();
JsonNode node = objectMapper.readTree(info);
String outTradeNo = node.get("out_trade_no").toString().substring(1, node.get("out_trade_no").toString().length() - 1);
String bankType = node.get("bank_type").toString().substring(1, node.get("bank_type").toString().length() - 1);
result.put("code", "SUCCESS");
String json = JSON.toJSONString(result);
PrintWriter out = response.getWriter();
out.write(json);
} catch (IOException | SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(conn);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
总结
几个常用的方法都已经封装在PayUtil工具类中,使用的时候根据官方文档传参即可,也可根据需求进行修改。
|