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