View Javadoc
1   package com.foxinmy.weixin4j.mp.api;
2   
3   import java.io.BufferedReader;
4   import java.io.BufferedWriter;
5   import java.io.File;
6   import java.io.FileInputStream;
7   import java.io.FileOutputStream;
8   import java.io.IOException;
9   import java.io.InputStream;
10  import java.io.InputStreamReader;
11  import java.io.OutputStreamWriter;
12  import java.security.KeyStore;
13  import java.security.SecureRandom;
14  import java.security.cert.CertificateFactory;
15  import java.util.Calendar;
16  import java.util.Date;
17  import java.util.HashMap;
18  import java.util.Map;
19  
20  import javax.net.ssl.KeyManagerFactory;
21  import javax.net.ssl.SSLContext;
22  import javax.net.ssl.TrustManagerFactory;
23  
24  import com.alibaba.fastjson.JSON;
25  import com.alibaba.fastjson.JSONObject;
26  import com.alibaba.fastjson.TypeReference;
27  import com.alibaba.fastjson.parser.Feature;
28  import com.foxinmy.weixin4j.cache.CacheStorager;
29  import com.foxinmy.weixin4j.cache.FileCacheStorager;
30  import com.foxinmy.weixin4j.exception.WeixinException;
31  import com.foxinmy.weixin4j.http.entity.FormUrlEntity;
32  import com.foxinmy.weixin4j.http.weixin.ApiResult;
33  import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
34  import com.foxinmy.weixin4j.model.Token;
35  import com.foxinmy.weixin4j.mp.oldpayment.OrderV2;
36  import com.foxinmy.weixin4j.mp.oldpayment.PayPackageV2;
37  import com.foxinmy.weixin4j.mp.oldpayment.RefundRecordV2;
38  import com.foxinmy.weixin4j.mp.oldpayment.RefundResultV2;
39  import com.foxinmy.weixin4j.mp.oldpayment.WeixinOldPayAccount;
40  import com.foxinmy.weixin4j.mp.oldpayment.WeixinOldPaymentSignature;
41  import com.foxinmy.weixin4j.mp.token.WeixinTokenCreator;
42  import com.foxinmy.weixin4j.payment.PayRequest;
43  import com.foxinmy.weixin4j.sign.WeixinPaymentSignature;
44  import com.foxinmy.weixin4j.sign.WeixinSignature;
45  import com.foxinmy.weixin4j.token.TokenManager;
46  import com.foxinmy.weixin4j.type.IdQuery;
47  import com.foxinmy.weixin4j.type.SignType;
48  import com.foxinmy.weixin4j.type.mch.BillType;
49  import com.foxinmy.weixin4j.type.mch.RefundType;
50  import com.foxinmy.weixin4j.util.Consts;
51  import com.foxinmy.weixin4j.util.DateUtil;
52  import com.foxinmy.weixin4j.util.DigestUtil;
53  import com.foxinmy.weixin4j.util.MapUtil;
54  import com.foxinmy.weixin4j.util.RandomUtil;
55  import com.foxinmy.weixin4j.util.StringUtil;
56  import com.foxinmy.weixin4j.util.Weixin4jConfigUtil;
57  import com.foxinmy.weixin4j.xml.ListsuffixResultDeserializer;
58  
59  /**
60   * V2老支付API
61   *
62   * @className PayOldApi
63   * @author jinyu(foxinmy@gmail.com)
64   * @date 2014年10月28日
65   * @since JDK 1.6
66   * @see
67   */
68  public class PayOldApi extends MpApi {
69  
70  	private final WeixinOldPayAccount weixinPayAccount;
71  	private final TokenManager tokenManager;
72  	private final WeixinSignature weixinMD5Signature;
73  	private final WeixinOldPaymentSignature weixinSignature;
74  
75  	/**
76  	 * 支付对象(使用weixin4j.properties配置的account商户信息,使用FileCacheStorager文件方式缓存TOKEN)
77  	 */
78  	public PayOldApi() {
79  		this(new FileCacheStorager<Token>());
80  	}
81  
82  	/**
83  	 * 支付对象(使用weixin4j.properties配置的account商户信息)
84  	 */
85  	public PayOldApi(CacheStorager<Token> cacheStorager) {
86  		this(JSON.parseObject(Weixin4jConfigUtil.getValue("account"),
87  				WeixinOldPayAccount.class), cacheStorager);
88  	}
89  
90  	/**
91  	 * 支付对象
92  	 * 
93  	 * @param weixinPayAccount
94  	 *            商户信息
95  	 * @param cacheStorager
96  	 *            token管理
97  	 */
98  	public PayOldApi(WeixinOldPayAccount weixinPayAccount,
99  			CacheStorager<Token> cacheStorager) {
100 		if (weixinPayAccount == null) {
101 			throw new IllegalArgumentException(
102 					"weixinPayAccount must not be empty");
103 		}
104 		if (cacheStorager == null) {
105 			throw new IllegalArgumentException(
106 					"cacheStorager must not be empty");
107 		}
108 		this.weixinPayAccount = weixinPayAccount;
109 		this.tokenManager = new TokenManager(new WeixinTokenCreator(
110 				weixinPayAccount.getId(), weixinPayAccount.getSecret()),
111 				cacheStorager);
112 		this.weixinMD5Signature = new WeixinPaymentSignature(
113 				weixinPayAccount.getPartnerKey());
114 		this.weixinSignature = new WeixinOldPaymentSignature(
115 				weixinPayAccount.getPaySignKey(),
116 				weixinPayAccount.getPartnerKey());
117 	}
118 
119 	/**
120 	 * 返回商户信息
121 	 * 
122 	 * @return
123 	 */
124 	public WeixinOldPayAccount getWeixinPayAccount() {
125 		return weixinPayAccount;
126 	}
127 
128 	/**
129 	 * token管理
130 	 *
131 	 * @return
132 	 */
133 	public TokenManager getTokenManager() {
134 		return this.tokenManager;
135 	}
136 
137 	/**
138 	 * 返回签名对象
139 	 * 
140 	 * @return
141 	 */
142 	public WeixinOldPaymentSignature getWeixinPaymentSignature() {
143 		return this.weixinSignature;
144 	}
145 
146 	/**
147 	 * 生成V2.x版本JSAPI支付字符串
148 	 *
149 	 * @param body
150 	 *            支付详情
151 	 * @param outTradeNo
152 	 *            订单号
153 	 * @param totalFee
154 	 *            订单总额(元)
155 	 * @param notifyUrl
156 	 *            支付回调URL
157 	 * @param createIp
158 	 *            订单生成的机器 IP
159 	 * @return 支付json串
160 	 */
161 	public String createPayJsRequestJson(String body, String outTradeNo,
162 			double totalFee, String notifyUrl, String createIp) {
163 		PayPackageV2 payPackage = new PayPackageV2(
164 				weixinPayAccount.getPartnerId(), body, outTradeNo, totalFee,
165 				notifyUrl, createIp);
166 		return createPayJsRequestJson(payPackage);
167 	}
168 
169 	/**
170 	 * 生成V2.x版本JSAPI支付字符串
171 	 *
172 	 * @param payPackage
173 	 *            支付信息
174 	 * @return 支付json串
175 	 */
176 	public String createPayJsRequestJson(PayPackageV2 payPackage) {
177 		PayRequest payRequest = new PayRequest(weixinPayAccount.getId(),
178 				weixinSignature.sign(payPackage));
179 		payRequest.setPaySign(weixinSignature.sign(payRequest));
180 		payRequest.setSignType(SignType.SHA1);
181 		return JSON.toJSONString(payRequest);
182 	}
183 
184 	/**
185 	 * 创建V2.x NativePay支付链接
186 	 *
187 	 * @param productId
188 	 *            与订单ID等价
189 	 * @return 支付链接
190 	 */
191 	public String createNativePayRequestURL(String productId) {
192 		Map<String, String> map = new HashMap<String, String>();
193 		String timestamp = DateUtil.timestamp2string();
194 		String noncestr = RandomUtil.generateString(16);
195 		map.put("appid", weixinPayAccount.getId());
196 		map.put("timestamp", timestamp);
197 		map.put("noncestr", noncestr);
198 		map.put("productid", productId);
199 		map.put("appkey", weixinPayAccount.getPaySignKey());
200 		String sign = weixinSignature.sign(map);
201 		String nativepay_uri = getRequestUri("nativepay_old_uri");
202 		return String.format(nativepay_uri, sign, weixinPayAccount.getId(),
203 				productId, timestamp, noncestr);
204 	}
205 
206 	/**
207 	 * 订单查询
208 	 *
209 	 * @param idQuery
210 	 *            订单号
211 	 * @return 订单信息
212 	 * @see com.foxinmy.weixin4j.mp.oldpayment.OrderV2
213 	 * @since V2
214 	 * @throws WeixinException
215 	 */
216 	public OrderV2 queryOrder(IdQuery idQuery) throws WeixinException {
217 		String orderquery_uri = getRequestUri("orderquery_old_uri");
218 		Token token = tokenManager.getCache();
219 		StringBuilder sb = new StringBuilder();
220 		sb.append(idQuery.getType().getName()).append("=")
221 				.append(idQuery.getId());
222 		sb.append("&partner=").append(weixinPayAccount.getPartnerId());
223 		String part = sb.toString();
224 		sb.append("&key=").append(weixinPayAccount.getPartnerKey());
225 		String sign = DigestUtil.MD5(sb.toString()).toUpperCase();
226 		sb.delete(0, sb.length());
227 		sb.append(part).append("&sign=").append(sign);
228 
229 		String timestamp = DateUtil.timestamp2string();
230 		JSONObject obj = new JSONObject();
231 		obj.put("appid", weixinPayAccount.getId());
232 		obj.put("appkey", weixinPayAccount.getPaySignKey());
233 		obj.put("package", sb.toString());
234 		obj.put("timestamp", timestamp);
235 		String signature = weixinSignature.sign(obj);
236 
237 		obj.clear();
238 		obj.put("appid", weixinPayAccount.getId());
239 		obj.put("package", sb.toString());
240 		obj.put("timestamp", timestamp);
241 		obj.put("app_signature", signature);
242 		obj.put("sign_method", SignType.SHA1.name().toLowerCase());
243 
244 		WeixinResponse response = weixinExecutor.post(
245 				String.format(orderquery_uri, token.getAccessToken()),
246 				obj.toJSONString());
247 
248 		String order_info = response.getAsJson().getString("order_info");
249 		OrderV2 order = JSON.parseObject(order_info, OrderV2.class,
250 				Feature.IgnoreNotMatch);
251 		if (order.getRetCode() != 0) {
252 			throw new WeixinException(Integer.toString(order.getRetCode()),
253 					order.getRetMsg());
254 		}
255 		return order;
256 	}
257 
258 	/**
259 	 * 申请退款(需要证书)</br>
260 	 * <p style="color:red">
261 	 * 交易时间超过 1 年的订单无法提交退款; </br> 支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no。一笔退款失
262 	 * 败后重新提交,要采用原来的 out_refund_no。总退款金额不能超过用户实际支付金额。</br>
263 	 * </p>
264 	 *
265 	 * @param certificate
266 	 *            证书文件(V2版本后缀为*.pfx)
267 	 * @param idQuery
268 	 *            商户系统内部的订单号, transaction_id 、 out_trade_no 二选一,如果同时存在优先级:
269 	 *            transaction_id> out_trade_no
270 	 * @param outRefundNo
271 	 *            商户系统内部的退款单号,商 户系统内部唯一,同一退款单号多次请求只退一笔
272 	 * @param totalFee
273 	 *            订单总金额,单位为元
274 	 * @param refundFee
275 	 *            退款总金额,单位为元,可以做部分退款
276 	 * @param opUserId
277 	 *            操作员帐号, 默认为商户号
278 	 * @param mopara
279 	 *            如 opUserPasswd
280 	 *
281 	 * @return 退款申请结果
282 	 * @see com.foxinmy.weixin4j.mp.oldpayment.RefundResultV2
283 	 * @since V2
284 	 * @throws WeixinException
285 	 */
286 	protected RefundResultV2 applyRefund(InputStream certificate,
287 			IdQuery idQuery, String outRefundNo, double totalFee,
288 			double refundFee, String opUserId, Map<String, String> mopara)
289 			throws WeixinException {
290 		String refund_uri = getRequestUri("refundapply_old_uri");
291 		WeixinResponse response = null;
292 		try {
293 			Map<String, String> map = new HashMap<String, String>();
294 			map.put("input_charset", Consts.UTF_8.name());
295 			// 版本号
296 			// 填写为 1.0 时,操作员密码为明文
297 			// 填写为 1.1 时,操作员密码为 MD5(密码)值
298 			map.put("service_version", "1.1");
299 			map.put("partner", weixinPayAccount.getPartnerId());
300 			map.put("out_refund_no", outRefundNo);
301 			map.put("total_fee",
302 					Integer.toString(DateUtil.formatYuan2Fen(totalFee)));
303 			map.put("refund_fee",
304 					Integer.toString(DateUtil.formatYuan2Fen(refundFee)));
305 			map.put(idQuery.getType().getName(), idQuery.getId());
306 			if (StringUtil.isBlank(opUserId)) {
307 				opUserId = weixinPayAccount.getPartnerId();
308 			}
309 			map.put("op_user_id", opUserId);
310 			if (mopara != null && !mopara.isEmpty()) {
311 				map.putAll(mopara);
312 			}
313 			String sign = weixinMD5Signature.sign(map);
314 			map.put("sign", sign.toUpperCase());
315 
316 			SSLContext ctx = null;
317 			KeyStore ks = null;
318 			String jksPwd = "";
319 			File jksFile = new File(String.format("%s%stenpay_cacert.jks",
320 					System.getProperty("java.io.tmpdir"), File.separator));
321 			// create jks ca
322 			if (!jksFile.exists()) {
323 				CertificateFactory cf = CertificateFactory
324 						.getInstance(Consts.X509);
325 				java.security.cert.Certificate cert = cf
326 						.generateCertificate(PayOldApi.class
327 								.getResourceAsStream("cacert.pem"));
328 				ks = KeyStore.getInstance(Consts.JKS);
329 				ks.load(null, null);
330 				ks.setCertificateEntry("tenpay", cert);
331 				FileOutputStream os = new FileOutputStream(jksFile);
332 				ks.store(os, jksPwd.toCharArray());
333 				os.close();
334 			}
335 			// load jks ca
336 			TrustManagerFactory tmf = TrustManagerFactory
337 					.getInstance(Consts.SunX509);
338 			ks = KeyStore.getInstance(Consts.JKS);
339 			FileInputStream is = new FileInputStream(jksFile);
340 			ks.load(is, jksPwd.toCharArray());
341 			tmf.init(ks);
342 			is.close();
343 			// load pfx ca
344 			KeyManagerFactory kmf = KeyManagerFactory
345 					.getInstance(Consts.SunX509);
346 			ks = KeyStore.getInstance(Consts.PKCS12);
347 			ks.load(certificate, weixinPayAccount.getPartnerId().toCharArray());
348 			kmf.init(ks, weixinPayAccount.getPartnerId().toCharArray());
349 
350 			ctx = SSLContext.getInstance(Consts.TLS);
351 			ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
352 					new SecureRandom());
353 			response = weixinExecutor.createSSLRequestExecutor(ctx).get(
354 					String.format("%s?%s", refund_uri,
355 							FormUrlEntity.formatParameters(map)));
356 		} catch (WeixinException e) {
357 			throw e;
358 		} catch (Exception e) {
359 			throw new WeixinException(e);
360 		} finally {
361 			if (certificate != null) {
362 				try {
363 					certificate.close();
364 				} catch (IOException e) {
365 					;
366 				}
367 			}
368 		}
369 		return response.getAsObject(new TypeReference<RefundResultV2>() {
370 		});
371 	}
372 
373 	/**
374 	 * 退款申请
375 	 *
376 	 * @param certificate
377 	 *            证书文件(V2版本后缀为*.pfx)
378 	 * @param idQuery
379 	 *            商户系统内部的订单号, transaction_id 、 out_trade_no 二选一,如果同时存在优先级:
380 	 *            transaction_id> out_trade_no
381 	 * @param outRefundNo
382 	 *            商户系统内部的退款单号,商 户系统内部唯一,同一退款单号多次请求只退一笔
383 	 * @param totalFee
384 	 *            订单总金额,单位为元
385 	 * @param refundFee
386 	 *            退款总金额,单位为元,可以做部分退款
387 	 * @param opUserId
388 	 *            操作员帐号, 默认为商户号
389 	 * @param opUserPasswd
390 	 *            操作员密码,默认为商户后台登录密码
391 	 * @see {@link #applyRefund(InputStream, IdQuery, String, double, double, String, Map)}
392 	 */
393 	public RefundResultV2 applyRefund(InputStream certificate, IdQuery idQuery,
394 			String outRefundNo, double totalFee, double refundFee,
395 			String opUserId, String opUserPasswd) throws WeixinException {
396 		Map<String, String> mopara = new HashMap<String, String>();
397 		mopara.put("op_user_passwd", DigestUtil.MD5(opUserPasswd));
398 		return applyRefund(certificate, idQuery, outRefundNo, totalFee,
399 				refundFee, opUserId, mopara);
400 	}
401 
402 	/**
403 	 * 退款申请
404 	 *
405 	 * @param certificate
406 	 *            证书文件(V2版本后缀为*.pfx)
407 	 * @param idQuery
408 	 *            商户系统内部的订单号, transaction_id 、 out_trade_no 二选一,如果同时存在优先级:
409 	 *            transaction_id> out_trade_no
410 	 * @param outRefundNo
411 	 *            商户系统内部的退款单号,商 户系统内部唯一,同一退款单号多次请求只退一笔
412 	 * @param totalFee
413 	 *            订单总金额,单位为元
414 	 * @param refundFee
415 	 *            退款总金额,单位为元,可以做部分退款
416 	 * @param opUserId
417 	 *            操作员帐号, 默认为商户号
418 	 * @param opUserPasswd
419 	 *            操作员密码,默认为商户后台登录密码
420 	 * @param recvUserId
421 	 *            转账退款接收退款的财付通帐号。 一般无需填写,只有退银行失败,资金转入商 户号现金账号时(即状态为转入代发,查询返 回的
422 	 *            refund_status 是 7 或 11),填写原退款 单号并填写此字段,资金才会退到指定财付通
423 	 *            账号。其他情况此字段忽略
424 	 * @param reccvUserName
425 	 *            转账退款接收退款的姓名(需与接收退款的财 付通帐号绑定的姓名一致)
426 	 * @param refundType
427 	 *            为空或者填 1:商户号余额退款;2:现金帐号 退款;3:优先商户号退款,若商户号余额不足, 再做现金帐号退款。使用 2 或
428 	 *            3 时,需联系财 付通开通此功能
429 	 * @see {@link #applyRefund(InputStream, IdQuery, String, double, double, String, Map)}
430 	 * @return 退款结果
431 	 */
432 	public RefundResultV2 applyRefund(InputStream certificate, IdQuery idQuery,
433 			String outRefundNo, double totalFee, double refundFee,
434 			String opUserId, String opUserPasswd, String recvUserId,
435 			String reccvUserName, RefundType refundType) throws WeixinException {
436 		Map<String, String> mopara = new HashMap<String, String>();
437 		mopara.put("op_user_passwd", DigestUtil.MD5(opUserPasswd));
438 		if (StringUtil.isNotBlank(recvUserId)) {
439 			mopara.put("recv_user_id", recvUserId);
440 		}
441 		if (StringUtil.isNotBlank(reccvUserName)) {
442 			mopara.put("reccv_user_name", reccvUserName);
443 		}
444 		if (refundType != null) {
445 			mopara.put("refund_type", Integer.toString(refundType.getVal()));
446 		}
447 		return applyRefund(certificate, idQuery, outRefundNo, totalFee,
448 				refundFee, opUserId, mopara);
449 	}
450 
451 	/**
452 	 * 下载对账单<br>
453 	 * 1.微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账 单中,跟原支付单订单号一致,bill_type 为
454 	 * REVOKED;<br>
455 	 * 2.微信在次日 9 点启动生成前一天的对账单,建议商户 9 点半后再获取;<br>
456 	 * 3.对账单中涉及金额的字段单位为“元”。<br>
457 	 *
458 	 * @param billDate
459 	 *            下载对账单的日期 为空则取前一天
460 	 * @param billType
461 	 *            下载对账单的类型 ALL,返回当日所有订单信息, 默认值 SUCCESS,返回当日成功支付的订单
462 	 *            REFUND,返回当日退款订单
463 	 * @param billPath
464 	 *            对账单保存路径
465 	 * @return excel表格
466 	 * @since V2
467 	 * @throws WeixinException
468 	 */
469 	public File downloadBill(Date billDate, BillType billType, String billPath)
470 			throws WeixinException {
471 		if (billDate == null) {
472 			Calendar now = Calendar.getInstance();
473 			now.add(Calendar.DAY_OF_MONTH, -1);
474 			billDate = now.getTime();
475 		}
476 		if (billType == null) {
477 			billType = BillType.ALL;
478 		}
479 		String formatBillDate = DateUtil.fortmat2yyyyMMdd(billDate);
480 		String fileName = String.format("weixin4j_bill_%s_%s_%s.txt",
481 				formatBillDate, billType.name().toLowerCase(),
482 				weixinPayAccount.getId());
483 		File file = new File(String.format("%s%s%s", billPath, File.separator,
484 				fileName));
485 		if (file.exists()) {
486 			return file;
487 		}
488 		String downloadbill_uri = getRequestUri("downloadbill_old_uri");
489 
490 		Map<String, String> map = new HashMap<String, String>();
491 		map.put("spid", weixinPayAccount.getPartnerId());
492 		map.put("trans_time", DateUtil.fortmat2yyyy_MM_dd(billDate));
493 		map.put("stamp", DateUtil.timestamp2string());
494 		map.put("cft_signtype", "0");
495 		map.put("mchtype", Integer.toString(billType.getVal()));
496 		map.put("key", weixinPayAccount.getPartnerKey());
497 		String sign = DigestUtil.MD5(MapUtil.toJoinString(map, false, false));
498 		map.put("sign", sign.toLowerCase());
499 		WeixinResponse response = weixinExecutor.get(String.format("%s?%s",
500 				downloadbill_uri, FormUrlEntity.formatParameters(map)));
501 		BufferedReader reader = null;
502 		BufferedWriter writer = null;
503 		try {
504 			writer = new BufferedWriter(new OutputStreamWriter(
505 					new FileOutputStream(file), Consts.GBK));
506 			reader = new BufferedReader(new InputStreamReader(
507 					response.getBody(), com.foxinmy.weixin4j.util.Consts.GBK));
508 			String line = null;
509 			while ((line = reader.readLine()) != null) {
510 				writer.write(line);
511 				writer.newLine();
512 			}
513 		} catch (IOException e) {
514 			throw new WeixinException(e);
515 		} finally {
516 			try {
517 				if (reader != null) {
518 					reader.close();
519 				}
520 				if (writer != null) {
521 					writer.close();
522 				}
523 			} catch (IOException ignore) {
524 				;
525 			}
526 		}
527 		return file;
528 	}
529 
530 	/**
531 	 * 退款查询</br> 退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态
532 	 *
533 	 * @param idQuery
534 	 *            单号 refund_id、out_refund_no、 out_trade_no 、 transaction_id
535 	 *            四个参数必填一个,优先级为:
536 	 *            refund_id>out_refund_no>transaction_id>out_trade_no
537 	 * @return 退款记录
538 	 * @see com.foxinmy.weixin4j.mp.oldpayment.RefundRecordV2
539 	 * @see com.foxinmy.weixin4j.mp.oldpayment.RefundDetailV2
540 	 * @since V2
541 	 * @throws WeixinException
542 	 */
543 	public RefundRecordV2 queryRefund(IdQuery idQuery) throws WeixinException {
544 		String refundquery_uri = getRequestUri("refundquery_old_uri");
545 		Map<String, String> map = new HashMap<String, String>();
546 		map.put("input_charset", Consts.UTF_8.name());
547 		map.put("partner", weixinPayAccount.getPartnerId());
548 		map.put(idQuery.getType().getName(), idQuery.getId());
549 		String sign = weixinMD5Signature.sign(map);
550 		map.put("sign", sign.toLowerCase());
551 		WeixinResponse response = weixinExecutor.get(String.format(
552 				refundquery_uri, FormUrlEntity.formatParameters(map)));
553 		return ListsuffixResultDeserializer.deserialize(response.getAsString(),
554 				RefundRecordV2.class);
555 	}
556 
557 	/**
558 	 * 发货通知
559 	 *
560 	 * @param openId
561 	 *            用户ID
562 	 * @param transid
563 	 *            交易单号
564 	 * @param outTradeNo
565 	 *            订单号
566 	 * @param status
567 	 *            成功|失败
568 	 * @param statusMsg
569 	 *            status为失败时携带的信息
570 	 * @return 发货处理结果
571 	 * @throws WeixinException
572 	 */
573 	public ApiResult deliverNotify(String openId, String transid,
574 			String outTradeNo, boolean status, String statusMsg)
575 			throws WeixinException {
576 		String delivernotify_uri = getRequestUri("delivernotify_old_uri");
577 		Token token = tokenManager.getCache();
578 
579 		Map<String, String> map = new HashMap<String, String>();
580 		map.put("appid", weixinPayAccount.getId());
581 		map.put("appkey", weixinPayAccount.getPaySignKey());
582 		map.put("openid", openId);
583 		map.put("transid", transid);
584 		map.put("out_trade_no", outTradeNo);
585 		map.put("deliver_timestamp", DateUtil.timestamp2string());
586 		map.put("deliver_status", status ? "1" : "0");
587 		map.put("deliver_msg", statusMsg);
588 		map.put("app_signature", weixinSignature.sign(map));
589 		map.put("sign_method", SignType.SHA1.name().toLowerCase());
590 
591 		WeixinResponse response = weixinExecutor.post(
592 				String.format(delivernotify_uri, token.getAccessToken()),
593 				JSON.toJSONString(map));
594 		return response.getAsResult();
595 	}
596 
597 	/**
598 	 * 维权处理
599 	 *
600 	 * @param openId
601 	 *            用户ID
602 	 * @param feedbackId
603 	 *            维权单号
604 	 * @return 维权处理结果
605 	 * @throws WeixinException
606 	 */
607 	public ApiResult updateFeedback(String openId, String feedbackId)
608 			throws WeixinException {
609 		String payfeedback_uri = getRequestUri("payfeedback_old_uri");
610 		Token token = tokenManager.getCache();
611 		WeixinResponse response = weixinExecutor.get(String.format(
612 				payfeedback_uri, token.getAccessToken(), openId, feedbackId));
613 		return response.getAsResult();
614 	}
615 }