diff --git a/weixin-java-channel/pom.xml b/weixin-java-channel/pom.xml
index 10417674f6..7542900495 100644
--- a/weixin-java-channel/pom.xml
+++ b/weixin-java-channel/pom.xml
@@ -133,6 +133,7 @@
org.apache.maven.plugins
maven-surefire-plugin
+ false
src/test/resources/testng.xml
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelGiftService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelGiftService.java
new file mode 100644
index 0000000000..23655e1905
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelGiftService.java
@@ -0,0 +1,102 @@
+package me.chanjar.weixin.channel.api;
+
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+import me.chanjar.weixin.channel.bean.product.GiftActivityAddResponse;
+import me.chanjar.weixin.channel.bean.product.GiftActivityInfo;
+import me.chanjar.weixin.channel.bean.product.GiftProductAddResponse;
+import me.chanjar.weixin.channel.bean.product.GiftProductGetResponse;
+import me.chanjar.weixin.channel.bean.product.GiftProductInfo;
+import me.chanjar.weixin.channel.bean.product.GiftProductListParam;
+import me.chanjar.weixin.channel.bean.product.GiftProductListResponse;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ * 微信小店赠品与买赠活动服务。
+ */
+public interface WxChannelGiftService {
+
+ /**
+ * 添加非卖商品。
+ *
+ * @param info 赠品信息
+ * @return 添加赠品响应
+ * @throws WxErrorException 异常
+ */
+ GiftProductAddResponse addGiftProduct(GiftProductInfo info) throws WxErrorException;
+
+ /**
+ * 更新非卖商品。
+ *
+ * @param info 赠品信息
+ * @return 操作响应
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse updateGiftProduct(GiftProductInfo info) throws WxErrorException;
+
+ /**
+ * 在售商品转赠品。
+ *
+ * @param productId 商品ID
+ * @return 操作响应
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse setProductAsGift(String productId) throws WxErrorException;
+
+ /**
+ * 获取赠品。
+ *
+ * @param productId 赠品商品ID
+ * @return 赠品详情响应
+ * @throws WxErrorException 异常
+ */
+ GiftProductGetResponse getGiftProduct(String productId) throws WxErrorException;
+
+ /**
+ * 获取赠品列表。
+ *
+ * @param param 查询参数
+ * @return 赠品列表
+ * @throws WxErrorException 异常
+ */
+ GiftProductListResponse listGiftProduct(GiftProductListParam param) throws WxErrorException;
+
+ /**
+ * 更新赠品库存。
+ *
+ * @param productId 赠品商品ID
+ * @param skuId 赠品sku_id
+ * @param diffType 修改类型 1增加 2减少 3设置
+ * @param num 增加、减少或者设置的库存值
+ * @return 操作响应
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse updateGiftStock(String productId, String skuId, Integer diffType, Integer num)
+ throws WxErrorException;
+
+ /**
+ * 创建赠品活动。
+ *
+ * @param info 活动信息
+ * @return 创建赠品活动响应
+ * @throws WxErrorException 异常
+ */
+ GiftActivityAddResponse addGiftActivity(GiftActivityInfo info) throws WxErrorException;
+
+ /**
+ * 删除赠品活动。
+ *
+ * @param activityId 活动ID
+ * @return 操作响应
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse deleteGiftActivity(String activityId) throws WxErrorException;
+
+ /**
+ * 停止赠品活动。
+ *
+ * @param activityId 活动ID
+ * @return 操作响应
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse stopGiftActivity(String activityId) throws WxErrorException;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java
new file mode 100644
index 0000000000..40e6776f60
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java
@@ -0,0 +1,62 @@
+package me.chanjar.weixin.channel.api;
+
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskAddResponse;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskListResponse;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskParam;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateParam;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateResponse;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ * 微信小店限时抢购服务。
+ */
+public interface WxChannelLimitedDiscountService {
+
+ /**
+ * 添加限时抢购任务。
+ *
+ * @param param 限时抢购任务
+ * @return 添加任务响应
+ * @throws WxErrorException 异常
+ */
+ LimitTaskAddResponse addLimitTask(LimitTaskParam param) throws WxErrorException;
+
+ /**
+ * 拉取限时抢购任务列表。
+ *
+ * @param pageSize 每页数量
+ * @param nextKey 翻页上下文
+ * @param status 抢购活动状态
+ * @return 任务列表响应
+ * @throws WxErrorException 异常
+ */
+ LimitTaskListResponse listLimitTask(Integer pageSize, String nextKey, Integer status) throws WxErrorException;
+
+ /**
+ * 停止限时抢购任务。
+ *
+ * @param taskId 限时抢购任务ID
+ * @return 操作响应
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse stopLimitTask(String taskId) throws WxErrorException;
+
+ /**
+ * 删除限时抢购任务。
+ *
+ * @param taskId 限时抢购任务ID
+ * @return 操作响应
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse deleteLimitTask(String taskId) throws WxErrorException;
+
+ /**
+ * 更新限时抢购任务。
+ *
+ * @param param 更新任务参数
+ * @return 更新任务响应
+ * @throws WxErrorException 异常
+ */
+ LimitTaskUpdateResponse updateLimitTask(LimitTaskUpdateParam param) throws WxErrorException;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java
new file mode 100644
index 0000000000..2f39249f0c
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java
@@ -0,0 +1,77 @@
+package me.chanjar.weixin.channel.api;
+
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+import me.chanjar.weixin.channel.bean.product.assistant.BeginTimingSaleParam;
+import me.chanjar.weixin.channel.bean.product.assistant.CancelTimingSaleParam;
+import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckParam;
+import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckResponse;
+import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewParam;
+import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewResponse;
+import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingParam;
+import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingResponse;
+import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendParam;
+import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendResponse;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ * 微信小店商品辅助功能服务。
+ */
+public interface WxChannelProductAssistantService {
+
+ /**
+ * 发品前校验。
+ *
+ * @param param 校验参数
+ * @return 校验结果
+ * @throws WxErrorException 异常
+ */
+ CategoryPreCheckResponse categoryPreCheck(CategoryPreCheckParam param) throws WxErrorException;
+
+ /**
+ * 获取商品品牌推荐。
+ *
+ * @param param 推荐参数
+ * @return 推荐结果
+ * @throws WxErrorException 异常
+ */
+ ProductBrandRecommendResponse getProductBrandRecommend(ProductBrandRecommendParam param)
+ throws WxErrorException;
+
+ /**
+ * 获取站内外商品属性映射。
+ *
+ * @param param 映射参数
+ * @return 映射结果
+ * @throws WxErrorException 异常
+ */
+ ExternalProductMappingResponse externalProductMapping(ExternalProductMappingParam param)
+ throws WxErrorException;
+
+ /**
+ * 获取商品属性映射及推荐。
+ *
+ * @param param 映射参数
+ * @return 映射结果
+ * @throws WxErrorException 异常
+ */
+ ExternalProductMappingNewResponse externalProductMappingNew(ExternalProductMappingNewParam param)
+ throws WxErrorException;
+
+ /**
+ * 将定时开售商品改为立即开售。
+ *
+ * @param param 开售参数
+ * @return 操作结果
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse beginTimingSale(BeginTimingSaleParam param) throws WxErrorException;
+
+ /**
+ * 取消商品定时开售。
+ *
+ * @param param 取消参数
+ * @return 操作结果
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse cancelTimingSale(CancelTimingSaleParam param) throws WxErrorException;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java
new file mode 100644
index 0000000000..dc2fa4f587
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java
@@ -0,0 +1,56 @@
+package me.chanjar.weixin.channel.api;
+
+import java.util.List;
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse;
+import me.chanjar.weixin.channel.bean.product.SkuStockResponse;
+import me.chanjar.weixin.channel.bean.product.stock.StockFlowParam;
+import me.chanjar.weixin.channel.bean.product.stock.StockFlowResponse;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ * 微信小店商品库存服务。
+ */
+public interface WxChannelProductStockService {
+
+ /**
+ * 更新商品库存。
+ *
+ * @param productId 商品ID
+ * @param skuId 商品sku_id
+ * @param diffType 修改类型 1增加 2减少 3设置
+ * @param num 增加、减少或者设置的库存值
+ * @return 操作响应
+ * @throws WxErrorException 异常
+ */
+ WxChannelBaseResponse updateStock(String productId, String skuId, Integer diffType, Integer num)
+ throws WxErrorException;
+
+ /**
+ * 获取商品实时库存。
+ *
+ * @param productId 商品ID
+ * @param skuId 商品sku_id
+ * @return 库存响应
+ * @throws WxErrorException 异常
+ */
+ SkuStockResponse getSkuStock(String productId, String skuId) throws WxErrorException;
+
+ /**
+ * 批量获取库存信息。
+ *
+ * @param productIds 商品ID列表,单次请求不超过50个
+ * @return 库存信息
+ * @throws WxErrorException 异常
+ */
+ SkuStockBatchResponse getSkuStockBatch(List productIds) throws WxErrorException;
+
+ /**
+ * 获取商品库存流水。
+ *
+ * @param param 库存流水查询参数
+ * @return 库存流水响应
+ * @throws WxErrorException 异常
+ */
+ StockFlowResponse getStockFlow(StockFlowParam param) throws WxErrorException;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java
index 64c21408c3..52cc924bc9 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java
@@ -35,6 +35,42 @@ public interface WxChannelService extends BaseWxChannelService {
*/
WxChannelProductService getProductService();
+ /**
+ * 赠品与买赠活动服务
+ *
+ * @return 赠品与买赠活动服务
+ */
+ default WxChannelGiftService getGiftService() {
+ throw new UnsupportedOperationException("Gift service is not supported by this implementation");
+ }
+
+ /**
+ * 限时抢购服务
+ *
+ * @return 限时抢购服务
+ */
+ default WxChannelLimitedDiscountService getLimitedDiscountService() {
+ throw new UnsupportedOperationException("Limited discount service is not supported by this implementation");
+ }
+
+ /**
+ * 商品库存服务
+ *
+ * @return 商品库存服务
+ */
+ default WxChannelProductStockService getProductStockService() {
+ throw new UnsupportedOperationException("Product stock service is not supported by this implementation");
+ }
+
+ /**
+ * 商品辅助功能服务
+ *
+ * @return 商品辅助功能服务
+ */
+ default WxChannelProductAssistantService getProductAssistantService() {
+ throw new UnsupportedOperationException("Product assistant service is not supported by this implementation");
+ }
+
/**
* 仓库服务
*
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelServiceImpl.java
index fd432e9725..b167af0d4d 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelServiceImpl.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelServiceImpl.java
@@ -37,7 +37,14 @@ public abstract class BaseWxChannelServiceImpl implements WxChannelService
private final WxChannelBasicService basicService = new WxChannelBasicServiceImpl(this);
private final WxChannelCategoryService categoryService = new WxChannelCategoryServiceImpl(this);
private final WxChannelBrandService brandService = new WxChannelBrandServiceImpl(this);
- private final WxChannelProductService productService = new WxChannelProductServiceImpl(this);
+ private final WxChannelGiftService giftService = new WxChannelGiftServiceImpl(this);
+ private final WxChannelLimitedDiscountService limitedDiscountService =
+ new WxChannelLimitedDiscountServiceImpl(this);
+ private final WxChannelProductStockService productStockService = new WxChannelProductStockServiceImpl(this);
+ private final WxChannelProductAssistantService productAssistantService =
+ new WxChannelProductAssistantServiceImpl(this);
+ private final WxChannelProductService productService = new WxChannelProductServiceImpl(
+ this, giftService, limitedDiscountService, productStockService);
private final WxChannelWarehouseService warehouseService = new WxChannelWarehouseServiceImpl(this);
private final WxChannelOrderService orderService = new WxChannelOrderServiceImpl(this);
private final WxChannelAfterSaleService afterSaleService = new WxChannelAfterSaleServiceImpl(this);
@@ -336,6 +343,26 @@ public WxChannelProductService getProductService() {
return productService;
}
+ @Override
+ public WxChannelGiftService getGiftService() {
+ return giftService;
+ }
+
+ @Override
+ public WxChannelLimitedDiscountService getLimitedDiscountService() {
+ return limitedDiscountService;
+ }
+
+ @Override
+ public WxChannelProductStockService getProductStockService() {
+ return productStockService;
+ }
+
+ @Override
+ public WxChannelProductAssistantService getProductAssistantService() {
+ return productAssistantService;
+ }
+
@Override
public WxChannelWarehouseService getWarehouseService() {
return warehouseService;
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelGiftServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelGiftServiceImpl.java
new file mode 100644
index 0000000000..5d6a16bdc1
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelGiftServiceImpl.java
@@ -0,0 +1,104 @@
+package me.chanjar.weixin.channel.api.impl;
+
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_ADD_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_DELETE_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_STOP_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_ADD_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_GET_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_LIST_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_ON_SALE_SET_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_STOCK_UPDATE_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_UPDATE_URL;
+
+import me.chanjar.weixin.channel.api.WxChannelGiftService;
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+import me.chanjar.weixin.channel.bean.product.GiftActivityAddParam;
+import me.chanjar.weixin.channel.bean.product.GiftActivityAddResponse;
+import me.chanjar.weixin.channel.bean.product.GiftActivityInfo;
+import me.chanjar.weixin.channel.bean.product.GiftProductAddResponse;
+import me.chanjar.weixin.channel.bean.product.GiftProductGetResponse;
+import me.chanjar.weixin.channel.bean.product.GiftProductInfo;
+import me.chanjar.weixin.channel.bean.product.GiftProductListParam;
+import me.chanjar.weixin.channel.bean.product.GiftProductListResponse;
+import me.chanjar.weixin.channel.bean.product.SkuStockParam;
+import me.chanjar.weixin.channel.util.JsonUtils;
+import me.chanjar.weixin.channel.util.ResponseUtils;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ * 微信小店赠品与买赠活动服务实现。
+ */
+public class WxChannelGiftServiceImpl implements WxChannelGiftService {
+
+ private final BaseWxChannelServiceImpl, ?> shopService;
+
+ public WxChannelGiftServiceImpl(BaseWxChannelServiceImpl, ?> shopService) {
+ this.shopService = shopService;
+ }
+
+ @Override
+ public GiftProductAddResponse addGiftProduct(GiftProductInfo info) throws WxErrorException {
+ String reqJson = JsonUtils.encode(info);
+ String resJson = shopService.post(GIFT_PRODUCT_ADD_URL, reqJson);
+ return ResponseUtils.decode(resJson, GiftProductAddResponse.class);
+ }
+
+ @Override
+ public WxChannelBaseResponse updateGiftProduct(GiftProductInfo info) throws WxErrorException {
+ String reqJson = JsonUtils.encode(info);
+ String resJson = shopService.post(GIFT_PRODUCT_UPDATE_URL, reqJson);
+ return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ }
+
+ @Override
+ public WxChannelBaseResponse setProductAsGift(String productId) throws WxErrorException {
+ String reqJson = "{\"product_id\":\"" + productId + "\"}";
+ String resJson = shopService.post(GIFT_PRODUCT_ON_SALE_SET_URL, reqJson);
+ return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ }
+
+ @Override
+ public GiftProductGetResponse getGiftProduct(String productId) throws WxErrorException {
+ String reqJson = "{\"product_id\":\"" + productId + "\"}";
+ String resJson = shopService.post(GIFT_PRODUCT_GET_URL, reqJson);
+ return ResponseUtils.decode(resJson, GiftProductGetResponse.class);
+ }
+
+ @Override
+ public GiftProductListResponse listGiftProduct(GiftProductListParam param) throws WxErrorException {
+ String reqJson = JsonUtils.encode(param);
+ String resJson = shopService.post(GIFT_PRODUCT_LIST_URL, reqJson);
+ return ResponseUtils.decode(resJson, GiftProductListResponse.class);
+ }
+
+ @Override
+ public WxChannelBaseResponse updateGiftStock(String productId, String skuId, Integer diffType, Integer num)
+ throws WxErrorException {
+ SkuStockParam param = new SkuStockParam(productId, skuId, diffType, num);
+ String reqJson = JsonUtils.encode(param);
+ String resJson = shopService.post(GIFT_PRODUCT_STOCK_UPDATE_URL, reqJson);
+ return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ }
+
+ @Override
+ public GiftActivityAddResponse addGiftActivity(GiftActivityInfo info) throws WxErrorException {
+ GiftActivityAddParam param = new GiftActivityAddParam(info);
+ String reqJson = JsonUtils.encode(param);
+ String resJson = shopService.post(GIFT_ACTIVITY_ADD_URL, reqJson);
+ return ResponseUtils.decode(resJson, GiftActivityAddResponse.class);
+ }
+
+ @Override
+ public WxChannelBaseResponse deleteGiftActivity(String activityId) throws WxErrorException {
+ String reqJson = "{\"activity_id\":\"" + activityId + "\"}";
+ String resJson = shopService.post(GIFT_ACTIVITY_DELETE_URL, reqJson);
+ return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ }
+
+ @Override
+ public WxChannelBaseResponse stopGiftActivity(String activityId) throws WxErrorException {
+ String reqJson = "{\"activity_id\":\"" + activityId + "\"}";
+ String resJson = shopService.post(GIFT_ACTIVITY_STOP_URL, reqJson);
+ return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ }
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java
new file mode 100644
index 0000000000..26cca4d400
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java
@@ -0,0 +1,68 @@
+package me.chanjar.weixin.channel.api.impl;
+
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.ADD_LIMIT_TASK_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.DELETE_LIMIT_TASK_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.LIST_LIMIT_TASK_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.STOP_LIMIT_TASK_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.UPDATE_LIMIT_TASK_URL;
+
+import me.chanjar.weixin.channel.api.WxChannelLimitedDiscountService;
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskAddResponse;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskListParam;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskListResponse;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskParam;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateParam;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateResponse;
+import me.chanjar.weixin.channel.util.JsonUtils;
+import me.chanjar.weixin.channel.util.ResponseUtils;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ * 微信小店限时抢购服务实现。
+ */
+public class WxChannelLimitedDiscountServiceImpl implements WxChannelLimitedDiscountService {
+
+ private final BaseWxChannelServiceImpl, ?> shopService;
+
+ public WxChannelLimitedDiscountServiceImpl(BaseWxChannelServiceImpl, ?> shopService) {
+ this.shopService = shopService;
+ }
+
+ @Override
+ public LimitTaskAddResponse addLimitTask(LimitTaskParam param) throws WxErrorException {
+ String reqJson = JsonUtils.encode(param);
+ String resJson = shopService.post(ADD_LIMIT_TASK_URL, reqJson);
+ return ResponseUtils.decode(resJson, LimitTaskAddResponse.class);
+ }
+
+ @Override
+ public LimitTaskListResponse listLimitTask(Integer pageSize, String nextKey, Integer status)
+ throws WxErrorException {
+ LimitTaskListParam param = new LimitTaskListParam(pageSize, nextKey, status);
+ String reqJson = JsonUtils.encode(param);
+ String resJson = shopService.post(LIST_LIMIT_TASK_URL, reqJson);
+ return ResponseUtils.decode(resJson, LimitTaskListResponse.class);
+ }
+
+ @Override
+ public WxChannelBaseResponse stopLimitTask(String taskId) throws WxErrorException {
+ String reqJson = "{\"task_id\": \"" + taskId + "\"}";
+ String resJson = shopService.post(STOP_LIMIT_TASK_URL, reqJson);
+ return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ }
+
+ @Override
+ public WxChannelBaseResponse deleteLimitTask(String taskId) throws WxErrorException {
+ String reqJson = "{\"task_id\": \"" + taskId + "\"}";
+ String resJson = shopService.post(DELETE_LIMIT_TASK_URL, reqJson);
+ return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ }
+
+ @Override
+ public LimitTaskUpdateResponse updateLimitTask(LimitTaskUpdateParam param) throws WxErrorException {
+ String reqJson = JsonUtils.encode(param);
+ String resJson = shopService.post(UPDATE_LIMIT_TASK_URL, reqJson);
+ return ResponseUtils.decode(resJson, LimitTaskUpdateResponse.class);
+ }
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java
new file mode 100644
index 0000000000..7f1de84d02
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java
@@ -0,0 +1,77 @@
+package me.chanjar.weixin.channel.api.impl;
+
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.BEGIN_TIMING_SALE_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.CANCEL_TIMING_SALE_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.CATEGORY_PRE_CHECK_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.EXTERNAL_PRODUCT_MAPPING_NEW_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.EXTERNAL_PRODUCT_MAPPING_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.PRODUCT_BRAND_RECOMMEND_URL;
+
+import me.chanjar.weixin.channel.api.WxChannelProductAssistantService;
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+import me.chanjar.weixin.channel.bean.product.assistant.BeginTimingSaleParam;
+import me.chanjar.weixin.channel.bean.product.assistant.CancelTimingSaleParam;
+import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckParam;
+import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckResponse;
+import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewParam;
+import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewResponse;
+import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingParam;
+import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingResponse;
+import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendParam;
+import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendResponse;
+import me.chanjar.weixin.channel.util.JsonUtils;
+import me.chanjar.weixin.channel.util.ResponseUtils;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ * 微信小店商品辅助功能服务实现。
+ */
+public class WxChannelProductAssistantServiceImpl implements WxChannelProductAssistantService {
+
+ /** 微信商店服务 */
+ private final BaseWxChannelServiceImpl, ?> shopService;
+
+ public WxChannelProductAssistantServiceImpl(BaseWxChannelServiceImpl, ?> shopService) {
+ this.shopService = shopService;
+ }
+
+ @Override
+ public CategoryPreCheckResponse categoryPreCheck(CategoryPreCheckParam param) throws WxErrorException {
+ return post(CATEGORY_PRE_CHECK_URL, param, CategoryPreCheckResponse.class);
+ }
+
+ @Override
+ public ProductBrandRecommendResponse getProductBrandRecommend(ProductBrandRecommendParam param)
+ throws WxErrorException {
+ return post(PRODUCT_BRAND_RECOMMEND_URL, param, ProductBrandRecommendResponse.class);
+ }
+
+ @Override
+ public ExternalProductMappingResponse externalProductMapping(ExternalProductMappingParam param)
+ throws WxErrorException {
+ return post(EXTERNAL_PRODUCT_MAPPING_URL, param, ExternalProductMappingResponse.class);
+ }
+
+ @Override
+ public ExternalProductMappingNewResponse externalProductMappingNew(ExternalProductMappingNewParam param)
+ throws WxErrorException {
+ return post(EXTERNAL_PRODUCT_MAPPING_NEW_URL, param, ExternalProductMappingNewResponse.class);
+ }
+
+ @Override
+ public WxChannelBaseResponse beginTimingSale(BeginTimingSaleParam param) throws WxErrorException {
+ return post(BEGIN_TIMING_SALE_URL, param, WxChannelBaseResponse.class);
+ }
+
+ @Override
+ public WxChannelBaseResponse cancelTimingSale(CancelTimingSaleParam param) throws WxErrorException {
+ return post(CANCEL_TIMING_SALE_URL, param, WxChannelBaseResponse.class);
+ }
+
+ private T post(String url, Object param, Class responseType)
+ throws WxErrorException {
+ String reqJson = JsonUtils.encode(param);
+ String resJson = shopService.post(url, reqJson);
+ return ResponseUtils.decode(resJson, responseType);
+ }
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java
index b3974e0aa7..a7fc91c840 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java
@@ -1,44 +1,29 @@
package me.chanjar.weixin.channel.api.impl;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.ADD_LIMIT_TASK_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.CANCEL_AUDIT_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.DELETE_LIMIT_TASK_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_ADD_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_DELETE_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_STOP_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_ADD_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_GET_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_LIST_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_ON_SALE_SET_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_STOCK_UPDATE_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_UPDATE_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.LIST_LIMIT_TASK_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_ADD_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_AUDIT_FREE_UPDATE_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_DELISTING_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_DEL_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_BATCH_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_H5URL_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_LISTING_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_LIST_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_QRCODE_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_TAGLINK_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_UPDATE_STOCK_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_UPDATE_URL;
-import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.STOP_LIMIT_TASK_URL;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.channel.api.WxChannelGiftService;
+import me.chanjar.weixin.channel.api.WxChannelLimitedDiscountService;
import me.chanjar.weixin.channel.api.WxChannelProductService;
+import me.chanjar.weixin.channel.api.WxChannelProductStockService;
import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
import me.chanjar.weixin.channel.bean.limit.LimitTaskAddResponse;
-import me.chanjar.weixin.channel.bean.limit.LimitTaskListParam;
import me.chanjar.weixin.channel.bean.limit.LimitTaskListResponse;
import me.chanjar.weixin.channel.bean.limit.LimitTaskParam;
-import me.chanjar.weixin.channel.bean.product.GiftActivityAddParam;
import me.chanjar.weixin.channel.bean.product.GiftActivityAddResponse;
import me.chanjar.weixin.channel.bean.product.GiftActivityInfo;
import me.chanjar.weixin.channel.bean.product.GiftProductAddResponse;
@@ -46,9 +31,7 @@
import me.chanjar.weixin.channel.bean.product.GiftProductInfo;
import me.chanjar.weixin.channel.bean.product.GiftProductListParam;
import me.chanjar.weixin.channel.bean.product.GiftProductListResponse;
-import me.chanjar.weixin.channel.bean.product.SkuStockBatchParam;
import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse;
-import me.chanjar.weixin.channel.bean.product.SkuStockParam;
import me.chanjar.weixin.channel.bean.product.SkuStockResponse;
import me.chanjar.weixin.channel.bean.product.SpuFastInfo;
import me.chanjar.weixin.channel.bean.product.SpuGetResponse;
@@ -74,9 +57,22 @@ public class WxChannelProductServiceImpl implements WxChannelProductService {
/** 微信商店服务 */
private final BaseWxChannelServiceImpl, ?> shopService;
+ private final WxChannelGiftService giftService;
+ private final WxChannelLimitedDiscountService limitedDiscountService;
+ private final WxChannelProductStockService productStockService;
public WxChannelProductServiceImpl(BaseWxChannelServiceImpl, ?> shopService) {
+ this(shopService, new WxChannelGiftServiceImpl(shopService),
+ new WxChannelLimitedDiscountServiceImpl(shopService), new WxChannelProductStockServiceImpl(shopService));
+ }
+
+ WxChannelProductServiceImpl(BaseWxChannelServiceImpl, ?> shopService, WxChannelGiftService giftService,
+ WxChannelLimitedDiscountService limitedDiscountService,
+ WxChannelProductStockService productStockService) {
this.shopService = shopService;
+ this.giftService = giftService;
+ this.limitedDiscountService = limitedDiscountService;
+ this.productStockService = productStockService;
}
@Override
@@ -117,10 +113,7 @@ public WxChannelBaseResponse updateProductAuditFree(SpuFastInfo info) throws WxE
@Override
public WxChannelBaseResponse updateStock(String productId, String skuId, Integer diffType, Integer num)
throws WxErrorException {
- SkuStockParam param = new SkuStockParam(productId, skuId, diffType, num);
- String reqJson = JsonUtils.encode(param);
- String resJson = shopService.post(SPU_UPDATE_STOCK_URL, reqJson);
- return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ return productStockService.updateStock(productId, skuId, diffType, num);
}
/**
@@ -194,17 +187,12 @@ public WxChannelBaseResponse downProduct(String productId) throws WxErrorExcepti
@Override
public SkuStockResponse getSkuStock(String productId, String skuId) throws WxErrorException {
- String reqJson = "{\"product_id\":\"" + productId + "\",\"sku_id\":\"" + skuId + "\"}";
- String resJson = shopService.post(SPU_GET_STOCK_URL, reqJson);
- return ResponseUtils.decode(resJson, SkuStockResponse.class);
+ return productStockService.getSkuStock(productId, skuId);
}
@Override
public SkuStockBatchResponse getSkuStockBatch(List productIds) throws WxErrorException {
- SkuStockBatchParam param = new SkuStockBatchParam(productIds);
- String reqJson = JsonUtils.encode(param);
- String resJson = shopService.post(SPU_GET_STOCK_BATCH_URL, reqJson);
- return ResponseUtils.decode(resJson, SkuStockBatchResponse.class);
+ return productStockService.getSkuStockBatch(productIds);
}
@Override
@@ -230,97 +218,68 @@ public ProductTagLinkResponse getProductTagLink(String productId) throws WxError
@Override
public GiftProductAddResponse addGiftProduct(GiftProductInfo info) throws WxErrorException {
- String reqJson = JsonUtils.encode(info);
- String resJson = shopService.post(GIFT_PRODUCT_ADD_URL, reqJson);
- return ResponseUtils.decode(resJson, GiftProductAddResponse.class);
+ return giftService.addGiftProduct(info);
}
@Override
public WxChannelBaseResponse updateGiftProduct(GiftProductInfo info) throws WxErrorException {
- String reqJson = JsonUtils.encode(info);
- String resJson = shopService.post(GIFT_PRODUCT_UPDATE_URL, reqJson);
- return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ return giftService.updateGiftProduct(info);
}
@Override
public WxChannelBaseResponse setProductAsGift(String productId) throws WxErrorException {
- String reqJson = "{\"product_id\":\"" + productId + "\"}";
- String resJson = shopService.post(GIFT_PRODUCT_ON_SALE_SET_URL, reqJson);
- return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ return giftService.setProductAsGift(productId);
}
@Override
public GiftProductGetResponse getGiftProduct(String productId) throws WxErrorException {
- String reqJson = "{\"product_id\":\"" + productId + "\"}";
- String resJson = shopService.post(GIFT_PRODUCT_GET_URL, reqJson);
- return ResponseUtils.decode(resJson, GiftProductGetResponse.class);
+ return giftService.getGiftProduct(productId);
}
@Override
public GiftProductListResponse listGiftProduct(GiftProductListParam param) throws WxErrorException {
- String reqJson = JsonUtils.encode(param);
- String resJson = shopService.post(GIFT_PRODUCT_LIST_URL, reqJson);
- return ResponseUtils.decode(resJson, GiftProductListResponse.class);
+ return giftService.listGiftProduct(param);
}
@Override
public WxChannelBaseResponse updateGiftStock(String productId, String skuId, Integer diffType, Integer num)
throws WxErrorException {
- SkuStockParam param = new SkuStockParam(productId, skuId, diffType, num);
- String reqJson = JsonUtils.encode(param);
- String resJson = shopService.post(GIFT_PRODUCT_STOCK_UPDATE_URL, reqJson);
- return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ return giftService.updateGiftStock(productId, skuId, diffType, num);
}
@Override
public GiftActivityAddResponse addGiftActivity(GiftActivityInfo info) throws WxErrorException {
- GiftActivityAddParam param = new GiftActivityAddParam(info);
- String reqJson = JsonUtils.encode(param);
- String resJson = shopService.post(GIFT_ACTIVITY_ADD_URL, reqJson);
- return ResponseUtils.decode(resJson, GiftActivityAddResponse.class);
+ return giftService.addGiftActivity(info);
}
@Override
public WxChannelBaseResponse deleteGiftActivity(String activityId) throws WxErrorException {
- String reqJson = "{\"activity_id\":\"" + activityId + "\"}";
- String resJson = shopService.post(GIFT_ACTIVITY_DELETE_URL, reqJson);
- return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ return giftService.deleteGiftActivity(activityId);
}
@Override
public WxChannelBaseResponse stopGiftActivity(String activityId) throws WxErrorException {
- String reqJson = "{\"activity_id\":\"" + activityId + "\"}";
- String resJson = shopService.post(GIFT_ACTIVITY_STOP_URL, reqJson);
- return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ return giftService.stopGiftActivity(activityId);
}
@Override
public LimitTaskAddResponse addLimitTask(LimitTaskParam param) throws WxErrorException {
- String reqJson = JsonUtils.encode(param);
- String resJson = shopService.post(ADD_LIMIT_TASK_URL, reqJson);
- return ResponseUtils.decode(resJson, LimitTaskAddResponse.class);
+ return limitedDiscountService.addLimitTask(param);
}
@Override
public LimitTaskListResponse listLimitTask(Integer pageSize, String nextKey, Integer status)
throws WxErrorException {
- LimitTaskListParam param = new LimitTaskListParam(pageSize, nextKey, status);
- String reqJson = JsonUtils.encode(param);
- String resJson = shopService.post(LIST_LIMIT_TASK_URL, reqJson);
- return ResponseUtils.decode(resJson, LimitTaskListResponse.class);
+ return limitedDiscountService.listLimitTask(pageSize, nextKey, status);
}
@Override
public WxChannelBaseResponse stopLimitTask(String taskId) throws WxErrorException {
- String reqJson = "{\"task_id\": \"" + taskId + "\"}";
- String resJson = shopService.post(STOP_LIMIT_TASK_URL, reqJson);
- return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ return limitedDiscountService.stopLimitTask(taskId);
}
@Override
public WxChannelBaseResponse deleteLimitTask(String taskId) throws WxErrorException {
- String reqJson = "{\"task_id\": \"" + taskId + "\"}";
- String resJson = shopService.post(DELETE_LIMIT_TASK_URL, reqJson);
- return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ return limitedDiscountService.deleteLimitTask(taskId);
}
}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java
new file mode 100644
index 0000000000..1b3b371420
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java
@@ -0,0 +1,62 @@
+package me.chanjar.weixin.channel.api.impl;
+
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_BATCH_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_FLOW_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_URL;
+import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_UPDATE_STOCK_URL;
+
+import java.util.List;
+import me.chanjar.weixin.channel.api.WxChannelProductStockService;
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+import me.chanjar.weixin.channel.bean.product.SkuStockBatchParam;
+import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse;
+import me.chanjar.weixin.channel.bean.product.SkuStockParam;
+import me.chanjar.weixin.channel.bean.product.SkuStockResponse;
+import me.chanjar.weixin.channel.bean.product.stock.StockFlowParam;
+import me.chanjar.weixin.channel.bean.product.stock.StockFlowResponse;
+import me.chanjar.weixin.channel.util.JsonUtils;
+import me.chanjar.weixin.channel.util.ResponseUtils;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ * 微信小店商品库存服务实现。
+ */
+public class WxChannelProductStockServiceImpl implements WxChannelProductStockService {
+
+ private final BaseWxChannelServiceImpl, ?> shopService;
+
+ public WxChannelProductStockServiceImpl(BaseWxChannelServiceImpl, ?> shopService) {
+ this.shopService = shopService;
+ }
+
+ @Override
+ public WxChannelBaseResponse updateStock(String productId, String skuId, Integer diffType, Integer num)
+ throws WxErrorException {
+ SkuStockParam param = new SkuStockParam(productId, skuId, diffType, num);
+ String reqJson = JsonUtils.encode(param);
+ String resJson = shopService.post(SPU_UPDATE_STOCK_URL, reqJson);
+ return ResponseUtils.decode(resJson, WxChannelBaseResponse.class);
+ }
+
+ @Override
+ public SkuStockResponse getSkuStock(String productId, String skuId) throws WxErrorException {
+ String reqJson = "{\"product_id\":\"" + productId + "\",\"sku_id\":\"" + skuId + "\"}";
+ String resJson = shopService.post(SPU_GET_STOCK_URL, reqJson);
+ return ResponseUtils.decode(resJson, SkuStockResponse.class);
+ }
+
+ @Override
+ public SkuStockBatchResponse getSkuStockBatch(List productIds) throws WxErrorException {
+ SkuStockBatchParam param = new SkuStockBatchParam(productIds);
+ String reqJson = JsonUtils.encode(param);
+ String resJson = shopService.post(SPU_GET_STOCK_BATCH_URL, reqJson);
+ return ResponseUtils.decode(resJson, SkuStockBatchResponse.class);
+ }
+
+ @Override
+ public StockFlowResponse getStockFlow(StockFlowParam param) throws WxErrorException {
+ String reqJson = JsonUtils.encode(param);
+ String resJson = shopService.post(SPU_GET_STOCK_FLOW_URL, reqJson);
+ return ResponseUtils.decode(resJson, StockFlowResponse.class);
+ }
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitSkuUpdate.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitSkuUpdate.java
new file mode 100644
index 0000000000..98c14a4024
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitSkuUpdate.java
@@ -0,0 +1,32 @@
+package me.chanjar.weixin.channel.bean.limit;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 限时抢购任务的 SKU 更新信息。
+ */
+@Data
+@NoArgsConstructor
+public class LimitSkuUpdate implements Serializable {
+
+ private static final long serialVersionUID = 4209672674401016015L;
+
+ /** SKU 所属商品 ID。 */
+ @JsonProperty("product_id")
+ private String productId;
+
+ /** SKU ID。 */
+ @JsonProperty("sku_id")
+ private String skuId;
+
+ /** SKU 抢购价格,单位为分。 */
+ @JsonProperty("sale_price")
+ private Integer salePrice;
+
+ /** 参与抢购的商品库存。 */
+ @JsonProperty("sale_stock")
+ private Integer saleStock;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateParam.java
new file mode 100644
index 0000000000..40c7650cea
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateParam.java
@@ -0,0 +1,41 @@
+package me.chanjar.weixin.channel.bean.limit;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import java.util.List;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 更新限时抢购任务请求参数。
+ */
+@Data
+@NoArgsConstructor
+public class LimitTaskUpdateParam implements Serializable {
+
+ private static final long serialVersionUID = 7277247203887803045L;
+
+ /** 限时抢购任务 ID。 */
+ @JsonProperty("task_id")
+ private String taskId;
+
+ /** 当前活动状态:0 待开始,1 进行中。 */
+ @JsonProperty("status")
+ private Integer status;
+
+ /** 活动开始时间,秒级时间戳。 */
+ @JsonProperty("start_time")
+ private Long startTime;
+
+ /** 活动结束时间,秒级时间戳。 */
+ @JsonProperty("end_time")
+ private Long endTime;
+
+ /** 活动名称。 */
+ @JsonProperty("title")
+ private String title;
+
+ /** SKU 抢购信息列表。 */
+ @JsonProperty("limited_discount_skus")
+ private List skus;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateResponse.java
new file mode 100644
index 0000000000..73afc8247e
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateResponse.java
@@ -0,0 +1,26 @@
+package me.chanjar.weixin.channel.bean.limit;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+
+/**
+ * 更新限时抢购任务响应。
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class LimitTaskUpdateResponse extends WxChannelBaseResponse {
+
+ private static final long serialVersionUID = 4429517792042527433L;
+
+ /** 限时抢购任务 ID。 */
+ @JsonProperty("task_id")
+ private String taskId;
+
+ /** 活动名称。 */
+ @JsonProperty("title")
+ private String title;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/BeginTimingSaleParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/BeginTimingSaleParam.java
new file mode 100644
index 0000000000..2a624ec434
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/BeginTimingSaleParam.java
@@ -0,0 +1,24 @@
+package me.chanjar.weixin.channel.bean.product.assistant;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 商品立即开售参数。
+ */
+@Data
+@NoArgsConstructor
+public class BeginTimingSaleParam implements Serializable {
+
+ private static final long serialVersionUID = -1525220756273987016L;
+
+ /** 商品 ID。 */
+ @JsonProperty("product_id")
+ private String productId;
+
+ /** 定时开售任务 ID。 */
+ @JsonProperty("task_id")
+ private String taskId;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CancelTimingSaleParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CancelTimingSaleParam.java
new file mode 100644
index 0000000000..25980a1489
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CancelTimingSaleParam.java
@@ -0,0 +1,20 @@
+package me.chanjar.weixin.channel.bean.product.assistant;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 取消商品开售参数。
+ */
+@Data
+@NoArgsConstructor
+public class CancelTimingSaleParam implements Serializable {
+
+ private static final long serialVersionUID = -3750831026611057323L;
+
+ /** 商品 ID。 */
+ @JsonProperty("product_id")
+ private String productId;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckParam.java
new file mode 100644
index 0000000000..ee93066660
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckParam.java
@@ -0,0 +1,20 @@
+package me.chanjar.weixin.channel.bean.product.assistant;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 发品前校验参数。
+ */
+@Data
+@NoArgsConstructor
+public class CategoryPreCheckParam implements Serializable {
+
+ private static final long serialVersionUID = 3616569394767815856L;
+
+ /** 叶子类目 ID,不传时只校验店铺相关条件。 */
+ @JsonProperty("cat_id")
+ private Long catId;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckResponse.java
new file mode 100644
index 0000000000..8559f2b0e2
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckResponse.java
@@ -0,0 +1,27 @@
+package me.chanjar.weixin.channel.bean.product.assistant;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+
+/**
+ * 发品前校验响应。
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class CategoryPreCheckResponse extends WxChannelBaseResponse {
+
+ private static final long serialVersionUID = 8912798390684239592L;
+
+ /** 是否全部校验通过。 */
+ @JsonProperty("all_pass")
+ private Boolean allPass;
+
+ /** 校验不通过的原因。 */
+ @JsonProperty("fail_reasons")
+ private List failReasons;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalAttribute.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalAttribute.java
new file mode 100644
index 0000000000..e173a85d02
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalAttribute.java
@@ -0,0 +1,24 @@
+package me.chanjar.weixin.channel.bean.product.assistant;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 商品属性键值对。
+ */
+@Data
+@NoArgsConstructor
+public class ExternalAttribute implements Serializable {
+
+ private static final long serialVersionUID = -8639178782951125101L;
+
+ /** 属性名。 */
+ @JsonProperty("key")
+ private String key;
+
+ /** 属性值。 */
+ @JsonProperty("value")
+ private String value;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewParam.java
new file mode 100644
index 0000000000..89e0be65cd
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewParam.java
@@ -0,0 +1,41 @@
+package me.chanjar.weixin.channel.bean.product.assistant;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import java.util.List;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 商品属性映射及推荐参数。
+ */
+@Data
+@NoArgsConstructor
+public class ExternalProductMappingNewParam implements Serializable {
+
+ private static final long serialVersionUID = -4942505655791636645L;
+
+ /** 叶子类目 ID。 */
+ @JsonProperty("cat_id")
+ private Long catId;
+
+ /** 外部商品类目名称。 */
+ @JsonProperty("external_category_name")
+ private String externalCategoryName;
+
+ /** 商品主图,至少一张。 */
+ @JsonProperty("head_imgs")
+ private List headImgs;
+
+ /** 商品详情图。 */
+ @JsonProperty("detail_imgs")
+ private List detailImgs;
+
+ /** 商品标题。 */
+ @JsonProperty("title")
+ private String title;
+
+ /** 外部商品属性列表。 */
+ @JsonProperty("external_attributes")
+ private List externalAttributes;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewResponse.java
new file mode 100644
index 0000000000..87b09e03b4
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewResponse.java
@@ -0,0 +1,23 @@
+package me.chanjar.weixin.channel.bean.product.assistant;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+
+/**
+ * 商品属性映射及推荐响应。
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class ExternalProductMappingNewResponse extends WxChannelBaseResponse {
+
+ private static final long serialVersionUID = -6192580254142696913L;
+
+ /** 映射属性结果。 */
+ @JsonProperty("attributes")
+ private List attributes;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingParam.java
new file mode 100644
index 0000000000..c31e0f0f2d
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingParam.java
@@ -0,0 +1,32 @@
+package me.chanjar.weixin.channel.bean.product.assistant;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 站内外商品属性映射参数。
+ */
+@Data
+@NoArgsConstructor
+public class ExternalProductMappingParam implements Serializable {
+
+ private static final long serialVersionUID = 1944528166283981889L;
+
+ /** 叶子类目 ID。 */
+ @JsonProperty("cat_id")
+ private Long catId;
+
+ /** 外部商品属性名。 */
+ @JsonProperty("external_attribute_name")
+ private String externalAttributeName;
+
+ /** 外部商品属性值。 */
+ @JsonProperty("external_attribute_value")
+ private String externalAttributeValue;
+
+ /** 外部商品类目名称。 */
+ @JsonProperty("external_category_name")
+ private String externalCategoryName;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingResponse.java
new file mode 100644
index 0000000000..e97d4a94d0
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingResponse.java
@@ -0,0 +1,35 @@
+package me.chanjar.weixin.channel.bean.product.assistant;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+
+/**
+ * 站内外商品属性映射响应。
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class ExternalProductMappingResponse extends WxChannelBaseResponse {
+
+ private static final long serialVersionUID = -2267639791023044849L;
+
+ /** 外部商品属性名。 */
+ @JsonProperty("external_attribute_name")
+ private String externalAttributeName;
+
+ /** 外部商品属性值。 */
+ @JsonProperty("external_attribute_value")
+ private String externalAttributeValue;
+
+ /** 内部商品属性名。 */
+ @JsonProperty("internal_attribute_name")
+ private String internalAttributeName;
+
+ /** 内部商品属性值。 */
+ @JsonProperty("internal_attribute_value")
+ private List internalAttributeValue;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendParam.java
new file mode 100644
index 0000000000..36a47bfc40
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendParam.java
@@ -0,0 +1,33 @@
+package me.chanjar.weixin.channel.bean.product.assistant;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import java.util.List;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 商品品牌推荐参数。
+ */
+@Data
+@NoArgsConstructor
+public class ProductBrandRecommendParam implements Serializable {
+
+ private static final long serialVersionUID = 4516219198778673928L;
+
+ /** 叶子类目 ID。 */
+ @JsonProperty("cat_id")
+ private Long catId;
+
+ /** 商品主图,至少一张。 */
+ @JsonProperty("head_imgs")
+ private List headImgs;
+
+ /** 商品详情图。 */
+ @JsonProperty("detail_imgs")
+ private List detailImgs;
+
+ /** 商品标题。 */
+ @JsonProperty("title")
+ private String title;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendResponse.java
new file mode 100644
index 0000000000..b7c0081085
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendResponse.java
@@ -0,0 +1,30 @@
+package me.chanjar.weixin.channel.bean.product.assistant;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+
+/**
+ * 商品品牌推荐响应。
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class ProductBrandRecommendResponse extends WxChannelBaseResponse {
+
+ private static final long serialVersionUID = -7903894941180639923L;
+
+ /** 品牌 ID。 */
+ @JsonProperty("brand_id")
+ private Long brandId;
+
+ /** 品牌中文名称。 */
+ @JsonProperty("brand_name_chinese")
+ private String brandNameChinese;
+
+ /** 品牌英文名称。 */
+ @JsonProperty("brand_name_english")
+ private String brandNameEnglish;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowExtInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowExtInfo.java
new file mode 100644
index 0000000000..c08e35c477
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowExtInfo.java
@@ -0,0 +1,44 @@
+package me.chanjar.weixin.channel.bean.product.stock;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 库存流水额外信息。
+ */
+@Data
+@NoArgsConstructor
+public class StockFlowExtInfo implements Serializable {
+
+ private static final long serialVersionUID = 1170328051641116647L;
+
+ /** 归还的源库存子类型。 */
+ @JsonProperty("unmove_from_stock_sub_type")
+ private Integer unmoveFromStockSubType;
+
+ /** 分配的目标库存子类型。 */
+ @JsonProperty("move_to_stock_sub_type")
+ private Integer moveToStockSubType;
+
+ /** 操作来源。 */
+ @JsonProperty("upload_source")
+ private Integer uploadSource;
+
+ /** 订单 ID。 */
+ @JsonProperty("order_id")
+ private String orderId;
+
+ /** 区域仓库 ID。 */
+ @JsonProperty("out_warehouse_id")
+ private String outWarehouseId;
+
+ /** 限时抢购任务 ID。 */
+ @JsonProperty("limited_discount_id")
+ private String limitedDiscountId;
+
+ /** 达人的视频号 finder_id。 */
+ @JsonProperty("finder_id")
+ private String finderId;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowInfo.java
new file mode 100644
index 0000000000..a15b0018ea
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowInfo.java
@@ -0,0 +1,44 @@
+package me.chanjar.weixin.channel.bean.product.stock;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 库存流水信息。
+ */
+@Data
+@NoArgsConstructor
+public class StockFlowInfo implements Serializable {
+
+ private static final long serialVersionUID = 4094168882102603379L;
+
+ /** 操作数量。 */
+ @JsonProperty("amount")
+ private Integer amount;
+
+ /** 操作前数量。 */
+ @JsonProperty("beginning_amount")
+ private Integer beginningAmount;
+
+ /** 操作后数量。 */
+ @JsonProperty("ending_amount")
+ private Integer endingAmount;
+
+ /** 库存子类型。 */
+ @JsonProperty("stock_sub_type")
+ private Integer stockSubType;
+
+ /** 库存事件类型。 */
+ @JsonProperty("op_type")
+ private Integer opType;
+
+ /** 流水发生时间,秒级时间戳。 */
+ @JsonProperty("update_time")
+ private Long updateTime;
+
+ /** 额外信息。 */
+ @JsonProperty("ext_info")
+ private StockFlowExtInfo extInfo;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowParam.java
new file mode 100644
index 0000000000..cacd2ca3e6
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowParam.java
@@ -0,0 +1,57 @@
+package me.chanjar.weixin.channel.bean.product.stock;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import java.util.List;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 获取库存流水请求参数。
+ */
+@Data
+@NoArgsConstructor
+public class StockFlowParam implements Serializable {
+
+ private static final long serialVersionUID = -7882480822919984178L;
+
+ /** 内部商品 ID。 */
+ @JsonProperty("product_id")
+ private String productId;
+
+ /** 内部 SKU ID。 */
+ @JsonProperty("sku_id")
+ private String skuId;
+
+ /** 库存类型。 */
+ @JsonProperty("stock_type")
+ private Integer stockType;
+
+ /** 达人的视频号 finder_id。 */
+ @JsonProperty("finder_id")
+ private String finderId;
+
+ /** 查询开始时间,秒级时间戳。 */
+ @JsonProperty("begin_time")
+ private Long beginTime;
+
+ /** 查询结束时间,秒级时间戳。 */
+ @JsonProperty("end_time")
+ private Long endTime;
+
+ /** 库存事件类型列表。 */
+ @JsonProperty("op_type_list")
+ private List opTypeList;
+
+ /** 每页数量。 */
+ @JsonProperty("page_size")
+ private Integer pageSize;
+
+ /** 上次请求返回的翻页上下文。 */
+ @JsonProperty("next_key")
+ private String nextKey;
+
+ /** 库存类型 ID。 */
+ @JsonProperty("stock_type_id")
+ private String stockTypeId;
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowResponse.java
new file mode 100644
index 0000000000..4f779b64ab
--- /dev/null
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowResponse.java
@@ -0,0 +1,48 @@
+package me.chanjar.weixin.channel.bean.product.stock;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+
+/**
+ * 获取库存流水响应。
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class StockFlowResponse extends WxChannelBaseResponse {
+
+ private static final long serialVersionUID = -7420844779570799705L;
+
+ /** 本次翻页的上下文。 */
+ private String nextKey;
+
+ /** 库存流水。 */
+ private List stockFlowInfoList;
+
+ @JsonProperty("data")
+ private void unpackData(StockFlowData data) {
+ if (data == null) {
+ return;
+ }
+ this.nextKey = data.getNextKey();
+ this.stockFlowInfoList = data.getStockFlowInfoList();
+ }
+
+ @Data
+ @NoArgsConstructor
+ private static class StockFlowData implements Serializable {
+
+ private static final long serialVersionUID = -5455751387420196045L;
+
+ @JsonProperty("next_key")
+ private String nextKey;
+
+ @JsonProperty("stock_flow_info_list")
+ private List stockFlowInfoList;
+ }
+}
diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java
index b095589bf9..92ece47b2e 100644
--- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java
+++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java
@@ -166,6 +166,8 @@ public interface Spu {
String SPU_GET_STOCK_BATCH_URL = "https://api.weixin.qq.com/channels/ec/product/stock/batchget";
/** 更新商品库存 */
String SPU_UPDATE_STOCK_URL = "https://api.weixin.qq.com/channels/ec/product/stock/update";
+ /** 获取库存流水 */
+ String SPU_GET_STOCK_FLOW_URL = "https://api.weixin.qq.com/channels/ec/product/stock/getflow";
/** 添加非卖商品 */
String GIFT_PRODUCT_ADD_URL = "https://api.weixin.qq.com/channels/ec/product/gift/add";
/** 更新非卖商品 */
@@ -192,6 +194,21 @@ public interface Spu {
String STOP_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/stop";
/** 删除限时抢购任务 */
String DELETE_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/delete";
+ /** 更新限时抢购任务 */
+ String UPDATE_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/update";
+ /** 发品前校验 */
+ String CATEGORY_PRE_CHECK_URL = "https://api.weixin.qq.com/channels/ec/product/categoryprecheck";
+ /** 商品品牌推荐 */
+ String PRODUCT_BRAND_RECOMMEND_URL = "https://api.weixin.qq.com/channels/ec/product/productbrandrecommend";
+ /** 站内外商品属性映射 */
+ String EXTERNAL_PRODUCT_MAPPING_URL = "https://api.weixin.qq.com/channels/ec/product/externalproductmapping";
+ /** 商品属性映射及推荐 */
+ String EXTERNAL_PRODUCT_MAPPING_NEW_URL =
+ "https://api.weixin.qq.com/channels/ec/product/externalproductmappingnew";
+ /** 商品立即开售 */
+ String BEGIN_TIMING_SALE_URL = "https://api.weixin.qq.com/channels/ec/product/begintimingsale";
+ /** 取消商品开售 */
+ String CANCEL_TIMING_SALE_URL = "https://api.weixin.qq.com/channels/ec/product/canceltimingsale";
}
/** 区域仓库 */
diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImplTest.java
new file mode 100644
index 0000000000..2ee7c5b701
--- /dev/null
+++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImplTest.java
@@ -0,0 +1,62 @@
+package me.chanjar.weixin.channel.api.impl;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Arrays;
+import me.chanjar.weixin.channel.bean.limit.LimitSkuUpdate;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateParam;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateResponse;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for {@link WxChannelLimitedDiscountServiceImpl}.
+ */
+public class WxChannelLimitedDiscountServiceImplTest {
+
+ @Test
+ public void shouldUpdateLimitedDiscountTaskAndDecodeResponse() throws WxErrorException {
+ CapturingChannelService channelService = new CapturingChannelService();
+ channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\","
+ + "\"task_id\":\"task-id\",\"title\":\"updated title\"}";
+ LimitSkuUpdate sku = new LimitSkuUpdate();
+ sku.setProductId("product-id");
+ sku.setSkuId("sku-id");
+ sku.setSalePrice(2888);
+ sku.setSaleStock(5);
+ LimitTaskUpdateParam param = new LimitTaskUpdateParam();
+ param.setTaskId("task-id");
+ param.setStatus(0);
+ param.setStartTime(1_700_000_000L);
+ param.setEndTime(1_700_003_600L);
+ param.setTitle("updated title");
+ param.setSkus(Arrays.asList(sku));
+
+ LimitTaskUpdateResponse response = channelService.getLimitedDiscountService().updateLimitTask(param);
+
+ assertEquals(channelService.url,
+ "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/update");
+ assertEquals(channelService.request,
+ "{\"task_id\":\"task-id\",\"status\":0,\"start_time\":1700000000,"
+ + "\"end_time\":1700003600,\"title\":\"updated title\","
+ + "\"limited_discount_skus\":[{\"product_id\":\"product-id\","
+ + "\"sku_id\":\"sku-id\",\"sale_price\":2888,\"sale_stock\":5}]}");
+ assertEquals(response.getErrCode(), 0);
+ assertEquals(response.getErrMsg(), "ok");
+ assertEquals(response.getTaskId(), "task-id");
+ assertEquals(response.getTitle(), "updated title");
+ }
+
+ private static class CapturingChannelService extends WxChannelServiceImpl {
+ private String url;
+ private String request;
+ private String response;
+
+ @Override
+ public String post(String url, String postData) {
+ this.url = url;
+ this.request = postData;
+ return this.response;
+ }
+ }
+}
diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImplTest.java
new file mode 100644
index 0000000000..7c19b137f9
--- /dev/null
+++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImplTest.java
@@ -0,0 +1,169 @@
+package me.chanjar.weixin.channel.api.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Arrays;
+import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
+import me.chanjar.weixin.channel.bean.product.assistant.BeginTimingSaleParam;
+import me.chanjar.weixin.channel.bean.product.assistant.CancelTimingSaleParam;
+import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckParam;
+import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckResponse;
+import me.chanjar.weixin.channel.bean.product.assistant.ExternalAttribute;
+import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewParam;
+import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewResponse;
+import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingParam;
+import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingResponse;
+import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendParam;
+import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendResponse;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for {@link WxChannelProductAssistantServiceImpl}.
+ */
+public class WxChannelProductAssistantServiceImplTest {
+
+ @Test
+ public void shouldPreCheckCategoryAndDecodeResponse() throws WxErrorException {
+ CapturingChannelService channelService = new CapturingChannelService();
+ channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\","
+ + "\"all_pass\":false,\"fail_reasons\":[\"保证金不足\"]}";
+ CategoryPreCheckParam param = new CategoryPreCheckParam();
+ param.setCatId(6261L);
+
+ CategoryPreCheckResponse response = channelService.getProductAssistantService().categoryPreCheck(param);
+
+ assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/categoryprecheck");
+ assertEquals(channelService.request, "{\"cat_id\":6261}");
+ assertEquals(response.getErrCode(), 0);
+ assertEquals(response.getErrMsg(), "ok");
+ assertEquals(response.getAllPass(), Boolean.FALSE);
+ assertEquals(response.getFailReasons(), Arrays.asList("保证金不足"));
+ }
+
+ @Test
+ public void shouldRecommendProductBrandAndDecodeResponse() throws WxErrorException {
+ CapturingChannelService channelService = new CapturingChannelService();
+ channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\",\"brand_id\":2100000000,"
+ + "\"brand_name_chinese\":\"品牌\",\"brand_name_english\":\"brand\"}";
+ ProductBrandRecommendParam param = new ProductBrandRecommendParam();
+ param.setCatId(6000L);
+ param.setHeadImgs(Arrays.asList("https://example.com/head.jpg"));
+ param.setDetailImgs(Arrays.asList("https://example.com/detail.jpg"));
+ param.setTitle("测试商品");
+
+ ProductBrandRecommendResponse response =
+ channelService.getProductAssistantService().getProductBrandRecommend(param);
+
+ assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/productbrandrecommend");
+ assertEquals(channelService.request,
+ "{\"cat_id\":6000,\"head_imgs\":[\"https://example.com/head.jpg\"],"
+ + "\"detail_imgs\":[\"https://example.com/detail.jpg\"],\"title\":\"测试商品\"}");
+ assertEquals(response.getErrCode(), 0);
+ assertEquals(response.getErrMsg(), "ok");
+ assertEquals(response.getBrandId(), Long.valueOf(2_100_000_000L));
+ assertEquals(response.getBrandNameChinese(), "品牌");
+ assertEquals(response.getBrandNameEnglish(), "brand");
+ }
+
+ @Test
+ public void shouldMapExternalProductAttributeAndDecodeResponse() throws WxErrorException {
+ CapturingChannelService channelService = new CapturingChannelService();
+ channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\","
+ + "\"external_attribute_name\":\"帮面材质\",\"external_attribute_value\":\"塑胶\","
+ + "\"internal_attribute_name\":\"鞋面材质\",\"internal_attribute_value\":[\"塑胶\"]}";
+ ExternalProductMappingParam param = new ExternalProductMappingParam();
+ param.setCatId(6261L);
+ param.setExternalAttributeName("帮面材质");
+ param.setExternalAttributeValue("塑胶");
+ param.setExternalCategoryName("母婴:童鞋:雨鞋");
+
+ ExternalProductMappingResponse response =
+ channelService.getProductAssistantService().externalProductMapping(param);
+
+ assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/externalproductmapping");
+ assertEquals(channelService.request,
+ "{\"cat_id\":6261,\"external_attribute_name\":\"帮面材质\","
+ + "\"external_attribute_value\":\"塑胶\",\"external_category_name\":\"母婴:童鞋:雨鞋\"}");
+ assertEquals(response.getErrCode(), 0);
+ assertEquals(response.getExternalAttributeName(), "帮面材质");
+ assertEquals(response.getExternalAttributeValue(), "塑胶");
+ assertEquals(response.getInternalAttributeName(), "鞋面材质");
+ assertEquals(response.getInternalAttributeValue(), Arrays.asList("塑胶"));
+ }
+
+ @Test
+ public void shouldMapMultipleExternalProductAttributesAndDecodeResponse() throws WxErrorException {
+ CapturingChannelService channelService = new CapturingChannelService();
+ channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\","
+ + "\"attributes\":[{\"key\":\"鞋面材质\",\"value\":\"塑胶\"}]}";
+ ExternalAttribute externalAttribute = new ExternalAttribute();
+ externalAttribute.setKey("帮面材质");
+ externalAttribute.setValue("塑胶");
+ ExternalProductMappingNewParam param = new ExternalProductMappingNewParam();
+ param.setCatId(6000L);
+ param.setExternalCategoryName("母婴:童鞋:雨鞋");
+ param.setHeadImgs(Arrays.asList("https://example.com/head.jpg"));
+ param.setDetailImgs(Arrays.asList("https://example.com/detail.jpg"));
+ param.setTitle("测试商品");
+ param.setExternalAttributes(Arrays.asList(externalAttribute));
+
+ ExternalProductMappingNewResponse response =
+ channelService.getProductAssistantService().externalProductMappingNew(param);
+
+ assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/externalproductmappingnew");
+ assertEquals(channelService.request,
+ "{\"cat_id\":6000,\"external_category_name\":\"母婴:童鞋:雨鞋\","
+ + "\"head_imgs\":[\"https://example.com/head.jpg\"],"
+ + "\"detail_imgs\":[\"https://example.com/detail.jpg\"],\"title\":\"测试商品\","
+ + "\"external_attributes\":[{\"key\":\"帮面材质\",\"value\":\"塑胶\"}]}");
+ assertEquals(response.getErrCode(), 0);
+ assertEquals(response.getAttributes().size(), 1);
+ assertEquals(response.getAttributes().get(0).getKey(), "鞋面材质");
+ assertEquals(response.getAttributes().get(0).getValue(), "塑胶");
+ }
+
+ @Test
+ public void shouldBeginTimingSaleWithStringIdentifiers() throws WxErrorException {
+ CapturingChannelService channelService = new CapturingChannelService();
+ channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"}";
+ BeginTimingSaleParam param = new BeginTimingSaleParam();
+ param.setProductId("9007199254740993");
+ param.setTaskId("000123456789");
+
+ WxChannelBaseResponse response = channelService.getProductAssistantService().beginTimingSale(param);
+
+ assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/begintimingsale");
+ assertEquals(channelService.request,
+ "{\"product_id\":\"9007199254740993\",\"task_id\":\"000123456789\"}");
+ assertTrue(response.isSuccess());
+ }
+
+ @Test
+ public void shouldCancelTimingSaleWithStringProductIdentifier() throws WxErrorException {
+ CapturingChannelService channelService = new CapturingChannelService();
+ channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"}";
+ CancelTimingSaleParam param = new CancelTimingSaleParam();
+ param.setProductId("9007199254740993");
+
+ WxChannelBaseResponse response = channelService.getProductAssistantService().cancelTimingSale(param);
+
+ assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/canceltimingsale");
+ assertEquals(channelService.request, "{\"product_id\":\"9007199254740993\"}");
+ assertTrue(response.isSuccess());
+ }
+
+ private static class CapturingChannelService extends WxChannelServiceImpl {
+ private String url;
+ private String request;
+ private String response;
+
+ @Override
+ public String post(String url, String postData) {
+ this.url = url;
+ this.request = postData;
+ return this.response;
+ }
+ }
+}
diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImplTest.java
new file mode 100644
index 0000000000..86c26043fb
--- /dev/null
+++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImplTest.java
@@ -0,0 +1,81 @@
+package me.chanjar.weixin.channel.api.impl;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Arrays;
+import me.chanjar.weixin.channel.bean.product.stock.StockFlowInfo;
+import me.chanjar.weixin.channel.bean.product.stock.StockFlowParam;
+import me.chanjar.weixin.channel.bean.product.stock.StockFlowResponse;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for {@link WxChannelProductStockServiceImpl}.
+ */
+public class WxChannelProductStockServiceImplTest {
+
+ @Test
+ public void shouldGetStockFlowAndDecodeResponse() throws WxErrorException {
+ CapturingChannelService channelService = new CapturingChannelService();
+ channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\",\"data\":{"
+ + "\"stock_flow_info_list\":[{\"amount\":300,\"beginning_amount\":842,"
+ + "\"ending_amount\":542,\"stock_sub_type\":1,\"op_type\":6,"
+ + "\"update_time\":1689735682,\"ext_info\":{\"unmove_from_stock_sub_type\":3,"
+ + "\"move_to_stock_sub_type\":4,"
+ + "\"upload_source\":2,\"order_id\":\"order-id\","
+ + "\"out_warehouse_id\":\"warehouse-id\","
+ + "\"limited_discount_id\":\"discount-id\",\"finder_id\":\"finder-id\"}}],"
+ + "\"next_key\":\"next-page\"}}";
+ StockFlowParam param = new StockFlowParam();
+ param.setProductId("product-id");
+ param.setSkuId("sku-id");
+ param.setStockType(1);
+ param.setFinderId("finder-id");
+ param.setBeginTime(1_689_218_360L);
+ param.setEndTime(1_689_736_760L);
+ param.setOpTypeList(Arrays.asList(1, 2));
+ param.setPageSize(10);
+ param.setNextKey("current-page");
+ param.setStockTypeId("stock-type-id");
+
+ StockFlowResponse response = channelService.getProductStockService().getStockFlow(param);
+
+ assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/stock/getflow");
+ assertEquals(channelService.request,
+ "{\"product_id\":\"product-id\",\"sku_id\":\"sku-id\",\"stock_type\":1,"
+ + "\"finder_id\":\"finder-id\",\"begin_time\":1689218360,"
+ + "\"end_time\":1689736760,\"op_type_list\":[1,2],\"page_size\":10,"
+ + "\"next_key\":\"current-page\",\"stock_type_id\":\"stock-type-id\"}");
+ assertEquals(response.getErrCode(), 0);
+ assertEquals(response.getErrMsg(), "ok");
+ assertEquals(response.getNextKey(), "next-page");
+ assertEquals(response.getStockFlowInfoList().size(), 1);
+ StockFlowInfo flow = response.getStockFlowInfoList().get(0);
+ assertEquals(flow.getAmount(), Integer.valueOf(300));
+ assertEquals(flow.getBeginningAmount(), Integer.valueOf(842));
+ assertEquals(flow.getEndingAmount(), Integer.valueOf(542));
+ assertEquals(flow.getStockSubType(), Integer.valueOf(1));
+ assertEquals(flow.getOpType(), Integer.valueOf(6));
+ assertEquals(flow.getUpdateTime(), Long.valueOf(1_689_735_682L));
+ assertEquals(flow.getExtInfo().getUnmoveFromStockSubType(), Integer.valueOf(3));
+ assertEquals(flow.getExtInfo().getMoveToStockSubType(), Integer.valueOf(4));
+ assertEquals(flow.getExtInfo().getUploadSource(), Integer.valueOf(2));
+ assertEquals(flow.getExtInfo().getOrderId(), "order-id");
+ assertEquals(flow.getExtInfo().getOutWarehouseId(), "warehouse-id");
+ assertEquals(flow.getExtInfo().getLimitedDiscountId(), "discount-id");
+ assertEquals(flow.getExtInfo().getFinderId(), "finder-id");
+ }
+
+ private static class CapturingChannelService extends WxChannelServiceImpl {
+ private String url;
+ private String request;
+ private String response;
+
+ @Override
+ public String post(String url, String postData) {
+ this.url = url;
+ this.request = postData;
+ return this.response;
+ }
+ }
+}
diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java
new file mode 100644
index 0000000000..a4bb5db6c2
--- /dev/null
+++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java
@@ -0,0 +1,154 @@
+package me.chanjar.weixin.channel.api.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import me.chanjar.weixin.channel.api.WxChannelService;
+import me.chanjar.weixin.channel.bean.limit.LimitSku;
+import me.chanjar.weixin.channel.bean.limit.LimitTaskParam;
+import me.chanjar.weixin.channel.bean.product.GiftProductInfo;
+import org.testng.annotations.Test;
+
+/**
+ * Verifies domain service accessors and product service compatibility delegates.
+ */
+public class WxChannelServiceImplTest {
+
+ @Test
+ public void shouldKeepNewDomainServiceAccessorsCompatibleWithExistingImplementations() throws Exception {
+ assertDefaultMethod("getGiftService");
+ assertDefaultMethod("getLimitedDiscountService");
+ assertDefaultMethod("getProductStockService");
+ assertDefaultMethod("getProductAssistantService");
+ }
+
+ @Test
+ public void shouldExposeProductDomainServices() {
+ CapturingChannelService channelService = new CapturingChannelService();
+ assertNotNull(channelService.getGiftService());
+ assertNotNull(channelService.getLimitedDiscountService());
+ assertNotNull(channelService.getProductStockService());
+ assertNotNull(channelService.getProductAssistantService());
+ }
+
+ @Test
+ public void shouldRouteGiftProductCallsExactlyOnce() throws Exception {
+ CapturingChannelService channelService = new CapturingChannelService();
+ GiftProductInfo info = new GiftProductInfo();
+ info.setListing(1);
+
+ assertRequest(channelService, new RequestCall() {
+ @Override
+ public void call() throws Exception {
+ channelService.getGiftService().addGiftProduct(info);
+ }
+ }, "https://api.weixin.qq.com/channels/ec/product/gift/add", "{\"listing\":1}");
+ assertRequest(channelService, new RequestCall() {
+ @Override
+ public void call() throws Exception {
+ channelService.getProductService().addGiftProduct(info);
+ }
+ }, "https://api.weixin.qq.com/channels/ec/product/gift/add", "{\"listing\":1}");
+ }
+
+ @Test
+ public void shouldRouteLimitedDiscountCallsExactlyOnce() throws Exception {
+ CapturingChannelService channelService = new CapturingChannelService();
+ LimitTaskParam param = new LimitTaskParam();
+ param.setProductId("product-id");
+ param.setStartTime(new Date(1_000));
+ param.setEndTime(new Date(2_000));
+ param.setSkus(Arrays.asList(new LimitSku("sku-id", 100, 2)));
+
+ assertRequest(channelService, new RequestCall() {
+ @Override
+ public void call() throws Exception {
+ channelService.getLimitedDiscountService().addLimitTask(param);
+ }
+ }, "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/add",
+ "{\"product_id\":\"product-id\",\"start_time\":1000,\"end_time\":2000," +
+ "\"limited_discount_skus\":[{\"sku_id\":\"sku-id\",\"sale_price\":100,\"sale_stock\":2}]}");
+ assertRequest(channelService, new RequestCall() {
+ @Override
+ public void call() throws Exception {
+ channelService.getProductService().addLimitTask(param);
+ }
+ }, "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/add",
+ "{\"product_id\":\"product-id\",\"start_time\":1000,\"end_time\":2000," +
+ "\"limited_discount_skus\":[{\"sku_id\":\"sku-id\",\"sale_price\":100,\"sale_stock\":2}]}");
+ }
+
+ @Test
+ public void shouldRouteStockCallsExactlyOnce() throws Exception {
+ CapturingChannelService channelService = new CapturingChannelService();
+
+ assertRequest(channelService, new RequestCall() {
+ @Override
+ public void call() throws Exception {
+ channelService.getProductStockService().updateStock("product-id", "sku-id", 1, 2);
+ }
+ }, "https://api.weixin.qq.com/channels/ec/product/stock/update",
+ "{\"product_id\":\"product-id\",\"sku_id\":\"sku-id\",\"diff_type\":1,\"num\":2}");
+ assertRequest(channelService, new RequestCall() {
+ @Override
+ public void call() throws Exception {
+ channelService.getProductService().updateStock("product-id", "sku-id", 1, 2);
+ }
+ }, "https://api.weixin.qq.com/channels/ec/product/stock/update",
+ "{\"product_id\":\"product-id\",\"sku_id\":\"sku-id\",\"diff_type\":1,\"num\":2}");
+ }
+
+ private void assertDefaultMethod(String methodName) throws Exception {
+ Method method = WxChannelService.class.getMethod(methodName);
+ assertTrue(method.isDefault(), methodName + " must remain compatible with existing implementations");
+ }
+
+ private void assertRequest(CapturingChannelService channelService, RequestCall call,
+ String expectedUrl, String expectedJson) throws Exception {
+ channelService.clearRequests();
+ call.call();
+ List requests = channelService.getRequests();
+ assertEquals(requests.size(), 1);
+ assertEquals(requests.get(0).url, expectedUrl);
+ assertEquals(requests.get(0).json, expectedJson);
+ }
+
+ private interface RequestCall {
+ void call() throws Exception;
+ }
+
+ private static class CapturingChannelService extends WxChannelServiceImpl {
+ private final List requests = new ArrayList<>();
+
+ @Override
+ public String post(String url, String postData) {
+ this.requests.add(new Request(url, postData));
+ return "{\"errcode\":0}";
+ }
+
+ private List getRequests() {
+ return new ArrayList<>(this.requests);
+ }
+
+ private void clearRequests() {
+ this.requests.clear();
+ }
+ }
+
+ private static class Request {
+ private final String url;
+ private final String json;
+
+ private Request(String url, String json) {
+ this.url = url;
+ this.json = json;
+ }
+
+ }
+}
diff --git a/weixin-java-channel/src/test/resources/testng.xml b/weixin-java-channel/src/test/resources/testng.xml
index 819ebcf5f9..caa7a2c548 100644
--- a/weixin-java-channel/src/test/resources/testng.xml
+++ b/weixin-java-channel/src/test/resources/testng.xml
@@ -16,4 +16,12 @@
+
+
+
+
+
+
+
+