diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaXPayService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaXPayService.java index 68d4dc0c9..52f3b82e6 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaXPayService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaXPayService.java @@ -51,6 +51,18 @@ public interface WxMaXPayService { */ WxMaXPayCancelCurrencyPayResponse cancelCurrencyPay(WxMaXPayCancelCurrencyPayRequest request, WxMaXPaySigParams sigParams) throws WxErrorException; + /** + * 生成调起 wx.requestVirtualPayment 所需的支付参数。 + * + * @param request 虚拟支付调起参数 + * @param sigParams 签名参数对象 + * @return 支付参数 + */ + default WxMaXPayRequestVirtualPaymentData createRequestVirtualPaymentData(WxMaXPayRequestVirtualPaymentRequest request, + WxMaXPaySigParams sigParams) { + return request.createPayData(sigParams); + } + /** * 通知发货。 * diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java index 6d01e5803..f81e7d37e 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java @@ -1,6 +1,8 @@ package cn.binarywang.wx.miniapp.bean; +import cn.binarywang.wx.miniapp.bean.xpay.WxMaXPayGoodsInfo; import cn.binarywang.wx.miniapp.bean.xpay.WxMaXPayTeamInfo; +import cn.binarywang.wx.miniapp.bean.xpay.WxMaXPayWeChatPayInfo; import cn.binarywang.wx.miniapp.config.WxMaConfig; import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils; import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; @@ -213,6 +215,31 @@ public class WxMaMessage implements Serializable { @XStreamAlias("SubscribeMsgSentEvent") private WxMaSubscribeMsgEvent.SubscribeMsgSentEvent subscribeMsgSentEvent; + /** + * 商户订单号. + * xpay_goods_deliver_notify + */ + @SerializedName("OutTradeNo") + @XStreamAlias("OutTradeNo") + @XStreamConverter(value = XStreamCDataConverter.class) + private String outTradeNo; + + /** + * 微信支付信息. + * xpay_goods_deliver_notify + */ + @SerializedName("WeChatPayInfo") + @XStreamAlias("WeChatPayInfo") + private WxMaXPayWeChatPayInfo weChatPayInfo; + + /** + * 道具信息. + * xpay_goods_deliver_notify + */ + @SerializedName("GoodsInfo") + @XStreamAlias("GoodsInfo") + private WxMaXPayGoodsInfo goodsInfo; + // 小程序基本信息 //region 小程序基本信息 infoType=notify_3rd_wxa_auth_and_icp diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGoodsInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGoodsInfo.java new file mode 100644 index 000000000..238004afd --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayGoodsInfo.java @@ -0,0 +1,26 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import com.google.gson.annotations.SerializedName; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamConverter; +import lombok.Data; +import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; + +import java.io.Serializable; + +/** + * xpay_goods_deliver_notify 推送中的道具信息。 + */ +@Data +public class WxMaXPayGoodsInfo implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + + @SerializedName("ProductId") + @XStreamAlias("ProductId") + @XStreamConverter(value = XStreamCDataConverter.class) + private String productId; + + @SerializedName("Quantity") + @XStreamAlias("Quantity") + private Integer quantity; +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayRequestVirtualPaymentData.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayRequestVirtualPaymentData.java new file mode 100644 index 000000000..000226f66 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayRequestVirtualPaymentData.java @@ -0,0 +1,49 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 调用 wx.requestVirtualPayment 所需的支付参数。 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayRequestVirtualPaymentData implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + + /** + * 支付模式,个人主体虚拟支付固定为 short_series_goods。 + */ + @SerializedName("mode") + private String mode; + + /** + * 下单签名原文,需与计算 paySig 和 signature 时使用的字符串完全一致。 + */ + @SerializedName("signData") + private String signData; + + /** + * 使用 AppKey 对 requestVirtualPayment&signData 计算得到的签名。 + */ + @SerializedName("paySig") + private String paySig; + + /** + * 使用 session_key 对 signData 计算得到的签名。 + */ + @SerializedName("signature") + private String signature; + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayRequestVirtualPaymentRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayRequestVirtualPaymentRequest.java new file mode 100644 index 000000000..32ca89110 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayRequestVirtualPaymentRequest.java @@ -0,0 +1,84 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.constant.WxMaConstants; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 个人主体虚拟支付调起 wx.requestVirtualPayment 的签名原文参数。 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WxMaXPayRequestVirtualPaymentRequest implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + + /** + * 支付应用 ID。 + */ + @SerializedName("offerId") + private String offerId; + + /** + * 购买数量。 + */ + @SerializedName("buyQuantity") + private Integer buyQuantity; + + /** + * 环境,个人主体虚拟支付固定为 0。 + */ + @SerializedName("env") + private Integer env; + + /** + * 币种,个人主体虚拟支付固定为 CNY。 + */ + @SerializedName("currencyType") + private String currencyType; + + /** + * 道具 ID。 + */ + @SerializedName("productId") + private String productId; + + /** + * 道具单价,单位为分。 + */ + @SerializedName("goodsPrice") + private Integer goodsPrice; + + /** + * 商户订单号,8 到 32 位且不能以下划线开头。 + */ + @SerializedName("outTradeNo") + private String outTradeNo; + + /** + * 商户透传数据。 + */ + @SerializedName("attach") + private String attach; + + public WxMaXPayRequestVirtualPaymentData createPayData(WxMaXPaySigParams sigParams) { + final String signData = this.toJson(); + return WxMaXPayRequestVirtualPaymentData.builder() + .mode(WxMaConstants.XPayPaymentMode.GOODS) + .signData(signData) + .paySig(sigParams.calcPaySig(WxMaConstants.XPayWxApiSigUri.WXAPI, signData)) + .signature(sigParams.calcSig(signData)) + .build(); + } + + public String toJson() { + return WxMaGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayWeChatPayInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayWeChatPayInfo.java new file mode 100644 index 000000000..c1580b2ba --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayWeChatPayInfo.java @@ -0,0 +1,22 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import com.google.gson.annotations.SerializedName; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamConverter; +import lombok.Data; +import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; + +import java.io.Serializable; + +/** + * xpay_goods_deliver_notify 推送中的微信支付信息。 + */ +@Data +public class WxMaXPayWeChatPayInfo implements Serializable { + private static final long serialVersionUID = 7495157056049312108L; + + @SerializedName("MchOrderNo") + @XStreamAlias("MchOrderNo") + @XStreamConverter(value = XStreamCDataConverter.class) + private String mchOrderNo; +} diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/WxMaMessageTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/WxMaMessageTest.java index 0b7060e79..13b6d62bb 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/WxMaMessageTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/WxMaMessageTest.java @@ -397,6 +397,77 @@ public class WxMaMessageTest { assertEquals(teamInfo.getTeamAction(), new Integer(0)); } + /** + * 个人主体虚拟支付发货通知事件 xpay_goods_deliver_notify 测试用例(XML格式)。 + */ + @Test + public void testXPayGoodsDeliverNotifyFromXml() { + String xml = "\n" + + " \n" + + " \n" + + " 1700000000\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 2\n" + + " \n" + + " 0\n" + + ""; + + WxMaMessage msg = WxMaMessage.fromXml(xml); + + checkXPayGoodsDeliverNotifyMessage(msg); + } + + /** + * 个人主体虚拟支付发货通知事件 xpay_goods_deliver_notify 测试用例(JSON格式)。 + */ + @Test + public void testXPayGoodsDeliverNotifyFromJson() { + String json = "{\n" + + " \"ToUserName\": \"gh_abcdefg\",\n" + + " \"FromUserName\": \"oABCDEFG\",\n" + + " \"CreateTime\": 1700000000,\n" + + " \"MsgType\": \"event\",\n" + + " \"Event\": \"xpay_goods_deliver_notify\",\n" + + " \"OpenId\": \"oABCDEFG\",\n" + + " \"OutTradeNo\": \"order12345\",\n" + + " \"WeChatPayInfo\": {\n" + + " \"MchOrderNo\": \"wx_order_123\"\n" + + " },\n" + + " \"GoodsInfo\": {\n" + + " \"ProductId\": \"product_001\",\n" + + " \"Quantity\": 2\n" + + " },\n" + + " \"RetryTimes\": 0\n" + + "}"; + + WxMaMessage msg = WxMaMessage.fromJson(json); + checkXPayGoodsDeliverNotifyMessage(msg); + } + + private void checkXPayGoodsDeliverNotifyMessage(WxMaMessage msg) { + assertEquals(msg.getToUser(), "gh_abcdefg"); + assertEquals(msg.getFromUser(), "oABCDEFG"); + assertEquals(msg.getCreateTime(), new Integer(1700000000)); + assertEquals(msg.getMsgType(), WxConsts.XmlMsgType.EVENT); + assertEquals(msg.getEvent(), WxMaConstants.XPayNotifyEvent.GOODS_DELIVER); + assertEquals(msg.getOpenId(), "oABCDEFG"); + assertEquals(msg.getOutTradeNo(), "order12345"); + assertNotNull(msg.getWeChatPayInfo()); + assertEquals(msg.getWeChatPayInfo().getMchOrderNo(), "wx_order_123"); + assertNotNull(msg.getGoodsInfo()); + assertEquals(msg.getGoodsInfo().getProductId(), "product_001"); + assertEquals(msg.getGoodsInfo().getQuantity(), new Integer(2)); + assertEquals(msg.getRetryTimes(), new Integer(0)); + } + /** * 虚拟支付投诉推送事件 xpay_complaint_notify 测试用例(XML格式) */ diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayRequestVirtualPaymentTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayRequestVirtualPaymentTest.java new file mode 100644 index 000000000..769403386 --- /dev/null +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/xpay/WxMaXPayRequestVirtualPaymentTest.java @@ -0,0 +1,76 @@ +package cn.binarywang.wx.miniapp.bean.xpay; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.impl.WxMaXPayServiceImpl; +import cn.binarywang.wx.miniapp.constant.WxMaConstants; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; +import com.google.gson.JsonObject; +import org.testng.annotations.Test; + +import static org.mockito.Mockito.mock; +import static org.testng.Assert.assertEquals; + +/** + * 验证个人主体虚拟支付调起 wx.requestVirtualPayment 所需参数。 + */ +public class WxMaXPayRequestVirtualPaymentTest { + + @Test + public void testCreatePayData() { + WxMaXPayRequestVirtualPaymentRequest request = WxMaXPayRequestVirtualPaymentRequest.builder() + .offerId("1450019686") + .buyQuantity(1) + .env(0) + .currencyType(WxMaConstants.XPayCurrencyType.CNY) + .productId("product_001") + .goodsPrice(100) + .outTradeNo("order12345") + .attach("attach中文") + .build(); + WxMaXPaySigParams sigParams = WxMaXPaySigParams.builder() + .appKey("app_key_123") + .sessionKey("session_key_123") + .build(); + + WxMaXPayRequestVirtualPaymentData payData = request.createPayData(sigParams); + + assertEquals(payData.getMode(), WxMaConstants.XPayPaymentMode.GOODS); + assertEquals(payData.getSignData(), + "{\"offerId\":\"1450019686\",\"buyQuantity\":1,\"env\":0,\"currencyType\":\"CNY\"," + + "\"productId\":\"product_001\",\"goodsPrice\":100,\"outTradeNo\":\"order12345\"," + + "\"attach\":\"attach中文\"}"); + assertEquals(payData.getPaySig(), "52b0abda3c933b0273d328b5c5102ee9ab9249309474ddcdf5e9ce0f80b23532"); + assertEquals(payData.getSignature(), "602f76d9cadf36c232f7f6c1faa5fe295b0f955d188049997d0df699c0619c86"); + + JsonObject jsonObject = WxMaGsonBuilder.create().fromJson(payData.toJson(), JsonObject.class); + assertEquals(jsonObject.get("mode").getAsString(), WxMaConstants.XPayPaymentMode.GOODS); + assertEquals(jsonObject.get("paySig").getAsString(), payData.getPaySig()); + assertEquals(jsonObject.get("signature").getAsString(), payData.getSignature()); + assertEquals(jsonObject.get("signData").getAsString(), payData.getSignData()); + } + + @Test + public void testCreatePayDataByService() { + WxMaXPayRequestVirtualPaymentRequest request = WxMaXPayRequestVirtualPaymentRequest.builder() + .offerId("1450019686") + .buyQuantity(1) + .env(0) + .currencyType(WxMaConstants.XPayCurrencyType.CNY) + .productId("product_001") + .goodsPrice(100) + .outTradeNo("order12345") + .attach("attach中文") + .build(); + WxMaXPaySigParams sigParams = WxMaXPaySigParams.builder() + .appKey("app_key_123") + .sessionKey("session_key_123") + .build(); + + WxMaXPayRequestVirtualPaymentData payData = new WxMaXPayServiceImpl(mock(WxMaService.class)) + .createRequestVirtualPaymentData(request, sigParams); + + assertEquals(payData.getMode(), WxMaConstants.XPayPaymentMode.GOODS); + assertEquals(payData.getPaySig(), "52b0abda3c933b0273d328b5c5102ee9ab9249309474ddcdf5e9ce0f80b23532"); + assertEquals(payData.getSignature(), "602f76d9cadf36c232f7f6c1faa5fe295b0f955d188049997d0df699c0619c86"); + } +}