App支付后申请退款

手机付款成功后,请求退款

后台服务端使用 Java开发

支付宝

退款

参考:https://docs.open.alipay.com/api_1/alipay.trade.refund

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;

/// 支付宝退款
public boolean alipayRefunds(Map<String, String> params){

/*
out_trade_no String 特殊可选 64 订单支付时传入的商户订单号,不能和 trade_no同时为空。 20150320010101001
trade_no String 特殊可选 64 支付宝交易号,和商户订单号不能同时为空 2014112611001004680073956707
refund_amount Price 必选 9 需要退款的金额,该金额不能大于订单金额,单位为元,支持两位小数 200.12
refund_reason String 可选 256 退款的原因说明 正常退款
out_request_no String 可选 64 标识一次退款请求,同一笔交易多次退款需要保证唯一,如需部分退款,则此参数必传。 HZ01RF001
operator_id String 可选 30 商户的操作员编号 OP001
store_id String 可选 32 商户的门店编号 NJ_S_001
terminal_id String 可选 32 商户的终端编号 NJ_T_001
*/
AlipayTradeRefundModel refundModel = new AlipayTradeRefundModel();
refundModel.setOutTradeNo(params.get("out_trade_no"));
refundModel.setRefundAmount(params.get("total_fee"));
refundModel.setRefundReason(params.get("reason"));
refundModel.setOutRequestNo(params.get("request_no"));
String jsonStr = JSON.toJSONString(refundModel);

String APP_ID = params.get("seller_id");
String CHARSET = params.get("_input_charset");
String APP_PRIVATE_KEY = params.get("key");
String ALIPAY_PUBLIC_KEY = params.get("publicKey");

//实例化客户端
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2");

AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
request.setBizContent(jsonStr);

try {
AlipayTradeRefundResponse response = alipayClient.execute(request);
if(response.isSuccess()) {
// 调用成功
return true;
}else{
// 支付宝退款失败
// response.getSubMsg();
return false;
}
} catch (AlipayApiException e) {
e.printStackTrace();
// 支付宝退款失败,请联系管理员
return false;
}
}

退款查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
private void refundsStatus(String refund_no){
// 支付宝 退款

/*
out_trade_no String 特殊可选 64 订单支付时传入的商户订单号,不能和 trade_no同时为空。 20150320010101001
trade_no String 特殊可选 64 支付宝交易号,和商户订单号不能同时为空 2014112611001004680073956707
refund_amount Price 必选 9 需要退款的金额,该金额不能大于订单金额,单位为元,支持两位小数 200.12
refund_reason String 可选 256 退款的原因说明 正常退款
out_request_no String 可选 64 标识一次退款请求,同一笔交易多次退款需要保证唯一,如需部分退款,则此参数必传。 HZ01RF001
operator_id String 可选 30 商户的操作员编号 OP001
store_id String 可选 32 商户的门店编号 NJ_S_001
terminal_id String 可选 32 商户的终端编号 NJ_T_001
*/

// 支付宝退款参数
Map<String, String> params = new HashMap<String, String>();
params.put("_input_charset","utf-8");
params.put("out_trade_no",trade_no);
params.put("total_fee",total_fee.toString());
params.put("reason",refunds.getMemo());
params.put("reason","正常退款");
params.put("request_no",refund_no);

AlipayTradeRefundModel refundModel = new AlipayTradeRefundModel();
refundModel.setOutTradeNo(params.get("out_trade_no"));
refundModel.setRefundAmount(params.get("total_fee"));
refundModel.setRefundReason(params.get("reason"));
refundModel.setOutRequestNo(params.get("request_no"));
String jsonStr = JSON.toJSONString(refundModel);

String APP_ID = pluginConfig.getAttribute("partner");
String CHARSET = params.get("_input_charset");
String APP_PRIVATE_KEY = pluginConfig.getAttribute("key");
String ALIPAY_PUBLIC_KEY = pluginConfig.getAttribute("publicKey");

//实例化客户端
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2");

AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
request.setBizContent(jsonStr);

try {
AlipayTradeRefundResponse response = alipayClient.execute(request);
if(response.isSuccess()) {
// 调用成功
return true;
}else{
renderArgumentError("支付宝退款失败,"+response.getSubMsg());
return false;
}
} catch (AlipayApiException e) {
e.printStackTrace();
renderArgumentError("支付宝退款失败,请联系管理员!");
return false;
}
}

微信

##退款

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

import com.jfinal.weixin.sdk.api.PaymentApi;

public static final String RETURN_CODE = "return_code";
public static final String RETURN_MSG = "return_msg";
public static final String RESULT_CODE = "result_code";

