微信提现-企业付款到零钱

微信提现-企业付款到零钱

p12证书下载

开发环境/参数准备

  • SpringBoot2.0框架 (仅限本例,其他框架自行解决,原理相同)

  • APP_ID (公众账号appid)

  • MCH_ID(商户号)

  • API_SECRET(API密钥)

pom.xml相关jar包/插件引入

  • xml相关jar包引入(微信接口要求以xml格式传参)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <dependencies>
    <!-- xml -->
    <dependency>
    <groupId>net.sf.kxml</groupId>
    <artifactId>kxml2</artifactId>
    <version>2.3.0</version>
    </dependency>
    <dependency>
    <groupId>xmlpull</groupId>
    <artifactId>xmlpull</artifactId>
    <version>1.1.3.1</version>
    </dependency>
    </dependencies>
  • maven过滤证书转码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <build>
    <plugins>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <configuration>
    <encoding>UTF-8</encoding>
    <!-- 过滤后缀为p12/crt/pem/pfx不需要转码的证书文件 -->
    <nonFilteredFileExtensions>
    <nonFilteredFileExtension>p12</nonFilteredFileExtension>
    <nonFilteredFileExtension>crt</nonFilteredFileExtension>
    <nonFilteredFileExtension>pem</nonFilteredFileExtension>
    <nonFilteredFileExtension>pfx</nonFilteredFileExtension>
    </nonFilteredFileExtensions>
    </configuration>
    </plugin>
    </plugins>
    </build>
  • 将下载好的证书放在 src/main/resources

代码实现

  • HttpUtils(企业付款http请求工具)
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
package com.zero.aliatry.utils.withdrawal;

