根据《微信商户平台文档》修缮Pay3Api类 & mp-server新增客服创建、关闭、转接会话的事件

This commit is contained in:
jinyu 2015-03-25 14:41:47 +08:00
parent a40ff51cd6
commit e4e05e5370
23 changed files with 937 additions and 251 deletions

View File

@ -193,4 +193,10 @@
+ **weixin-mp**: 新增素材管理多个接口
+ **weixin-mp**: 新增多客服会话管理多个接口
* 2015-03-25
+ **weixin-mp**: 根据《微信商户平台文档》修缮[Pay3Api](./weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/Pay3Api.java)类
+ **weixin-mp**: 新增客服创建、关闭、转接会话事件

View File

@ -10,7 +10,7 @@ weixin4j
`公众平台API封装`
`微信支付(公众号)`
`微信支付(刷卡/扫码/公众号)`
`netty服务器&消息分发`
@ -71,6 +71,8 @@ netty的代码没有放到maven中心仓库,也没什么意义,因为最终需
接下来
------
* 代金券 & 红包接口
* 公众号服务应用
* 企业号第三方应用

View File

@ -188,4 +188,10 @@ weixin4j-mp
+ **weixin-mp-api**: 新增素材管理多个接口
+ **weixin-mp-api**: 新增多客服会话管理多个接口
+ **weixin-mp-api**: 新增多客服会话管理多个接口
* 2015-03-25
+ **weixin-mp-api**: 根据《微信商户平台文档》修缮[Pay3Api](./weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/Pay3Api.java)类
+ **weixin-mp-server**: 新增客服创建、关闭、转接会话事件

View File

@ -167,4 +167,8 @@ weixin.properties说明
+ 新增素材管理多个接口
+ 新增多客服会话管理多个接口
+ 新增多客服会话管理多个接口
* 2015-03-25
+ 根据《微信商户平台文档》修缮[Pay3Api](./weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/Pay3Api.java)类

View File

