mitao
2025-02-21 31573d6180d15ef65ed0df9c2732495f40b12663
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
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();
            }
        }
 
    }
 
}