import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
* 企业付款http请求工具
*/
public class HttpUtils {

private static final String DEFAULT_CHARSET = "UTF-8";

private static final int CONNECT_TIME_OUT = 5000; //链接超时时间3秒

private static final RequestConfig REQUEST_CONFIG = RequestConfig.custom().setConnectTimeout(CONNECT_TIME_OUT).build();

private static SSLContext wx_ssl_context = null; //微信支付ssl证书

private static final String MCH_ID = "1000000000";//证书密码默认是商户号

static{
Resource resource = new ClassPathResource("apiclient_cert.p12");//该证书名字最好改为别人猜不到的
try {
KeyStore keystore = KeyStore.getInstance("PKCS12");
char[] keyPassword = MCH_ID.toCharArray(); //证书密码
keystore.load(resource.getInputStream(), keyPassword);
wx_ssl_context = SSLContexts.custom().loadKeyMaterial(keystore, keyPassword).build();
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* @description 功能描述: get 请求
* @param url 请求地址
* @param params 参数
* @param headers headers参数
* @return 请求失败返回null
*/
public static String get(String url, Map<String, String> params, Map<String, String> headers) {

CloseableHttpClient httpClient = null;
if (params != null && !params.isEmpty()) {
StringBuffer param = new StringBuffer();
boolean flag = true; // 是否开始
for (Entry<String, String> entry : params.entrySet()) {
if (flag) {
param.append("?");
flag = false;
} else {
param.append("&");
}
param.append(entry.getKey()).append("=");

try {
param.append(URLEncoder.encode(entry.getValue(), DEFAULT_CHARSET));
} catch (UnsupportedEncodingException e) {
//编码失败
}
}
url += param.toString();
}

String body = null;
CloseableHttpResponse response = null;
try {
httpClient = HttpClients.custom().setDefaultRequestConfig(REQUEST_CONFIG).build();
HttpGet httpGet = new HttpGet(url);
response = httpClient.execute(httpGet);
body = EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return body;
}

/**
* @description 功能描述: get 请求
* @param url 请求地址
* @return 请求失败返回null
*/
public static String get(String url) {
return get(url, null);
}

/**
* @description 功能描述: get 请求
* @param url 请求地址
* @param params 参数
* @return 请求失败返回null
*/
public static String get(String url, Map<String, String> params) {
return get(url, params, null);
}

/**
* @description 功能描述: post 请求
* @param url 请求地址
* @param params 参数
* @return 请求失败返回null
*/
public static String post(String url, Map<String, String> params) {
CloseableHttpClient httpClient = null;
HttpPost httpPost = new HttpPost(url);
List<NameValuePair> nameValuePairs = new ArrayList<>();
if (params != null && !params.isEmpty()) {
for (Entry<String, String> entry : params.entrySet()) {
nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
}

String body = null;
CloseableHttpResponse response = null;
try {
httpClient = HttpClients.custom().setDefaultRequestConfig(REQUEST_CONFIG).build();
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, DEFAULT_CHARSET));
response = httpClient.execute(httpPost);
body = EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return body;
}

/**
* @description 功能描述: post 请求
* @param url 请求地址
* @param s 参数xml
* @return 请求失败返回null
*/
public static String post(String url, String s) {
CloseableHttpClient httpClient = null;
HttpPost httpPost = new HttpPost(url);
String body = null;
CloseableHttpResponse response = null;
try {
httpClient = HttpClients.custom().setDefaultRequestConfig(REQUEST_CONFIG).build();
httpPost.setEntity(new StringEntity(s, DEFAULT_CHARSET));
response = httpClient.execute(httpPost);
body = EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return body;
}

/**
* @description 功能描述: post https请求,服务器双向证书验证
* @param url 请求地址
* @param params 参数
* @return 请求失败返回null
*/
public static String posts(String url, Map<String, String> params) {
CloseableHttpClient httpClient = null;
HttpPost httpPost = new HttpPost(url);
List<NameValuePair> nameValuePairs = new ArrayList<>();
if (params != null && !params.isEmpty()) {
for (Entry<String, String> entry : params.entrySet()) {
nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
}

String body = null;
CloseableHttpResponse response = null;
try {
httpClient = HttpClients.custom().setDefaultRequestConfig(REQUEST_CONFIG).setSSLSocketFactory(getSSLConnectionSocket()).build();
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, DEFAULT_CHARSET));
response = httpClient.execute(httpPost);
body = EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return body;
}

/**
* @description 功能描述: post https请求,服务器双向证书验证
* @param url 请求地址
* @param s 参数xml
* @return 请求失败返回null
*/
public static String posts(String url, String s) {
CloseableHttpClient httpClient = null;
HttpPost httpPost = new HttpPost(url);
String body = null;
CloseableHttpResponse response = null;
try {
httpClient = HttpClients.custom().setDefaultRequestConfig(REQUEST_CONFIG).setSSLSocketFactory(getSSLConnectionSocket()).build();
httpPost.setEntity(new StringEntity(s, DEFAULT_CHARSET));
response = httpClient.execute(httpPost);
body = EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return body;
}

//获取ssl connection链接
private static SSLConnectionSocketFactory getSSLConnectionSocket() {
return new SSLConnectionSocketFactory(wx_ssl_context, new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
}
}
  • XmlUtil (xml、map转换工具)
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
package com.zero.aliatry.utils.withdrawal;

import com.github.pagehelper.util.StringUtil;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

/**
* xml、map转换工具
*/
public class XmlUtil {

private static final String PREFIX_XML = "<xml>";

private static final String SUFFIX_XML = "</xml>";

private static final String PREFIX_CDATA = "<![CDATA[";

private static final String SUFFIX_CDATA = "]]>";

/**
* 转化成xml, 单层无嵌套
*
* @param parm
* @param isAddCDATA
* @return
*/
public static String xmlFormat(Map<String, String> parm, boolean isAddCDATA) {

StringBuffer strbuff = new StringBuffer(PREFIX_XML);
if (CollectionUtil.isNotEmpty(parm)) {
for (Entry<String, String> entry : parm.entrySet()) {
strbuff.append("<").append(entry.getKey()).append(">");
if (isAddCDATA) {
strbuff.append(PREFIX_CDATA);
if (StringUtil.isNotEmpty(entry.getValue())) {
strbuff.append(entry.getValue());
}
strbuff.append(SUFFIX_CDATA);
} else {
if (StringUtil.isNotEmpty(entry.getValue())) {
strbuff.append(entry.getValue());
}
}
strbuff.append("</").append(entry.getKey()).append(">");
}
}
return strbuff.append(SUFFIX_XML).toString();
}

/**
* 解析xml
*
* @param xml
* @return
* @throws XmlPullParserException
* @throws IOException
*/
public static Map<String, String> xmlParse(String xml) throws XmlPullParserException, IOException {
Map<String, String> map = null;
if (StringUtil.isNotEmpty(xml)) {
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
XmlPullParser pullParser = XmlPullParserFactory.newInstance().newPullParser();
pullParser.setInput(inputStream, "UTF-8"); // 为xml设置要解析的xml数据
int eventType = pullParser.getEventType();

while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
map = new HashMap<String, String>();
break;
case XmlPullParser.START_TAG:
String key = pullParser.getName();
if (key.equals("xml"))
break;
String value = pullParser.nextText().trim();
map.put(key, value);
break;
case XmlPullParser.END_TAG:
break;
}
eventType = pullParser.next();
}
}
return map;
}
}
  • PayUtil
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
package com.zero.aliatry.utils.withdrawal;

import com.github.pagehelper.util.StringUtil;
import com.zero.aliatry.utils.ChineseCharToEnUtil;
import com.zero.aliatry.utils.DateUtil;
import com.zero.aliatry.utils.Encrypt;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.Set;

public class PayUtil {

/**
* 生成订单号
*
* @return
*/
public static String getTradeNo() {
// 自增8位数 00000001
return "TNO" + DateUtil.formatDate(new Date(), DateUtil.TIME_STAMP_PATTERN) + "00000001";
}

/**
* 退款单号
*
* @return
*/
public static String getRefundNo() {
// 自增8位数 00000001
return "RNO" + DateUtil.formatDate(new Date(), DateUtil.TIME_STAMP_PATTERN) + "00000001";
}

/**
* 退款单号
*
* @return
*/
public static String getTransferNo() {
// 自增8位数 00000001
return "TNO" + DateUtil.formatDate(new Date(), DateUtil.TIME_STAMP_PATTERN) + "00000001";
}

/**
* 返回客户端ip
*
* @param request
* @return
*/
public static String getRemoteAddrIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = ip.indexOf(",");
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
}
ip = request.getHeader("X-Real-IP");
if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
return ip;
}
return request.getRemoteAddr();
}

/**
* 获取服务器的ip地址
*
* @param request
* @return
*/
public static String getLocalIp(HttpServletRequest request) {
return request.getLocalAddr();
}

public static String getSign(Map<String, String> params, String paternerKey) throws UnsupportedEncodingException {
return Encrypt.getMD5(createSign(params, false) + "&key=" + paternerKey).toUpperCase();
}

/**
* 构造签名
*
* @param params
* @param encode
* @return
* @throws UnsupportedEncodingException
*/
public static String createSign(Map<String, String> params, boolean encode) throws UnsupportedEncodingException {
Set<String> keysSet = params.keySet();
Object[] keys = keysSet.toArray();
Arrays.sort(keys);
StringBuffer temp = new StringBuffer();
boolean first = true;
for (Object key : keys) {
if (key == null || StringUtil.isEmpty(params.get(key))) // 参数为空不参与签名
continue;
if (first) {
first = false;
} else {
temp.append("&");
}
temp.append(key).append("=");
Object value = params.get(key);
String valueStr = "";
if (null != value) {
valueStr = value.toString();
}
if (encode) {
temp.append(URLEncoder.encode(valueStr, "UTF-8"));
} else {
temp.append(valueStr);
}
}
return temp.toString();
}

/**
* 创建支付随机字符串
* @return
*/
public static String getNonceStr(){
return ChineseCharToEnUtil.randomString(ChineseCharToEnUtil.LETTER_NUMBER_CHAR, 32);
}

/**
* 支付时间戳
* @return
*/
public static String payTimestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}
  • CollectionUtil
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
package com.zero.aliatry.utils.withdrawal;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
* 集合类工具
*/
public class CollectionUtil {

private CollectionUtil() {
super();
}

// 判断一个集合是否为空
public static <T> boolean isEmpty(Collection<T> col) {
if (col == null || col.isEmpty()) {
return true;
}

return false;
}

// 判断一个集合是否不为空
public static <T> boolean isNotEmpty(Collection<T> col) {
return !isEmpty(col);
}

// 判断Map是否为空
public static <K, V> boolean isEmpty(Map<K, V> map) {
if (map == null || map.isEmpty()) {
return true;
}

return false;
}

// 判断Map是否不为空为空
public static <K, V> boolean isNotEmpty(Map<K, V> map) {
return !isEmpty(map);
}

// 去除list中的重复数据
public static <T> List<T> removeRepeat(List<T> list) {
if (isEmpty(list)) {
return list;
}

List<T> result = new ArrayList<T>();
for (T e : list) {
if (!result.contains(e)) {
result.add(e);
}
}

return result;
}

// 将集合转换为String数组
public static <T> String[] toArray(List<T> list) {
if (isEmpty(list)) {
return null;
}

String[] result = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
result[i] = String.valueOf(list.get(i));
}

return result;
}

}
  • TransferController(提现相关接口)
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
package com.zero.aliatry.controller;