@ -12,6 +12,7 @@ import com.foxinmy.weixin4j.mp.api.Pay3Api;
import com.foxinmy.weixin4j.mp.api.PayApi;
import com.foxinmy.weixin4j.mp.payment.v3.ApiResult;
import com.foxinmy.weixin4j.mp.type.BillType;
import com.foxinmy.weixin4j.mp.type.CurrencyType;
import com.foxinmy.weixin4j.mp.type.IdQuery;
import com.foxinmy.weixin4j.mp.type.IdType;
import com.foxinmy.weixin4j.mp.type.RefundType;
@ -30,6 +31,7 @@ import com.foxinmy.weixin4j.util.ConfigUtil;
* @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.api.Pay2Api
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @see <a href="http://pay.weixin.qq.com/wiki/doc/api/index.html">商户平台支付API</a>
*/
public class WeixinPayProxy {
private final PayApi payApi;
@ -122,6 +124,10 @@ public class WeixinPayProxy {
/**
* V3订单查询
* <p>
* 当商户后台网络服务器等出现异常商户系统最终未接收到支付通知</br> 调用支付接口后返回系统错误或未知交易状态情况</br>
* 调用被扫支付API返回USERPAYING的状态</br> 调用关单或撤销接口API之前需确认支付状态
* </P>
*
* @param idQuery
* 商户系统内部的订单号, transaction_idout_trade_no 选一,如果同时存在优先级:
@ -130,6 +136,8 @@ public class WeixinPayProxy {
* @see com.foxinmy.weixin4j.mp.payment.v3.Order
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2">订单查询API</a>
* @return 订单详情
* @throws WeixinException
*/
@ -253,9 +261,14 @@ public class WeixinPayProxy {
/**
* V3申请退款(请求需要双向证书)</br>
* <p>
* 当交易发生之后一段时间内由于买家或者卖家的原因需要退款时卖家可以通过退款接口将支付款退还给买家微信支付将在收到退款请求并且验证成功之后
* 按照退款规则将支付款按原路退到买家帐号上
* </p>
* <p style="color:red">
* 交易时间超过 1 年的订单无法提交退款; </br> 支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no一笔退款失
* 败后重新提交,要采用原来的 out_refund_no总退款金额不能超过用户实际支付金额</br>
* 1.交易时间超过半年的订单无法提交退款
* 2.微信支付退款支持单笔交易分多次退款多次退款需要提交原支付订单的商户订单号和设置不同的退款单号一笔退款失败后重新提交
* 要采用原来的退款单号总退款金额不能超过用户实际支付金额
* </p>
*
* @param caFile
@ -269,6 +282,8 @@ public class WeixinPayProxy {
* 订单总金额,单位为元
* @param refundFee
* 退款总金额,单位为元,可以做部分退款
* @param refundFeeType
* 货币类型符合ISO 4217标准的三位字母代码默认人民币CNY
* @param opUserId
* 操作员帐号, 默认为商户号
*
@ -276,31 +291,37 @@ public class WeixinPayProxy {
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundResult
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @since V3
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4">申请退款API</a>
* @since V3 TODO
* @throws WeixinException
*/
public com.foxinmy.weixin4j.mp.payment.v3.RefundResult refundV3(
File caFile, IdQuery idQuery, String outRefundNo, double totalFee,
double refundFee, String opUserId) throws WeixinException {
double refundFee, CurrencyType refundFeeType, String opUserId)
throws WeixinException {
return pay3Api.refund(caFile, idQuery, outRefundNo, totalFee,
refundFee, opUserId);
refundFee, refundFeeType, opUserId);
}
/**
* V3退款申请采用properties中配置的ca文件
*
* @see {@link com.foxinmy.weixin4j.mp.WeixinPayProxy#refundV3(File, IdQuery, String, double, double, String)}
* @see {@link com.foxinmy.weixin4j.mp.WeixinPayProxy#refundV3(File, IdQuery, String, double, double,CurrencyType, String)}
*/
public com.foxinmy.weixin4j.mp.payment.v3.RefundResult refundV3(
IdQuery idQuery, String outRefundNo, double totalFee,
double refundFee, String opUserId) throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return pay3Api.refund(caFile, idQuery, outRefundNo, totalFee,
refundFee, opUserId);
refundFee, CurrencyType.CNY, opUserId);
}
/**
* V3退款查询</br> 退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态
* V3退款查询
* <p>
* 提交退款申请后通过调用该接口查询退款状态退款有一定延时用零钱支付的退款20分钟内到账银行卡支付的退款3个工作日后重新查询退款状态
* </p>
*
* @param idQuery
* 单号 refund_idout_refund_no out_trade_no transaction_id
@ -310,6 +331,8 @@ public class WeixinPayProxy {
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundRecord
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5">退款查询API</a>
* @since V3
* @throws WeixinException
*/
@ -333,6 +356,8 @@ public class WeixinPayProxy {
* @return excel表格
* @since V2 & V3
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6">下载对账单API</a>
* @throws WeixinException
*/
public File downloadbill(Date billDate, BillType billType)
@ -378,8 +403,11 @@ public class WeixinPayProxy {
}
/**
* 关闭订单</br> 当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完
* 成支付请按正常支付处理如果出现银行掉单,调用关单成功后,微信后台会主动发起退款
* 关闭订单
* <p>
* 商户订单支付失败需要生成新单号重新发起支付要对原订单号调用关单避免重复支付系统下单后用户支付超时系统退出不再受理避免用户继续
* 请调用关单接口,如果关单失败,返回已完 成支付请按正常支付处理如果出现银行掉单,调用关单成功后,微信后台会主动发起退款
* </p>
*
* @param outTradeNo
* 商户系统内部的订单号
@ -388,13 +416,16 @@ public class WeixinPayProxy {
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @since V3
* @throws WeixinException
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3">关闭订单API</a>
*/
public ApiResult closeOrder(String outTradeNo) throws WeixinException {
return payApi.closeOrder(outTradeNo);
}
/**
* native支付URL转短链接
* native支付URL转短链接:用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX)减小二维码数据量
* 提升扫描速度和精确度
*
* @param url
* 具有native标识的支付URL
@ -402,6 +433,8 @@ public class WeixinPayProxy {
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay2Api
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_9">转换短链接API</a>
* @since V2 & V3
* @throws WeixinException
*/
@ -428,6 +461,8 @@ public class WeixinPayProxy {
* @return 处理结果
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_8">接口测试上报API</a>
* @throws WeixinException
*/
public XmlResult interfaceReport(String interfaceUrl, int executeTime,

View File

@ -39,7 +39,7 @@ import com.foxinmy.weixin4j.http.SSLHttpRequest;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.mp.payment.RefundConverter;
import com.foxinmy.weixin4j.mp.payment.conver.RefundConverter;
import com.foxinmy.weixin4j.mp.payment.v2.Order;
import com.foxinmy.weixin4j.mp.payment.v2.RefundRecord;
import com.foxinmy.weixin4j.mp.payment.v2.RefundResult;

View File

@ -30,12 +30,14 @@ import com.foxinmy.weixin4j.http.SSLHttpRequest;
import com.foxinmy.weixin4j.http.XmlResult;
import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.mp.payment.RefundConverter;
import com.foxinmy.weixin4j.mp.payment.conver.CouponConverter;
import com.foxinmy.weixin4j.mp.payment.conver.RefundConverter;
import com.foxinmy.weixin4j.mp.payment.v3.ApiResult;
import com.foxinmy.weixin4j.mp.payment.v3.Order;
import com.foxinmy.weixin4j.mp.payment.v3.RefundRecord;
import com.foxinmy.weixin4j.mp.payment.v3.RefundResult;
import com.foxinmy.weixin4j.mp.type.BillType;
import com.foxinmy.weixin4j.mp.type.CurrencyType;
import com.foxinmy.weixin4j.mp.type.IdQuery;
import com.foxinmy.weixin4j.mp.type.IdType;
import com.foxinmy.weixin4j.mp.util.ExcelUtil;
@ -51,7 +53,7 @@ import com.foxinmy.weixin4j.util.RandomUtil;
* @author jy
* @date 2014年10月28日
* @since JDK 1.7
* @see
* @see <a href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php">公众号支付API</a>
*/
public class Pay3Api extends PayApi {
@ -61,12 +63,18 @@ public class Pay3Api extends PayApi {
/**
* 订单查询
* <p>
* 当商户后台网络服务器等出现异常商户系统最终未接收到支付通知</br> 调用支付接口后返回系统错误或未知交易状态情况</br>
* 调用被扫支付API返回USERPAYING的状态</br> 调用关单或撤销接口API之前需确认支付状态
* </P>
*
* @param idQuery
* 商户系统内部的订单号, transaction_idout_trade_no 选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @return 订单信息
* @see com.foxinmy.weixin4j.mp.payment.v3.Order
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2">订单查询API</a>
* @since V3
* @throws WeixinException
*/
@ -77,15 +85,19 @@ public class Pay3Api extends PayApi {
String param = map2xml(map);
String orderquery_uri = getRequestUri("orderquery_v3_uri");
Response response = request.post(orderquery_uri, param);
return response.getAsObject(new TypeReference<Order>() {
});
return CouponConverter.fromXML(response.getAsString(), Order.class);
}
/**
* 申请退款(请求需要双向证书)</br>
* 申请退款(请求需要双向证书)
* <p>
* 当交易发生之后一段时间内由于买家或者卖家的原因需要退款时卖家可以通过退款接口将支付款退还给买家微信支付将在收到退款请求并且验证成功之后
* 按照退款规则将支付款按原路退到买家帐号上
* </p>
* <p style="color:red">
* 交易时间超过 1 年的订单无法提交退款; </br> 支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no一笔退款失
* 败后重新提交,要采用原来的 out_refund_no总退款金额不能超过用户实际支付金额</br>
* 1.交易时间超过半年的订单无法提交退款
* 2.微信支付退款支持单笔交易分多次退款多次退款需要提交原支付订单的商户订单号和设置不同的退款单号一笔退款失败后重新提交
* 要采用原来的退款单号总退款金额不能超过用户实际支付金额
* </p>
*
* @param caFile
@ -104,6 +116,8 @@ public class Pay3Api extends PayApi {
*
* @return 退款申请结果
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundResult
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4">申请退款API</a>
* @since V3
* @throws WeixinException
*/
@ -147,15 +161,15 @@ public class Pay3Api extends PayApi {
}
}
}
return response.getAsObject(new TypeReference<RefundResult>() {
});
return CouponConverter.fromXML(response.getAsString(),
RefundResult.class);
}
/**
* 退款申请
*
* @param caFile
* 证书文件(V2版本后缀为*.pfx)
* 证书文件(V3版本后缀为*.p12)
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
@ -165,15 +179,22 @@ public class Pay3Api extends PayApi {
* 订单总金额,单位为元
* @param refundFee
* 退款总金额,单位为元,可以做部分退款
* @param refundFeeType
* 货币类型符合ISO 4217标准的三位字母代码默认人民币CNY
* @param opUserId
* 操作员帐号, 默认为商户号
* @see {@link com.foxinmy.weixin4j.mp.api.Pay3Api#refund(File, IdQuery, String, double, double, String, Map)}
*/
public RefundResult refund(File caFile, IdQuery idQuery,
String outRefundNo, double totalFee, double refundFee,
String opUserId) throws WeixinException {
CurrencyType refundFeeType, String opUserId) throws WeixinException {
Map<String, String> mopara = new HashMap<String, String>();
if (refundFeeType == null) {
refundFeeType = CurrencyType.CNY;
}
mopara.put("refund_fee_type", refundFeeType.name());
return refund(caFile, idQuery, outRefundNo, totalFee, refundFee,
opUserId, null);
opUserId, mopara);
}
/**
@ -221,12 +242,15 @@ public class Pay3Api extends PayApi {
}
/**
* native支付URL转短链接
* native支付URL转短链接用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX)减小二维码数据量
* 提升扫描速度和精确度
*
* @param url
* 具有native标识的支付URL
* @return 转换后的短链接
* @throws WeixinException
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_9">转换短链接API</a>
*/
public String getShorturl(String url) throws WeixinException {
Map<String, String> map = baseMap(null);
@ -245,14 +269,19 @@ public class Pay3Api extends PayApi {
}
/**
* 关闭订单</br> 当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完
* 成支付请按正常支付处理如果出现银行掉单,调用关单成功后,微信后台会主动发起退款
* 关闭订单
* <p>
* 商户订单支付失败需要生成新单号重新发起支付要对原订单号调用关单避免重复支付系统下单后用户支付超时系统退出不再受理避免用户继续
* 请调用关单接口,如果关单失败,返回已完 成支付请按正常支付处理如果出现银行掉单,调用关单成功后,微信后台会主动发起退款
* </p>
*
* @param outTradeNo
* 商户系统内部的订单号
* @return 处理结果
* @since V3
* @throws WeixinException
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3">关闭订单API</a>
*/
public ApiResult closeOrder(String outTradeNo) throws WeixinException {
Map<String, String> map = baseMap(new IdQuery(outTradeNo,
@ -280,6 +309,8 @@ public class Pay3Api extends PayApi {
* REFUND,返回当日退款订单
* @return excel表格
* @since V3
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6">下载对账单API</a>
* @throws WeixinException
*/
public File downloadbill(Date billDate, BillType billType)
@ -349,7 +380,11 @@ public class Pay3Api extends PayApi {
}
/**
* 退款查询</br> 退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态
* 退款查询
*
* <p>
* 提交退款申请后通过调用该接口查询退款状态退款有一定延时用零钱支付的退款20分钟内到账银行卡支付的退款3个工作日后重新查询退款状态
* </p>
*
* @param idQuery
* 单号 refund_idout_refund_no out_trade_no transaction_id
@ -358,6 +393,8 @@ public class Pay3Api extends PayApi {
* @return 退款记录
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundRecord
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundDetail
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5">退款查询API</a>
* @since V3
* @throws WeixinException
*/
@ -390,6 +427,8 @@ public class Pay3Api extends PayApi {
* 调用接口返回的基本数据
* @return 处理结果
* @throws WeixinException
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_8">接口测试上报API</a>
*/
@SuppressWarnings("unchecked")
public XmlResult interfaceReport(String interfaceUrl, int executeTime,

View File

@ -21,6 +21,7 @@ public class PayPackage implements Serializable {
private static final long serialVersionUID = 3450161267802545790L;
private String body; // 商品描述 必须
private String detail; // 商品详情 非必须
private String attach; // 附加数据,原样返回 非必须
@XStreamAlias("out_trade_no")
@JSONField(name = "out_trade_no")
@ -56,6 +57,14 @@ public class PayPackage implements Serializable {
this.body = body;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public String getAttach() {
return attach;
}
@ -151,10 +160,10 @@ public class PayPackage implements Serializable {
@Override
public String toString() {
return "PayPackage [body=" + body + ", attach=" + attach
+ ", outTradeNo=" + outTradeNo + ", totalFee=" + totalFee
+ ", spbillCreateIp=" + spbillCreateIp + ", timeStart="
+ timeStart + ", timeExpire=" + timeExpire + ", goodsTag="
+ goodsTag + ", notifyUrl=" + notifyUrl + "]";
return "PayPackage [body=" + body + ", detail=" + detail + ", attach="
+ attach + ", outTradeNo=" + outTradeNo + ", totalFee="
+ totalFee + ", spbillCreateIp=" + spbillCreateIp
+ ", timeStart=" + timeStart + ", timeExpire=" + timeExpire
+ ", goodsTag=" + goodsTag + ", notifyUrl=" + notifyUrl + "]";
}
}

View File

@ -208,7 +208,9 @@ public class PayUtil {
}
/**
* 创建预支付对象</br>
* 统一下单接口</br>
* 除被扫支付场景以外商户系统先调用该接口在微信支付服务后台生成预支付交易单返回正确的预支付交易回话标识后再按扫码JSAPI
* APP等不同场景生成交易串调起支付
*
* @param payPackage
* 包含订单信息的对象
@ -216,6 +218,8 @@ public class PayUtil {
* <font color="red">如果sign为空 则拿paysignkey进行签名</font>
* @see com.foxinmy.weixin4j.mp.payment.v3.PayPackageV3
* @see com.foxinmy.weixin4j.mp.payment.v3.PrePay
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1">统一下单接口</a>
* @return 预支付对象
*/
public static PrePay createPrePay(PayPackageV3 payPackage, String paySignKey)
@ -306,13 +310,14 @@ public class PayUtil {
}
/**
* 创建V3.x NativePay支付链接
* 创建V3.x NativePay支付(扫码支付)链接
*
* @param weixinAccount
* 支付配置信息
* @param productId
* 与订单ID等价
* @return
* @see <a href="http://pay.weixin.qq.com/wiki/doc/api/native.php">扫码支付</a>
*/
public static String createNativePayRequestURLV3(
WeixinMpAccount weixinAccount, String productId) {
@ -330,7 +335,7 @@ public class PayUtil {
}
/**
* NATIVE回调时的响应
* 创建V2.x NATIVE回调时的响应字符串
*
* @param weixinAccount
* 商户信息
@ -360,7 +365,7 @@ public class PayUtil {
* 提交被扫支付
*
* @param authCode
* 扫码支付授权码 ,设备读取用 户微信中的条码或者二维码 信息
* 扫码支付授权码 ,设备读取用户微信中的条码或者二维码信息
* @param body
* 商品描述
* @param attach
@ -373,7 +378,7 @@ public class PayUtil {
* 订单生成的机器 IP
* @param weixinAccount
* 商户信息
* @return 返回数据
* @return 支付的订单信息
* @see {@link com.foxinmy.weixin4j.mp.payment.PayUtil#createMicroPay(MicroPayPackage, WeixinMpAccount)}
* @throws WeixinException
*/
@ -387,14 +392,17 @@ public class PayUtil {
}
/**
* 提交被扫支付
* 提交被扫支付:收银员使用扫码设备读取微信用户刷卡授权码以后二维码或条码信息传送至商户收银台由商户收银台或者商户后台调用该接口发起支付.
*
* @param payPackage
* 订单信息
* @param weixinAccount
* 商户信息
* @return 返回数据
* @return 支付的订单信息
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.payment.v3.Order
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10">提交被扫支付API</a>
*/
public static com.foxinmy.weixin4j.mp.payment.v3.Order createMicroPay(
MicroPayPackage payPackage, WeixinMpAccount weixinAccount)

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.mp.payment;
package com.foxinmy.weixin4j.mp.payment.conver;
import java.lang.reflect.Field;
import java.util.HashMap;
@ -9,6 +9,9 @@ import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponInfo;
import com.foxinmy.weixin4j.mp.payment.v3.Order;
import com.foxinmy.weixin4j.mp.payment.v3.RefundResult;
import com.foxinmy.weixin4j.xml.XmlStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
@ -20,45 +23,41 @@ import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;
/**
* 退款查询接口调用结果转换类
* V3订单详情转换类
*
* @className RefundConverter
* @className OrderConverter
* @author jy
* @date 2014年11月2
* @date 2015年3月24
* @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundRecord
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundDetail
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundRecord
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundDetail
* @see
*/
public class RefundConverter {
public class CouponConverter {
private final static XmlStream xStream = XmlStream.get();
private final static Mapper mapper;
private final static ReflectionProvider reflectionProvider;
private final static Pattern pattern = Pattern.compile("(_\\d)$");
private static Class<CouponInfo> COUPON_CLASS = CouponInfo.class;
private static Class<?> clazz;
private final static Class<com.foxinmy.weixin4j.mp.payment.v2.RefundRecord> REFUNDRECORD2 = com.foxinmy.weixin4j.mp.payment.v2.RefundRecord.class;
private final static Class<com.foxinmy.weixin4j.mp.payment.v3.RefundRecord> REFUNDRECORD3 = com.foxinmy.weixin4j.mp.payment.v3.RefundRecord.class;
static {
xStream.processAnnotations(new Class[] { REFUNDRECORD2, REFUNDRECORD3,
com.foxinmy.weixin4j.mp.payment.v2.RefundDetail.class,
com.foxinmy.weixin4j.mp.payment.v3.RefundDetail.class });
xStream.aliasField("refund_state", com.foxinmy.weixin4j.mp.payment.v2.RefundDetail.class, "refundStatus");
xStream.processAnnotations(new Class[] { COUPON_CLASS });
xStream.registerConverter(new $());
mapper = xStream.getMapper();
reflectionProvider = xStream.getReflectionProvider();
}
public static <T> T fromXML(String xml, Class<T> clazz) {
RefundConverter.clazz = clazz;
CouponConverter.clazz = clazz;
xStream.processAnnotations(clazz);
return xStream.fromXML(xml, clazz);
}
private static class $ implements Converter {
@Override
public boolean canConvert(@SuppressWarnings("rawtypes") Class clazz) {
return clazz.equals(REFUNDRECORD2) || clazz.equals(REFUNDRECORD3);
return clazz.equals(Order.class)
|| clazz.equals(RefundResult.class);
}
@Override
@ -71,9 +70,9 @@ public class RefundConverter {
@Override
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Object refund = null;
Object object = null;
try {
refund = clazz.newInstance();
object = clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
@ -88,9 +87,9 @@ public class RefundConverter {
Field field = reflectionProvider.getFieldOrNull(clazz,
fieldName);
if (field != null) {
Object value = context.convertAnother(refund,
Object value = context.convertAnother(object,
field.getType());
reflectionProvider.writeField(refund, fieldName, value,
reflectionProvider.writeField(object, fieldName, value,
field.getDeclaringClass());
} else if ((matcher = pattern.matcher(nodeName)).find()) {
String key = matcher.group();
@ -103,27 +102,34 @@ public class RefundConverter {
}
reader.moveUp();
}
StringBuilder detailXml = new StringBuilder();
detailXml.append("<list>");
String detailCanonicalName = clazz.getCanonicalName().replaceFirst(
"RefundRecord", "RefundDetail");
for (Iterator<Entry<String, Map<String, String>>> outIt = outMap
.entrySet().iterator(); outIt.hasNext();) {
detailXml.append("<").append(detailCanonicalName).append(">");
for (Iterator<Entry<String, String>> innerIt = outIt.next()
.getValue().entrySet().iterator(); innerIt.hasNext();) {
Entry<String, String> entry = innerIt.next();
detailXml.append("<").append(entry.getKey()).append(">");
detailXml.append(entry.getValue());
detailXml.append("</").append(entry.getKey()).append(">");
if (!outMap.isEmpty()) {
StringBuilder couponXml = new StringBuilder();
couponXml.append("<list>");
for (Iterator<Entry<String, Map<String, String>>> outIt = outMap
.entrySet().iterator(); outIt.hasNext();) {
couponXml.append("<")
.append(COUPON_CLASS.getCanonicalName())
.append(">");
for (Iterator<Entry<String, String>> innerIt = outIt.next()
.getValue().entrySet().iterator(); innerIt
.hasNext();) {
Entry<String, String> entry = innerIt.next();
couponXml.append("<").append(entry.getKey())
.append(">");
couponXml.append(entry.getValue());
couponXml.append("</").append(entry.getKey())
.append(">");
}
couponXml.append("</")
.append(COUPON_CLASS.getCanonicalName())
.append(">");
}
detailXml.append("</").append(detailCanonicalName).append(">");
couponXml.append("</list>");
reflectionProvider.writeField(object, "couponList",
xStream.fromXML(couponXml.toString(), List.class),
List.class.getDeclaringClass());
}
detailXml.append("</list>");
reflectionProvider.writeField(refund, "details",
xStream.fromXML(detailXml.toString(), List.class),
List.class.getDeclaringClass());
return refund;
return object;
}
}
}
}

View File

@ -0,0 +1,190 @@
package com.foxinmy.weixin4j.mp.payment.conver;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponInfo;
import com.foxinmy.weixin4j.xml.XmlStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;
/**
* 退款查询接口调用结果转换类
*
* @className RefundConverter
* @author jy
* @date 2014年11月2日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundRecord
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundDetail
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundRecord
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundDetail
*/
public class RefundConverter {
private final static XmlStream xStream = XmlStream.get();
private final static Mapper mapper;
private final static ReflectionProvider reflectionProvider;
private final static Pattern REFUND_PATTERN = Pattern.compile("_\\d{1,}$");
private final static Pattern COUPON_PATTERN = Pattern
.compile("_\\d{1,}_\\d{1,}$");
private static Class<?> clazz;
private final static Class<CouponInfo> COUPON_CLASS = CouponInfo.class;
private final static Class<com.foxinmy.weixin4j.mp.payment.v2.RefundRecord> REFUNDRECORD2 = com.foxinmy.weixin4j.mp.payment.v2.RefundRecord.class;
private final static Class<com.foxinmy.weixin4j.mp.payment.v3.RefundRecord> REFUNDRECORD3 = com.foxinmy.weixin4j.mp.payment.v3.RefundRecord.class;
static {
xStream.processAnnotations(new Class[] { REFUNDRECORD2, REFUNDRECORD3 });
xStream.aliasField("refund_state",
com.foxinmy.weixin4j.mp.payment.v2.RefundDetail.class,
"refundStatus");
xStream.registerConverter(new $());
mapper = xStream.getMapper();
reflectionProvider = xStream.getReflectionProvider();
}
public static <T> T fromXML(String xml, Class<T> clazz) {
RefundConverter.clazz = clazz;
xStream.processAnnotations(clazz);
return xStream.fromXML(xml, clazz);
}
private static class $ implements Converter {
@Override
public boolean canConvert(@SuppressWarnings("rawtypes") Class clazz) {
return clazz.equals(REFUNDRECORD2) || clazz.equals(REFUNDRECORD3);
}
@Override
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
new ReflectionConverter(mapper, reflectionProvider).marshal(source,
writer, context);
}
@Override
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Object refund = null;
try {
refund = clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Matcher matcher = null;
Map<String, Map<String, String>> refundMap = new HashMap<String, Map<String, String>>();
Map<String, Map<String, String>> couponMap = new HashMap<String, Map<String, String>>();
while (reader.hasMoreChildren()) {
reader.moveDown();
String nodeName = reader.getNodeName();
String fieldName = mapper.realMember(clazz, nodeName);
Field field = reflectionProvider.getFieldOrNull(clazz,
fieldName);
if (field != null) {
Object value = context.convertAnother(refund,
field.getType());
reflectionProvider.writeField(refund, fieldName, value,
field.getDeclaringClass());
} else if ((matcher = REFUND_PATTERN.matcher(nodeName)).find()) {
String key = matcher.group();
Map<String, String> innerMap = null;
if ((matcher = COUPON_PATTERN.matcher(nodeName)).find()) {
key = matcher.group();
if ((innerMap = couponMap.get(key)) == null) {
innerMap = new HashMap<String, String>();
couponMap.put(key, innerMap);
}
} else {
if ((innerMap = refundMap
.get(String.format("%s_", key))) == null) {
innerMap = new HashMap<String, String>();
refundMap.put(String.format("%s_", key), innerMap);
}
}
innerMap.put(nodeName.replaceFirst(key, ""),
reader.getValue());
}
reader.moveUp();
}
if (!refundMap.isEmpty()) {
StringBuilder detailXml = new StringBuilder();
detailXml.append("<list>");
String detailCanonicalName = clazz.getCanonicalName()
.replaceFirst("RefundRecord", "RefundDetail");
for (Iterator<Entry<String, Map<String, String>>> refundIT = refundMap
.entrySet().iterator(); refundIT.hasNext();) {
detailXml.append("<").append(detailCanonicalName)
.append(">");
Entry<String, Map<String, String>> refundEntry = refundIT
.next();
for (Iterator<Entry<String, String>> innerIt = refundEntry
.getValue().entrySet().iterator(); innerIt
.hasNext();) {
Entry<String, String> entry = innerIt.next();
detailXml.append("<").append(entry.getKey())
.append(">");
detailXml.append(entry.getValue());
detailXml.append("</").append(entry.getKey())
.append(">");
}
if (!couponMap.isEmpty()) {
detailXml.append("<couponList class=\"")
.append(ArrayList.class.getCanonicalName())
.append("\">");
Iterator<Entry<String, Map<String, String>>> couponIT = couponMap
.entrySet().iterator();
while (couponIT.hasNext()) {
Entry<String, Map<String, String>> couponEntry = couponIT
.next();
if (couponEntry.getKey().startsWith(
refundEntry.getKey())) {
detailXml
.append("<")
.append(COUPON_CLASS.getCanonicalName())
.append(">");
for (Iterator<Entry<String, String>> innerIt = couponEntry
.getValue().entrySet().iterator(); innerIt
.hasNext();) {
Entry<String, String> entry = innerIt
.next();
detailXml.append("<")
.append(entry.getKey()).append(">");
detailXml.append(entry.getValue());
detailXml.append("</")
.append(entry.getKey()).append(">");
}
detailXml
.append("</")
.append(COUPON_CLASS.getCanonicalName())
.append(">");
couponIT.remove();
}
}
detailXml.append("</couponList>");
}
detailXml.append("</").append(detailCanonicalName)
.append(">");
}
detailXml.append("</list>");
reflectionProvider.writeField(refund, "details",
xStream.fromXML(detailXml.toString(), List.class),
List.class.getDeclaringClass());
}
return refund;
}
}
}

View File

@ -0,0 +1,65 @@
package com.foxinmy.weixin4j.mp.payment.coupon;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 代金券信息
*
* @className CouponBase
* @author jy
* @date 2015年3月24日
* @since JDK 1.7
* @see
*/
public class CouponInfo implements Serializable {
private static final long serialVersionUID = -8744999305258786901L;
// 代金券或立减优惠批次ID
@XStreamAlias("coupon_batch_id")
@JSONField(name = "coupon_batch_id")
private String couponBatchId;
// 代金券或立减优惠ID
@XStreamAlias("coupon_id")
@JSONField(name = "coupon_id")
private String couponId;
// 单个代金券或立减优惠支付金额
@XStreamAlias("coupon_fee")
@JSONField(name = "coupon_fee")
private Integer couponFee;
public String getCouponBatchId() {
return couponBatchId;
}
public String getCouponId() {
return couponId;
}
public Integer getCouponFee() {
return couponFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatCouponFee() {
return couponFee / 100d;
}
public void setCouponId(String couponId) {
this.couponId = couponId;
}
@Override
public String toString() {
return "couponBatchId=" + couponBatchId + ", couponId=" + couponId
+ ", couponFee=" + couponFee;
}
}

View File

@ -110,6 +110,6 @@ public class ApiResult extends XmlResult {
public String toString() {
return "appId=" + appId + ", mchId=" + mchId + ", subMchId=" + subMchId
+ ", nonceStr=" + nonceStr + ", sign=" + sign + ", deviceInfo="
+ deviceInfo + ", recall=" + recall + ", " + super.toString();
+ deviceInfo + ", recall=" + getFormatRecall() + ", " + super.toString();
}
}

View File

@ -1,15 +1,16 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponInfo;
import com.foxinmy.weixin4j.mp.type.CurrencyType;
import com.foxinmy.weixin4j.mp.type.TradeState;
import com.foxinmy.weixin4j.mp.type.TradeType;
import com.foxinmy.weixin4j.util.DateUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
/**
* V3订单信息
@ -54,6 +55,14 @@ public class Order extends ApiResult {
@XStreamAlias("coupon_fee")
@JSONField(name = "coupon_fee")
private Integer couponFee;
// 代金券或立减优惠使用数量
@XStreamAlias("coupon_count")
@JSONField(name = "coupon_count")
private Integer couponCount;
// 代金券信息
@XStreamOmitField
@JSONField(serialize = false)
private List<CouponInfo> couponList;
@XStreamAlias("cash_fee")
@JSONField(name = "cash_fee")
private int cashFee;
@ -75,6 +84,10 @@ public class Order extends ApiResult {
@XStreamAlias("time_end")
@JSONField(name = "time_end")
private String timeEnd;
// 交易状态描述
@XStreamAlias("trade_state_desc")
@JSONField(name = "trade_state_desc")
private String tradeStateDesc;
public TradeState getTradeState() {
return tradeState;
@ -88,6 +101,11 @@ public class Order extends ApiResult {
return isSubscribe;
}
@JSONField(serialize = false, deserialize = false)
public boolean getFormatIsSubscribe() {
return isSubscribe != null && isSubscribe.equalsIgnoreCase("y");
}
public TradeType getTradeType() {
return tradeType;
}
@ -124,6 +142,15 @@ public class Order extends ApiResult {
return couponFee != null ? couponFee / 100d : 0d;
}
public Integer getCouponCount() {
return couponCount;
}
@JSONField(serialize = false, deserialize = false)
public int getFormatCouponCount() {
return couponCount != null ? couponCount.intValue() : 0;
}
public int getCashFee() {
return cashFee;
}
@ -151,7 +178,7 @@ public class Order extends ApiResult {
}
public String getAttach() {
return StringUtils.isBlank(attach) ? null : attach;
return attach;
}
public String getTimeEnd() {
@ -163,18 +190,30 @@ public class Order extends ApiResult {
return DateUtil.parse2yyyyMMddHHmmss(timeEnd);
}
public String getTradeStateDesc() {
return tradeStateDesc;
}
public List<CouponInfo> getCouponList() {
return couponList;
}
public void setCouponList(List<CouponInfo> couponList) {
this.couponList = couponList;
}
@Override
public String toString() {
return "Order [tradeState=" + tradeState + ", openId=" + openId
+ ", isSubscribe=" + isSubscribe + ", tradeType=" + tradeType
+ ", bankType=" + bankType + ", totalFee=" + totalFee
+ ", couponFee=" + couponFee + ", cashFee=" + cashFee
+ ", feeType=" + feeType + ", transactionId=" + transactionId
+ ", outTradeNo=" + outTradeNo + ", attach=" + attach
+ ", timeEnd=" + timeEnd + ", getFormatTotalFee()="
+ getFormatTotalFee() + ", getFormatCouponFee()="
+ getFormatCouponFee() + ", getFormatCashFee()="
+ getFormatCashFee() + ", getFormatTimeEnd()="
+ getFormatTimeEnd() + ", " + super.toString() + "]";
+ ", isSubscribe=" + getFormatIsSubscribe() + ", tradeType="
+ tradeType + ", bankType=" + bankType + ", feeType=" + feeType
+ ", transactionId=" + transactionId + ", outTradeNo="
+ outTradeNo + ", attach=" + attach + ", timeEnd=" + timeEnd
+ ", totalFee=" + getFormatTotalFee() + ", couponFee="
+ getFormatCouponFee() + ", couponCount="
+ getFormatCouponCount() + ", couponList=" + couponList
+ ", cashFee=" + getFormatCashFee() + ", timeEnd="
+ getFormatTimeEnd() + ", tradeStateDesc=" + tradeStateDesc
+ ", " + super.toString() + "]";
}
}

View File

@ -18,12 +18,20 @@ public class PrePay extends ApiResult {
private static final long serialVersionUID = -8430005768959715444L;
@XStreamAlias("trade_type")
private TradeType tradeType;// 交易类型JSAPINATIVEAPP 非空
/**
* 调用接口提交的交易类型取值如下JSAPINATIVEAPP
*/
private TradeType tradeType;
@XStreamAlias("prepay_id")
private String prepayId;// 微信生成的预支付 ID,用于后续接口调用中使用二维码链接 非空
/**
* 微信生成的预支付回话标识用于后续接口调用中使用该值有效期为2小时
*/
private String prepayId;
@XStreamAlias("code_url")
private String codeUrl;// trade_type NATIVE 是有 返回,此参数可直接生成二 维码展示出来进行扫码支付
// 可能为空
/**
* trade_type NATIVE 是有 返回,此参数可直接生成二 维码展示出来进行扫码支付 可能为空
*/
private String codeUrl;
public PrePay() {

View File

@ -1,8 +1,12 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponInfo;
import com.foxinmy.weixin4j.mp.type.CurrencyType;
import com.foxinmy.weixin4j.mp.type.RefundChannel;
import com.foxinmy.weixin4j.mp.type.RefundStatus;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@ -32,12 +36,43 @@ public class RefundDetail extends ApiResult {
@XStreamAlias("refund_fee")
@JSONField(name = "refund_fee")
private int refundFee; // 退款总金额,单位为分,可以做部分退款
@XStreamAlias("refund_fee_type")
@JSONField(name = "refund_fee_type")
private CurrencyType refundFeeType; // 退款货币种类
@XStreamAlias("total_fee")
@JSONField(name = "total_fee")
private int totalFee; // 订单总金额
@XStreamAlias("fee_type")
@JSONField(name = "fee_type")
private CurrencyType feeType; // 订单金额货币种类
@XStreamAlias("cash_fee")
@JSONField(name = "cash_fee")
private int cashFee; // 现金支付金额
@XStreamAlias("cash_fee_type")
@JSONField(name = "cash_fee_type")
private CurrencyType cashFeeType; // 现金支付货币种类
@XStreamAlias("cash_refund_fee")
@JSONField(name = "cash_refund_fee")
private Integer cashRefundFee; // 现金退款金额
@XStreamAlias("cash_refund_fee_type")
@JSONField(name = "cash_refund_fee_type")
private CurrencyType cashRefundFeeType; // 现金退款货币类型
@XStreamAlias("refund_status")
@JSONField(name = "refund_status")
private String refundStatus; // 退款状态
@XStreamAlias("coupon_refund_fee")
@JSONField(name = "coupon_refund_fee")
private int couponRefundFee; // 现金券退款金额<=退款金额,退款金额-现金券退款金额为现金
private Integer couponRefundFee; // 现金券退款金额<=退款金额,退款金额-现金券退款金额为现金
/**
* <font
* color="red">微信支付文档上写的coupon_count,而实际测试拿到的是coupon_refund_count,做个记号
* </font>
*/
@XStreamAlias("coupon_refund_count")
@JSONField(name = "coupon_refund_count")
private Integer couponRefundCount; // 代金券或立减优惠使用数量
@JSONField(serialize = false)
private List<CouponInfo> couponList; // 代金券信息
public String getOutRefundNo() {
return outRefundNo;
@ -63,6 +98,10 @@ public class RefundDetail extends ApiResult {
return refundFee;
}
public CurrencyType getFeeType() {
return feeType;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
@ -85,7 +124,7 @@ public class RefundDetail extends ApiResult {
return null;
}
public int getCouponRefundFee() {
public Integer getCouponRefundFee() {
return couponRefundFee;
}
@ -96,17 +135,92 @@ public class RefundDetail extends ApiResult {
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCouponRefundFee() {
return couponRefundFee / 100d;
return couponRefundFee != null ? couponRefundFee.intValue() / 100d : 0d;
}
public CurrencyType getRefundFeeType() {
return refundFeeType;
}
public int getTotalFee() {
return totalFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatTotalFee() {
return totalFee / 100d;
}
public int getCashFee() {
return cashFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCashFee() {
return cashFee / 100d;
}
public CurrencyType getCashFeeType() {
return cashFeeType;
}
public Integer getCashRefundFee() {
return cashRefundFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCashRefundFee() {
return cashRefundFee != null ? cashRefundFee.intValue() / 100d : 0d;
}
public CurrencyType getCashRefundFeeType() {
return cashRefundFeeType;
}
public Integer getCouponRefundCount() {
return couponRefundCount;
}
@JSONField(deserialize = false, serialize = false)
public int getFormatCouponRefundCount() {
return couponRefundCount != null ? couponRefundCount.intValue() : 0;
}
public List<CouponInfo> getCouponList() {
return couponList;
}
public void setCouponList(List<CouponInfo> couponList) {
this.couponList = couponList;
}
@Override
public String toString() {
return "RefundDetail [outRefundNo=" + outRefundNo + ", refundId="
+ refundId + ", refundChannel=" + refundChannel
+ ", refundFee=" + refundFee + ", refundStatus=" + refundStatus
+ ", couponRefundFee=" + couponRefundFee
+ ", getFormatRefundChannel()=" + getFormatRefundChannel()
+ ", getFormatRefundStatus()=" + getFormatRefundStatus()
+ ", getFormatCouponRefundFee()=" + getFormatCouponRefundFee();
+ ", refundFee=" + getFormatRefundFee() + ", refundFeeType="
+ refundFeeType + ", totalFee=" + getFormatTotalFee()
+ ", feeType=" + feeType + ", cashFee=" + getFormatCashFee()
+ ", cashFeeType=" + cashFeeType + ", cashRefundFee="
+ getFormatCashRefundFee() + ", cashRefundFeeType="
+ cashRefundFeeType + ", refundStatus=" + refundStatus
+ ", couponRefundFee=" + getFormatCouponRefundFee()
+ ", couponCount=" + getCouponRefundCount() + ", couponList="
+ couponList + ", " + super.toString() + "]";
}
}

View File

@ -3,6 +3,7 @@ package com.foxinmy.weixin4j.mp.payment.v3;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.type.CurrencyType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
@ -26,9 +27,24 @@ public class RefundRecord extends ApiResult {
@XStreamAlias("out_trade_no")
@JSONField(name = "out_trade_no")
private String outTradeNo;// 商户订单号
@XStreamAlias("total_fee")
@JSONField(name = "total_fee")
private int totalFee; // 订单总金额
@XStreamAlias("fee_type")
@JSONField(name = "fee_type")
private CurrencyType feeType; // 订单金额货币种类
@XStreamAlias("cash_fee")
@JSONField(name = "cash_fee")
private int cashFee;
private int cashFee; // 现金支付金额
@XStreamAlias("cash_fee_type")
@JSONField(name = "cash_fee_type")
private CurrencyType cashFeeType; // 现金支付金额货币种类
@XStreamAlias("refund_fee")
@JSONField(name = "refund_fee")
private int refundFee; // 退款总金额
@XStreamAlias("coupon_refund_fee")
@JSONField(name = "coupon_refund_fee")
private Integer couponRefundFee; // 代金券或立减优惠退款金额=订单金额-现金退款金额注意满立减金额不会退回
@XStreamAlias("refund_count")
@JSONField(name = "refund_count")
private int count;// 退款笔数
@ -58,6 +74,42 @@ public class RefundRecord extends ApiResult {
return cashFee;
}
public CurrencyType getFeeType() {
return feeType;
}
public CurrencyType getCashFeeType() {
return cashFeeType;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatCouponRefundFee() {
return couponRefundFee != null ? couponRefundFee.intValue() / 100d : 0d;
}
public Integer getCouponRefundFee() {
return couponRefundFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatTotalFee() {
return totalFee / 100d;
}
public int getTotalFee() {
return totalFee;
}
public int getCount() {
return count;
}
@ -70,10 +122,28 @@ public class RefundRecord extends ApiResult {
this.details = details;
}
public int getRefundFee() {
return refundFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatRefundFee() {
return refundFee / 100d;
}
@Override
public String toString() {
return "RefundRecord [transactionId=" + transactionId + ", outTradeNo="
+ outTradeNo + ", cashFee=" + cashFee + ", count=" + count
+ outTradeNo + ", totalFee=" + getFormatTotalFee()
+ ", feeType=" + feeType + ", cashFee=" + getFormatCashFee()
+ ", cashFeeType=" + cashFeeType + ", refundFee="
+ getFormatRefundFee() + ", couponRefundFee="
+ getFormatCouponRefundFee() + ", count=" + count
+ ", details=" + details + ", " + super.toString() + "]";
}
}

View File

@ -25,151 +25,160 @@ import com.foxinmy.weixin4j.token.WeixinTokenCreator;
import com.foxinmy.weixin4j.type.AccountType;
public class PayTest {
private final static WeixinPayProxy PAY2;
private final static WeixinPayProxy PAY3;
private final static WeixinMpAccount ACCOUNT2;
private final static WeixinMpAccount ACCOUNT3;
static {
ACCOUNT2 = new WeixinMpAccount(
"wxba294f2c6f330361",
"8e33f5371a1afea1f7bce88088cb4bba",
"gADrKITv3qYWu9JEg1NS0WPaU5yFgTwS9WfPueskfPpt3OZGpnUN1uBom36G2tP701vi2pPRJLZF9dEDFj9pqxidPn10Y91Lj8kK37Svz6S4MfeAHo9svFZmHkIKScGb",
"1221928801", "8d1b26231827a965ef54fe6a3a151551");
PAY2 = new WeixinPayProxy(ACCOUNT2, new FileTokenHolder(
new WeixinTokenCreator(ACCOUNT2.getId(), ACCOUNT2.getSecret(),
AccountType.MP)));
ACCOUNT3 = new WeixinMpAccount("wx0d1d598c0c03c999",
"2513ac683f1beabdb6b98d9ddd9e5755",
"GATFzDwbQdbbci3QEQxX2rUBvwTrsMiZ", "10020674");
PAY3 = new WeixinPayProxy(ACCOUNT3, new FileTokenHolder(
new WeixinTokenCreator(ACCOUNT3.getId(), ACCOUNT3.getSecret(),
AccountType.MP)));
}
private final static WeixinPayProxy PAY2;
private final static WeixinPayProxy PAY3;
private final static WeixinMpAccount ACCOUNT2;
private final static WeixinMpAccount ACCOUNT3;
static {
ACCOUNT2 = new WeixinMpAccount(
"请填入v2版本的appid",
"请填入v2版本的appsecret",
"请填入v2版本的paysignkey",
"请填入v2版本的partnerId", "请填入v2版本的partnerKey");
PAY2 = new WeixinPayProxy(ACCOUNT2, new FileTokenHolder(
new WeixinTokenCreator(ACCOUNT2.getId(), ACCOUNT2.getSecret(),
AccountType.MP)));
ACCOUNT3 = new WeixinMpAccount("请填入v3版本的appid",
"请填入v3版本的appSecret",
"请填入v3版本的paysignkey", "请填入v3版本的mchid");
PAY3 = new WeixinPayProxy(ACCOUNT3, new FileTokenHolder(
new WeixinTokenCreator(ACCOUNT3.getId(), ACCOUNT3.getSecret(),
AccountType.MP)));
}
@Test
public void orderQueryV2() throws WeixinException {
System.err.println(PAY2.orderQueryV2("D14110500021"));
}
@Test
public void orderQueryV2() throws WeixinException {
System.err.println(PAY2.orderQueryV2("D14110500021"));
}
@Test
public void refundV2() throws WeixinException {
File caFile = new File(
"/Users/jy/workspace/feican/canyi-weixin-parent/canyi-weixin-service/src/main/resources/1221928801.pfx");
IdQuery idQuery = new IdQuery("D15020300005", IdType.TRADENO);
System.err.println(PAY2.refundV2(caFile, idQuery, "1422925555037", 16d, 16d,
"1221928801", "111111", null, null, null));
}
@Test
public void refundV2() throws WeixinException {
File caFile = new File(
"签名文件如12333.pfx");
IdQuery idQuery = new IdQuery("D15020300005", IdType.TRADENO);
System.err.println(PAY2.refundV2(caFile, idQuery, "1422925555037", 16d,
16d, "1221928801", "111111", null, null, null));
}
@Test
public void refundQueryV2() throws WeixinException {
System.err.println(PAY2.refundQueryV2(new IdQuery("D14123000004",
IdType.TRADENO)));
refundQueryV3();
}
@Test
public void refundQueryV2() throws WeixinException {
System.err.println(PAY2.refundQueryV2(new IdQuery("D14123000004",
IdType.TRADENO)));
refundQueryV3();
}
@Test
public void downbillV2() throws WeixinException {
Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, 2014);
c.set(Calendar.MONTH, 11);
c.set(Calendar.DAY_OF_MONTH, 22);
File file = PAY2.downloadbill(c.getTime(), null);
System.err.println(file);
}
@Test
public void downbillV2() throws WeixinException {
Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, 2014);
c.set(Calendar.MONTH, 11);
c.set(Calendar.DAY_OF_MONTH, 22);
File file = PAY2.downloadbill(c.getTime(), null);
System.err.println(file);
}
@Test
public void orderQueryV3() throws WeixinException {
Order order = PAY3.orderQueryV3(new IdQuery("T0002", IdType.TRADENO));
System.err.println(order);
String sign = order.getSign();
order.setSign(null);
String valiSign = PayUtil.paysignMd5(order, ACCOUNT3.getPaySignKey());
System.err
.println(String.format("sign=%s,valiSign=%s", sign, valiSign));
Assert.assertEquals(valiSign, sign);
}
@Test
public void orderQueryV3() throws WeixinException {
Order order = PAY3.orderQueryV3(new IdQuery("T0002", IdType.TRADENO));
System.err.println(order);
String sign = order.getSign();
order.setSign(null);
String valiSign = PayUtil.paysignMd5(order, ACCOUNT3.getPaySignKey());
System.err
.println(String.format("sign=%s,valiSign=%s", sign, valiSign));
Assert.assertEquals(valiSign, sign);
}
@Test
public void refundQueryV3() throws WeixinException {
System.err.println(PAY3.refundQueryV3(new IdQuery("T00015",
IdType.TRADENO)));
}
@Test
public void refundQueryV3() throws WeixinException {
com.foxinmy.weixin4j.mp.payment.v3.RefundRecord record = PAY3.refundQueryV3(new IdQuery("TT_1427183696238",
IdType.TRADENO));
System.err.println(record);
// 这里的验证签名需要把details循环拼接
String sign = record.getSign();
record.setSign(null);
String valiSign = PayUtil.paysignMd5(record, ACCOUNT3.getPaySignKey());
System.err
.println(String.format("sign=%s,valiSign=%s", sign, valiSign));
Assert.assertEquals(valiSign, sign);
}
@Test
public void downbillV3() throws WeixinException {
Calendar c = Calendar.getInstance();
System.err.println(c.getTime());
c.set(Calendar.YEAR, 2014);
c.set(Calendar.MONTH, 9);
c.set(Calendar.DAY_OF_MONTH, 22);
System.err.println(c.getTime());
File file = PAY3.downloadbill(c.getTime(), null);
System.err.println(file);
}
@Test
public void downbillV3() throws WeixinException {
Calendar c = Calendar.getInstance();
System.err.println(c.getTime());
c.set(Calendar.YEAR, 2015);
c.set(Calendar.MONTH, 2);
c.set(Calendar.DAY_OF_MONTH, 24);
System.err.println(c.getTime());
File file = PAY3.downloadbill(c.getTime(), null);
System.err.println(file);
}
@Test
public void refundV3() throws WeixinException {
File caFile = new File(
"/Users/jy/workspace/feican/canyi-weixin-parent/canyi-weixin-service/src/main/resources/10020674.p12");
IdQuery idQuery = new IdQuery("T00015", IdType.TRADENO);
com.foxinmy.weixin4j.mp.payment.v3.RefundResult result = PAY3.refundV3(
caFile, idQuery, "R0002", 1d, 1d, "10020674");
System.err.println(result);
String sign = result.getSign();
result.setSign(null);
String valiSign = PayUtil.paysignMd5(result, ACCOUNT3.getPaySignKey());
System.err
.println(String.format("sign=%s,valiSign=%s", sign, valiSign));
Assert.assertEquals(valiSign, sign);
}
@Test
public void refundV3() throws WeixinException {
File caFile = new File(
"签名文件如123.p12");
IdQuery idQuery = new IdQuery("TT_1427183696238", IdType.TRADENO);
com.foxinmy.weixin4j.mp.payment.v3.RefundResult result = PAY3.refundV3(
caFile, idQuery, "TT_R" + System.currentTimeMillis(), 0.01d, 0.01d,
null, "10020674");
System.err.println(result);
String sign = result.getSign();
result.setSign(null);
String valiSign = PayUtil.paysignMd5(result, ACCOUNT3.getPaySignKey());
System.err
.println(String.format("sign=%s,valiSign=%s", sign, valiSign));
Assert.assertEquals(valiSign, sign);
}
@Test
public void nativeV3() throws WeixinException {
PayPackageV3 payPackageV3 = new PayPackageV3(ACCOUNT3,
"oyFLst1bqtuTcxK-ojF8hOGtLQao", "native测试", "T0001", 0.1d,
"127.0.0.1", TradeType.NATIVE);
payPackageV3.setProductId("0001");
payPackageV3.setNotifyUrl("xxxx");
PrePay prePay = null;
try {
prePay = PayUtil.createPrePay(payPackageV3,
ACCOUNT3.getPaySignKey());
} catch (PayException e) {
e.printStackTrace();
}
System.err.println(prePay);
}
@Test
public void nativeV3() throws WeixinException {
PayPackageV3 payPackageV3 = new PayPackageV3(ACCOUNT3,
"oyFLst1bqtuTcxK-ojF8hOGtLQao", "native测试", "T0001", 0.1d,
"127.0.0.1", TradeType.NATIVE);
payPackageV3.setProductId("0001");
payPackageV3.setNotifyUrl("xxxx");
PrePay prePay = null;
try {
prePay = PayUtil.createPrePay(payPackageV3,
ACCOUNT3.getPaySignKey());
} catch (PayException e) {
e.printStackTrace();
}
System.err.println(prePay);
}
@Test
public void closeOrder() throws WeixinException {
ApiResult result = PAY3.closeOrder("D111");
System.err.println(result);
String sign = result.getSign();
result.setSign(null);
String valiSign = PayUtil.paysignMd5(result, ACCOUNT3.getPaySignKey());
System.err
.println(String.format("sign=%s,valiSign=%s", sign, valiSign));
Assert.assertEquals(valiSign, sign);
}
@Test
public void closeOrder() throws WeixinException {
ApiResult result = PAY3.closeOrder("D111");
System.err.println(result);
String sign = result.getSign();
result.setSign(null);
String valiSign = PayUtil.paysignMd5(result, ACCOUNT3.getPaySignKey());
System.err
.println(String.format("sign=%s,valiSign=%s", sign, valiSign));
Assert.assertEquals(valiSign, sign);
}
@Test
public void shortUrl() throws WeixinException {
String url = "weixin://wxpay/bizpayurl?xxxxxx";
String shortUrl = PAY3.getPayShorturl(url);
System.err.println(shortUrl);
}
@Test
public void shortUrl() throws WeixinException {
String url = "weixin://wxpay/bizpayurl?xxxxxx";
String shortUrl = PAY3.getPayShorturl(url);
System.err.println(shortUrl);
}
@Test
public void interfaceReport() throws WeixinException {
String interfaceUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
int executeTime = 2500;
String outTradeNo = null;
String ip = "127.0.0.1";
Date time = new Date();
XmlResult returnXml = new XmlResult("SUCCESS", "");
returnXml.setResultCode("SUCCESS");
returnXml = PAY3.interfaceReport(interfaceUrl, executeTime, outTradeNo,
ip, time, returnXml);
System.err.println(returnXml);
}
@Test
public void interfaceReport() throws WeixinException {
String interfaceUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
int executeTime = 2500;
String outTradeNo = null;
String ip = "127.0.0.1";
Date time = new Date();
XmlResult returnXml = new XmlResult("SUCCESS", "");
returnXml.setResultCode("SUCCESS");
returnXml = PAY3.interfaceReport(interfaceUrl, executeTime, outTradeNo,
ip, time, returnXml);
System.err.println(returnXml);
}
}

View File

@ -67,4 +67,8 @@ weixin4j-mp-server
* 2014-11-23
+ `WeixinServerBootstrap`重命名为`WeixinMpServerBootstrap`
+ `WeixinServerBootstrap`重命名为`WeixinMpServerBootstrap`
* 2015-03-25
+ 新增客服创建、关闭、转接会话事件

View File

@ -5,6 +5,8 @@ import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -16,6 +18,7 @@ import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.payment.JsPayNotify;
import com.foxinmy.weixin4j.mp.payment.PayPackage;
import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.mp.payment.conver.CouponConverter;
import com.foxinmy.weixin4j.mp.payment.v2.NativePayNotifyV2;
import com.foxinmy.weixin4j.mp.payment.v2.NativePayResponseV2;
import com.foxinmy.weixin4j.mp.payment.v2.PayFeedback;
@ -149,16 +152,22 @@ public class PayAction {
* &ltreturn_code&gtSUCCESS/FAIL&lt/return_code&gt<br>
* &ltreturn_msg&gt如非空,为错误 原因签名失败参数格式校验错误&lt/return_msg&gt<br>
* &lt/xml&gt
* @throws DocumentException
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7">支付结果通知</a>
*/
public String jsNotifyV3(InputStream inputStream) {
com.foxinmy.weixin4j.mp.payment.v3.Order order = XmlStream.get(
inputStream, com.foxinmy.weixin4j.mp.payment.v3.Order.class);
public String jsNotifyV3(InputStream inputStream) throws DocumentException {
SAXReader sax = new SAXReader();
String orderXml = sax.read(inputStream).asXML();
com.foxinmy.weixin4j.mp.payment.v3.Order order = CouponConverter
.fromXML(orderXml, com.foxinmy.weixin4j.mp.payment.v3.Order.class);
log.info("jsapi_notify_order_info:", order);
String sign = order.getSign();
order.setSign(null);
WeixinMpAccount weixinAccount = ConfigUtil.getWeixinMpAccount();
String valid_sign = PayUtil.paysignMd5(order,
weixinAccount.getPaySignKey());
// 如果订单中存在代金券的情况并不适用
log.info("微信签名----->sign={},vaild_sign={}", sign, valid_sign);
if (!sign.equals(valid_sign)) {
return XmlStream.to(new XmlResult(Consts.FAIL, "签名错误"));

View File

@ -0,0 +1,21 @@
package com.foxinmy.weixin4j.mp.action.event;
import com.foxinmy.weixin4j.action.DebugAction;
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
import com.foxinmy.weixin4j.msg.event.KfCloseEventMessage;
import com.foxinmy.weixin4j.type.EventType;
import com.foxinmy.weixin4j.type.MessageType;
/**
* 客服关闭会话消息
*
* @className KfCloseAction
* @author jy
* @date 2015年3月25日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.msg.event.KfCloseEventMessage
*/
@ActionAnnotation(msgType = MessageType.event, eventType = { EventType.kf_close_session })
public class KfCloseAction extends DebugAction<KfCloseEventMessage> {
}

View File

@ -0,0 +1,21 @@
package com.foxinmy.weixin4j.mp.action.event;
import com.foxinmy.weixin4j.action.DebugAction;
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
import com.foxinmy.weixin4j.msg.event.KfCreateEventMessage;
import com.foxinmy.weixin4j.type.EventType;
import com.foxinmy.weixin4j.type.MessageType;
/**
* 客服接入会话消息
*
* @className KfCreateAction
* @author jy
* @date 2015年3月25日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.msg.event.KfCreateEventMessage
*/
@ActionAnnotation(msgType = MessageType.event, eventType = { EventType.kf_create_session })
public class KfCreateAction extends DebugAction<KfCreateEventMessage> {
}

View File

@ -0,0 +1,21 @@
package com.foxinmy.weixin4j.mp.action.event;
import com.foxinmy.weixin4j.action.DebugAction;
import com.foxinmy.weixin4j.action.mapping.ActionAnnotation;
import com.foxinmy.weixin4j.msg.event.KfSwitchEventMessage;
import com.foxinmy.weixin4j.type.EventType;
import com.foxinmy.weixin4j.type.MessageType;
/**
* 客服转接会话消息
*
* @className KfSwitchAction
* @author jy
* @date 2015年3月25日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.msg.event.KfSwitchEventMessage
*/
@ActionAnnotation(msgType = MessageType.event, eventType = { EventType.kf_switch_session })
public class KfSwitchAction extends DebugAction<KfSwitchEventMessage> {
}