1
0
mirror of synced 2026-10-07 23:39:43 +08:00

🎨 #4120 【小程序】新增个人主体虚拟支付参数支持

This commit is contained in:
Binary Wang
2026-09-08 10:56:59 +08:00
committed by GitHub
parent c1591bae9c
commit 017421583a
8 changed files with 367 additions and 0 deletions

View File

@@ -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);
}
/**
* 通知发货。
*

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -397,6 +397,77 @@ public class WxMaMessageTest {
assertEquals(teamInfo.getTeamAction(), new Integer(0));
}
/**
* 个人主体虚拟支付发货通知事件 xpay_goods_deliver_notify 测试用例(XML格式)。
*/
@Test
public void testXPayGoodsDeliverNotifyFromXml() {
String xml = "<xml>\n" +
" <ToUserName><![CDATA[gh_abcdefg]]></ToUserName>\n" +
" <FromUserName><![CDATA[oABCDEFG]]></FromUserName>\n" +
" <CreateTime>1700000000</CreateTime>\n" +
" <MsgType><![CDATA[event]]></MsgType>\n" +
" <Event><![CDATA[xpay_goods_deliver_notify]]></Event>\n" +
" <OpenId><![CDATA[oABCDEFG]]></OpenId>\n" +
" <OutTradeNo><![CDATA[order12345]]></OutTradeNo>\n" +
" <WeChatPayInfo>\n" +
" <MchOrderNo><![CDATA[wx_order_123]]></MchOrderNo>\n" +
" </WeChatPayInfo>\n" +
" <GoodsInfo>\n" +
" <ProductId><![CDATA[product_001]]></ProductId>\n" +
" <Quantity>2</Quantity>\n" +
" </GoodsInfo>\n" +
" <RetryTimes>0</RetryTimes>\n" +
"</xml>";
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格式)
*/

View File

@@ -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");
}
}