import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.util.StringUtil;
import com.zero.aliatry.entity.WithdrawalRecord;
import com.zero.aliatry.service.WithdrawalRecord.WithdrawalRecordService;
import com.zero.aliatry.service.appUser.AppUserService;
import com.zero.aliatry.service.sysUser.SysUserService;
import com.zero.aliatry.utils.CheckParamsUtil;
import com.zero.aliatry.utils.DateUtil;
import com.zero.aliatry.utils.IpAddrUtil;
import com.zero.aliatry.utils.exception.ErrorEnum;
import com.zero.aliatry.utils.exception.Result;
import com.zero.aliatry.utils.exception.ResultUtil;
import com.zero.aliatry.utils.withdrawal.CollectionUtil;
import com.zero.aliatry.utils.withdrawal.HttpUtils;
import com.zero.aliatry.utils.withdrawal.PayUtil;
import com.zero.aliatry.utils.withdrawal.XmlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
* 提现相关接口
*/
@RestController
@RequestMapping("/transfer")
public class TransferController {

@Autowired
SysUserService sysUserService;
@Autowired
AppUserService appUserService;
@Autowired
WithdrawalRecordService withdrawalRecordService;

private static final Logger logger = LoggerFactory.getLogger(TransferController.class);

private static final String TRANSFERS_PAY = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"; // 企业付款API

private static final String TRANSFERS_PAY_QUERY = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo"; // 企业付款查询API

private static final String APP_ID = "APP_ID" ;//公众账号appid

private static final String MCH_ID = "MCH_ID";//商户号

private static final String API_SECRET = "API_SECRET";//API密钥

/**
* 企业向个人支付转账
* @param request
* @param params
*/
@PostMapping(value = "/pay")
@ResponseBody
@Transactional
public Result transferPay(@RequestBody String params,HttpServletRequest request) {

/** =============================================接口参数校验================================================*/
JSONObject jsonObject = JSONObject.parseObject(params);//解析json
//校验参数是否为空
if(CheckParamsUtil.isBlank(jsonObject,"userId","amount","userType")){
return ResultUtil.fail(ErrorEnum.ERROR_PARAM_ISBLANK);
}

/**==============================================设置局部变量=================================================*/
String userId = jsonObject.getString("userId");//用户id
String openid = "";//用户openid
Float applyMoney = jsonObject.getFloat("amount");//用户申请提现金额 0.3-5000
int userType = jsonObject.getInteger("userType");//用户类型
String amount;//企业付款金额
Float poundage = 0f;//手续费
String desc;//企业付款描述信息
String id = UUID.randomUUID().toString().replaceAll("-", "");//提现记录表id

if(applyMoney<0.3||applyMoney>5000){
return ResultUtil.fail("00000001","申请提现金额不符:低于最小金额0.30元或高于5000.00元");
}

/** ======================================业务判断 1.userId是否有收款资格=====================================*/
if(userType == 1){
openid = sysUserService.getOpenidBySysUserId(userId);
}
if(userType == 2){
openid = appUserService.getOpenidByAppUserId(userId);
}
if(openid==null||openid.equals("")){
return ResultUtil.fail("00000001","该用户不可提现");
}

/** ======================================业务判断 2.openid可提现金额校验=====================================*/
/** userType==1 商家身份*/
if(userType == 1){
//校验用户提现金额
//1.提现金额<=可提现金额 15%的抽成




}
Float account = 0f;
/** userType==2 用户身份*/
if(userType == 2){
//校验用户提现金额
//1.提现金额<=可提现金额
account = appUserService.getAccount(userId);
if(applyMoney>account){
return ResultUtil.fail("00000001","非法操作:申请提现金额大于可提现金额");
}
}


/** =================================业务判断 3.userId当天提现次数及当天提现金额限制==================================*/
//当日提现次数 首次免手续费
List<WithdrawalRecord> withdrawalRecords = withdrawalRecordService.selectWithdrawalRecordOfToday(userId,openid,userType);
//当日非初次提现
if(withdrawalRecords.size()>0&&withdrawalRecords.size()<=10){
poundage = 2f;//当日非初次提现,手续费为2元
desc = "申请提现金额:"+applyMoney+"元,当天已提现"+withdrawalRecords.size()+"次,需扣除2元手续费";

//此处对用户当天累计提现金额可做限制
// Float withdrawalAmountRequested = 0f;//用户今日累计已申请的提现额度
// for(int i = 0;i < withdrawalRecords.size();i++){
// withdrawalAmountRequested = (withdrawalAmountRequested*1000+withdrawalRecords.get(i).getActualMoney()*1000)/1000;
// }



}else if(withdrawalRecords.size()>10){
return ResultUtil.fail("00000001","该用户当天提现次数上限:微信企业付款每天最多可向同一个用户付款10次");
}else{
desc = "申请提现金额:"+applyMoney+"元,当天首次提现免手续费";
}

/** =================================业务判断 4.一个商户同一日付款总额限额10万元==================================*/
Float amountWithdrawn = withdrawalRecordService.selectAllWithdrawalRecordNumOfToday();//商户同一日付款总额
if(amountWithdrawn>100000){
return ResultUtil.fail("00000001","今日商户付款总额限额,请明天再试");
}

/** ========================校验实际提现金额是否在微信企业付款实际允许的提现(0.3-5000)范围内=========================*/
Float actualMoney = (applyMoney*1000-poundage*1000)/1000;//实际提现金额
if(actualMoney<0.3||actualMoney>5000){
return ResultUtil.fail("00000001","扣除手续费后的实际提现金额不符:"+actualMoney+"元:低于最小金额0.30元或高于5000.00元");
}
amount = Integer.parseInt((int)((actualMoney)*10)+"")*10+"";//微信企业付款金额 30-500000


/** ==================================================================================================================*/
/** ================================================== 封装提现所需参数 ================================================*/
/** ==================================================================================================================*/
Map<String, String> restmap = null;
try {
Map<String, String> parm = new HashMap<String, String>();
parm.put("mch_appid", APP_ID); //公众账号appid
parm.put("mchid", MCH_ID); //商户号
parm.put("nonce_str", PayUtil.getNonceStr()); //随机字符串
parm.put("partner_trade_no", PayUtil.getTransferNo()); //商户订单号
parm.put("openid", openid); //用户openid oCVr20N2YLH9VQztnkZTaCj2aYYY
parm.put("check_name", "NO_CHECK"); //校验用户姓名选项 OPTION_CHECK
//parm.put("re_user_name", "安迪"); //check_name设置为FORCE_CHECK或OPTION_CHECK,则必填
parm.put("amount",amount); //转账金额
parm.put("desc", desc); //企业付款描述信息
parm.put("spbill_create_ip", IpAddrUtil.getIpAddr(request)); //Ip地址
parm.put("sign", PayUtil.getSign(parm, API_SECRET));

String restxml = HttpUtils.posts(TRANSFERS_PAY, XmlUtil.xmlFormat(parm, false));
restmap = XmlUtil.xmlParse(restxml);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return ResultUtil.fail("00000001","转账发生异常");
}

/** =================================================== 提现结果处理 ===================================================*/

/** ============================================ 生成提现及流水记录/改变余额 ============================================*/
if (CollectionUtil.isNotEmpty(restmap) && "SUCCESS".equals(restmap.get("result_code"))) {
logger.info("转账成功");
Map<String, String> transferMap = new HashMap<>();
transferMap.put("partnerTradeNo", restmap.get("partner_trade_no"));//商户转账订单号
transferMap.put("paymentNo", restmap.get("payment_no")); //微信订单号
transferMap.put("paymentTime", restmap.get("payment_time")); //微信支付成功时间

//生成提现记录
withdrawalRecordService.insert(new WithdrawalRecord(id,userType,userId,applyMoney,poundage,actualMoney,1,restmap.get("partner_trade_no"),restmap.get("payment_no"),
restmap.get("payment_time")==null?null:DateUtil.strToDate(restmap.get("payment_time"),"yyyy-MM-dd HH:mm:ss"),openid,desc,restmap.get("spbill_create_ip"),"0"));
//生成流水表
String waterId = UUID.randomUUID().toString().replaceAll("-", "");
Map<String,Object> map = new HashMap<>();
map.put("id",waterId);//编号
map.put("userId",userId);//用户id
map.put("title","提现");//该流水的标题
map.put("waterType",2);//商家流水类型1.场地收入 2.提现 用户流水类型1.分销收入 2.提现 3.预订场地
map.put("incomeExpense",1);//收入还是支出 0.收入 1.支出
map.put("applyMoney",applyMoney);//申请提现金额
map.put("poundage",poundage);//手续费
map.put("actualMoney",actualMoney);//实际金额
map.put("whetherToAccount",1);//是否到账0.未到账1.已到账
if(userType==1){
sysUserService.insertUserWater(map);
//改变用户可提现余额


}
if(userType==2){
appUserService.insertUserWater1(map);
//改变用户可提现余额
appUserService.updateUserAccount(userId,(account*1000-applyMoney*1000)/1000);//float精度问题
}
return ResultUtil.success(transferMap);
}

/** =================================================3.转账失败========================================================*/
else {
if (CollectionUtil.isNotEmpty(restmap)) {
logger.info("转账失败:" + restmap.get("err_code") + ":" + restmap.get("err_code_des"));
}
return ResultUtil.fail("00000001","转账失败"+restmap.get("err_code_des"));
}

}


/**
* 企业向个人转账查询
* @param request
* @param response
* @param tradeno 商户转账订单号
* @param callback
*/
@PostMapping(value = "/pay/query")
public Result orderPayQuery(HttpServletRequest request, HttpServletResponse response, String tradeno,
String callback) {
if (StringUtil.isEmpty(tradeno)) {
return ResultUtil.fail("00000001","转账订单号不能为空");
}

Map<String, String> restmap = null;
try {
Map<String, String> parm = new HashMap<String, String>();
parm.put("appid", APP_ID);
parm.put("mch_id", MCH_ID);
parm.put("partner_trade_no", tradeno);
parm.put("nonce_str", PayUtil.getNonceStr());
parm.put("sign", PayUtil.getSign(parm, API_SECRET));

String restxml = HttpUtils.posts(TRANSFERS_PAY_QUERY, XmlUtil.xmlFormat(parm, true));
restmap = XmlUtil.xmlParse(restxml);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}

if (CollectionUtil.isNotEmpty(restmap) && "SUCCESS".equals(restmap.get("result_code"))) {
// 订单查询成功 处理业务逻辑
logger.info("订单查询:订单" + restmap.get("partner_trade_no") + "支付成功");
Map<String, String> transferMap = new HashMap<>();
transferMap.put("partnerTradeNo", restmap.get("partner_trade_no"));//商户转账订单号
transferMap.put("openid", restmap.get("openid")); //收款微信号
transferMap.put("paymentAmount", restmap.get("payment_amount")); //转账金额
transferMap.put("transferTime", restmap.get("transfer_time")); //转账时间
transferMap.put("desc", restmap.get("desc")); //转账描述
return ResultUtil.success(transferMap);
}else {
if (CollectionUtil.isNotEmpty(restmap)) {
logger.info("订单转账失败:" + restmap.get("err_code") + ":" + restmap.get("err_code_des"));
}
return ResultUtil.fail("00000001","订单转账失败");
}
}
}