| @@ -0,0 +1,311 @@ | |||
| package com.github.binarywang.wxpay.bean.coupon; | |||
| import com.github.binarywang.wxpay.bean.request.WxPayBaseRequest; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import me.chanjar.weixin.common.annotation.Required; | |||
| /** | |||
| * <pre> | |||
| * 发送代金券请求对象类 | |||
| * Created by Binary Wang on 2017-7-15. | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| @XStreamAlias("xml") | |||
| public class WxPayCouponSendRequest extends WxPayBaseRequest { | |||
| /** | |||
| * <pre> | |||
| * 字段名:代金券批次id | |||
| * 变量名:coupon_stock_id | |||
| * 是否必填:是 | |||
| * 示例值:1757 | |||
| * 类型:String | |||
| * 说明:代金券批次id | |||
| * </pre> | |||
| */ | |||
| @Required | |||
| @XStreamAlias("coupon_stock_id") | |||
| private String couponStockId; | |||
| /** | |||
| * <pre> | |||
| * 字段名:openid记录数 | |||
| * 变量名:openid_count | |||
| * 是否必填:是 | |||
| * 示例值:1 | |||
| * 类型:int | |||
| * 说明:openid记录数(目前支持num=1) | |||
| * </pre> | |||
| */ | |||
| @Required | |||
| @XStreamAlias("openid_count") | |||
| private Integer openidCount; | |||
| /** | |||
| * <pre> | |||
| * 字段名:商户单据号 | |||
| * 变量名:partner_trade_no | |||
| * 是否必填:是 | |||
| * 示例值:1000009820141203515766 | |||
| * 类型:String | |||
| * 说明:商户此次发放凭据号(格式:商户id+日期+流水号),商户侧需保持唯一性 | |||
| * </pre> | |||
| */ | |||
| @Required | |||
| @XStreamAlias("partner_trade_no") | |||
| private String partnerTradeNo; | |||
| /** | |||
| * <pre> | |||
| * 字段名:用户openid | |||
| * 变量名:openid | |||
| * 是否必填:是 | |||
| * 示例值:onqOjjrXT-776SpHnfexGm1_P7iE | |||
| * 类型:String | |||
| * 说明:Openid信息,用户在appid下的openid。 | |||
| * </pre> | |||
| */ | |||
| @Required | |||
| @XStreamAlias("openid") | |||
| private String openid; | |||
| /** | |||
| * <pre> | |||
| * 字段名:操作员 | |||
| * 变量名:op_user_id | |||
| * 是否必填:否 | |||
| * 示例值:10000098 | |||
| * 类型:String(32) | |||
| * 说明:操作员帐号, 默认为商户号,可在商户平台配置操作员对应的api权限 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("op_user_id") | |||
| private String opUserId; | |||
| /** | |||
| * <pre> | |||
| * 字段名:设备号 | |||
| * 变量名:device_info | |||
| * 是否必填:否 | |||
| * 示例值: | |||
| * 类型:String(32) | |||
| * 说明:微信支付分配的终端设备号 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("device_info") | |||
| private String deviceInfo; | |||
| /** | |||
| * <pre> | |||
| * 字段名:协议版本 | |||
| * 变量名:version | |||
| * 是否必填:否 | |||
| * 示例值:1.0 | |||
| * 类型:String(32) | |||
| * 说明:默认1.0 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("version") | |||
| private String version; | |||
| /** | |||
| * <pre> | |||
| * 字段名:协议类型 | |||
| * 变量名:type | |||
| * 是否必填:否 | |||
| * 示例值:XML | |||
| * 类型:String(32) | |||
| * 说明:XML【目前仅支持默认XML】 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("type") | |||
| private String type; | |||
| public WxPayCouponSendRequest() { | |||
| } | |||
| private WxPayCouponSendRequest(Builder builder) { | |||
| setAppid(builder.appid); | |||
| setMchId(builder.mchId); | |||
| setSubAppId(builder.subAppId); | |||
| setSubMchId(builder.subMchId); | |||
| setNonceStr(builder.nonceStr); | |||
| setSign(builder.sign); | |||
| setCouponStockId(builder.couponStockId); | |||
| setOpenidCount(builder.openidCount); | |||
| setPartnerTradeNo(builder.partnerTradeNo); | |||
| setOpenid(builder.openid); | |||
| setOpUserId(builder.opUserId); | |||
| setDeviceInfo(builder.deviceInfo); | |||
| setVersion(builder.version); | |||
| setType(builder.type); | |||
| } | |||
| public static Builder newBuilder() { | |||
| return new Builder(); | |||
| } | |||
| public String getCouponStockId() { | |||
| return this.couponStockId; | |||
| } | |||
| public void setCouponStockId(String couponStockId) { | |||
| this.couponStockId = couponStockId; | |||
| } | |||
| public Integer getOpenidCount() { | |||
| return this.openidCount; | |||
| } | |||
| public void setOpenidCount(Integer openidCount) { | |||
| this.openidCount = openidCount; | |||
| } | |||
| public String getPartnerTradeNo() { | |||
| return this.partnerTradeNo; | |||
| } | |||
| public void setPartnerTradeNo(String partnerTradeNo) { | |||
| this.partnerTradeNo = partnerTradeNo; | |||
| } | |||
| public String getOpenid() { | |||
| return this.openid; | |||
| } | |||
| public void setOpenid(String openid) { | |||
| this.openid = openid; | |||
| } | |||
| public String getOpUserId() { | |||
| return this.opUserId; | |||
| } | |||
| public void setOpUserId(String opUserId) { | |||
| this.opUserId = opUserId; | |||
| } | |||
| public String getDeviceInfo() { | |||
| return this.deviceInfo; | |||
| } | |||
| public void setDeviceInfo(String deviceInfo) { | |||
| this.deviceInfo = deviceInfo; | |||
| } | |||
| public String getVersion() { | |||
| return this.version; | |||
| } | |||
| public void setVersion(String version) { | |||
| this.version = version; | |||
| } | |||
| public String getType() { | |||
| return this.type; | |||
| } | |||
| public void setType(String type) { | |||
| this.type = type; | |||
| } | |||
| @Override | |||
| protected void checkConstraints() { | |||
| //do nothing | |||
| } | |||
| public static final class Builder { | |||
| private String appid; | |||
| private String mchId; | |||
| private String subAppId; | |||
| private String subMchId; | |||
| private String nonceStr; | |||
| private String sign; | |||
| private String couponStockId; | |||
| private Integer openidCount; | |||
| private String partnerTradeNo; | |||
| private String openid; | |||
| private String opUserId; | |||
| private String deviceInfo; | |||
| private String version; | |||
| private String type; | |||
| private Builder() { | |||
| } | |||
| public Builder appid(String appid) { | |||
| this.appid = appid; | |||
| return this; | |||
| } | |||
| public Builder mchId(String mchId) { | |||
| this.mchId = mchId; | |||
| return this; | |||
| } | |||
| public Builder subAppId(String subAppId) { | |||
| this.subAppId = subAppId; | |||
| return this; | |||
| } | |||
| public Builder subMchId(String subMchId) { | |||
| this.subMchId = subMchId; | |||
| return this; | |||
| } | |||
| public Builder nonceStr(String nonceStr) { | |||
| this.nonceStr = nonceStr; | |||
| return this; | |||
| } | |||
| public Builder sign(String sign) { | |||
| this.sign = sign; | |||
| return this; | |||
| } | |||
| public Builder couponStockId(String couponStockId) { | |||
| this.couponStockId = couponStockId; | |||
| return this; | |||
| } | |||
| public Builder openidCount(Integer openidCount) { | |||
| this.openidCount = openidCount; | |||
| return this; | |||
| } | |||
| public Builder partnerTradeNo(String partnerTradeNo) { | |||
| this.partnerTradeNo = partnerTradeNo; | |||
| return this; | |||
| } | |||
| public Builder openid(String openid) { | |||
| this.openid = openid; | |||
| return this; | |||
| } | |||
| public Builder opUserId(String opUserId) { | |||
| this.opUserId = opUserId; | |||
| return this; | |||
| } | |||
| public Builder deviceInfo(String deviceInfo) { | |||
| this.deviceInfo = deviceInfo; | |||
| return this; | |||
| } | |||
| public Builder version(String version) { | |||
| this.version = version; | |||
| return this; | |||
| } | |||
| public Builder type(String type) { | |||
| this.type = type; | |||
| return this; | |||
| } | |||
| public WxPayCouponSendRequest build() { | |||
| return new WxPayCouponSendRequest(this); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,204 @@ | |||
| package com.github.binarywang.wxpay.bean.coupon; | |||
| import com.github.binarywang.wxpay.bean.result.WxPayBaseResult; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| /** | |||
| * <pre> | |||
| * 发送代金券响应结果类 | |||
| * Created by Binary Wang on 2017-7-15. | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| @XStreamAlias("xml") | |||
| public class WxPayCouponSendResult extends WxPayBaseResult { | |||
| /** | |||
| * <pre> | |||
| * 字段名:设备号 | |||
| * 变量名:device_info | |||
| * 是否必填:否 | |||
| * 示例值:123456sb | |||
| * 类型:String(32) | |||
| * 描述:微信支付分配的终端设备号, | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("device_info") | |||
| private String deviceInfo; | |||
| /** | |||
| * <pre> | |||
| * 字段名:代金券批次id | |||
| * 变量名:coupon_stock_id | |||
| * 是否必填:是 | |||
| * 示例值:1757 | |||
| * 类型:String | |||
| * 描述:用户在商户appid下的唯一标识 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("coupon_stock_id") | |||
| private String couponStockId; | |||
| /** | |||
| * <pre> | |||
| * 字段名:返回记录数 | |||
| * 变量名:resp_count | |||
| * 是否必填:是 | |||
| * 示例值:1 | |||
| * 类型:Int | |||
| * 描述:返回记录数 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("resp_count") | |||
| private Integer respCount; | |||
| /** | |||
| * <pre> | |||
| * 字段名:成功记录数 | |||
| * 变量名:success_count | |||
| * 是否必填:是 | |||
| * 示例值:1或者0 | |||
| * 类型:Int | |||
| * 描述:成功记录数 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("success_count") | |||
| private Integer successCount; | |||
| /** | |||
| * <pre> | |||
| * 字段名:失败记录数 | |||
| * 变量名:failed_count | |||
| * 是否必填:是 | |||
| * 示例值:1或者0 | |||
| * 类型:Int | |||
| * 描述:失败记录数 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("failed_count") | |||
| private Integer failedCount; | |||
| /** | |||
| * <pre> | |||
| * 字段名:用户标识 | |||
| * 变量名:openid | |||
| * 是否必填:是 | |||
| * 示例值:onqOjjrXT-776SpHnfexGm1_P7iE | |||
| * 类型:String | |||
| * 描述:用户在商户appid下的唯一标识 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("openid") | |||
| private String openid; | |||
| /** | |||
| * <pre> | |||
| * 字段名:返回码 | |||
| * 变量名:ret_code | |||
| * 是否必填:是 | |||
| * 示例值:SUCCESS或者FAILED | |||
| * 类型:String | |||
| * 描述:返回码,SUCCESS/FAILED | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("ret_code") | |||
| private String retCode; | |||
| /** | |||
| * <pre> | |||
| * 字段名:代金券id | |||
| * 变量名:coupon_id | |||
| * 是否必填:是 | |||
| * 示例值:1870 | |||
| * 类型:String | |||
| * 描述:对一个用户成功发放代金券则返回代金券id,即ret_code为SUCCESS的时候;如果ret_code为FAILED则填写空串"" | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("coupon_id") | |||
| private String couponId; | |||
| /** | |||
| * <pre> | |||
| * 字段名:返回信息 | |||
| * 变量名:ret_msg | |||
| * 是否必填:是 | |||
| * 示例值:失败描述信息,例如:“用户已达领用上限” | |||
| * 类型:String | |||
| * 描述:返回信息,当返回码是FAILED的时候填写,否则填空串“” | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("ret_msg") | |||
| private String retMsg; | |||
| public String getDeviceInfo() { | |||
| return this.deviceInfo; | |||
| } | |||
| public void setDeviceInfo(String deviceInfo) { | |||
| this.deviceInfo = deviceInfo; | |||
| } | |||
| public String getCouponStockId() { | |||
| return this.couponStockId; | |||
| } | |||
| public void setCouponStockId(String couponStockId) { | |||
| this.couponStockId = couponStockId; | |||
| } | |||
| public Integer getRespCount() { | |||
| return this.respCount; | |||
| } | |||
| public void setRespCount(Integer respCount) { | |||
| this.respCount = respCount; | |||
| } | |||
| public Integer getSuccessCount() { | |||
| return this.successCount; | |||
| } | |||
| public void setSuccessCount(Integer successCount) { | |||
| this.successCount = successCount; | |||
| } | |||
| public Integer getFailedCount() { | |||
| return this.failedCount; | |||
| } | |||
| public void setFailedCount(Integer failedCount) { | |||
| this.failedCount = failedCount; | |||
| } | |||
| public String getOpenid() { | |||
| return this.openid; | |||
| } | |||
| public void setOpenid(String openid) { | |||
| this.openid = openid; | |||
| } | |||
| public String getRetCode() { | |||
| return this.retCode; | |||
| } | |||
| public void setRetCode(String retCode) { | |||
| this.retCode = retCode; | |||
| } | |||
| public String getCouponId() { | |||
| return this.couponId; | |||
| } | |||
| public void setCouponId(String couponId) { | |||
| this.couponId = couponId; | |||
| } | |||
| public String getRetMsg() { | |||
| return this.retMsg; | |||
| } | |||
| public void setRetMsg(String retMsg) { | |||
| this.retMsg = retMsg; | |||
| } | |||
| } | |||
| @@ -17,13 +17,6 @@ import java.math.BigDecimal; | |||
| * <pre> | |||
| * Created by Binary Wang on 2016-10-24. | |||
| * 微信支付请求对象共用的参数存放类 | |||
| * 注释中各行每个字段描述对应如下: | |||
| * <li>字段名 | |||
| * <li>变量名 | |||
| * <li>是否必填 | |||
| * <li>类型 | |||
| * <li>示例值 | |||
| * <li>描述 | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> | |||
| @@ -31,72 +24,72 @@ import java.math.BigDecimal; | |||
| public abstract class WxPayBaseRequest { | |||
| /** | |||
| * <pre> | |||
| * 公众账号ID | |||
| * appid | |||
| * 是 | |||
| * String(32) | |||
| * wxd678efh567hg6787 | |||
| * 微信分配的公众账号ID(企业号corpid即为此appId) | |||
| * 字段名:公众账号ID | |||
| * 变量名:appid | |||
| * 是否必填:是 | |||
| * 类型:String(32) | |||
| * 示例值:wxd678efh567hg6787 | |||
| * 描述:微信分配的公众账号ID(企业号corpid即为此appId) | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("appid") | |||
| protected String appid; | |||
| /** | |||
| * <pre> | |||
| * 商户号 | |||
| * mch_id | |||
| * 是 | |||
| * String(32) | |||
| * 1230000109 | |||
| * 微信支付分配的商户号 | |||
| * 字段名:商户号 | |||
| * 变量名:mch_id | |||
| * 是否必填:是 | |||
| * 类型:String(32) | |||
| * 示例值:1230000109 | |||
| * 描述:微信支付分配的商户号 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("mch_id") | |||
| protected String mchId; | |||
| /** | |||
| * <pre> | |||
| * 服务商模式下的子商户公众账号ID | |||
| * sub_appid | |||
| * 是 | |||
| * String(32) | |||
| * wxd678efh567hg6787 | |||
| * 微信分配的子商户公众账号ID | |||
| * 字段名:服务商模式下的子商户公众账号ID | |||
| * 变量名:sub_appid | |||
| * 是否必填:是 | |||
| * 类型:String(32) | |||
| * 示例值:wxd678efh567hg6787 | |||
| * 描述:微信分配的子商户公众账号ID | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("sub_appid") | |||
| protected String subAppId; | |||
| /** | |||
| * <pre> | |||
| * 服务商模式下的子商户号 | |||
| * sub_mch_id | |||
| * 是 | |||
| * String(32) | |||
| * 1230000109 | |||
| * 微信支付分配的子商户号,开发者模式下必填 | |||
| * 字段名:服务商模式下的子商户号 | |||
| * 变量名:sub_mch_id | |||
| * 是否必填:是 | |||
| * 类型:String(32) | |||
| * 示例值:1230000109 | |||
| * 描述:微信支付分配的子商户号,开发者模式下必填 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("sub_mch_id") | |||
| protected String subMchId; | |||
| /** | |||
| * <pre> | |||
| * 随机字符串 | |||
| * nonce_str | |||
| * 是 | |||
| * String(32) | |||
| * 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | |||
| * 随机字符串,不长于32位。推荐随机数生成算法 | |||
| * 字段名:随机字符串 | |||
| * 变量名:nonce_str | |||
| * 是否必填:是 | |||
| * 类型:String(32) | |||
| * 示例值:5K8264ILTKCH16CQ2502SI8ZNMTM67VS | |||
| * 描述:随机字符串,不长于32位。推荐随机数生成算法 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("nonce_str") | |||
| protected String nonceStr; | |||
| /** | |||
| * <pre> | |||
| * 签名 | |||
| * sign | |||
| * 是 | |||
| * String(32) | |||
| * C380BEC2BFD727A4B6845133519F3AD6 | |||
| * 签名,详见签名生成算法 | |||
| * 字段名:签名 | |||
| * 变量名:sign | |||
| * 是否必填:是 | |||
| * 类型:String(32) | |||
| * 示例值:C380BEC2BFD727A4B6845133519F3AD6 | |||
| * 描述:签名,详见签名生成算法 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("sign") | |||
| @@ -1,5 +1,7 @@ | |||
| package com.github.binarywang.wxpay.service; | |||
| import com.github.binarywang.wxpay.bean.coupon.WxPayCouponSendRequest; | |||
| import com.github.binarywang.wxpay.bean.coupon.WxPayCouponSendResult; | |||
| import com.github.binarywang.wxpay.bean.request.*; | |||
| import com.github.binarywang.wxpay.bean.result.*; | |||
| import com.github.binarywang.wxpay.config.WxPayConfig; | |||
| @@ -339,4 +341,14 @@ public interface WxPayService { | |||
| * </pre> | |||
| */ | |||
| String getSandboxSignKey() throws WxPayException; | |||
| /** | |||
| * <pre> | |||
| * 发放代金券 | |||
| * 接口请求链接:https://api.mch.weixin.qq.com/mmpaymkttransfers/send_coupon | |||
| * 是否需要证书:请求需要双向证书。 | |||
| * 文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_3 | |||
| * </pre> | |||
| */ | |||
| WxPayCouponSendResult sendCoupon(WxPayCouponSendRequest request) throws WxPayException; | |||
| } | |||
| @@ -1,6 +1,8 @@ | |||
| package com.github.binarywang.wxpay.service.impl; | |||
| import com.github.binarywang.utils.qrcode.QrcodeUtils; | |||
| import com.github.binarywang.wxpay.bean.coupon.WxPayCouponSendRequest; | |||
| import com.github.binarywang.wxpay.bean.coupon.WxPayCouponSendResult; | |||
| import com.github.binarywang.wxpay.bean.request.*; | |||
| import com.github.binarywang.wxpay.bean.result.*; | |||
| import com.github.binarywang.wxpay.config.WxPayConfig; | |||
| @@ -463,4 +465,15 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService { | |||
| result.checkResult(this); | |||
| return result.getSandboxSignKey(); | |||
| } | |||
| @Override | |||
| public WxPayCouponSendResult sendCoupon(WxPayCouponSendRequest request) throws WxPayException { | |||
| request.checkAndSign(this.getConfig()); | |||
| String url = this.getPayBaseUrl() + "/mmpaymkttransfers/send_coupon"; | |||
| String responseContent = this.post(url, request.toXML(), true); | |||
| WxPayCouponSendResult result = WxPayBaseResult.fromXML(responseContent, WxPayCouponSendResult.class); | |||
| result.checkResult(this); | |||
| return result; | |||
| } | |||
| } | |||
| @@ -1,6 +1,8 @@ | |||
| package com.github.binarywang.wxpay.service.impl; | |||
| import com.github.binarywang.utils.qrcode.QrcodeUtils; | |||
| import com.github.binarywang.wxpay.bean.coupon.WxPayCouponSendRequest; | |||
| import com.github.binarywang.wxpay.bean.coupon.WxPayCouponSendResult; | |||
| import com.github.binarywang.wxpay.bean.request.*; | |||
| import com.github.binarywang.wxpay.bean.result.*; | |||
| import com.github.binarywang.wxpay.exception.WxPayException; | |||
| @@ -26,7 +28,7 @@ import static org.testng.Assert.*; | |||
| */ | |||
| @Test | |||
| @Guice(modules = ApiTestModule.class) | |||
| public class WxPayServiceImplTest { | |||
| public class WxPayServiceAbstractImplTest { | |||
| private final Logger logger = LoggerFactory.getLogger(this.getClass()); | |||
| @Inject | |||
| @@ -271,4 +273,14 @@ public class WxPayServiceImplTest { | |||
| this.logger.info(signKey); | |||
| } | |||
| @Test | |||
| public void testSendCoupon() throws Exception { | |||
| WxPayCouponSendResult result = this.payService.sendCoupon(WxPayCouponSendRequest.newBuilder() | |||
| .couponStockId("123") | |||
| .openid("122") | |||
| .partnerTradeNo("1212") | |||
| .openidCount(1) | |||
| .build()); | |||
| this.logger.info(result.toString()); | |||
| } | |||
| } | |||