微信扫码支付
最近重构项目时,负责了支付模块,微信扫码支付(NATIVE)和 支付宝扫码支付,也是第一次接触,虽然根据官方文档和一些博客写出来了,但是遇到的问题却很多,走了很多弯路,浪费了很多精力和时间,抽出时间来记录一下,以后难免还是会用到。
支付宝扫码支付传送门
微信API
微信开发流程:
开发前,商户必须在公众平台后台设置支付回调URL。URL实现的功能:接收用户扫码后微信支付系统回调的productid
和openid
;
业务流程说明:
(1)商户后台系统根据微信支付规定格式生成二维码(规则见下文),展示给用户扫码。
(2)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(3)微信支付系统收到客户端请求,发起对商户后台系统支付回调URL的调用。调用请求将带productid和用户的openid等参数,并要求商户系统返回交数据包,详细请见"本节3.1回调数据输入参数"
(4)商户后台系统收到微信支付系统的回调请求,根据productid生成商户系统的订单。
(5)商户系统调用微信支付【统一下单API】请求下单,获取交易会话标识(prepay_id)
(6)微信支付系统根据商户系统的请求生成预支付交易,并返回交易会话标识(prepay_id)。
(7)商户后台系统得到交易会话标识prepay_id(2小时内有效)。
(8)商户后台系统将prepay_id返回给微信支付系统。返回数据见"本节3.2回调数据输出参数"
(9)微信支付系统根据交易会话标识,发起用户端授权支付流程。
(10)用户在微信客户端输入密码,确认支付后,微信客户端提交支付授权。
(11)微信支付系统验证后扣款,完成支付交易。
(12)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(13)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(14)未收到支付通知的情况,商户后台系统调用【查询订单API】。
(15)商户确认订单已支付后给用户发货
整个流程看下来其实就是用户通过订单信息和微信提供的统一下单API的接口直接进行http请求,然后微信会生成一个预支付交易,之后后返回一个url,将这里的url埋入二维码就可以了。到这里二维码就生成了,各位就可以消费了,消费之后微信会调支付回调接口,通知支付结果。这里提到的支付接口和回调接口需要在微信商户平台进行配置。
获取二维码:
将二维码返回前端展示 也二维码以文件格式输出到本地
private Map<String, Object> getPayCodeByWeChat(PayCodeRequest payCodeRequest) {
log.info("获取微信二维码请求*******");
Map<String, Object> resultMap = new HashMap<String, Object>();
String currentUserID = null;
String orderID = null;
ResultParams makeCode = null;
BizContentByWeChatPay bizContent = null;
try {
orderID = payCodeRequest.getOrderID();
currentUserID = payCodeRequest.getCurrentUserID();
SystemConfig queryOneSystemConfig = orderPayService.queryOneSystemConfig("cm2_weChatAccount");
PayRecord queryOrderPayRecordByOrderID = orderPayService.queryPayRecordByOrderID(orderID);
if (null != queryOrderPayRecordByOrderID && OrderPayStatusEnum.PAID.getValue() == queryOrderPayRecordByOrderID.getPayStatus()) {
log.info("支付的类型:" + queryOrderPayRecordByOrderID.getPayType());
log.debug("订单已存在支付过记录");
resultMap.put("code", -20003);
resultMap.put("desc", "订单已支付,无法再次操作");
return resultMap;
}
bizContent = new BizContentByWeChatPay();
String payAmount = payCodeRequest.getPayAmount().replace(",", "");
bizContent.setBody(payCodeRequest.getValidityPeriod());
bizContent.setNonce_str(CommonUtils.genId());
bizContent.setOut_trade_no(CommonUtils.genPayId());
bizContent.setSubject(payCodeRequest.getProductName());
bizContent.setTotal_amount(new BigDecimal(payAmount));
String configValue = queryOneSystemConfig.getConfigValue();
makeCode = WeChatPayUtil.makeCode(configValue, bizContent);
if (null == makeCode || !makeCode.getReturn_code().equals("SUCCESS") || !makeCode.getResult_code().equals("SUCCESS")) {
resultMap.put("code", -20018);
resultMap.put("desc", "获取微信支付二维码失败");
payLogService.createPayLog(currentUserID, new Gson().toJson(bizContent), orderID, new Gson().toJson(makeCode), -1);
return resultMap;
}
queryOneSystemConfig = orderPayService.queryOneSystemConfig("qrCodebyAddress");
String code_url = makeCode.getCode_url();
// 生成二维码
String base64Code = QRCodeUtil.getBase64Code(code_url);
// 有记录,且非已支付
if (null != queryOrderPayRecordByOrderID) {
queryOrderPayRecordByOrderID.setOrderID(payCodeRequest.getOrderID());
queryOrderPayRecordByOrderID.setPayType(OrderPayTypeEnum.WECHAT.getValue());
queryOrderPayRecordByOrderID.setSign(makeCode.getSign());
queryOrderPayRecordByOrderID.setEditor(payCodeRequest.getCurrentUserID());
queryOrderPayRecordByOrderID.setEditTime(new Date());
queryOrderPayRecordByOrderID.setPayStatus(OrderPayStatusEnum.UNPAID.getValue());
queryOrderPayRecordByOrderID.setQr_code(code_url);
queryOrderPayRecordByOrderID.setNonce_str(bizContent.getNonce_str());
queryOrderPayRecordByOrderID.setReturn_nonce_str(makeCode.getNonce_str());
long longValue = (new BigDecimal(payAmount).multiply(new BigDecimal("100"))).longValue();
queryOrderPayRecordByOrderID.setPayAmount(longValue);
queryOrderPayRecordByOrderID.setPrepay_id(makeCode.getPrepay_id());
queryOrderPayRecordByOrderID.setTrade_type(makeCode.getTrade_type());
queryOrderPayRecordByOrderID.setOut_trade_no(bizContent.getOut_trade_no());
orderPayService.updateOrderPayRecord(queryOrderPayRecordByOrderID);
} else {
PayRecord payRecord = new PayRecord();
payRecord.setOrderID(payCodeRequest.getOrderID());
payRecord.setPayType(OrderPayTypeEnum.WECHAT.getValue());
payRecord.setSign(makeCode.getSign());
payRecord.setOperator(payCodeRequest.getCurrentUserID());
payRecord.setCreateTime(new Date());
payRecord.setPayStatus(OrderPayStatusEnum.UNPAID.getValue());
long longValue = (new BigDecimal(payAmount).multiply(new BigDecimal("100"))).longValue();
payRecord.setPayAmount(longValue);
payRecord.setQr_code(code_url);
payRecord.setNonce_str(bizContent.getNonce_str());
payRecord.setReturn_nonce_str(makeCode.getNonce_str());
payRecord.setPrepay_id(makeCode.getPrepay_id());
payRecord.setTrade_type(makeCode.getTrade_type());
payRecord.setOut_trade_no(bizContent.getOut_trade_no());
orderPayService.createOrderPayRecord(payRecord);
}
resultMap.put("code", 0);
resultMap.put("desc", "获取成功");
resultMap.put("data", base64Code);
resultMap.put("out_trade_no", bizContent.getOut_trade_no());
payLogService.createPayLog(currentUserID, new Gson().toJson(bizContent), orderID, new Gson().toJson(makeCode), 0);
return resultMap;
} catch (Exception e) {
log.error("获取微信二维码处理异常*******", e);
resultMap.put("code", -20018);
resultMap.put("desc", "获取微信支付二维码失败");
payLogService.createPayLog(currentUserID, new Gson().toJson(bizContent), orderID, "获取微信二维码处理异常", 1);
return resultMap;
}
}
微信支付生成二维码和签名
签名生成的通用步骤如下:
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
代码展示:
public static ResultParams makeCode(String weChatAccountData, BizContentByWeChatPay bizContent){
log.info("获取微信支付二维码********");
log.info("aliPayAccountData:"+weChatAccountData);
log.info("bizContent:"+new Gson().toJson(bizContent));
try{
WeChatAccount weChatAccount = new Gson().fromJson(weChatAccountData, WeChatAccount.class);
String app_id = weChatAccount.getApp_id();
String merchant_number = weChatAccount.getMerchant_number();
String notifyUrl = weChatAccount.getNotifyUrl();
String secret_key = weChatAccount.getSecret_key();
String pay_path = weChatAccount.getPay_path();
//商户订单号,也是订单号
String out_trade_no = bizContent.getOut_trade_no();
String body = bizContent.getBody();
BigDecimal total_amount = bizContent.getTotal_amount();
String nonce_str = bizContent.getNonce_str();
int intAmount = total_amount.multiply(new BigDecimal("100")).intValue();
String timeout_express = weChatAccount.getTimeout_express();
//签名所需要的参数
SortedMap<String,String> parameters = new TreeMap<String,String>();
parameters.put("appid",app_id);
//商户号
parameters.put("mch_id",merchant_number);
parameters.put("body",body);
//SAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付
parameters.put("trade_type","NATIVE");
//商户订单号
parameters.put("out_trade_no",out_trade_no);
//订单总金额(单位分)
parameters.put("total_fee",String.valueOf(intAmount));
//终端IP //支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP
parameters.put("spbill_create_ip",GetIp());
//商品ID trade_type=NATIVE时,此参数必传。此参数为二维码中包含的商品ID,商户自行定义。
parameters.put("product_id",out_trade_no);
//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
parameters.put("notify_url",notifyUrl);
//随机字符串
parameters.put("nonce_str",nonce_str);
//接口所需要的参数
PayParams order = new PayParams();
order.setAppid(app_id);
order.setMch_id(merchant_number);
order.setBody(body);
order.setTrade_type("NATIVE");
order.setOut_trade_no(out_trade_no);
order.setTotal_fee(intAmount);
order.setSpbill_create_ip(GetIp());
order.setProduct_id(out_trade_no);
order.setNotify_url(notifyUrl);
order.setNonce_str(nonce_str);
if(null!=timeout_express && !"".equals(timeout_express)){
Calendar c = Calendar.getInstance();
c.setTime(new Date());
int parseTime = Integer.parseInt(timeout_express);
c.add(Calendar.SECOND, parseTime);
long time = c.getTime().getTime();
parameters.put("time_expire", String.valueOf(time));
order.setTime_expire(String.valueOf(time));
}
//签名
String sign = WXPayUtil.getPaySign(parameters,secret_key);
order.setSign(sign);
//调用微信支付统一下单接口,让微信也生成一个预支付订单
String xmlResult = HttpUtil.sendPost(pay_path,XMLUtil.convertToXml(order));
log.info("xmlResult:"+xmlResult);
//把返回的xml字符串转成对象
ResultParams entity = (ResultParams)XMLUtil.convertXmlStrToObject(ResultParams.class,xmlResult);
log.info("ResultParams:"+new Gson().toJson(entity));
return entity;
}catch(Exception e){
log.info("获取微信支付二维码,调用接口异常********",e);
return null;
}
}
微信支付工具类 (微信提供的demo 可以下载使用)
public class WXPayUtil {
private static final String SYMBOLS = "xxx";
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) {
log.error("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) {
log.info("mapToXml开始转换**************");
StringWriter writer = null;
try {
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");
writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
log.info("转换完:", output);
writer.flush();
return output;
} catch (Exception ex) {
log.error("mapToXml转换异常", ex);
return null;
} finally {
if (null != writer) {
try {
writer.close();
} catch (IOException e) {
log.error("关闭writer异常", e);
}
}
}
}
/**
* 生成带有 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, WXPayConstants.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, WXPayConstants.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, WXPayConstants.SignType.MD5);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名类型,默认为MD5,支持HMAC-SHA256和MD5。
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
log.info("开始验证******");
if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
log.debug("验证失败******");
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, WXPayConstants.SignType.MD5);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.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 (WXPayConstants.SignType.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
} else if (WXPayConstants.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 log getlog() {
log log = logFactory.getlog("wxpay java sdk");
return log;
}*/
/**
* 获取当前时间戳,单位秒
*
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis() / 1000;
}
/**
* 获取当前时间戳,单位毫秒
*
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
/**
* 获取签名
*
* @param obj
* @return
* @throws IllegalAccessException
* @throws IOException
*/
public static String getPaySign(Object obj, String secret_key) throws Exception {
StringBuilder sb = new StringBuilder();
//把对象转为TreeMap集合(按照key的ASCII 码从小到大排序)
TreeMap<String, Object> map = (TreeMap) obj;
Set<Map.Entry<String, Object>> entrySet = map.entrySet();
//遍历键值对
for (Map.Entry<String, Object> entry : entrySet) {
//如果值为空,不参与签名
if (entry.getValue() != null) {
sb.append(entry.getKey() + "=" + entry.getValue() + "&");
}
}
//最后拼接商户的API密钥
String stringSignTemp = sb.toString() + "key=" + secret_key;
return MD5(stringSignTemp);
}
/**
* 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
*
* @param xmlStr API返回的XML格式数据
* @param weChatAccountData 微信账号信息 json
* @return Map类型数据
* @throws Exception
*/
public static Map<String, String> processResponseXml(String weChatAccountData, String xmlStr) throws Exception {
log.info("开始处理xml文件***转换Map***");
String RETURN_CODE = "return_code";
String return_code;
Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
log.info("转换完的map:" + respData);
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)) {
WeChatAccount weChatAccount = new Gson().fromJson(weChatAccountData, WeChatAccount.class);
if (isResponseSignatureValid(respData, weChatAccount.getSecret_key())) {
log.debug("验证成功******");
return respData;
} else {
log.debug("验证异常******");
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));
}
}
/**
* 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
*
* @param reqData 向wxpay post的请求数据
* @return 签名是否有效
* @throws Exception
*/
public static boolean isResponseSignatureValid(Map<String, String> reqData, String secret_key) throws Exception {
// 返回数据的签名方式和请求中给定的签名方式是一致的
return isSignatureValid(reqData, secret_key, WXPayConstants.SignType.MD5);
}
/* public static String genSandBoxSign(String mch_id,String nonce_str,String key){
Map param = new HashMap();
try {
param.put("mch_id", mch_id);// 商户号
param.put("nonce_str", nonce_str);// 随机字符串
param.put("sign", WXPayUtil.generateSignature(param, key,WXPayConstants.SignType.MD5));// 沙盒测试貌似只支持MD5加密
//String xml = mapToXml(param);
TestM m = new TestM();
m.setMch_id(mch_id);
m.setNonce_str(nonce_str);
m.setSign(param.get("sign"));
String xmlResult = requestWithoutCert("/sandboxnew/pay/getsignkey",param,1000,1000);
return xmlResult;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String requestWithoutCert(String urlSuffix, Map reqData,
int connectTimeoutMs, int readTimeoutMs) throws Exception {
String msgUUID = reqData.get("nonce_str");
String reqBody = WXPayUtil.mapToXml(reqData);
String resp = requestWithoutCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, autoReport);
return resp;
}
*/
/**
* 可重试的,非双向认证的请求
*
* @return
*//*
public static String requestWithoutCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {
return request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, false, autoReport);
}
private static String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception {
Exception exception = null;
long elapsedTimeMillis = 0;
long startTimestampMs = WXPayUtil.getCurrentTimestampMs();
boolean firstHasDnsErr = false;
boolean firstHasConnectTimeout = false;
boolean firstHasReadTimeout = false;
DomainInfo domainInfo = config.getWXPayDomain().getDomain(config);
if(domainInfo == null){
throw new Exception("WXPayConfig.getWXPayDomain().getDomain() is empty or null");
}
try {
String result = requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert);
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, null);
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout);
return result;
}
catch (UnknownHostException ex) { // dns 解析错误,或域名不存在
exception = ex;
firstHasDnsErr = true;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
WXPayUtil.getlog().warn("UnknownHostException for domainInfo {}", domainInfo);
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout
);
}
catch (ConnectTimeoutException ex) {
exception = ex;
firstHasConnectTimeout = true;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
WXPayUtil.getlog().warn("connect timeout happened for domainInfo {}", domainInfo);
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout
);
}
catch (SocketTimeoutException ex) {
exception = ex;
firstHasReadTimeout = true;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
WXPayUtil.getlog().warn("timeout happened for domainInfo {}", domainInfo);
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout);
}
catch (Exception ex) {
exception = ex;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
WXPayReport.getInstance(config).report(
uuid,
elapsedTimeMillis,
domainInfo.domain,
domainInfo.primaryDomain,
connectTimeoutMs,
readTimeoutMs,
firstHasDnsErr,
firstHasConnectTimeout,
firstHasReadTimeout);
}
config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, exception);
throw exception;
}
*/
}
class TestM {
private String mch_id;
private String nonce_str;
private String sign;
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
}
class DomainInfo {
public String domain; //域名
public boolean primaryDomain; //该域名是否为主域名。例如:api.mch.weixin.qq.com为主域名
public DomainInfo(String domain, boolean primaryDomain) {
this.domain = domain;
this.primaryDomain = primaryDomain;
}
@Override
public String toString() {
return "DomainInfo{" +
"domain='" + domain + '\'' +
", primaryDomain=" + primaryDomain +
'}';
}
}
扫码支付完成后需要微信回调:
回调参数:
回调返回:
代码展示:
public Map<String, String> doWeChatNotify() {
log.info("微信回调支付返回结果:");
Map<String, String> resultMap = new HashMap<String, String>();
Map<String, String> params = new HashMap<String, String>();
InputStream inStream = null;
ByteArrayOutputStream outSteam = null;
String orderID = null;
try {
inStream = request.getInputStream();
outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
String resultxml = new String(outSteam.toByteArray(), "utf-8");
log.info("获取的xml:" + resultxml);
outSteam.flush();
SystemConfig queryOneSystemConfig = orderPayService.queryOneSystemConfig("cm2_weChatAccount");
params = WXPayUtil.processResponseXml(queryOneSystemConfig.getConfigValue(), resultxml);
log.info("xml转换map结果:" + params);
/**
* 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一
*/
String out_trade_no = params.get("out_trade_no");
PayRecord queryOrderPayRecordByOrderID = this.orderPayService.queryOrderPayRecordByOut_Trade_NoAndType(out_trade_no, OrderPayTypeEnum.WECHAT.getValue());
if (null == queryOrderPayRecordByOrderID) {
resultMap.put("return_code", "FAIL");
resultMap.put("return_msg", "当前订单未找微信支付记录");
return resultMap;
}
orderID = queryOrderPayRecordByOrderID.getOrderID();
String return_code = params.get("return_code");
String return_msg = params.get("return_msg");
if ("FAIL".equals(return_code)) {
resultMap.put("return_code", "FAIL");
resultMap.put("return_msg", return_msg);
this.payLogService.createPayLogByReceive(orderID, new Gson().toJson(params), 1, "微信异步调用,返回失败");
return resultMap;
}
/**
* 订单金额(分)
*/
String total_fee = params.get("total_fee");
/**
* 商户号
*/
//String mch_id = params.get("mch_id");
String nonce_str = params.get("nonce_str");
String sign = params.get("sign");
/**
* SUCCESS/FAIL
*/
String result_code = params.get("result_code");
/**
* 用户在商户appid下的唯一标识
*/
String openid = params.get("openid");
/**
* 微信支付订单号
*/
String transaction_id = params.get("transaction_id");
/**
* 支付完成的时间
*/
String time_end = params.get("time_end");
String trade_type = params.get("trade_type");
queryOrderPayRecordByOrderID.setPayType(OrderPayTypeEnum.WECHAT.getValue());
queryOrderPayRecordByOrderID.setComment(queryOrderPayRecordByOrderID.getComment() + "时间:" + new Date() + "==>微信异步数据");
queryOrderPayRecordByOrderID.setEditTime(new Date());
Date payTime = null;
if (null != time_end && !"".equals(time_end)) {
SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMddhhmm");
payTime = sDateFormat.parse(time_end);
}
queryOrderPayRecordByOrderID.setPayTime(payTime);
queryOrderPayRecordByOrderID.setPayAmount(Long.parseLong(total_fee));
queryOrderPayRecordByOrderID.setSign(sign);
queryOrderPayRecordByOrderID.setBuyer_logon_id(openid);
queryOrderPayRecordByOrderID.setTrade_type(trade_type);
/**
* 交易状态:WAIT_BUYER_PAY(交易创建,等待买家付款)、TRADE_CLO
*/
if ("SUCCESS".equals(result_code)) {
queryOrderPayRecordByOrderID.setNonce_str(nonce_str);
queryOrderPayRecordByOrderID.setPayStatus(OrderPayStatusEnum.PAID.getValue());
queryOrderPayRecordByOrderID.setTrade_no(transaction_id);
this.orderPayService.updateOrderPayRecord(queryOrderPayRecordByOrderID);
resultMap.put("return_code", "SUCCESS");
resultMap.put("return_msg", "OK");
this.payLogService.createPayLogByReceive(orderID, new Gson().toJson(params), 0, "微信异步调用成功");
return resultMap;
}
queryOrderPayRecordByOrderID.setPayStatus(OrderPayStatusEnum.PAYMENT_FAILED.getValue());
this.orderPayService.updateOrderPayRecord(queryOrderPayRecordByOrderID);
resultMap.put("return_code", "FAIL");
resultMap.put("return_msg", "微信支付回调失败,返回result_code不为SUCCESS");
this.payLogService.createPayLogByReceive(orderID, new Gson().toJson(params), 1, "微信异步调用失败");
return resultMap;
} catch (Exception e) {
log.error("微信支付回调返回异常:+", e);
resultMap.put("return_code", "FAIL");
resultMap.put("return_msg", "微信支付回调失败");
this.payLogService.createPayLogByReceive(orderID, new Gson().toJson(params), 1, "微信异步调用异常");
return resultMap;
} finally {
if (null != inStream) {
try {
inStream.close();
} catch (IOException e) {
log.error("关闭inStream异常");
}
}
if (null != outSteam) {
try {
outSteam.close();
} catch (IOException e) {
log.error("关闭outSteam异常");
}
}
}
}
微信退款:
退款需要证书的,这个上边提到过,去微信商户平台生成下载,微信是通过微信订单号(transaction_id)来退款的,transaction_id这个参数是支付回调时候返回的,注意保存
private Map<String, Object> getPayCodeByWeChat(PayRecord queryPayRecordByOrderID, long transactionPrice, TradeRefundRequest tradeRefundRequest) {
log.info("微信退款请求*******");
Map<String, Object> resultMap = new HashMap<>(16);
try {
SystemConfig queryOneSystemConfig = orderPayService.queryOneSystemConfig("cm2_weChatAccount");
RefundParams refundParams = new RefundParams();
refundParams.setNonce_str(CommonUtils.genId());
refundParams.setOut_refund_no(CommonUtils.genId());
String refund_amount = tradeRefundRequest.getRefund_amount().replace(",", "");
int intValue = new BigDecimal(refund_amount).multiply(new BigDecimal("100")).intValue();
refundParams.setRefund_fee(intValue);
refundParams.setTotal_fee(new BigDecimal(transactionPrice).intValue());
refundParams.setOut_trade_no(tradeRefundRequest.getOrderID());
RefundResultParams refundResponse = WeChatPayUtil.refund(queryOneSystemConfig.getConfigValue(), refundParams);
log.debug("微信退款结果:" + new Gson().toJson(refundResponse));
if (null == refundResponse || !refundResponse.getReturn_code().equals("SUCCESS") || !refundResponse.getResult_code().equals("SUCCESS")) {
log.error("微信退款失败*******");
resultMap.put("code", -20010);
resultMap.put("desc", "退款失败");
return resultMap;
}
queryPayRecordByOrderID.setRefund_request_no(refundParams.getOut_refund_no());
queryPayRecordByOrderID.setRefund_amount(new BigDecimal(refund_amount).multiply(new BigDecimal("100")).longValue());
queryPayRecordByOrderID.setRefundTime(new Date());
queryPayRecordByOrderID.setRefund_operator(tradeRefundRequest.getCurrentUserID());
queryPayRecordByOrderID.setRefund_reason(tradeRefundRequest.getRefund_reason());
queryPayRecordByOrderID.setPayStatus(OrderPayStatusEnum.REFUNDING.getValue());
orderPayService.updateOrderPayRecord(queryPayRecordByOrderID);
resultMap.put("code", 0);
resultMap.put("desc", "操作成功");
resultMap.put("request_no", queryPayRecordByOrderID.getRefund_request_no());
resultMap.put("refund_amount", queryPayRecordByOrderID.getRefund_amount());
return resultMap;
} catch (Exception e) {
log.error("微信退款处理异常*******", e);
resultMap.put("code", -20010);
resultMap.put("desc", "退款失败");
return resultMap;
}
}