申请退款接⼊经验(坑已踩完)
前⾔
明确⼀点。我写的是Java语⾔的native(扫码⽀付)的踩坑记录,但是其他的⽀付⽅式也是异曲同⼯。
接⼊配置
1.申请退款接⼝是需要双向证书的---apiclient_cert.p12,没有证书会⼀直返回400,证书下载的位置:商户平台-》账户中⼼-》 API安全 中下载的 。
3.申请退款最好有个退款通知地址,如果没有就需要⼿动发起退款查询或者跑定时任务去轮询退款订单是否退款成功(有时候通知可能会出现不可达的问题,所以只能⾃⼰⼿动调⽤)。
4.接收退款通知的url和接收⽀付通知的url⼀样,接收通知成功都需要返回⼀个成功或者失败
状态给,不然会⼀直给你发通知(有时候收到3条,有时候收到4条)。
成功的xml:  String xml ="<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]> </return_msg></xml>";
扫码⽀付申请退款⽂档地址:
代码部分
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", ateNonceStr());
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 + UUID();
}
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", TransactionId());
// 给的⽣成签名的⼯具类
String generateSignature = ateSignature(param, ConfigConstants.API_KEY, SignType.MD5);
param.put("sign", generateSignature);
String requestParam = ateSignedXml(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.ssl.SSLContext;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.fig.RequestConfig;
import org.apache.hods.HttpPost;
import org.ssl.SSLConnectionSocketFactory; import org.ssl.SSLContexts;
import org.ity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import ade.fig.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 = ute(httpPost);
}  catch (IOException e) {
e.printStackTrace();
}
HttpEntity entity = Entity();
try {
result = String(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 = Instance("PKCS12");
// 读取本机存放的PKCS12证书⽂件
FileInputStream instream = new FileInputStream(new File(path));
try {
// 指定PKCS12的密码(商户ID)
keyStore.load(instream, CharArray());
} finally {
instream.close();
}
SSLContext sslcontext = SSLContexts
.custom()
.loadKeyMaterial(keyStore, CharArray())
.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包证书加载不了的问题:
解决办法:需要在l的对应位置加⼊,设置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</non
FilteredFileExtension>      <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>
希望本⽂能对⼤家有所帮助