app微信支付
pom依赖
<!--微信支付-->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>org.jodd</groupId>
<artifactId>jodd-core</artifactId>
<version>5.1.5</version>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.2.1</version>
</dependency>
yml中的相关的配置
实现类里的具体内容,(一般流程是,要调用预下单接口,然后返回给安卓的二次签名,安卓拿着二次签名去拉起微信支付,如果成功拉起支付,就可以付款,付款成功以后,就会主动回调咱们自己写的回调接口,修改业务逻辑,最后完成此次支付)
预下单接口
@Value("${ayj.wechat.notifyUrlMall}")
private String notifyUrlMall;
@Value("${ayj.wechat.notifyUrlMiniMall}")
private String notifyUrlMiniMall;
@Value("${ayj.wechat.appId}")
private String appId;
@Value("${ayj.wechat.miniAppId}")
private String miniAppId;
@Value("${ayj.wechat.mchId}")
private String mchId;
@Value("${ayj.wechat.bodyMall}")
private String bodyMall;
@Value("${ayj.wechat.bodyCharity}")
private String bodyCharity;
@Value("${ayj.wechat.key}")
private String key;
@Value("${ayj.wechat.feeType}")
private String feeType;
@Value("${ayj.wechat.tradeType}")
private String tradeType;
@Value("${ayj.wechat.miniTradeType}")
private String miniTradeType;
@Value("${ayj.wechat.package}")
private String wxPackage;
/**
* 预下单接口--商城-app
*
* @return
*/
@Override
public R<WXPaymentResponseDto> prePayMall(Integer userId, Integer orderId,BigDecimal money) {
int penny = money.multiply(new BigDecimal("100")).intValue();//已分为单位
Map<String, String> resp = new HashMap<>();
try {
WXPay wxpay = appConfigService.myWXPay();
SortedMap<String, String> data = new TreeMap<String, String>();
data.put("appid", appId);//微信支付id
data.put("mch_id", mchId);//商户号
data.put("nonce_str", WXPayUtil.generateNonceStr());
data.put("sign_type", "MD5");
data.put("body", bodyMall);
data.put("out_trade_no", OrderNoUtil.getOrderId());//订单号
data.put("fee_type", feeType);// 金额类型 默认CNY
data.put("total_fee", penny+"");//订单处理金额
data.put("notify_url", notifyUrlMall);//支付成功以后调用的地址
data.put("trade_type", tradeType); // 此处指定为app支付
// data.put("profit_sharing","Y");//分账
//生成签名--一次签名,在预下单之前
String characterEncoding = "UTF-8";
String oneSign= createSign(characterEncoding,data,key);
data.put("sign",oneSign);
resp = wxpay.unifiedOrder(data);
log.info("微信预下单返回的对象:", resp);
if (resp.get("return_code").equals("SUCCESS")) {
log.info("微信预下单成功!");
//生成微信预下单对象
WXPrePay prePay = new WXPrePay();
prePay.setMemberId(userId);//用户id
prePay.setOrderNo(data.get("out_trade_no"));//支付订单单号
prePay.setOrderId(orderId);//项目自增id
prePay.setOrderType(MallEmums.PRE_PAY_TYPE_MALL.getCode());//订单类型
prePay.setTotalAmount(penny);//金额
baseMapper.insert(prePay);
//返回WXPaymentResponseDto对象给前端
WXPaymentResponseDto dto = new WXPaymentResponseDto();
dto.setNonceStr(resp.get("nonce_str"));
dto.setPrepayId(resp.get("prepay_id"));
dto.setPartnerId(mchId); //商户id
dto.setAppId(appId); //商户id
dto.setWxPackage(wxPackage);
String time = new Date().getTime()+"";
dto.setTimestamp(time.substring(0,10));
//统一下单以后,生成的二次签名
Map<String, String> signmap = new TreeMap<>();
signmap.put("appid",appId);
signmap.put("noncestr",resp.get("nonce_str"));
signmap.put("package",wxPackage);
signmap.put("partnerid",mchId);
signmap.put("prepayid",resp.get("prepay_id"));
// signmap.put("signtype","MD5");
signmap.put("timestamp",time.substring(0,10));//官网上写到,时间戳要十位并且精确到秒,平常使用的时间戳都是毫秒13位
String sign = WxUtils.createSign(characterEncoding,signmap,key);
System.out.println("二次签名:"+sign);
dto.setSign(sign.substring(0,30)); //注意,就是这里,为什么是截取签名的前30位,正常生成的签名是32位,这是个坑,我被困扰很久,一会附上图片,告诉大家
return R.success(dto);
}
log.info("微信预下单失败:",resp.get("return_msg"));
return R.fail("微信预下单失败:"+resp.get("return_msg"));
} catch (Exception e) {
e.printStackTrace();
return R.fail("微信预下单失败:"+resp.get("return_msg"));
}
}
在这里给大家附上图片,我被这个二次签名困扰48小时,每次安卓一拉起微信支付,就报错,说是签名错误,我就百度,看到有个大神说,截取前30位就好了,
安卓拉起微信支付以后,并支付成功,这个时候,微信会去自动调用咱们的写的回调接口
public String callBackMall(HttpServletRequest request, HttpServletResponse response) {
response.setHeader("Content-Type", "application/xml"); //设置响应数据格式为xml
Map<String, String> returnMap = new HashMap<>();
returnMap.put("return_code", "SUCCESS");
returnMap.put("return_msg", "");
Long hxcode =null;
// 读取参数,解析Xml为map
Map<String, String> map = null;
try {
map = WXPayUtil.xmlToMap(wxUtils.readRequest(request));
// 转换为有序 map,判断签名是否正确
boolean isSignSuccess = WXPayUtil.isSignatureValid(new TreeMap<String,
String>(map), key, WXPayConstants.SignType.HMACSHA256);
// 签名校验成功,说明是微信服务器发出的数据
if (isSignSuccess) {
//拿出订单支付编号
String orderNo = map.get("out_trade_no");
//根据订单支付编号去查找订单id,等相关的数据
WXPrePay pay = prePayService.getWxPrePayByOrderNo(orderNo).getData();
System.out.println("预下单对象:" + pay);
Integer orderId = pay.getOrderId();
String transactionId = map.get("transaction_id");
System.out.println("微信订单支付单号" + transactionId);
Boolean b = orderFeignService.hasPayByOrderId(orderId).getData();
System.out.println(b);
if (!b) { //false,已支付------一定要在这里去判断是否支付成功,因为微信支付的回调,是分时间,一直在调用,这个具体看官网怎么写的
returnMap.put("hx_code", hxcode.toString());
return WXPayUtil.mapToXml(returnMap);
}
if (map.get("return_code").equals("SUCCESS")) {
if (map.get("result_code").equals("SUCCESS")) {
System.out.println("微信回调:支付成功,orderNo为:" + orderNo);
//------------------自己处理业务逻辑
pay.setTransactionId(transactionId);
prePayService.updateById(pay);
System.out.println(map);
returnMap.put("hx_code", hxcode.toString());
return WXPayUtil.mapToXml(returnMap);
} else {
System.out.println("微信回调:支付失败,orderNo为:" + orderNo);
}
}
// 签名校验失败(可能不是微信服务器发出的数据)
returnMap.put("return_code", "FAIL");
returnMap.put("return_msg", "");
return WXPayUtil.mapToXml(returnMap);
} else {
// 签名校验失败(可能不是微信服务器发出的数据)
returnMap.put("return_code", "FAIL");
returnMap.put("return_msg", "");
return WXPayUtil.mapToXml(returnMap);
}
} catch (IOException e) {
e.printStackTrace();
return "<xml>\n" +
" <return_code><![CDATA[FAIL]]></return_code>\n" +
" <return_msg><![CDATA[]]></return_msg>\n" +
"</xml>";
} catch (Exception e) {
e.printStackTrace();
return "<xml>\n" +
" <return_code><![CDATA[FAIL]]></return_code>\n" +
" <return_msg><![CDATA[]]></return_msg>\n" +
"</xml>";
}
}
这里附上两个工具类,上面会用到
package com.ayjmall.thirdparty.wechat.utils;
import jodd.util.ResourcesUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
/**
* @Auther swj
* @description 带双向证书的post请求工具类
* @Date 2021/1/20
*/
public class WxCertHttpUtil {
private static int socketTimeout = 10000;// 连接超时时间,默认10秒
private static int connectTimeout = 30000;// 传输超时时间,默认30秒
private static RequestConfig requestConfig;// 请求配置
private static CloseableHttpClient httpClient;// HTTP请求
/**
*
* @param url API地址
* @param --xmlObj 要提交的XML
* @param mchId 服务商商户ID
* @param --certPath证书路径
* @return
*/
public static String postData(String url, String xml, String mchId, String certPath) {
// 加载证书
try {
loadCert(mchId, certPath);
} catch (Exception e) {
e.printStackTrace();
}
String result = null;
HttpPost httpPost = new HttpPost(url);
StringEntity postEntity = new StringEntity(xml, "UTF-8");
httpPost.addHeader("Content-Type", "application/xml");
httpPost.setEntity(postEntity);
requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
httpPost.setConfig(requestConfig);
try {
HttpResponse response = null;
try {
response = httpClient.execute(httpPost);
} catch (IOException e) {
e.printStackTrace();
}
HttpEntity entity = response.getEntity();
try {
result = EntityUtils.toString(entity, "UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
} finally {
httpPost.abort();
}
return result;
}
/**
*加载证书
*
* @param mchId 服务商商户ID
* @param certPath 证书路径
* @throws Exception
*/
private static void loadCert(String mchId, String certPath) throws Exception {
// 证书密码,默认为服务商商戶ID
String key = mchId;
// 证书路径
String path = certPath;
if (!path.startsWith("/")) {
path = "/" + path;
}
// 指定证书格式为PKCS12
KeyStore keyStore = KeyStore.getInstance("PKCS12");
// 读取PKCS12证书文件
InputStream instream = ResourcesUtil.getResourceAsStream(path);
try {
// 指定PKCS12的密碼(商戶ID)
keyStore.load(instream, key.toCharArray());
} finally {
instream.close();
}
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, key.toCharArray()).build();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE);
httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
}
}
package com.ayjmall.thirdparty.wechat.utils;
import com.ayjmall.common.utils.MD5Util;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
/**
* Created by suneo.
* User: neo
* Date: 29/01/2018
* Time: 5:21 PM
* Describe:
* <编码格式统一为 UTF-8>
* <p>
* 便于使用,将所有的工具方法都集中在此,包含:
* 1. 执行 HTTP POST 请求,返回执行结果的 String
* 2. 创建签名(为下单数据创建)
* 3. 创建签名(为 APP 创建)
* 4. 检验签名
* 5. 读取 HTTP Request 内容
* 6. 读取 HTTP Response 内容
* 7. 将 Map 转化为 Xml
* 8. 将 Xml 转化为 Map
* 9. 生成 32 位随机字符串
* 10. MD5 签名
*/
@Service
public class WxUtils {
/**
* 微信支付签名算法sign
*
* @param characterEncoding
* @param parameters
* @return
*/
public static String createSign(String characterEncoding, Map<String, String> parameters, String key) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();// 所有参与传参的参数按照accsii排序(升序)
Iterator it = es.iterator();
while (it.hasNext()) {
@SuppressWarnings("rawtypes")
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v) && !"sign".equals(k)
&& !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + key); //KEY是商户秘钥
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding)
.toUpperCase();
return sign;
}
/**
* 读取 request body 内容作为字符串
*
* @param request
* @return
* @throws IOException
*/
public String readRequest(HttpServletRequest request) throws IOException {
InputStream inputStream;
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream();
String str;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((str = in.readLine()) != null) {
sb.append(str);
}
in.close();
inputStream.close();
return sb.toString();
}
}
小程序支付
小程序支付跟app支付有不一样的地方,但是区别不大,我已经在代码中注释了,小程序有单独的小程序id.还要在单独指定小程序的支付方式,而且还有单独的小程序的openid,请大家注意
public R<WXPaymentResponseDto> MiniPrePayMall(Integer userId, Integer orderId, BigDecimal money) {
AyjMallMembers members = memberFeignService.getMembersById(userId).getData();
int penny = money.multiply(new BigDecimal("100")).intValue();//已分为单位
Map<String, String> resp = new HashMap<>();
try {
MiniWXPay miniWXPay = miniUserMyConfig.miniMyWXPay();
SortedMap<String, String> data = new TreeMap<String, String>();
data.put("appid", miniAppId);//微信小程序支付id
data.put("mch_id", mchId);//商户号
data.put("nonce_str", MiniWXPayUtil.generateNonceStr());
data.put("sign_type", "MD5");
data.put("body", bodyMall);
data.put("out_trade_no", OrderNoUtil.getOrderId());//订单号
data.put("fee_type", feeType);// 金额类型 默认CNY
data.put("total_fee", penny+"");//订单处理金额
data.put("notify_url", notifyUrlMiniMall);//支付成功以后调用的地址
data.put("trade_type", miniTradeType); // 此处指定为小程序支付
data.put("openid",members.getOpenidMiniUserId());//小程序支付必须使用小程序登录的openid
//生成签名--一次签名,在预下单之前
String characterEncoding = "UTF-8";
String oneSign= createSign(characterEncoding,data, key);
data.put("sign",oneSign);
resp = miniWXPay.unifiedOrder(data);
log.info("微信预下单返回的对象-----------------:", resp);
if (resp.get("return_code").equals("SUCCESS")) {
log.info("微信预下单成功!");
//生成微信预下单对象
WXPrePay prePay = new WXPrePay();
prePay.setMemberId(userId);//用户id
prePay.setOrderNo(data.get("out_trade_no"));//支付订单单号
prePay.setOrderId(orderId);//项目自增id
prePay.setOrderType(4);//订单类型
prePay.setTotalAmount(penny);//金额
baseMapper.insert(prePay);
//返回WXPaymentResponseDto对象给前端
WXPaymentResponseDto dto = new WXPaymentResponseDto();
dto.setNonceStr(resp.get("nonce_str"));
dto.setPrepayId(resp.get("prepay_id"));
dto.setPartnerId(mchId); //商户id
dto.setAppId(miniAppId); //商户id
dto.setWxPackage(wxPackage);
String time = new Date().getTime()+"";
dto.setTimestamp(time.substring(0,10));
//统一下单以后,生成的二次签名
Map<String, String> signmap = new TreeMap<>();
signmap.put("appId",miniAppId);
signmap.put("nonceStr",resp.get("nonce_str"));
signmap.put("package","prepay_id="+resp.get("prepay_id"));
signmap.put("signType","MD5");
signmap.put("timeStamp",time.substring(0,10));
//signmap.put("mchId",mchId);
String sign = WxUtils.createSign(characterEncoding,signmap,key);//商户的key
System.out.println(signmap+"---------------");
System.out.println("二次签名:"+sign);
dto.setSign(sign);
System.out.println(resp);
return R.success(dto);
}
log.info("微信预下单失败:",resp.get("return_msg"));
return R.fail("微信预下单失败:"+resp.get("return_msg"));
} catch (Exception e) {
e.printStackTrace();
return R.fail("微信预下单失败:"+resp.get("return_msg"));
}
}