package com.panzhihua.applets.unionpay;
|
|
import com.panzhihua.common.utlis.DateUtils;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.codec.digest.DigestUtils;
|
|
import javax.crypto.Mac;
|
import javax.crypto.spec.SecretKeySpec;
|
import java.io.*;
|
import java.net.HttpURLConnection;
|
import java.net.URL;
|
import java.net.URLConnection;
|
import java.time.LocalDateTime;
|
import java.util.List;
|
|
import static com.panzhihua.common.utlis.PayUtil.makeUUID;
|
|
/**
|
* @author kkqiao
|
* 全民付移动支付小程序支付退款接口
|
* 说明:
|
* 当成功交易之后一段时间内,由于买家或商户的原因需要退款时,商户可以通过本接口将支付款退还给
|
* 买家,退款请求验证成功之后,银商将通知支付渠道方按照退款规则把支付款按原路退回到买家帐号
|
* 上。
|
* 退款订单号refundOrderId也需遵循商户订单号生成规范,即以银商分配的4位来源编号作为账单号的前
|
* 4位,且在商户系统中此账单号保证唯一。总长度需大于6位,小于28位。银商的推荐规则为(无特殊情
|
* 况下,建议遵守此规则):
|
* {来源编号(4位)}{时间(yyyyMMddmmHHssSSS)(17位)}{7位随机数}
|
* 测试环境:http://58.247.0.18:29015/v1/netpay/refund
|
* 生产环境:https://api-mop.chinaums.com/v1/netpay/refund
|
*/
|
@Slf4j
|
public class Refund {
|
|
static String url = "https://api-mop.chinaums.com/v1/netpay/refund";
|
|
public static String sendOrder(String refundAmount,String refundOrderId)
|
{
|
//1. 组建请求报文
|
LocalDateTime time= DateUtils.getCurrentDate();
|
RefundBody reqBody = new RefundBody();
|
reqBody.requestTimestamp = time.format(DateUtils.format_ymdhms);// "2019-08-09 17:30:55";
|
reqBody.merOrderId =refundOrderId;
|
reqBody.mid =UnionpayContent.MID;
|
reqBody.tid = UnionpayContent.TID;
|
reqBody.instMid = UnionpayContent.INSTMID;
|
reqBody.refundAmount = refundAmount;
|
|
log.error("银联退款参数 :" + reqBody);
|
//2. 获取认证报文,timestamp为当前日期,老旧日期无法请求成功
|
String authorization = null;
|
try {
|
authorization = getAuthorization(UnionpayContent.APPID,UnionpayContent.APPKEY,
|
time.format(DateUtils.format_ymdhms_string),"nonce",reqBody.toString());
|
} catch (Exception e) {
|
e.printStackTrace();
|
log.error("退款失败");
|
return "退款失败";
|
}
|
log.error("银联退款参数authorization :" + authorization);
|
//3. 发送http请求,并解析返回信息
|
String response = request(url,authorization,reqBody.toString());
|
log.error("银联退款参数response :" + response);
|
return response;
|
}
|
|
/**
|
* 发送http请求
|
* @param url 请求url
|
* @param authorization 认证报文
|
* @param reqBody 请求体
|
* @return response
|
*/
|
static String request(String url, String authorization, String reqBody){
|
String response = "";
|
PrintWriter out = null;
|
BufferedReader in = null;
|
try{
|
URL realUrl = new URL(url);
|
URLConnection conn = realUrl.openConnection();
|
HttpURLConnection httpUrlConnection = (HttpURLConnection) conn;
|
httpUrlConnection.setRequestProperty("Content-Type", "application/json");
|
httpUrlConnection.setRequestProperty("authorization",authorization);
|
httpUrlConnection.setDoOutput(true);
|
httpUrlConnection.setDoInput(true);
|
out = new PrintWriter(httpUrlConnection.getOutputStream());
|
out.write(reqBody);
|
out.flush();
|
httpUrlConnection.connect();
|
in = new BufferedReader(new InputStreamReader(httpUrlConnection.getInputStream()));
|
String line;
|
while ((line = in.readLine()) != null) {
|
response += line;
|
}
|
}catch(Exception e){
|
e.printStackTrace();
|
} finally {
|
try {
|
if (out != null) { out.close();}
|
if (in != null) {in.close();}
|
} catch (Exception ex) {
|
ex.printStackTrace();
|
}
|
}
|
return response;
|
}
|
|
/**
|
* 获取签名头
|
* @param appid
|
* @param appkey
|
* @param timestamp 格式:"yyyyMMddHHmmss"
|
* @param nonce 随机字符串,
|
* @param body 请求体
|
* @return authorization 认证报文
|
* @throws Exception
|
*/
|
static String getAuthorization(String appid, String appkey, String timestamp, String nonce, String body) throws Exception {
|
byte[] data = body.getBytes("utf-8");
|
InputStream is = new ByteArrayInputStream(data);
|
String testSH = DigestUtils.sha256Hex(is);
|
String s1 = appid+timestamp+nonce+testSH;
|
Mac mac = Mac.getInstance("HmacSHA256");
|
mac.init(new SecretKeySpec(appkey.getBytes("utf-8"),"HmacSHA256"));
|
byte[] localSignature = mac.doFinal(s1.getBytes("utf-8"));
|
String localSignatureStr = Base64.encodeBase64String(localSignature);
|
return "OPEN-BODY-SIG AppId="+"\""+appid+"\""+", Timestamp="+"\""+timestamp+"\""+", Nonce="+"\""+nonce+"\""+", Signature="+"\""+localSignatureStr+"\"";
|
}
|
|
static class RefundBody{
|
//消息ID
|
String msgId;
|
//报文请求时间,格式yyyy-MM-ddHH:mm:ss
|
String requestTimestamp;
|
//报文系统预留字段
|
String srcReserve;
|
//商户订单号
|
String merOrderId;
|
//商户号
|
String mid;
|
//终端号
|
String tid;
|
//业务类型
|
String instMid;
|
//要退货的金额
|
String refundAmount;
|
//要退款的订单号
|
String refundOrderId;
|
//平台商户退款分账金额
|
String platformAmount;
|
//子订单信息
|
List<SubOrderItem> subOrders;
|
//退货说明
|
String refundDesc;
|
|
String toJson(){
|
StringBuilder sb = new StringBuilder();
|
sb.append("{");
|
if (this.msgId != null) sb.append("\"msgId\":\"" + this.msgId + "\",");
|
if (this.requestTimestamp != null) sb.append("\"requestTimestamp\":\"" + this.requestTimestamp + "\",");
|
if (this.merOrderId != null) sb.append("\"merOrderId\":\"" + this.merOrderId + "\",");
|
if (this.srcReserve != null) sb.append("\"srcReserve\":\"" + this.srcReserve + "\",");
|
if (this.mid != null) sb.append("\"mid\":\"" + this.mid + "\",");
|
if (this.tid != null) sb.append("\"tid\":\"" + this.tid + "\",");
|
if (this.instMid != null) sb.append("\"instMid\":\"" + this.instMid + "\",");
|
if (this.refundAmount != null) sb.append("\"refundAmount\":\"" + this.refundAmount + "\",");
|
if (this.refundOrderId != null) sb.append("\"refundOrderId\":\"" + this.refundOrderId + "\",");
|
if (this.platformAmount != null) sb.append("\"platformAmount\":\"" + this.platformAmount + "\",");
|
if (this.subOrders != null && this.subOrders.size()>0) {
|
sb.append("\"subOrders\":[");
|
for(int i=0;i<subOrders.size();i++){
|
sb.append(subOrders.get(i));
|
sb.append(",");
|
}
|
if (sb.charAt(sb.length() - 1) == ',')
|
sb.deleteCharAt(sb.length() - 1);
|
sb.append("],");
|
}
|
if (this.refundDesc != null) sb.append("\"refundDesc\":\"" + this.refundDesc + "\",");
|
if (sb.charAt(sb.length() - 1) == ',')
|
sb.deleteCharAt(sb.length() - 1);
|
sb.append("}");
|
return sb.toString();
|
}
|
|
public String toString(){
|
return this.toJson();
|
}
|
static class SubOrderItem{
|
//子商户号
|
String mid;
|
//子商户分账金额
|
int totalAmount;
|
String toJson() {
|
StringBuilder sb = new StringBuilder();
|
sb.append("{");
|
if (this.mid != null) {
|
sb.append("\"mid\":\"" + this.mid + "\",");
|
}
|
if (this.totalAmount != 0) {
|
sb.append("\"totalAmount\":\"" + this.totalAmount + "\",");
|
}
|
if (sb.charAt(sb.length() - 1) == ',')
|
sb.deleteCharAt(sb.length() - 1);
|
sb.append("}");
|
return sb.toString();
|
}
|
|
public String toString(){
|
return this.toJson();
|
}
|
}
|
|
}
|
|
}
|