/// 微信 退款
public boolean wechatRefunds(){

/*
+ 公众账号ID appid 是 String(32) 微信分配的公众账号ID(企业号corpid即为此appId)
+ 商户号 mch_id 是 String(32) 微信支付分配的商户号
+ 随机字符串 nonce_str 是 String(32) 随机字符串,不长于32位。推荐随机数生成算法
+ 签名 sign 是 String(32) 签名,详见签名生成算法
+ 签名类型 sign_type 否 String(32) 签名类型,目前支持HMAC-SHA256和MD5,默认为MD5
+ 微信订单号 transaction_id 二选一 String(28) 微信生成的订单号,在支付通知中有返回
+ 商户订单号 out_trade_no String(32) 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
+ 商户退款单号 out_refund_no 是 String(64) 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
+ 订单金额 total_fee 是 Int 订单总金额,单位为分,只能为整数,详见支付金额
+ 退款金额 refund_fee 是 Int 退款总金额,订单总金额,单位为分,只能为整数,详见支付金额
+ 货币种类 refund_fee_type 否 String(8) 货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
+ 退款原因 refund_desc 否 String(80) 商品已售完 若商户传入,会在下发给用户的退款消息中体现退款原因
+ 退款资金来源 refund_account 否 String(30) 仅针对老资金流商户使用 REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款(默认使用未结算资金退款) REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款
*/

// 微信退款参数

WechatConfig pluginConfig = getPluginConfig();
String trade_no = "";
String refund_no = "";
BigDecimal total_fee = "";//这里是元,下面转分

Map<String, String> params = new HashMap<String, String>();
params.put("appid", pluginConfig.getAttribute("appid"));
params.put("mch_id", pluginConfig.getAttribute("mch_id"));
//params.put("nonce_str", pluginConfig.getAttribute("appid"));
//params.put("sign", pluginConfig.getAttribute("appid"));
//params.put("sign_type", "");
//params.put("transaction_id", "");
params.put("out_trade_no", trade_no);
params.put("out_refund_no", refund_no);
// 元 --> 分
params.put("total_fee", order.getAmount().setScale(2).multiply(BigDecimal.TEN).multiply(BigDecimal.TEN).toString());
params.put("refund_fee", total_fee.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN).toString());
//params.put("refund_fee_type", "CNY");
params.put("refund_desc", "正常退款");
//params.put("refund_account", pluginConfig.getAttribute("appid"));

String paternerKey = pluginConfig.getAttribute("paternerKey");
String certPath;
try {
certPath = this.getClass().getResource("/wechat.cer").getPath();
}catch (Exception ex){
ex.printStackTrace();
renderArgumentError("微信退款需要双向证书");
return false;
}

// 申请退款,内部添加了随机字符串nonce_str和签名sign
Map<String, String> result = PaymentApi.refund(params,paternerKey,certPath);
String returnCode = result.get(RETURN_CODE);
String returnMsg = result.get(RETURN_MSG);
if (StrKit.isBlank(returnCode) || !StringUtils.equals("SUCCESS", returnCode)) {
renderArgumentError("微信退款失败,"+returnMsg);
return false;
}
String resultCode = result.get(RESULT_CODE);
if (StrKit.isBlank(resultCode) || !StringUtils.equals("SUCCESS", returnCode)) {
renderArgumentError("微信退款失败,"+returnMsg);
return false;
}
}

官方Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

package httpstest;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.security.KeyStore;

import javax.net.ssl.SSLContext;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

/**
* This example demonstrates how to create secure connections with a custom SSL
* context.
*/
public class ClientCustomSSL {

public final static void main(String[] args) throws Exception {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
FileInputStream instream = new FileInputStream(new File("D:/10016225.p12"));
try {
keyStore.load(instream, "10016225".toCharArray());
} finally {
instream.close();
}

// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, "10016225".toCharArray())
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1" },
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
try {

HttpGet httpget = new HttpGet("https://api.mch.weixin.qq.com/secapi/pay/refund");

System.out.println("executing request" + httpget.getRequestLine());

CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();

System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
if (entity != null) {
System.out.println("Response content length: " + entity.getContentLength());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));
String text;
while ((text = bufferedReader.readLine()) != null) {
System.out.println(text);
}

}
EntityUtils.consume(entity);
} finally {
response.close();
}
} finally {
httpclient.close();
}
}
}

退款查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

import com.jfinal.weixin.sdk.api.PaymentApi;

public static final String RETURN_CODE = "return_code";
public static final String RETURN_MSG = "return_msg";
public static final String RESULT_CODE = "result_code";

/// 根据 商户退款单号 查询退款
private void refundsStatus(String refund_no){
// 微信 退款
/*
应用ID appid 是 String(32) wx8888888888888888 微信开放平台审核通过的应用APPID
商户号 mch_id 是 String(32) 1900000109 微信支付分配的商户号
随机字符串 nonce_str 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位。推荐随机数生成算法
签名 sign 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 签名,详见签名生成算法
商户退款单号 out_refund_no String(64)
*/
// 微信退款参数
String appid = "";
String mch_id = "";
String paternerKey = "";

// 商户退款单号 内部添加了随机字符串nonce_str和签名sign
Map<String, String> result = PaymentApi.refundQueryByOutRefundNo(appid,mch_id,paternerKey,refund_no);

String returnCode = result.get(RETURN_CODE);
String returnMsg = result.get(RETURN_MSG);
if (StrKit.isBlank(returnCode) || !StringUtils.equals("SUCCESS", returnCode)) {
//.put("refund_msg", "微信退款查询失败,"+returnMsg);
return ;
}
String resultCode = result.get(RESULT_CODE);
if (StrKit.isBlank(resultCode) || !StringUtils.equals("SUCCESS", returnCode)) {
//.put("refund_msg", "微信退款查询失败,"+returnMsg);
return ;
}
String refund_status = result.get("refund_status_$n");
if ("SUCCESS".equals(refund_status)) {
refund_status = "退款成功";

}else if ("REFUNDCLOSE".equals(refund_status)) {
refund_status = "退款关闭";

}else if ("PROCESSING".equals(refund_status)) {
refund_status = "退款处理中";

}else if ("CHANGE".equals(refund_status)) {
refund_status = "退款异常";

}else {
refund_status = "其他";
}
//.put("refund_status", refund_status);
String refund_success_time = result.get("refund_success_time_$n");
//.put("refund_success_time", refund_success_time);
}

本文标题:App支付后申请退款

文章作者:史彦超

发布时间:2017年08月24日 - 10:08

最后更新:2021年07月20日 - 16:07

原始链接:https://doingself.github.io/2017/08/24/2017-08-24-App%E6%94%AF%E4%BB%98%E5%90%8E%E7%94%B3%E8%AF%B7%E9%80%80%E6%AC%BE/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Donate comment here