微信支付官网SDK下载
其中WXPayConstants和WXPayUtil会在后边使用到
import java.security.SecureRandom;
import java.util.*;
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* 获取随机字符串 32位 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);
}
/**
* 获取当前时间戳,单位秒
*
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis() / 1000;
}
/**
* 获取当前时间戳,单位毫秒
*
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
}
import org.apache.http.client.HttpClient;
/**
* 常量
*/
public class WXPayConstants {
public enum SignType {
MD5, HMACSHA256
}
public static final String DOMAIN_API = "api.mch.weixin.qq.com";
public static final String DOMAIN_API2 = "api2.mch.weixin.qq.com";
public static final String DOMAIN_APIHK = "apihk.mch.weixin.qq.com";
public static final String DOMAIN_APIUS = "apius.mch.weixin.qq.com";
public static final String FAIL = "FAIL";
public static final String SUCCESS = "SUCCESS";
public static final String HMACSHA256 = "HMAC-SHA256";
public static final String MD5 = "MD5";
public static final String FIELD_SIGN = "sign";
public static final String FIELD_SIGN_TYPE = "sign_type";
public static final String WXPAYSDK_VERSION = "WXPaySDK/3.0.9";
public static final String USER_AGENT = WXPAYSDK_VERSION +
" (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") +
") Java/" + System.getProperty("java.version") + " HttpClient/" + HttpClient.class.getPackage().getImplementationVersion();
public static final String MICROPAY_URL_SUFFIX = "/pay/micropay";
public static final String UNIFIEDORDER_URL_SUFFIX = "/pay/unifiedorder";
public static final String ORDERQUERY_URL_SUFFIX = "/pay/orderquery";
public static final String REVERSE_URL_SUFFIX = "/secapi/pay/reverse";
public static final String CLOSEORDER_URL_SUFFIX = "/pay/closeorder";
public static final String REFUND_URL_SUFFIX = "/secapi/pay/refund";
public static final String REFUNDQUERY_URL_SUFFIX = "/pay/refundquery";
public static final String DOWNLOADBILL_URL_SUFFIX = "/pay/downloadbill";
public static final String REPORT_URL_SUFFIX = "/payitil/report";
public static final String SHORTURL_URL_SUFFIX = "/tools/shorturl";
public static final String AUTHCODETOOPENID_URL_SUFFIX = "/tools/authcodetoopenid";
// sandbox
public static final String SANDBOX_MICROPAY_URL_SUFFIX = "/sandboxnew/pay/micropay";
public static final String SANDBOX_UNIFIEDORDER_URL_SUFFIX = "/sandboxnew/pay/unifiedorder";
public static final String SANDBOX_ORDERQUERY_URL_SUFFIX = "/sandboxnew/pay/orderquery";
public static final String SANDBOX_REVERSE_URL_SUFFIX = "/sandboxnew/secapi/pay/reverse";
public static final String SANDBOX_CLOSEORDER_URL_SUFFIX = "/sandboxnew/pay/closeorder";
public static final String SANDBOX_REFUND_URL_SUFFIX = "/sandboxnew/secapi/pay/refund";
public static final String SANDBOX_REFUNDQUERY_URL_SUFFIX = "/sandboxnew/pay/refundquery";
public static final String SANDBOX_DOWNLOADBILL_URL_SUFFIX = "/sandboxnew/pay/downloadbill";
public static final String SANDBOX_REPORT_URL_SUFFIX = "/sandboxnew/payitil/report";
public static final String SANDBOX_SHORTURL_URL_SUFFIX = "/sandboxnew/tools/shorturl";
public static final String SANDBOX_AUTHCODETOOPENID_URL_SUFFIX = "/sandboxnew/tools/authcodetoopenid";
}
创建商户号,登录商户平台配置小程序appid以及获取证书序列号、微信支付商户号、APIV3密钥,这部分比较简单不做详述
微信支付接口调用方法有预支付、验签、回调方法、关闭订单以及查询订单和退款,其中的难点在验签和回调,废话不多说直接上代码。
导入依赖
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.4</version>
</dependency>
将需要的密钥导入项目,也可以添加到properties文件中方便后期修改
//小程序appid
public static final String appid = "***************";
//商户号
public static final String mchId = "*****************";
//证书序列号
public static final String mchSerialNo = "***********";
//V3密钥
public static final String apiV3Key = "**************";
//支付通知回调
public static final String notifyUrl = "http://******";
//小程序密钥
public static final String secret="******************";
//证书密钥
public static final String privateKey = "************";
//http客户端
public static CloseableHttpClient httpClient;
//退款通知回调
public static final String refundNotifyUrl="http://**";
public static AutoUpdateCertificatesVerifier verifier;
1.预支付
public static void setup() throws IOException {
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
apiV3Key.getBytes("utf-8"));
// 初始化httpClient
httpClient = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
}
/**
* 获取小程序请求用户openId
* @param jsCode
* @return
* @throws URISyntaxException
* @throws IOException
*/
public static String getOpenId(String jsCode) throws URISyntaxException, IOException {
String url = "https://api.weixin.qq.com/sns/jscode2session?appid="
+appid+"&secret="+secret+"&js_code="+jsCode+"&grant_type=authorization_code";
URIBuilder uriBuilder = new URIBuilder(url);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);
String openIdParse = EntityUtils.toString(response.getEntity());
Map<String,String> parse = (Map) JSON.parse(openIdParse);
String openId = parse.get("openid");
return openId;
}
//预支付方法,返回prepay_id(预支付id)PayBean是用于接收前端数据的实体类
public static String order(PayBean payBean,String outTradeNo) throws IOException, URISyntaxException {
setup();
//url: 哪一种支付
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();
String openId = getOpenId(payBean.getJsCode());
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("mchid", mchId)
.put("appid", appid)
.put("description", "")
.put("notify_url",notifyUrl )//异步通知 yrl
.put("out_trade_no", outTradeNo);//自己平台的商户号
rootNode.putObject("amount").put("total", 1).put("currency", "CNY");
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());
System.out.println(bodyAsString);
return bodyAsString;
}
2.验签(V3加密方式只有RSA非对称性加密)
public AjaxResult pay(PayBean payBean) throws Exception {
//后端生成
String outTradeNo = "LY" + System.currentTimeMillis();// 平台商品号码
String order = "";
try {
order = order(payBean, outTradeNo);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//生成时间戳
Long timeStamp = WXPayUtil.getCurrentTimestamp();
生成随机数订单号
String generateNonceStr = WXPayUtil.generateNonceStr();
Map<String, String> parse = (Map) JSON.parse(order);
// 需要签名的
StringBuilder builder = new StringBuilder();
builder.append(appid + "\n");
builder.append(timeStamp + "\n");
builder.append(generateNonceStr + "\n");
builder.append("prepay_id=" + parse.get("prepay_id") + "\n");// 加签 商户号一致
byte[] signBytes = builder.toString().getBytes("UTF-8");
//加密方法
String paySign = sign(signBytes);
HashMap<String, String> signData = new HashMap<String, String>(8);
signData.put("mchId", WeChatPay.mchId);
signData.put("appId", WeChatPay.appid);
signData.put("package", "prepay_id=" + parse.get("prepay_id"));
signData.put("nonceStr", generateNonceStr);// 随机字符串32位
signData.put("timeStamp", timeStamp.toString());// 时间戳
signData.put("sign", paySign);
return AjaxResult.success(signData);
}
/**
StringBuilder builder = new StringBuilder();
builder.append(WeChatPay.appid + "\n");
builder.append(timeStamp + "\n");
builder.append(generateNonceStr + "\n");
builder.append("prepay_id=" + parse.get("prepay_id") + "\n");// 加签 商户号一致
byte[] message = builder.toString().getBytes("UTF-8");
* @param message 需要签名的字符传byte数组
* @return
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws SignatureException
*/
public String sign(byte[] message) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = WeChatPay.getPrivateKey();
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(merchantPrivateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
3.回调方法
public Map callBack(HttpServletRequest request, HttpServletResponse response)
throws IOException, GeneralSecurityException {
Map<String, String> result = new HashMap<String, String>();
// 响应接口
result.put("code", "FAIL");
/** 从请求头获取验签字段 */
// 时间戳
String Timestamp = request.getHeader("Wechatpay-Timestamp");
String Nonce = request.getHeader("Wechatpay-Nonce");// 字符数按
String Signature = request.getHeader("Wechatpay-Signature");
String Serial = request.getHeader("Wechatpay-Serial");
System.out.println("开始读取请求头的信息");
// 请求头
System.out.println("Wechatpay-Timestamp=" + Timestamp);
System.out.println("Wechatpay-Nonce=" + Nonce);
System.out.println("Wechatpay-Signature=" + Signature);
System.out.println("Wechatpay-Serial=" + Serial);
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(mchId,
new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
apiV3Key.getBytes("utf-8"));
// 读取请求体的信息
System.out.println("开始读取请求体的信息");
ServletInputStream inputStream = request.getInputStream();
StringBuffer stringBuffer = new StringBuffer();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s;
// 读取回调请求体
while ((s = bufferedReader.readLine()) != null) {
stringBuffer.append(s);
}
String s1 = stringBuffer.toString();
System.out.println("请求体" + s1);
Map<String, String> requestMap = (Map) JSON.parse(s1);
// 开始按照验签进行拼接
String id = requestMap.get("id");
System.out.println("id=" + id);
String resource = String.valueOf(requestMap.get("resource"));
System.out.println("resource=" + resource);
Map<String, String> requestMap2 = (Map) JSON.parse(resource);
String associated_data = requestMap2.get("associated_data");
String nonce = requestMap2.get("nonce");
String ciphertext = requestMap2.get("ciphertext");// 数据密文
// 按照文档要求拼接验签串
String VerifySignature = Timestamp + "\n" + Nonce + "\n" + s1 + "\n";
System.out.println("拼接后的验签串=" + VerifySignature);
// 使用官方验签工具进行验签
boolean verify = verifier.verify(Serial, VerifySignature.getBytes(), Signature);
System.out.println("官方工具验签=" + verify);
// 判断验签的结果
System.out.println("=======判断验签结果=======");
if (verify == false) {
result.put("message", "sign error");
return result;
}
System.out.println("验签成功后,开始进行解密");
// 解密,如果这里报错,就一定是APIv3密钥错误
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes("utf-8"));
String aes = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
System.out.println("解密后=" + aes);
Map<String, String> callBackData = getCallBackData(aes);
String tradeState = callBackData.get("trade_state");
if ("SUCCESS".equals(tradeState)) {
result.put("code", "SUCCESS");
result.put("message", "成功");
} else {
result.put("message", "trade_state error");
}
return result;
}
public static Map<String,String> getCallBackData(String body){
Map<String,String> resMap=new HashMap<>();
Map<String,Object> strToMap = (Map) JSON.parse(body);
resMap.put("out_trade_no",strToMap.get("out_trade_no").toString());
resMap.put("transaction_id",strToMap.get("transaction_id").toString());
resMap.put("trade_state",strToMap.get("trade_state").toString());
Map<String,Object> payerMap = (Map) JSON.parse(strToMap.get("payer").toString());
Map<String,Object> amountMap = (Map) JSON.parse(strToMap.get("amount").toString());
resMap.put("openid",payerMap.get("openid").toString());
resMap.put("total",amountMap.get("total").toString());
resMap.put("payer_total",amountMap.get("payer_total").toString());
return resMap;
}
4.查询订单
/**
* 查询订单
* @param outTradeNo 商户订单号(本地平台的订单号)
* @return
* @throws URISyntaxException
* @throws IOException
*
*/
public static String findOrder(String outTradeNo) throws URISyntaxException, IOException {
String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo + "?mchid=" + mchId;
URIBuilder uriBuilder = new URIBuilder(url);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);
String bodyAsString = EntityUtils.toString(response.getEntity());
return bodyAsString;
}
5.退款与退款通知
退款是前端用户退款时请求的方法,退款通知是微信平台收到退款请求的回调
/**
* 退款(api)
*/
public Map refund(String outTradeNo){
Map<String, Object> result = new HashMap<String, Object>();
String out_refund_no = "TB" + System.currentTimeMillis();// 平台商品号码
ShaChargeRecord recode = shaChargeRecordService.selectShaChargeRecordByoutTradeNo(outTradeNo);
long price = recode.getRealTotalPrice().longValueExact();
try {
JSONObject order = new JSONObject();
order.put("out_trade_no", outTradeNo);//商户订单号
order.put("out_refund_no", out_refund_no);//商户退款单号
// order.put("reason", reason);//退款原因
order.put("notify_url", refundNotifyUrl);//退款通知
JSONObject amount = new JSONObject();
amount.put("refund", price * 100);//退款金额 price * 100
amount.put("currency", "CNY");
amount.put("total", price * 100);//原订单金额
order.put("amount", amount);
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(WeChatPay.privateKey.getBytes("utf-8")));
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(mchId,
new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
apiV3Key.getBytes("utf-8"));
// 初始化httpClient
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).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");
httpPost.setEntity(new StringEntity(order.toJSONString(), "UTF-8"));
HttpResponse response = httpClient.execute(httpPost);
//获取返回数据
String bodyAsString = EntityUtils.toString(response.getEntity());
JSONObject bodyAsJSON = JSONObject.parseObject(bodyAsString);
logger.info(bodyAsJSON.toJSONString());
final String status = bodyAsJSON.getString("status");
result.put("status", status);
if("SUCCESS".equals(status)){
logger.info("退款成功");
result.put("message", "退款成功");
}else if("CLOSED".equals(status)){
logger.info("退款关闭");
result.put("message", "退款关闭");
}else if("PROCESSING".equals(status)){
logger.info("退款处理中");
result.put("message", "退款处理中");
result.put("code", 200);
}else if("ABNORMAL".equals(status)){
result.put("message", "退款异常");
logger.info("退款异常");
}
} catch (Exception e) {
logger.info(e.toString());
e.printStackTrace();
}
return result;
}
/**
* 退款通知(api)
*/
public Map refundNotice(HttpServletRequest request, HttpServletResponse response) {
Map<String, String> result = new HashMap<String, String>();
result.put("code", "FAIL");
try {
String reqParams = WeChatPay.read(request.getInputStream());
logger.info("-------支付结果:" + reqParams);
JSONObject json = JSONObject.parseObject(reqParams);
String ciphertext = json.getJSONObject("resource").getString("ciphertext");
final String associated_data = json.getJSONObject("resource").getString("associated_data");
final String nonce = json.getJSONObject("resource").getString("nonce");
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes("utf-8"));
//解密数据
ciphertext = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
Map<String, Object> resMap = new HashMap<>();
Map<String, Object> strToMap = (Map) JSON.parse(ciphertext);
String outTradeNo = (String) strToMap.get("out_trade_no");
final String eventType = json.getString("event_type");
if ("REFUND.SUCCESS".equals(eventType)) {
result.put("code", "SUCCESS");
result.put("message", "退款成功");
} else if ("REFUND.ABNORMAL".equals(eventType)) {
result.put("message", "退款异常");
} else if ("REFUND.CLOSED".equals(eventType)) {
result.put("message", "退款关闭");
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 查询单笔退款订单
*
* @param outTradeNo
* @return
* @throws IOException
* @throws URISyntaxException
*
* 微信开发文档 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_10.shtml
*/
public AjaxResult getRefundsOrderByOutTradeNo(String outTradeNo) throws IOException, URISyntaxException {
String order = getRefundsOrderByOutTradeNo(outTradeNo);
Map<String,Object> resMap=new HashMap<>();
Map<String,Object> strToMap = (Map) JSON.parse(order);
resMap.put("status", strToMap.get("status"));
if ("SUCCESS".equals(strToMap.get("status"))) {
}
return AjaxResult.success(resMap);
}
/**
* 查询订单
* @param outTradeNo 商户订单号(力云平台的订单号)
* @return
* @throws URISyntaxException
* @throws IOException
*
*/
public static String getRefundsOrderByOutTradeNo(String outTradeNo) throws URISyntaxException, IOException {
String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/" + outTradeNo ;
URIBuilder uriBuilder = new URIBuilder(url);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);
String bodyAsString = EntityUtils.toString(response.getEntity());
return bodyAsString;
}
public static String read(InputStream is){
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len = 0;
byte[] buffer = new byte[512];
while((len = is.read(buffer)) != -1){
baos.write(buffer, 0, len);
}
return new String(baos.toByteArray(), 0, baos.size(), "utf-8");
}catch (Exception e) {
e.printStackTrace();
}
return "";
}