View Javadoc
1   package com.foxinmy.weixin4j.api;
2   
3   import java.io.BufferedReader;
4   import java.io.BufferedWriter;
5   import java.io.IOException;
6   import java.io.InputStreamReader;
7   import java.io.OutputStream;
8   import java.io.OutputStreamWriter;
9   import java.io.UnsupportedEncodingException;
10  import java.net.URLEncoder;
11  import java.util.Calendar;
12  import java.util.Date;
13  import java.util.HashMap;
14  import java.util.Map;
15  
16  import com.alibaba.fastjson.JSON;
17  import com.alibaba.fastjson.TypeReference;
18  import com.foxinmy.weixin4j.exception.WeixinException;
19  import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
20  import com.foxinmy.weixin4j.http.weixin.XmlResult;
21  import com.foxinmy.weixin4j.model.WeixinPayAccount;
22  import com.foxinmy.weixin4j.payment.mch.APPPayRequest;
23  import com.foxinmy.weixin4j.payment.mch.JSAPIPayRequest;
24  import com.foxinmy.weixin4j.payment.mch.MICROPayRequest;
25  import com.foxinmy.weixin4j.payment.mch.MchPayPackage;
26  import com.foxinmy.weixin4j.payment.mch.MchPayRequest;
27  import com.foxinmy.weixin4j.payment.mch.MerchantResult;
28  import com.foxinmy.weixin4j.payment.mch.NATIVEPayRequest;
29  import com.foxinmy.weixin4j.payment.mch.NativePayResponse;
30  import com.foxinmy.weixin4j.payment.mch.OpenIdResult;
31  import com.foxinmy.weixin4j.payment.mch.Order;
32  import com.foxinmy.weixin4j.payment.mch.PrePay;
33  import com.foxinmy.weixin4j.payment.mch.RefundRecord;
34  import com.foxinmy.weixin4j.payment.mch.RefundResult;
35  import com.foxinmy.weixin4j.payment.mch.SceneInfoApp;
36  import com.foxinmy.weixin4j.payment.mch.SceneInfoStore;
37  import com.foxinmy.weixin4j.payment.mch.WAPPayRequest;
38  import com.foxinmy.weixin4j.type.CurrencyType;
39  import com.foxinmy.weixin4j.type.IdQuery;
40  import com.foxinmy.weixin4j.type.IdType;
41  import com.foxinmy.weixin4j.type.SignType;
42  import com.foxinmy.weixin4j.type.TarType;
43  import com.foxinmy.weixin4j.type.TradeType;
44  import com.foxinmy.weixin4j.type.mch.BillType;
45  import com.foxinmy.weixin4j.type.mch.RefundAccountType;
46  import com.foxinmy.weixin4j.util.Consts;
47  import com.foxinmy.weixin4j.util.DateUtil;
48  import com.foxinmy.weixin4j.util.DigestUtil;
49  import com.foxinmy.weixin4j.util.IOUtil;
50  import com.foxinmy.weixin4j.util.MapUtil;
51  import com.foxinmy.weixin4j.util.RandomUtil;
52  import com.foxinmy.weixin4j.util.StringUtil;
53  import com.foxinmy.weixin4j.xml.ListsuffixResultDeserializer;
54  import com.foxinmy.weixin4j.xml.XmlStream;
55  
56  /**
57   * 支付API
58   *
59   * @className PayApi
60   * @author jinyu(foxinmy@gmail.com)
61   * @date 2014年10月28日
62   * @since JDK 1.6
63   * @deprecated 商户平台API迁移到子模块weixin4j-pay
64   */
65  @Deprecated
66  public class PayApi extends MchApi {
67  
68  	public PayApi(WeixinPayAccount weixinAccount) {
69  		super(weixinAccount);
70  	}
71  
72  	/**
73  	 * 统一下单接口</br>
74  	 * 除被扫支付场景以外,商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易回话标识后再按扫码、JSAPI
75  	 * 、APP等不同场景生成交易串调起支付。
76  	 *
77  	 * @param payPackage
78  	 *            包含订单信息的对象
79  	 * @see com.foxinmy.weixin4j.payment.mch.MchPayPackage
80  	 * @see com.foxinmy.weixin4j.payment.mch.PrePay
81  	 * @see <a href=
82  	 *      "http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1">统一下单接口
83  	 *      </a>
84  	 * @return 预支付对象
85  	 */
86  	public PrePay createPrePay(MchPayPackage payPackage) throws WeixinException {
87  		super.declareMerchant(payPackage);
88  		payPackage.setSign(weixinSignature.sign(payPackage));
89  		String payJsRequestXml = XmlStream.toXML(payPackage);
90  		WeixinResponse response = weixinExecutor.post(
91  				getRequestUri("order_create_uri"), payJsRequestXml);
92  		return response.getAsObject(new TypeReference<PrePay>() {
93  		});
94  	}
95  
96  	/**
97  	 * 创建支付请求对象
98  	 *
99  	 * @param payPackage
100 	 *            支付详情
101 	 * @return 支付请求对象
102 	 * @see com.foxinmy.weixin4j.payment.mch.JSAPIPayRequest JS支付
103 	 * @see com.foxinmy.weixin4j.payment.mch.NATIVEPayRequest 扫码支付
104 	 * @see com.foxinmy.weixin4j.payment.mch.MICROPayRequest 刷卡支付
105 	 * @see com.foxinmy.weixin4j.payment.mch.APPPayRequest APP支付
106 	 * @see com.foxinmy.weixin4j.payment.mch.WAPPayRequest WAP支付
107 	 * @throws WeixinException
108 	 */
109 	public MchPayRequest createPayRequest(MchPayPackage payPackage)
110 			throws WeixinException {
111 		if (StringUtil.isBlank(payPackage.getTradeType())) {
112 			throw new WeixinException("tradeType not be empty");
113 		}
114 		String tradeType = payPackage.getTradeType().toUpperCase();
115 		if (TradeType.MICROPAY.name().equals(tradeType)) {
116 			MchPayPackage _payPackage = new MchPayPackage(payPackage.getBody(),
117 					payPackage.getDetail(), payPackage.getOutTradeNo(),
118 					DateUtil.formatFee2Yuan(payPackage.getTotalFee()), null,
119 					null, payPackage.getCreateIp(), null, null,
120 					payPackage.getAuthCode(), null, payPackage.getAttach(),
121 					null, null, payPackage.getGoodsTag(),
122 					payPackage.getLimitPay(), payPackage.getSubAppId());
123 			super.declareMerchant(_payPackage);
124 			_payPackage.setSign(weixinSignature.sign(_payPackage));
125 			String para = XmlStream.toXML(_payPackage);
126 			WeixinResponse response = weixinExecutor.post(
127 					getRequestUri("micropay_uri"), para);
128 			MICROPayRequest microPayRequest = response
129 					.getAsObject(new TypeReference<MICROPayRequest>() {
130 					});
131 			microPayRequest.setPaymentAccount(weixinAccount);
132 			return microPayRequest;
133 		}
134 		PrePay prePay = createPrePay(payPackage);
135 		if (TradeType.APP.name().equals(tradeType)) {
136 			return new APPPayRequest(prePay.getPrepayId(), weixinAccount);
137 		} else if (TradeType.JSAPI.name().equals(tradeType)) {
138 			return new JSAPIPayRequest(prePay.getPrepayId(), weixinAccount);
139 		} else if (TradeType.NATIVE.name().equals(tradeType)) {
140 			return new NATIVEPayRequest(prePay.getPrepayId(),
141 					prePay.getPayUrl(), weixinAccount);
142 		} else if (TradeType.MWEB.name().equals(tradeType)) {
143 			return new WAPPayRequest(prePay.getPrepayId(), prePay.getPayUrl(),
144 					weixinAccount);
145 		} else {
146 			throw new WeixinException("unknown tradeType:" + tradeType);
147 		}
148 	}
149 
150 	/**
151 	 * 创建JSAPI支付请求对象
152 	 *
153 	 * @param openId
154 	 *            用户ID
155 	 * @param body
156 	 *            订单描述
157 	 * @param outTradeNo
158 	 *            订单号
159 	 * @param totalFee
160 	 *            订单总额(元)
161 	 * @param notifyUrl
162 	 *            支付通知地址
163 	 * @param createIp
164 	 *            ip地址
165 	 * @param attach
166 	 *            附加数据 非必填
167 	 * @see com.foxinmy.weixin4j.payment.mch.JSAPIPayRequest
168 	 * @return JSAPI支付对象
169 	 * @throws WeixinException
170 	 */
171 	public MchPayRequest createJSPayRequest(String openId, String body,
172 			String outTradeNo, double totalFee, String notifyUrl,
173 			String createIp, String attach) throws WeixinException {
174 		MchPayPackage payPackage = new MchPayPackage(body, outTradeNo,
175 				totalFee, notifyUrl, createIp, TradeType.JSAPI, openId, null,
176 				null, attach);
177 		return createPayRequest(payPackage);
178 	}
179 
180 	/**
181 	 * <p>
182 	 * 生成编辑地址请求
183 	 * </p>
184 	 *
185 	 * err_msg edit_address:ok获取编辑收货地址成功</br> edit_address:fail获取编辑收货地址失败</br>
186 	 * userName 收货人姓名</br> telNumber 收货人电话</br> addressPostalCode 邮编</br>
187 	 * proviceFirstStageName 国标收货地址第一级地址</br> addressCitySecondStageName
188 	 * 国标收货地址第二级地址</br> addressCountiesThirdStageName 国标收货地址第三级地址</br>
189 	 * addressDetailInfo 详细收货地址信息</br> nationalCode 收货地址国家码</br>
190 	 *
191 	 * @param url
192 	 *            当前访问页的URL
193 	 * @param oauthToken
194 	 *            oauth授权时产生的token
195 	 * @see <a href=
196 	 *      "https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_8&index=7">
197 	 *      收货地址共享</a>
198 	 * @return 编辑地址请求JSON串
199 	 */
200 	public String createAddressRequestJSON(String url, String oauthToken) {
201 		Map<String, String> map = new HashMap<String, String>();
202 		map.put("appId", weixinAccount.getId());
203 		map.put("timeStamp", DateUtil.timestamp2string());
204 		map.put("nonceStr", RandomUtil.generateString(16));
205 		map.put("url", url);
206 		map.put("accessToken", oauthToken);
207 		String sign = DigestUtil.SHA1(MapUtil.toJoinString(map, false, true));
208 		map.remove("url");
209 		map.remove("accessToken");
210 		map.put("scope", "jsapi_address");
211 		map.put("signType", SignType.SHA1.name().toLowerCase());
212 		map.put("addrSign", sign);
213 		return JSON.toJSONString(map);
214 	}
215 
216 	/**
217 	 * 创建Native支付(扫码支付)链接【模式一】
218 	 *
219 	 * @param productId
220 	 *            与订单ID等价
221 	 * @return 支付链接
222 	 * @see <a href=
223 	 *      "https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1">扫码支付
224 	 *      </a>
225 	 * @see <a href=
226 	 *      "https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4">模式一
227 	 *      </a>
228 	 */
229 	public String createNativePayRequest(String productId) {
230 		Map<String, String> map = new HashMap<String, String>();
231 		String timestamp = DateUtil.timestamp2string();
232 		String noncestr = RandomUtil.generateString(16);
233 		map.put("appid", weixinAccount.getId());
234 		map.put("mch_id", weixinAccount.getMchId());
235 		map.put("time_stamp", timestamp);
236 		map.put("nonce_str", noncestr);
237 		map.put("product_id", productId);
238 		String sign = weixinSignature.sign(map);
239 		return String.format(getRequestUri("native_pay_uri"), sign,
240 				weixinAccount.getId(), weixinAccount.getMchId(), productId,
241 				timestamp, noncestr);
242 	}
243 
244 	/**
245 	 * 创建Native支付(扫码支付)回调对象【模式一】
246 	 *
247 	 * @param productId
248 	 *            商品ID
249 	 * @param body
250 	 *            商品描述
251 	 * @param outTradeNo
252 	 *            商户内部唯一订单号
253 	 * @param totalFee
254 	 *            商品总额 单位元
255 	 * @param notifyUrl
256 	 *            支付回调URL
257 	 * @param createIp
258 	 *            订单生成的机器 IP
259 	 * @param attach
260 	 *            附加数据 非必填
261 	 * @return Native回调对象
262 	 * @see com.foxinmy.weixin4j.payment.mch.NativePayResponse
263 	 * @see <a href=
264 	 *      "https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1">扫码支付
265 	 *      </a>
266 	 * @see <a href=
267 	 *      "https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4">模式一
268 	 *      </a>
269 	 * @throws WeixinException
270 	 */
271 	public NativePayResponse createNativePayResponse(String productId,
272 			String body, String outTradeNo, double totalFee, String notifyUrl,
273 			String createIp, String attach) throws WeixinException {
274 		MchPayPackage payPackage = new MchPayPackage(body, outTradeNo,
275 				totalFee, notifyUrl, createIp, TradeType.NATIVE, null, null,
276 				productId, attach);
277 		PrePay prePay = createPrePay(payPackage);
278 		return new NativePayResponse(weixinAccount, prePay.getPrepayId());
279 	}
280 
281 	/**
282 	 * 创建Native支付(扫码支付)链接【模式二】
283 	 *
284 	 * @param productId
285 	 *            商品ID
286 	 * @param body
287 	 *            商品描述
288 	 * @param outTradeNo
289 	 *            商户内部唯一订单号
290 	 * @param totalFee
291 	 *            商品总额 单位元
292 	 * @param notifyUrl
293 	 *            支付回调URL
294 	 * @param createIp
295 	 *            订单生成的机器 IP
296 	 * @param attach
297 	 *            附加数据 非必填
298 	 * @return Native支付对象
299 	 * @see com.foxinmy.weixin4j.payment.mch.NATIVEPayRequest
300 	 * @see <a href=
301 	 *      "https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1">扫码支付
302 	 *      </a>
303 	 * @see <a href=
304 	 *      "https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5">模式二
305 	 *      </a>
306 	 * @throws WeixinException
307 	 */
308 	public MchPayRequest createNativePayRequest(String productId, String body,
309 			String outTradeNo, double totalFee, String notifyUrl,
310 			String createIp, String attach) throws WeixinException {
311 		MchPayPackage payPackage = new MchPayPackage(body, outTradeNo,
312 				totalFee, notifyUrl, createIp, TradeType.NATIVE, null, null,
313 				productId, attach);
314 		return createPayRequest(payPackage);
315 	}
316 
317 	/**
318 	 * 创建APP支付请求对象
319 	 *
320 	 * @param body
321 	 *            商品描述
322 	 * @param outTradeNo
323 	 *            商户内部唯一订单号
324 	 * @param totalFee
325 	 *            商品总额 单位元
326 	 * @param notifyUrl
327 	 *            支付回调URL
328 	 * @param createIp
329 	 *            订单生成的机器 IP
330 	 * @param attach
331 	 *            附加数据 非必填
332 	 * @param store
333 	 *            门店信息 非必填
334 	 * @return APP支付对象
335 	 * @see com.foxinmy.weixin4j.payment.mch.SceneInfoStore
336 	 * @see com.foxinmy.weixin4j.payment.mch.APPPayRequest
337 	 * @see <a href=
338 	 *      "https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1">
339 	 *      APP支付</a>
340 	 * @throws WeixinException
341 	 */
342 	public MchPayRequest createAppPayRequest(String body, String outTradeNo,
343 			double totalFee, String notifyUrl, String createIp, String attach,
344 			SceneInfoStore store) throws WeixinException {
345 		MchPayPackage payPackage = new MchPayPackage(body, outTradeNo,
346 				totalFee, notifyUrl, createIp, TradeType.APP, null, null, null,
347 				attach);
348 		if (store != null) {
349 			payPackage.setSceneInfo(String.format(
350 					"{\"store_id\": \"%s\", \"store_name\":\"%s\"}",
351 					store.getId(), store.getName()));
352 		}
353 		return createPayRequest(payPackage);
354 	}
355 
356 	/**
357 	 * 创建WAP支付请求对象:正常流程用户支付完成后会返回至发起支付的页面,如需返回至指定页面,
358 	 * 则可以在MWEB_URL后拼接上redirect_url参数,来指定回调页面
359 	 *
360 	 * @param body
361 	 *            商品描述
362 	 * @param outTradeNo
363 	 *            商户内部唯一订单号
364 	 * @param totalFee
365 	 *            商品总额 单位元
366 	 * @param notifyUrl
367 	 *            支付回调URL
368 	 * @param createIp
369 	 *            订单生成的机器 IP
370 	 * @param attach
371 	 *            附加数据 非必填
372 	 * @param app
373 	 *            应用信息
374 	 * @return WAP支付对象
375 	 * @see com.foxinmy.weixin4j.payment.mch.SceneInfoApp
376 	 * @see com.foxinmy.weixin4j.payment.mch.WAPPayRequest
377 	 * @see <a href=
378 	 *      "https://pay.weixin.qq.com/wiki/doc/api/wap.php?chapter=15_1">WAP支付
379 	 *      </a>
380 	 * @throws WeixinException
381 	 */
382 	public MchPayRequest createWapPayRequest(String body, String outTradeNo,
383 			double totalFee, String notifyUrl, String createIp, String attach,
384 			SceneInfoApp app) throws WeixinException {
385 		MchPayPackage payPackage = new MchPayPackage(body, outTradeNo,
386 				totalFee, notifyUrl, createIp, TradeType.MWEB, null, null,
387 				null, attach);
388 		if (app != null) {
389 			payPackage.setSceneInfo(String.format("{\"h5_info\":\"%s\"}",
390 					app.getSceneInfo()));
391 		}
392 		return createPayRequest(payPackage);
393 	}
394 
395 	/**
396 	 * 提交被扫支付
397 	 *
398 	 * @param authCode
399 	 *            扫码支付授权码 ,设备读取用户微信中的条码或者二维码信息
400 	 * @param body
401 	 *            商品描述
402 	 * @param outTradeNo
403 	 *            商户内部唯一订单号
404 	 * @param totalFee
405 	 *            商品总额 单位元
406 	 * @param createIp
407 	 *            订单生成的机器 IP
408 	 * @param attach
409 	 *            附加数据 非必填
410 	 * @param store
411 	 *            门店信息 非必填
412 	 * @return 支付的订单信息
413 	 * @see com.foxinmy.weixin4j.payment.mch.MICROPayRequest
414 	 * @see com.foxinmy.weixin4j.payment.mch.Order
415 	 * @see com.foxinmy.weixin4j.payment.mch.SceneInfoStore
416 	 * @see <a href=
417 	 *      "http://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10">
418 	 *      提交被扫支付API</a>
419 	 * @throws WeixinException
420 	 */
421 	public MchPayRequest createMicroPayRequest(String authCode, String body,
422 			String outTradeNo, double totalFee, String createIp, String attach,
423 			SceneInfoStore store) throws WeixinException {
424 		MchPayPackage payPackage = new MchPayPackage(body, outTradeNo,
425 				totalFee, null, createIp, TradeType.MICROPAY, null, authCode,
426 				null, attach);
427 		if (store != null) {
428 			payPackage.setSceneInfo(String.format("{\"store_info\":\"%s\"}",
429 					JSON.toJSONString(store)));
430 		}
431 		return createPayRequest(payPackage);
432 	}
433 
434 	/**
435 	 * 订单查询
436 	 * <p>
437 	 * 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;</br> 调用支付接口后,返回系统错误或未知交易状态情况;</br>
438 	 * 调用被扫支付API,返回USERPAYING的状态;</br> 调用关单或撤销接口API之前,需确认支付状态;
439 	 * </P>
440 	 *
441 	 * @param idQuery
442 	 *            商户系统内部的订单号, transaction_id、out_trade_no 二 选一,如果同时存在优先级:
443 	 *            transaction_id> out_trade_no
444 	 * @return 订单信息
445 	 * @see com.foxinmy.weixin4j.payment.mch.Order
446 	 * @see <a href=
447 	 *      "http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2">
448 	 *      订单查询API</a>
449 	 * @since V3
450 	 * @throws WeixinException
451 	 */
452 	public Order queryOrder(IdQuery idQuery) throws WeixinException {
453 		Map<String, String> map = createBaseRequestMap(idQuery);
454 		map.put("sign", weixinSignature.sign(map));
455 		String param = XmlStream.map2xml(map);
456 		WeixinResponse response = weixinExecutor.post(
457 				getRequestUri("order_query_uri"), param);
458 		return ListsuffixResultDeserializer.deserialize(response.getAsString(),
459 				Order.class);
460 	}
461 
462 	/**
463 	 * 申请退款(请求需要双向证书)
464 	 *
465 	 * <p>
466 	 * 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,
467 	 * 按照退款规则将支付款按原路退到买家帐号上。
468 	 * </p>
469 	 *
470 	 * <ol>
471 	 *   <li>交易时间超过一年的订单无法提交退款;</li>
472 	 *   <li>
473 	 *     微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。
474 	 *     申请退款总金额不能超过订单金额。
475 	 *     <span style="color:red">一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号。</span>
476 	 *   </li>
477 	 *   <li>
478 	 *     请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过150次。
479 	 *     错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次。
480 	 *   </li>
481 	 *   <li>每个支付订单的部分退款次数不能超过50次。</li>
482 	 * </ol>
483 	 *
484 	 * @param idQuery
485 	 *            商户系统内部的订单号, transaction_id 、 out_trade_no 二选一,如果同时存在优先级:
486 	 *            transaction_id> out_trade_no
487 	 * @param outRefundNo
488 	 *            商户系统内部的退款单号,商 户系统内部唯一,同一退款单号多次请求只退一笔
489 	 * @param totalFee
490 	 *            订单总金额,单位为元
491 	 * @param refundFee
492 	 *            退款总金额,单位为元,可以做部分退款
493 	 * @param refundFeeType
494 	 *            货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY
495 	 * @param opUserId
496 	 *            操作员帐号, 默认为商户号
497 	 * @param refundDesc
498 	 *            退款原因,若商户传入,会在下发给用户的退款消息中体现退款原因
499 	 * @param refundAccountType
500 	 *            退款资金来源,默认使用未结算资金退款:REFUND_SOURCE_UNSETTLED_FUNDS
501 	 * @return 退款申请结果
502 	 * @see com.foxinmy.weixin4j.payment.mch.RefundResult
503 	 * @see <a href=
504 	 *      "http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4">
505 	 *      申请退款API</a>
506 	 * @since V3
507 	 * @throws WeixinException
508 	 */
509 	public RefundResult applyRefund(IdQuery idQuery, String outRefundNo,
510 			double totalFee, double refundFee, CurrencyType refundFeeType,
511 			String opUserId, String refundDesc,
512 			RefundAccountType refundAccountType) throws WeixinException {
513 		Map<String, String> map = createBaseRequestMap(idQuery);
514 		map.put("out_refund_no", outRefundNo);
515 		map.put("total_fee",
516 				Integer.toString(DateUtil.formatYuan2Fen(totalFee)));
517 		map.put("refund_fee",
518 				Integer.toString(DateUtil.formatYuan2Fen(refundFee)));
519 		if (StringUtil.isBlank(opUserId)) {
520 			opUserId = weixinAccount.getMchId();
521 		}
522 		map.put("op_user_id", opUserId);
523 		if (refundFeeType == null) {
524 			refundFeeType = CurrencyType.CNY;
525 		}
526 		if (refundAccountType == null) {
527 			refundAccountType = RefundAccountType.REFUND_SOURCE_UNSETTLED_FUNDS;
528 		}
529 		if (StringUtil.isNotBlank(refundDesc)) {
530 			map.put("refund_desc", refundDesc);
531 		}
532 		map.put("refund_fee_type", refundFeeType.name());
533 		map.put("refund_account", refundAccountType.name());
534 		map.put("sign", weixinSignature.sign(map));
535 		String param = XmlStream.map2xml(map);
536 		WeixinResponse response = getWeixinSSLExecutor().post(
537 				getRequestUri("refund_apply_uri"), param);
538 		return response.getAsObject(new TypeReference<RefundResult>() {
539 		});
540 	}
541 
542 	/**
543 	 * 退款申请(全额退款)
544 	 *
545 	 * @param idQuery
546 	 *            商户系统内部的订单号, transaction_id 、 out_trade_no 二选一,如果同时存在优先级:
547 	 *            transaction_id> out_trade_no
548 	 * @param outRefundNo
549 	 *            商户系统内部的退款单号,商 户系统内部唯一,同一退款单号多次请求只退一笔
550 	 * @param totalFee
551 	 *            订单总金额,单位为元
552 	 * @see #applyRefund(IdQuery, String, double, double, CurrencyType, String, String, RefundAccountType)
553 	 */
554 	public RefundResult applyRefund(IdQuery idQuery, String outRefundNo,
555 			double totalFee) throws WeixinException {
556 		return applyRefund(idQuery, outRefundNo, totalFee, totalFee, null,
557 				null, null, null);
558 	}
559 
560 	/**
561 	 * 冲正订单(需要证书)</br> 当支付返回失败,或收银系统超时需要取消交易,可以调用该接口</br> 接口逻辑:支
562 	 * 付失败的关单,支付成功的撤销支付</br> <font color="red">7天以内的单可撤销,其他正常支付的单
563 	 * 如需实现相同功能请调用退款接口</font></br> <font
564 	 * color="red">调用扣款接口后请勿立即调用撤销,需要等待5秒以上。先调用查单接口,如果没有确切的返回,再调用撤销</font> </br>
565 	 *
566 	 * @param idQuery
567 	 *            商户系统内部的订单号, transaction_id 、 out_trade_no 二选一,如果同时存在优先级:
568 	 *            transaction_id> out_trade_no
569 	 * @return 撤销结果
570 	 * @since V3
571 	 * @throws WeixinException
572 	 */
573 	public MerchantResult reverseOrder(IdQuery idQuery) throws WeixinException {
574 		Map<String, String> map = createBaseRequestMap(idQuery);
575 		map.put("sign", weixinSignature.sign(map));
576 		String param = XmlStream.map2xml(map);
577 		WeixinResponse response = getWeixinSSLExecutor().post(
578 				getRequestUri("order_reverse_uri"), param);
579 		return response.getAsObject(new TypeReference<MerchantResult>() {
580 		});
581 	}
582 
583 	/**
584 	 * native支付URL转短链接:用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX),减小二维码数据量
585 	 * ,提升扫描速度和精确度。
586 	 *
587 	 * @param url
588 	 *            具有native标识的支付URL
589 	 * @return 转换后的短链接
590 	 * @throws WeixinException
591 	 * @see <a href=
592 	 *      "http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_9">
593 	 *      转换短链接API</a>
594 	 */
595 	public String getShorturl(String url) throws WeixinException {
596 		Map<String, String> map = createBaseRequestMap(null);
597 		try {
598 			map.put("long_url", URLEncoder.encode(url, Consts.UTF_8.name()));
599 		} catch (UnsupportedEncodingException e) {
600 			;
601 		}
602 		map.put("sign", weixinSignature.sign(map));
603 		String param = XmlStream.map2xml(map);
604 		WeixinResponse response = weixinExecutor.post(
605 				getRequestUri("longurl_convert_uri"), param);
606 		map = XmlStream.xml2map(response.getAsString());
607 		return map.get("short_url");
608 	}
609 
610 	/**
611 	 * 关闭订单
612 	 * <p>
613 	 * 商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;系统下单后,用户支付超时,系统退出不再受理,避免用户继续
614 	 * ,请调用关单接口,如果关单失败,返回已完 成支付请按正常支付处理。如果出现银行掉单,调用关单成功后,微信后台会主动发起退款。
615 	 * </p>
616 	 *
617 	 * @param outTradeNo
618 	 *            商户系统内部的订单号
619 	 * @return 处理结果
620 	 * @since V3
621 	 * @throws WeixinException
622 	 * @see <a href=
623 	 *      "http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3">
624 	 *      关闭订单API</a>
625 	 */
626 	public MerchantResult closeOrder(String outTradeNo) throws WeixinException {
627 		Map<String, String> map = createBaseRequestMap(new IdQuery(outTradeNo,
628 				IdType.TRADENO));
629 		map.put("sign", weixinSignature.sign(map));
630 		String param = XmlStream.map2xml(map);
631 		WeixinResponse response = weixinExecutor.post(
632 				getRequestUri("order_close_uri"), param);
633 		return response.getAsObject(new TypeReference<MerchantResult>() {
634 		});
635 	}
636 
637 	/**
638 	 * 下载对账单<br>
639 	 * 1.微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账 单中,跟原支付单订单号一致,bill_type 为
640 	 * REVOKED;<br>
641 	 * 2.微信在次日 9 点启动生成前一天的对账单,建议商户 9 点半后再获取;<br>
642 	 * 3.对账单中涉及金额的字段单位为“元”。<br>
643 	 *
644 	 * @param billDate
645 	 *            下载对账单的日期
646 	 * @param billType
647 	 *            下载对账单的类型 ALL,返回当日所有订单信息, 默认值 SUCCESS,返回当日成功支付的订单
648 	 *            REFUND,返回当日退款订单
649 	 * @param outputStream
650 	 *            输出流
651 	 * @param tarType
652 	 *            非必传参数,固定值:GZIP,返回格式为.gzip的压缩包账单。不传则默认为数据流形式。
653 	 * @since V3
654 	 * @see <a href=
655 	 *      "http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6">
656 	 *      下载对账单API</a>
657 	 * @throws WeixinException
658 	 */
659 	public void downloadBill(Date billDate, BillType billType,
660 			OutputStream outputStream, TarType tarType) throws WeixinException {
661 		if (billDate == null) {
662 			Calendar now = Calendar.getInstance();
663 			now.add(Calendar.DAY_OF_MONTH, -1);
664 			billDate = now.getTime();
665 		}
666 		if (billType == null) {
667 			billType = BillType.ALL;
668 		}
669 		String formatBillDate = DateUtil.fortmat2yyyyMMdd(billDate);
670 		Map<String, String> map = createBaseRequestMap(null);
671 		map.put("bill_date", formatBillDate);
672 		map.put("bill_type", billType.name());
673 		if (tarType != null) {
674 			map.put("tar_type", tarType.name());
675 		}
676 		map.put("sign", weixinSignature.sign(map));
677 		String param = XmlStream.map2xml(map);
678 		WeixinResponse response = weixinExecutor.post(
679 				getRequestUri("downloadbill_uri"), param);
680 
681 		if (TarType.GZIP == tarType) {
682 			try {
683 				IOUtil.copy(response.getBody(), outputStream);
684 			} catch (IOException e) {
685 				;
686 			}
687 		} else {
688 			BufferedReader reader = null;
689 			BufferedWriter writer = null;
690 			try {
691 				writer = new BufferedWriter(new OutputStreamWriter(
692 						outputStream, Consts.UTF_8));
693 				reader = new BufferedReader(new InputStreamReader(
694 						response.getBody(), Consts.UTF_8));
695 				String line = null;
696 				while ((line = reader.readLine()) != null) {
697 					writer.write(line);
698 					writer.newLine();
699 				}
700 			} catch (IOException e) {
701 				throw new WeixinException(e);
702 			} finally {
703 				try {
704 					if (reader != null) {
705 						reader.close();
706 					}
707 					if (writer != null) {
708 						writer.close();
709 					}
710 				} catch (IOException ignore) {
711 					;
712 				}
713 			}
714 		}
715 	}
716 
717 	/**
718 	 * 退款查询
719 	 *
720 	 * <p>
721 	 * 提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。
722 	 * </p>
723 	 *
724 	 * @param idQuery
725 	 *            单号 refund_id、out_refund_no、 out_trade_no 、 transaction_id
726 	 *            四个参数必填一个,优先级为:
727 	 *            refund_id>out_refund_no>transaction_id>out_trade_no
728 	 * @return 退款记录
729 	 * @see com.foxinmy.weixin4j.payment.mch.RefundRecord
730 	 * @see com.foxinmy.weixin4j.payment.mch.RefundDetail
731 	 * @see <a href=
732 	 *      "http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5">
733 	 *      退款查询API</a>
734 	 * @since V3
735 	 * @throws WeixinException
736 	 */
737 	public RefundRecord queryRefund(IdQuery idQuery) throws WeixinException {
738 		Map<String, String> map = createBaseRequestMap(idQuery);
739 		map.put("sign", weixinSignature.sign(map));
740 		String param = XmlStream.map2xml(map);
741 		WeixinResponse response = weixinExecutor.post(
742 				getRequestUri("refund_query_uri"), param);
743 		return ListsuffixResultDeserializer.deserialize(response.getAsString(),
744 				RefundRecord.class);
745 	}
746 
747 	/**
748 	 * 接口上报
749 	 *
750 	 * @param interfaceUrl
751 	 *            上报对应的接口的完整 URL, 类似: https://api.mch.weixin.q
752 	 *            q.com/pay/unifiedorder
753 	 * @param executeTime
754 	 *            接口耗时情况,单位为毫秒
755 	 * @param outTradeNo
756 	 *            商户系统内部的订单号,商 户可以在上报时提供相关商户订单号方便微信支付更好 的提高服务质量。
757 	 * @param ip
758 	 *            发起接口调用时的机器 IP
759 	 * @param time
760 	 *            商户调用该接口时商户自己 系统的时间
761 	 * @param returnXml
762 	 *            调用接口返回的基本数据
763 	 * @return 处理结果
764 	 * @throws WeixinException
765 	 * @see <a href=
766 	 *      "http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_8">
767 	 *      交易保障</a>
768 	 */
769 	@SuppressWarnings("unchecked")
770 	public XmlResult reportInterface(String interfaceUrl, int executeTime,
771 			String outTradeNo, String ip, Date time, XmlResult returnXml)
772 			throws WeixinException {
773 		Map<String, String> map = createBaseRequestMap(null);
774 		map.put("interface_url", interfaceUrl);
775 		map.put("execute_time_", Integer.toString(executeTime));
776 		map.put("out_trade_no", outTradeNo);
777 		map.put("user_ip", ip);
778 		map.put("time", DateUtil.fortmat2yyyyMMddHHmmss(time));
779 		map.putAll((Map<String, String>) JSON.toJSON(returnXml));
780 		map.put("sign", weixinSignature.sign(map));
781 		String param = XmlStream.map2xml(map);
782 		WeixinResponse response = weixinExecutor.post(
783 				getRequestUri("interface_report_uri"), param);
784 		return response.getAsXml();
785 	}
786 
787 	/**
788 	 * 授权码查询OPENID接口
789 	 *
790 	 * @param authCode
791 	 *            扫码支付授权码,设备读取用户微信中的条码或者二维码信息
792 	 * @return 查询结果
793 	 * @see com.foxinmy.weixin4j.payment.mch.OpenIdResult
794 	 * @see <a href=
795 	 *      "https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_13&index=9">
796 	 *      授权码查询OPENID</a>
797 	 * @throws WeixinException
798 	 */
799 	public OpenIdResult authCode2openId(String authCode) throws WeixinException {
800 		Map<String, String> map = createBaseRequestMap(null);
801 		map.put("auth_code", authCode);
802 		map.put("sign", weixinSignature.sign(map));
803 		String param = XmlStream.map2xml(map);
804 		WeixinResponse response = weixinExecutor.post(
805 				getRequestUri("authcode_openid_uri"), param);
806 		return response.getAsObject(new TypeReference<OpenIdResult>() {
807 		});
808 	}
809 }