微信申请退款接入经验(坑已踩完)

前言

  明确一点。我写的是Java语言的native(扫码支付)的踩坑记录,但是其他的支付方式也是异曲同工。

 

接入配置

1.微信申请退款接口是需要双向证书的---apiclient_cert.p12,没有证书微信会一直返回400,证书下载的位置:微信商户平台-》账户中心-》 API安全 中下载的 。

2.微信提供的工具类的下载地址:https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=11_1

3.微信申请退款最好有个退款通知地址,如果没有就需要手动发起退款查询或者跑定时任务去轮询退款订单是否退款成功(有时候通知可能会出现不可达的问题,所以只能自己手动调用)。

4.微信接收退款通知的url和微信接收支付通知的url一样,接收通知成功都需要返回一个成功或者失败状态给微信,不然会一直给你发通知(有时候收到3条,有时候收到4条)。

              成功的xml:   String xml ="<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";

扫码支付申请退款文档地址:https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=9_4

 

代码部分

1.组装微信申请退款的参数

		// 组装微信退款参数
		SortedMap<String, String> param = new TreeMap<String, String>();
		// 自家的appId
		param.put("appid", ConfigConstants.APP_ID);   
		// 自家的mchId
		param.put("mch_id", ConfigConstants.MCH_ID);     
		// 微信给的生成随机串的工具类
		param.put("nonce_str", WXPayUtil.generateNonceStr());
		param.put("notify_url", ConfigConstants.ORDER_REFUND_NOTIFY);
		// out_refund_no和out_trade_no可以都传也可以只传一个
		param.put("out_trade_no", orderNo);
		if (StringUtils.isBlank(refundOrderNo)) {
			refundOrderNo = orderNo + UuidUtils.getUUID();
		}
		param.put("out_refund_no", refundOrderNo);
		param.put("refund_fee", String.valueOf(refundFee));
		param.put("refund_desc", refundReasons);
		param.put("total_fee",  String.valueOf(refundFee));
		param.put("transaction_id", orderPay.getTransactionId());
		// 微信给的生成签名的工具类
		String generateSignature = WXPayUtil.generateSignature(param, ConfigConstants.API_KEY, SignType.MD5);
		param.put("sign", generateSignature);
		String requestParam = WXPayUtil.generateSignedXml(param, ConfigConstants.API_KEY);
		// 证书路径
		// 这是我自己服务器的路径
		String path = "/home/rhpassadmin/config"+ConfigConstants.CERT_PATH;
		log.info("证书路径:path---->"+path);
		try {
			log.info("订单号为:"+orderNo+",申请退款请求参数:"+requestParam);
			String result = CommonUtil.postData(ConfigConstants.PAY_REFUND, requestParam, path);
			log.info("订单号为:"+orderNo+",申请退款返回结果:"+result);
			// 以下是自己的业务逻辑,自行处理
			
		} catch (Exception e) {
			e.printStackTrace();
		}

2.证书的加载,是在发起http-post请求之前,我放在CommonUtil工具类中

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;

import javax.net.ssl.SSLContext;

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.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
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 com.rhpass.trade.pay.common.wxpay.config.ConfigConstants;
/**
 * 描述:
 * @author Nanci
 * @date 2020年6月30日
 */
public class CommonUtil {
	private static int socketTimeout = 10000;// 连接超时时间,默认10秒
	private static int connectTimeout = 30000;// 传输超时时间,默认30秒
	private static RequestConfig requestConfig;// 请求器的配置
	private static CloseableHttpClient httpClient;// HTTP请求器
	
	/**
	 * 通过Https往API post xml数据
	 * @param url	API地址
	 * @param xmlObj	要提交的XML数据对象
	 * @return
	 */
	public static String postData(String url, String xmlObj, String path) {
		try {
			// 加载证书
			initCert(path);
		} catch (Exception e) {
			e.printStackTrace();
		}
		String result = null;
		HttpPost httpPost = new HttpPost(url);
		// 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
		StringEntity postEntity = new StringEntity(xmlObj, "UTF-8");
		httpPost.addHeader("Content-Type", "text/xml");
		httpPost.setEntity(postEntity);
		// 根据默认超时限制初始化requestConfig
		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;
	}
	
	/**
	 * 加载证书
	 * 
	 */
	@SuppressWarnings("deprecation")
	private static void initCert(String path) throws Exception {
		// 证书密码,默认为商户ID
		String key = ConfigConstants.MCH_ID;
		// 指定读取证书格式为PKCS12
		KeyStore keyStore = KeyStore.getInstance("PKCS12");
		// 读取本机存放的PKCS12证书文件
		FileInputStream instream = new FileInputStream(new File(path));
		try {
			// 指定PKCS12的密码(商户ID)
			keyStore.load(instream, key.toCharArray());
		} finally {
			instream.close();
		}
		SSLContext sslcontext = SSLContexts
				.custom()
				.loadKeyMaterial(keyStore, key.toCharArray())
				.build();
		// 指定TLS版本
		SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
				sslcontext, new String[] { "TLSv1" }, null,
				SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
		// 设置httpclient的SSLSocketFactory
		httpClient = HttpClients
				.custom()
				.setSSLSocketFactory(sslsf)
				.build();
	}

}

3.工具类单独所属的依赖

		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>4.5.5</version>
		</dependency>

4.经常都会遇到打jar包证书加载不了的问题:

 解决办法:需要在pom.xml的对应位置加入,设置p12的证书不被过滤

<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<includeSystemScope>true</includeSystemScope>
				</configuration>
			</plugin>
			<!-- 不需要转码的文件 -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-resources-plugin</artifactId>
				<configuration>
					<encoding>UTF-8</encoding>
					<!-- 过滤后缀为pem、pfx的证书文件 -->
					<nonFilteredFileExtensions>
						<nonFilteredFileExtension>pem</nonFilteredFileExtension>
						<nonFilteredFileExtension>pfx</nonFilteredFileExtension>
						<nonFilteredFileExtension>p12</nonFilteredFileExtension>
					</nonFilteredFileExtensions>
				</configuration>
			</plugin>
		</plugins>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
			</resource>
			<!-- 设置此文件不过滤 -->
			<resource>
				<directory>src/main/resources</directory>
				<includes>
					<include>**/*.p12</include>
				</includes>
				<filtering>false</filtering>
			</resource>
		</resources>
	</build>

 

希望本文能对大家有所帮助


版权声明:本文为zheng2780071070原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。