//config
public class AlipayRefundConfig {
private final String charset = "UTF-8"; //参数编码字符集
private final String signType = "MD5"; //签名方式
private String service = "refund_fastpay_by_platform_nopwd"; //接口名称
private String gatewayUrl = "https://mapi.alipay.com/gateway.do"; //支付宝网关
private String partner; //合作者身份ID
private String notifyUrl; //服务器异步通知页面路径
private String sellerId; //收款支付宝账号,以2088开头由16位纯数字组成的字符串,一般情况下收款账号就是签约账号
private String signKey; // MD5密钥,安全检验码,由数字和字母组成的32位字符串,查看地址
public void setService(String service) {
this.service = service;
}
public String getService() {
return service;
}
public String getCharset() {
return charset;
}
public String getSignType() {
return signType;
}
public String getGatewayUrl() {
return gatewayUrl;
}
public void setGatewayUrl(String gatewayUrl) {
this.gatewayUrl = gatewayUrl;
}
public String getPartner() {
return partner;
}
public void setPartner(String partner) {
this.partner = partner;
}
public String getNotifyUrl() {
return notifyUrl;
}
public void setNotifyUrl(String notifyUrl) {
this.notifyUrl = notifyUrl;
}
public String getSellerId() {
return sellerId;
}
public void setSellerId(String sellerId) {
this.sellerId = sellerId;
}
public String getSignKey() {
return signKey;
}
public void setSignKey(String signKey) {
this.signKey = signKey;
}
}
//??????
public class AlipayRefundRequest {
private String refundDate; // 退款请求时间 yyyy-MM-dd HH:mm:ss
private String batchNo; // 退款批次号,退款日期(8位)+流水号(3~24位)
private String tradeNo; // 原付款支付宝交易号
private BigDecimal amount; // 退款总金额
private String reason; // 退款理由
private String notifyUrl; //服务器异步通知页面路径,优先使用
public String getRefundDate() {
return refundDate;
}
public void setRefundDate(String refundDate) {
this.refundDate = refundDate;
}
public String getBatchNo() {
return batchNo;
}
public void setBatchNo(String batchNo) {
this.batchNo = batchNo;
}
public String getTradeNo() {
return tradeNo;
}
public void setTradeNo(String tradeNo) {
this.tradeNo = tradeNo;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public String getNotifyUrl() {
return notifyUrl;
}
public void setNotifyUrl(String notifyUrl) {
this.notifyUrl = notifyUrl;
}
}
//??????
public class AlipayRefundHelper {
public static String createGateway(AlipayRefundConfig config, AlipayRefundRequest request) {
TreeMap<String, String> parameter = new TreeMap<>();
parameter.put("_input_charset", config.getCharset());
parameter.put("notify_url", request.getNotifyUrl() == null ? config.getNotifyUrl() : request.getNotifyUrl());
parameter.put("partner", config.getPartner());
parameter.put("seller_id", config.getSellerId());
parameter.put("service", config.getService());
parameter.put("sign_type", config.getSignType());
parameter.put("batch_no", request.getBatchNo());
parameter.put("refund_date", request.getRefundDate());
parameter.put("batch_num", "1");
StringBuilder sb = new StringBuilder(80);
sb.append(request.getTradeNo()).append("^");
sb.append(request.getAmount()).append("^");
sb.append(request.getReason());
parameter.put("detail_data", sb.toString());
String queryString = AlipayUtil.buildQueryString(parameter, config.getSignKey());
return config.getGatewayUrl() + queryString;
}
public static AlipayRefundReturn parseReturn(String xml) {
AlipayRefundReturn r = new AlipayRefundReturn();
r.setIsSuccess(extractXml(xml, "is_success"));
r.setError(extractXml(xml, "error"));
return r;
}
private static String extractXml(String xml, String tag) {
int p = xml.indexOf(tag);
if (p < 0) return null;
int p1 = -1;
int p2 = -1;
for (int i = p + tag.length(); i < xml.length(); i++) {
char c = xml.charAt(i);
if (c == ‘ ‘ || c == ‘\t‘) continue;
if (c != ‘>‘) return null;
p1 = i + 1;
p2 = xml.indexOf(‘<‘, p1);
break;
}
if (p1 < 0 || p2 < 0 || p2 < p1) return null;
return xml.substring(p1, p2);
}
}
/**
*/
public class AlipayUtil {
public static boolean isSuccess(String str) {
return str != null && "T".equalsIgnoreCase(str.trim());
}
public static boolean isFailed(String str) {
return str != null && "F".equalsIgnoreCase(str.trim());
}
public static boolean isProcess(String str) {
return str != null && "P".equalsIgnoreCase(str.trim());
}
public static boolean signSkip(String k, String v) {
return v == null || v.length() == 0 || k == null || "sign_type".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k);
}
public static String buildQueryString(TreeMap<String, String> parameter, String signKey) {
StringBuilder sign = new StringBuilder();
StringBuilder query = new StringBuilder();
for (Map.Entry<String, String> entry : parameter.entrySet()) {
String k = entry.getKey();
String v = entry.getValue();
if (!AlipayUtil.signSkip(k, v)) {
sign.append("&").append(k).append("=").append(v);
try {
v = URLEncoder.encode(v, "UTF8");
} catch (Exception e) {
// ignore
}
query.append("&").append(k).append("=").append(v);
}
}
sign.append(signKey);
String digest = Md5Util.sumUtf8(sign.substring(1));
query.append("&sign=").append(digest);
query.setCharAt(0, ‘?‘);
return query.toString();
}
/**
* @return
*/
public enum VerifyResult {
UNSUPPORTED,
NOT_PASS,
MD5_PASS,
ALL_PASS
};
public static VerifyResult verifyNotifyMd5(TreeMap<String, String> params, String signKey, String partnerId) {
if (params == null || params.isEmpty()) return VerifyResult.UNSUPPORTED;
if (!"md5".equalsIgnoreCase(params.get("sign_type"))) return VerifyResult.UNSUPPORTED;
StringBuilder sign = new StringBuilder(300);
for (Map.Entry<String, String> entry : params.entrySet()) {
String k = entry.getKey();
if ("sign".equalsIgnoreCase(k) || "sign_type".equalsIgnoreCase(k)) {
continue;
}
sign.append("&").append(k).append("=").append(entry.getValue());
}
sign.append(signKey);
String signStr = sign.substring(1);
String signMd5 = Md5Util.sumUtf8(signStr);
String paraMd5 = params.get("sign");
if (!signMd5.equalsIgnoreCase(paraMd5)) {
return VerifyResult.NOT_PASS;
}
// chick notify_id
String verifyUrl = "https://mapi.alipay.com/gateway.do?service=notify_verify&partner=";
StringBuilder queryUrl = new StringBuilder(100);
queryUrl.append(verifyUrl);
queryUrl.append(partnerId);
queryUrl.append("¬ify_id=");
queryUrl.append(params.get("notify_id"));
BufferedReader in = null;
try {
URL url = new URL(queryUrl.toString());
URLConnection urlConnection = url.openConnection();
in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String inputLine = in.readLine();
if (inputLine.equalsIgnoreCase("true")) {
return VerifyResult.ALL_PASS;
} else {
return VerifyResult.MD5_PASS;
}
} catch (Exception e) {
return VerifyResult.MD5_PASS;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
}
}
}
/**
* 生成支付宝批次号
*
* @return
*/
public String getRandomBatchNum() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
String format = dateFormat.format(new Date());
int max = 24;
int min = 3;
Random random = new Random();
int s = random.nextInt(max) % (max - min + 1) + min;
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < s; i++) {
Integer val = (int) (Math.random() * 9 + 1);
buffer.append(val.toString());
}
return format + buffer.toString();
}
//测试
public static void main(String[] args) {
AlipayRefundConfig config = new AlipayRefundConfig();
config.setNotifyUrl("http://127.0.0.1:9966/alipay.html");
config.setPartner(PayConstants.PARTNER_ID);
config.setSellerId(PayConstants.PARTNER_ID);
config.setSignKey(PayConstants.SECURITY_KEY);
AlipayRefundRequest req = new AlipayRefundRequest();
req.setBatchNo(getRandomBatchNum()); //退款流水号
SimpleDateFormat dt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
req.setRefundDate(dt.format(new Date())); //退款请求时间
req.setAmount(new BigDecimal("0.01"));
req.setReason("测试");
req.setTradeNo("888888888"); //交易号
String payUrl = AlipayRefundHelper.createGateway(config, req);
System.out.println(payUrl);
String xml = PayUtil.request(payUrl);
AlipayRefundReturn r = AlipayRefundHelper.parseReturn(xml);
System.out.println(xml);
System.out.println(r.getIsSuccess());
System.out.println(r.getError());
}