diff --git a/.gitignore b/.gitignore index 34150b7b3..1dc25f0c5 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,9 @@ sonar-project.properties !/.mvn/wrapper/maven-wrapper.jar *.versionsBackup +# Local Superpowers planning artifacts +docs/superpowers/ + # STS .factorypath *.zip diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductService.java index ce85e9f0a..fb019eefe 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductService.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductService.java @@ -13,6 +13,26 @@ 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.AddProductThirdPartySourceParam; +import me.chanjar.weixin.channel.bean.product.AddProductThirdPartySourceResponse; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingNewParam; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingNewResponse; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingParam; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditQuotaResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategyResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategySetParam; +import me.chanjar.weixin.channel.bean.product.ProductBrandRecommendParam; +import me.chanjar.weixin.channel.bean.product.ProductBrandRecommendResponse; +import me.chanjar.weixin.channel.bean.product.ProductCategoryClassifyParam; +import me.chanjar.weixin.channel.bean.product.ProductCategoryClassifyResponse; +import me.chanjar.weixin.channel.bean.product.ProductCategoryPreCheckParam; +import me.chanjar.weixin.channel.bean.product.ProductCategoryPreCheckResponse; +import me.chanjar.weixin.channel.bean.product.ProductSchemeParam; +import me.chanjar.weixin.channel.bean.product.ProductSchemeResponse; +import me.chanjar.weixin.channel.bean.product.ProductStockFlowParam; +import me.chanjar.weixin.channel.bean.product.ProductStockFlowResponse; +import me.chanjar.weixin.channel.bean.product.ProductTimingSaleParam; import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse; import me.chanjar.weixin.channel.bean.product.SkuStockResponse; import me.chanjar.weixin.channel.bean.product.SpuFastInfo; @@ -213,6 +233,123 @@ WxChannelBaseResponse updateStock(String productId, String skuId, Integer diffTy */ ProductTagLinkResponse getProductTagLink(String productId) throws WxErrorException; + /** + * 获取商品的移动应用跳转 scheme 码. + * + * @param param 商品 ID、来源 appid、过期时间和附加信息 + * @return 商品跳转 scheme 码 + * @throws WxErrorException 调用微信接口失败 + */ + ProductSchemeResponse getProductScheme(ProductSchemeParam param) throws WxErrorException; + + /** + * 商品类目推荐. + * + * @param param 请求类型、商品标题、主图和可选类目 ID;当请求类型为 2 时必须提供类目 ID + * @return 推荐类目及店铺经营权限 + * @throws WxErrorException 调用微信接口失败 + */ + ProductCategoryClassifyResponse classifyProductCategory(ProductCategoryClassifyParam param) throws WxErrorException; + + /** + * 将定时开售商品改为立即开售. + * + * @param param 商品 ID 和定时开售任务 ID + * @return 操作结果 + * @throws WxErrorException 调用微信接口失败 + */ + WxChannelBaseResponse beginTimingSale(ProductTimingSaleParam param) throws WxErrorException; + + /** + * 取消商品开售. + * + * @param productId 商品 ID + * @return 操作结果 + * @throws WxErrorException 调用微信接口失败 + */ + WxChannelBaseResponse cancelTimingSale(String productId) throws WxErrorException; + + /** + * 查询站内外商品属性映射. + * + * @param param 叶子类目 ID、外部类目和外部属性 + * @return 对应的站内属性及可选属性值 + * @throws WxErrorException 调用微信接口失败 + */ + ExternalProductMappingResponse externalProductMapping(ExternalProductMappingParam param) throws WxErrorException; + + /** + * 发品前校验店铺类目资质. + * + * @param param 待发布商品的叶子类目 ID + * @return 校验结果和未通过原因 + * @throws WxErrorException 调用微信接口失败 + */ + ProductCategoryPreCheckResponse categoryPreCheck(ProductCategoryPreCheckParam param) throws WxErrorException; + + /** + * 获取店铺维度的商品上架策略. + * + * @return 当前上架策略 + * @throws WxErrorException 调用微信接口失败 + */ + ProductAuditStrategyResponse getProductAuditStrategy() throws WxErrorException; + + /** + * 设置店铺维度的商品上架策略. + * + * @param param 要设置的上架策略 + * @return 操作结果 + * @throws WxErrorException 调用微信接口失败 + */ + WxChannelBaseResponse setProductAuditStrategy(ProductAuditStrategySetParam param) throws WxErrorException; + + /** + * 获取当前店铺的商品提审限额. + * + * @return 提审总额度和新品剩余额度 + * @throws WxErrorException 调用微信接口失败 + */ + ProductAuditQuotaResponse getProductAuditQuota() throws WxErrorException; + + /** + * 商品属性映射及推荐. + * + * @param param 叶子类目、商品标题、主图及可选的外部属性 + * @return 推荐的站内属性 + * @throws WxErrorException 调用微信接口失败 + */ + ExternalProductMappingNewResponse externalProductMappingNew(ExternalProductMappingNewParam param) + throws WxErrorException; + + /** + * 根据商品信息推荐店铺已有资质的品牌. + * + * @param param 商品叶子类目、标题和图片 + * @return 推荐品牌 + * @throws WxErrorException 调用微信接口失败 + */ + ProductBrandRecommendResponse productBrandRecommend(ProductBrandRecommendParam param) throws WxErrorException; + + /** + * 新增第三方货源信息. + * + * @param param 场景、发布方式、货主及货源商品信息 + * @return 包含第三方货源 ID 的操作结果 + * @throws WxErrorException 调用微信接口失败 + */ + AddProductThirdPartySourceResponse addProductThirdPartySource(AddProductThirdPartySourceParam param) + throws WxErrorException; + + /** + * 获取商品库存流水. + * + * @param param 商品、SKU、库存类型、时间范围和分页参数;pageSize 必填,stockType 为 1 时 finderId 必填,库存类型非 0 和 1 时 stockTypeId 必填 + * @return 库存流水及下一页标识 + * @throws WxErrorException 调用微信接口失败 + */ + ProductStockFlowResponse getStockFlow(ProductStockFlowParam param) throws WxErrorException; + /** * 添加非卖商品 * 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 a7fc91c84..fb00f1874 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 @@ -2,18 +2,45 @@ 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_PRODUCT_THIRD_PARTY_SOURCE_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_AUDIT_STRATEGY_GET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_AUDIT_STRATEGY_SET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_BEGIN_TIMING_SALE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_CANCEL_TIMING_SALE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_CATEGORY_CLASSIFY_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_CATEGORY_PRE_CHECK_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_EXTERNAL_PRODUCT_MAPPING_NEW_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_EXTERNAL_PRODUCT_MAPPING_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_AUDIT_QUOTA_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_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_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_PRODUCT_BRAND_RECOMMEND_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_QRCODE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_SCHEME_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_TAGLINK_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_UPDATE_URL; +import java.util.Collections; import java.util.List; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.channel.api.WxChannelGiftService; @@ -24,6 +51,13 @@ 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.product.AddProductThirdPartySourceParam; +import me.chanjar.weixin.channel.bean.product.AddProductThirdPartySourceResponse; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingNewParam; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingNewResponse; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingParam; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingResponse; +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; @@ -31,6 +65,21 @@ 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.ProductAuditQuotaResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategyResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategySetParam; +import me.chanjar.weixin.channel.bean.product.ProductBrandRecommendParam; +import me.chanjar.weixin.channel.bean.product.ProductBrandRecommendResponse; +import me.chanjar.weixin.channel.bean.product.ProductCategoryClassifyParam; +import me.chanjar.weixin.channel.bean.product.ProductCategoryClassifyResponse; +import me.chanjar.weixin.channel.bean.product.ProductCategoryPreCheckParam; +import me.chanjar.weixin.channel.bean.product.ProductCategoryPreCheckResponse; +import me.chanjar.weixin.channel.bean.product.ProductSchemeParam; +import me.chanjar.weixin.channel.bean.product.ProductSchemeResponse; +import me.chanjar.weixin.channel.bean.product.ProductStockFlowParam; +import me.chanjar.weixin.channel.bean.product.ProductStockFlowResponse; +import me.chanjar.weixin.channel.bean.product.ProductTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.SkuStockBatchParam; import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse; import me.chanjar.weixin.channel.bean.product.SkuStockResponse; import me.chanjar.weixin.channel.bean.product.SpuFastInfo; @@ -216,6 +265,85 @@ public ProductTagLinkResponse getProductTagLink(String productId) throws WxError return ResponseUtils.decode(resJson, ProductTagLinkResponse.class); } + @Override + public ProductSchemeResponse getProductScheme(ProductSchemeParam param) throws WxErrorException { + return postAndDecode(SPU_SCHEME_URL, param, ProductSchemeResponse.class); + } + + @Override + public ProductCategoryClassifyResponse classifyProductCategory(ProductCategoryClassifyParam param) + throws WxErrorException { + return postAndDecode(SPU_CATEGORY_CLASSIFY_URL, param, ProductCategoryClassifyResponse.class); + } + + @Override + public WxChannelBaseResponse beginTimingSale(ProductTimingSaleParam param) throws WxErrorException { + return postAndDecode(SPU_BEGIN_TIMING_SALE_URL, param, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse cancelTimingSale(String productId) throws WxErrorException { + return postAndDecode(SPU_CANCEL_TIMING_SALE_URL, Collections.singletonMap("product_id", productId), + WxChannelBaseResponse.class); + } + + @Override + public ExternalProductMappingResponse externalProductMapping(ExternalProductMappingParam param) + throws WxErrorException { + return postAndDecode(SPU_EXTERNAL_PRODUCT_MAPPING_URL, param, ExternalProductMappingResponse.class); + } + + @Override + public ProductCategoryPreCheckResponse categoryPreCheck(ProductCategoryPreCheckParam param) + throws WxErrorException { + return postAndDecode(SPU_CATEGORY_PRE_CHECK_URL, param, ProductCategoryPreCheckResponse.class); + } + + @Override + public ProductAuditStrategyResponse getProductAuditStrategy() throws WxErrorException { + return postAndDecode(SPU_AUDIT_STRATEGY_GET_URL, "{}", ProductAuditStrategyResponse.class); + } + + @Override + public WxChannelBaseResponse setProductAuditStrategy(ProductAuditStrategySetParam param) throws WxErrorException { + return postAndDecode(SPU_AUDIT_STRATEGY_SET_URL, param, WxChannelBaseResponse.class); + } + + @Override + public ProductAuditQuotaResponse getProductAuditQuota() throws WxErrorException { + return postAndDecode(SPU_GET_AUDIT_QUOTA_URL, "{}", ProductAuditQuotaResponse.class); + } + + @Override + public ExternalProductMappingNewResponse externalProductMappingNew(ExternalProductMappingNewParam param) + throws WxErrorException { + return postAndDecode(SPU_EXTERNAL_PRODUCT_MAPPING_NEW_URL, param, ExternalProductMappingNewResponse.class); + } + + @Override + public ProductBrandRecommendResponse productBrandRecommend(ProductBrandRecommendParam param) + throws WxErrorException { + return postAndDecode(SPU_PRODUCT_BRAND_RECOMMEND_URL, param, ProductBrandRecommendResponse.class); + } + + @Override + public AddProductThirdPartySourceResponse addProductThirdPartySource(AddProductThirdPartySourceParam param) + throws WxErrorException { + return postAndDecode(SPU_ADD_PRODUCT_THIRD_PARTY_SOURCE_URL, param, AddProductThirdPartySourceResponse.class); + } + + @Override + public ProductStockFlowResponse getStockFlow(ProductStockFlowParam param) throws WxErrorException { + return postAndDecode(SPU_GET_STOCK_FLOW_URL, param, ProductStockFlowResponse.class); + } + + private T postAndDecode(String url, Object param, Class responseType) + throws WxErrorException { + String reqJson = param instanceof String ? (String) param : JsonUtils.encode(param); + String resJson = shopService.post(url, reqJson); + return ResponseUtils.decode(resJson, responseType); + } + @Override public GiftProductAddResponse addGiftProduct(GiftProductInfo info) throws WxErrorException { return giftService.addGiftProduct(info); diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AddProductThirdPartySourceParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AddProductThirdPartySourceParam.java new file mode 100644 index 000000000..6f258358b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AddProductThirdPartySourceParam.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import java.io.Serializable; +import lombok.Data; + +/** 新增第三方货源信息请求参数. */ +@Data +public class AddProductThirdPartySourceParam implements Serializable { + private static final long serialVersionUID = -5784320217481497742L; + + @JsonProperty("scene_value") + private Integer sceneValue; + @JsonProperty("publish_method") + private Integer publishMethod; + private JsonNode supplier; + @JsonProperty("supplier_shop_performance") + private JsonNode supplierShopPerformance; + @JsonProperty("product_source_info") + private JsonNode productSourceInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AddProductThirdPartySourceResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AddProductThirdPartySourceResponse.java new file mode 100644 index 000000000..aec4cef99 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/AddProductThirdPartySourceResponse.java @@ -0,0 +1,16 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 新增第三方货源信息响应. */ +@Data +@EqualsAndHashCode(callSuper = true) +public class AddProductThirdPartySourceResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -7528226120383065861L; + + @JsonProperty("third_party_source_id") + private Long thirdPartySourceId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingNewParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingNewParam.java new file mode 100644 index 000000000..d78ac8d31 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingNewParam.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** 商品属性映射及推荐请求参数. */ +@Data +public class ExternalProductMappingNewParam implements Serializable { + private static final long serialVersionUID = -7982070319116550518L; + + @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; + private String title; + @JsonProperty("external_attributes") + private List externalAttributes; + + @Data + public static class ExternalAttribute implements Serializable { + private static final long serialVersionUID = 300805187240781417L; + private String key; + private String value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingNewResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingNewResponse.java new file mode 100644 index 000000000..8342071a4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingNewResponse.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 商品属性映射及推荐响应. */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ExternalProductMappingNewResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 4536547956225312823L; + + @JsonProperty("attributes") + private List attributes; + + /** 推荐属性. */ + @Data + @NoArgsConstructor + public static class Attribute implements Serializable { + private static final long serialVersionUID = -4072024462101489333L; + + private String key; + private String value; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingParam.java new file mode 100644 index 000000000..849ec64ae --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingParam.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** 站内外商品属性映射请求参数. */ +@Data +public class ExternalProductMappingParam implements Serializable { + private static final long serialVersionUID = 3288069294712374035L; + + @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/ExternalProductMappingResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingResponse.java new file mode 100644 index 000000000..8899edcfd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ExternalProductMappingResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 站内外商品属性映射响应. */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ExternalProductMappingResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -8356596972896906087L; + + @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/ProductAuditQuotaResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditQuotaResponse.java new file mode 100644 index 000000000..2f17d0e18 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditQuotaResponse.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 商品提审限额响应. */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ProductAuditQuotaResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -6242837308752181147L; + + @JsonProperty("audit_quota") + private AuditQuota auditQuota; + + @Data + public static class AuditQuota implements Serializable { + private static final long serialVersionUID = 6066821247844334714L; + + @JsonProperty("block_status") + private Integer blockStatus; + @JsonProperty("avail_quota") + private Integer availQuota; + @JsonProperty("total_quota") + private Integer totalQuota; + @JsonProperty("unlimited_type") + private Integer unlimitedType; + @JsonProperty("audit_total_quota") + private Integer auditTotalQuota; + @JsonProperty("audit_total_remaining") + private Integer auditTotalRemaining; + @JsonProperty("new_product_total_quota") + private Integer newProductTotalQuota; + @JsonProperty("new_product_remaining") + private Integer newProductRemaining; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategyInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategyInfo.java new file mode 100644 index 000000000..5bcdcacb8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategyInfo.java @@ -0,0 +1,18 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** 商品上架策略信息. */ +@Data +public class ProductAuditStrategyInfo implements Serializable { + private static final long serialVersionUID = -2747596416115475981L; + + @JsonProperty("hide_err_field_flag") + private Integer hideErrFieldFlag; + @JsonProperty("hit_duplicated_flag") + private Integer hitDuplicatedFlag; + @JsonProperty("hit_low_risk_rule_flag") + private Integer hitLowRiskRuleFlag; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategyResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategyResponse.java new file mode 100644 index 000000000..92684bcba --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategyResponse.java @@ -0,0 +1,16 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 商品上架策略响应. */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ProductAuditStrategyResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = -1074784511408331849L; + + @JsonProperty("audit_strategy") + private ProductAuditStrategyInfo auditStrategy; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategySetParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategySetParam.java new file mode 100644 index 000000000..b07ff314e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductAuditStrategySetParam.java @@ -0,0 +1,14 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** 设置商品上架策略请求参数. */ +@Data +public class ProductAuditStrategySetParam implements Serializable { + private static final long serialVersionUID = 7542738744842032508L; + + @JsonProperty("audit_strategy") + private ProductAuditStrategyInfo auditStrategy; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductBrandRecommendParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductBrandRecommendParam.java new file mode 100644 index 000000000..1fbdc0bb0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductBrandRecommendParam.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** 商品品牌推荐请求参数. */ +@Data +public class ProductBrandRecommendParam implements Serializable { + private static final long serialVersionUID = 6462717198206491138L; + + @JsonProperty("cat_id") + private Long catId; + @JsonProperty("head_imgs") + private List headImgs; + @JsonProperty("detail_imgs") + private List detailImgs; + private String title; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductBrandRecommendResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductBrandRecommendResponse.java new file mode 100644 index 000000000..59344058f --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductBrandRecommendResponse.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 商品品牌推荐响应. */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ProductBrandRecommendResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 4350605866373432810L; + + @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/ProductCategoryClassifyParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryClassifyParam.java new file mode 100644 index 000000000..2e0a45243 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryClassifyParam.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** 商品类目推荐请求参数. */ +@Data +public class ProductCategoryClassifyParam implements Serializable { + private static final long serialVersionUID = 4665563979720739777L; + + @JsonProperty("req_type") + private Integer reqType; + private String title; + @JsonProperty("head_imgs") + private List headImgs; + @JsonProperty("cat_id") + private String catId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryClassifyResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryClassifyResponse.java new file mode 100644 index 000000000..acf31b7f2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryClassifyResponse.java @@ -0,0 +1,48 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 商品类目推荐响应. */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ProductCategoryClassifyResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 8258747142248203374L; + + private List categories; + @JsonProperty("wrong_cat") + private Boolean wrongCat; + + @Data + public static class CategoryInfo implements Serializable { + private static final long serialVersionUID = -4800760946330901306L; + + private List cats; + } + + @Data + public static class CategoryLevel implements Serializable { + private static final long serialVersionUID = 8010801623725584755L; + + @JsonProperty("cat_info") + private Category catInfo; + @JsonProperty("has_permission") + private Boolean hasPermission; + } + + @Data + public static class Category implements Serializable { + private static final long serialVersionUID = -9013991576741902059L; + + @JsonProperty("cat_id") + private String catId; + @JsonProperty("cat_name") + private String catName; + @JsonProperty("is_shop_no_audit") + private Boolean shopNoAudit; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryPreCheckParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryPreCheckParam.java new file mode 100644 index 000000000..aab647472 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryPreCheckParam.java @@ -0,0 +1,14 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** 发品前校验请求参数. */ +@Data +public class ProductCategoryPreCheckParam implements Serializable { + private static final long serialVersionUID = 5155253060483296766L; + + @JsonProperty("cat_id") + private Long catId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryPreCheckResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryPreCheckResponse.java new file mode 100644 index 000000000..42a8221a7 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductCategoryPreCheckResponse.java @@ -0,0 +1,19 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 发品前校验响应. */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ProductCategoryPreCheckResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 7136603000806024499L; + + @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/ProductSchemeParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSchemeParam.java new file mode 100644 index 000000000..f63774c32 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSchemeParam.java @@ -0,0 +1,19 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** 获取商品移动应用跳转 scheme 码请求参数. */ +@Data +public class ProductSchemeParam implements Serializable { + private static final long serialVersionUID = 613832623081127830L; + + @JsonProperty("product_id") + private String productId; + @JsonProperty("from_appid") + private String fromAppid; + private Integer expire; + @JsonProperty("ext_info") + private String extInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSchemeResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSchemeResponse.java new file mode 100644 index 000000000..d44c6b4a1 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductSchemeResponse.java @@ -0,0 +1,14 @@ +package me.chanjar.weixin.channel.bean.product; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 获取商品移动应用跳转 scheme 码响应. */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ProductSchemeResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 7310433919100539990L; + + private String openlink; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductStockFlowParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductStockFlowParam.java new file mode 100644 index 000000000..1354705bd --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductStockFlowParam.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** 获取库存流水请求参数. */ +@Data +public class ProductStockFlowParam implements Serializable { + private static final long serialVersionUID = -407227347279113050L; + + @JsonProperty("product_id") + private String productId; + @JsonProperty("sku_id") + private String skuId; + @JsonProperty("stock_type") + private Integer stockType; + @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; + @JsonProperty("stock_type_id") + private String stockTypeId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductStockFlowResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductStockFlowResponse.java new file mode 100644 index 000000000..d94df28e8 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductStockFlowResponse.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** 获取库存流水响应. */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ProductStockFlowResponse extends WxChannelBaseResponse { + private static final long serialVersionUID = 7600529379926896515L; + + private StockFlowData data; + + @Data + public static class StockFlowData implements Serializable { + private static final long serialVersionUID = -4963813730951045381L; + + @JsonProperty("stock_flow_info_list") + private List stockFlowInfoList; + @JsonProperty("next_key") + private String nextKey; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductTimingSaleParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductTimingSaleParam.java new file mode 100644 index 000000000..bb0e17396 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/ProductTimingSaleParam.java @@ -0,0 +1,16 @@ +package me.chanjar.weixin.channel.bean.product; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; + +/** 商品立即开售请求参数. */ +@Data +public class ProductTimingSaleParam implements Serializable { + private static final long serialVersionUID = -7185451543781817487L; + + @JsonProperty("product_id") + private String productId; + @JsonProperty("task_id") + private Long taskId; +} 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 92ece47b2..149bd0390 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 @@ -158,16 +158,40 @@ public interface Spu { String SPU_H5URL_URL = "https://api.weixin.qq.com/channels/ec/product/h5url/get"; /** 获取商品二维码 */ String SPU_QRCODE_URL = "https://api.weixin.qq.com/channels/ec/product/qrcode/get"; + /** 获取商品移动应用跳转 scheme 码 */ + String SPU_SCHEME_URL = "https://api.weixin.qq.com/channels/ec/product/scheme/get"; /** 获取商品口令 */ String SPU_TAGLINK_URL = "https://api.weixin.qq.com/channels/ec/product/taglink/get"; + /** 商品类目推荐 */ + String SPU_CATEGORY_CLASSIFY_URL = "https://api.weixin.qq.com/channels/ec/product/category/classify"; + /** 商品立即开售 */ + String SPU_BEGIN_TIMING_SALE_URL = "https://api.weixin.qq.com/channels/ec/product/begintimingsale"; + /** 取消商品开售 */ + String SPU_CANCEL_TIMING_SALE_URL = "https://api.weixin.qq.com/channels/ec/product/canceltimingsale"; + /** 站内外商品属性映射 */ + String SPU_EXTERNAL_PRODUCT_MAPPING_URL = "https://api.weixin.qq.com/channels/ec/product/externalproductmapping"; + /** 发品前校验 */ + String SPU_CATEGORY_PRE_CHECK_URL = "https://api.weixin.qq.com/channels/ec/product/categoryprecheck"; + /** 获取商品上架策略 */ + String SPU_AUDIT_STRATEGY_GET_URL = "https://api.weixin.qq.com/channels/ec/product/auditstrategy/get"; + /** 设置商品上架策略 */ + String SPU_AUDIT_STRATEGY_SET_URL = "https://api.weixin.qq.com/channels/ec/product/auditstrategy/set"; + /** 获取商品提审限额 */ + String SPU_GET_AUDIT_QUOTA_URL = "https://api.weixin.qq.com/channels/ec/product/getauditquota"; + /** 商品属性映射及推荐 */ + String SPU_EXTERNAL_PRODUCT_MAPPING_NEW_URL = "https://api.weixin.qq.com/channels/ec/product/externalproductmappingnew"; + /** 商品品牌推荐 */ + String SPU_PRODUCT_BRAND_RECOMMEND_URL = "https://api.weixin.qq.com/channels/ec/product/productbrandrecommend"; + /** 新增第三方货源信息 */ + String SPU_ADD_PRODUCT_THIRD_PARTY_SOURCE_URL = "https://api.weixin.qq.com/channels/ec/product/addproductthirdpartysource"; /** 获取实时库存 */ String SPU_GET_STOCK_URL = "https://api.weixin.qq.com/channels/ec/product/stock/get"; + /** 获取库存流水 */ + String SPU_GET_STOCK_FLOW_URL = "https://api.weixin.qq.com/channels/ec/product/stock/getflow"; /** 获取实时库存 */ 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"; /** 更新非卖商品 */ diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductManagementServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductManagementServiceImplTest.java new file mode 100644 index 000000000..9ccaeed3d --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductManagementServiceImplTest.java @@ -0,0 +1,283 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.AddProductThirdPartySourceParam; +import me.chanjar.weixin.channel.bean.product.AddProductThirdPartySourceResponse; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingNewParam; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingNewResponse; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingParam; +import me.chanjar.weixin.channel.bean.product.ExternalProductMappingResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditQuotaResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategyInfo; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategyResponse; +import me.chanjar.weixin.channel.bean.product.ProductAuditStrategySetParam; +import me.chanjar.weixin.channel.bean.product.ProductBrandRecommendParam; +import me.chanjar.weixin.channel.bean.product.ProductBrandRecommendResponse; +import me.chanjar.weixin.channel.bean.product.ProductCategoryClassifyParam; +import me.chanjar.weixin.channel.bean.product.ProductCategoryClassifyResponse; +import me.chanjar.weixin.channel.bean.product.ProductCategoryPreCheckParam; +import me.chanjar.weixin.channel.bean.product.ProductCategoryPreCheckResponse; +import me.chanjar.weixin.channel.bean.product.ProductSchemeParam; +import me.chanjar.weixin.channel.bean.product.ProductSchemeResponse; +import me.chanjar.weixin.channel.bean.product.ProductStockFlowParam; +import me.chanjar.weixin.channel.bean.product.ProductStockFlowResponse; +import me.chanjar.weixin.channel.bean.product.ProductTimingSaleParam; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Test; + +/** Tests the product-management endpoint contracts without making network calls. */ +public class WxChannelProductManagementServiceImplTest { + + @Test + public void shouldGetProductScheme() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"openlink\":\"weixin://dl/business/?t=abc\"}"); + ProductSchemeParam param = new ProductSchemeParam(); + param.setProductId("10001"); + param.setFromAppid("wx-app"); + param.setExpire(3600); + param.setExtInfo("source"); + + ProductSchemeResponse result = productService(channelService).getProductScheme(param); + + assertRequest(channelService, "/channels/ec/product/scheme/get", "{\"product_id\":\"10001\",\"from_appid\":\"wx-app\",\"expire\":3600,\"ext_info\":\"source\"}"); + assertEquals(result.getOpenlink(), "weixin://dl/business/?t=abc"); + } + + @Test + public void shouldClassifyProductCategoryAndDecodePermission() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"categories\":[{\"cats\":[{\"cat_info\":{\"cat_id\":\"6000\",\"cat_name\":\"童鞋\"},\"has_permission\":true}]}],\"wrong_cat\":false}"); + ProductCategoryClassifyParam param = new ProductCategoryClassifyParam(); + param.setReqType(2); + param.setTitle("儿童雨鞋"); + param.setHeadImgs(Arrays.asList("https://example.com/image")); + param.setCatId("6000"); + + ProductCategoryClassifyResponse result = productService(channelService).classifyProductCategory(param); + + assertRequest(channelService, "/channels/ec/product/category/classify", "{\"req_type\":2,\"title\":\"儿童雨鞋\",\"head_imgs\":[\"https://example.com/image\"],\"cat_id\":\"6000\"}"); + assertEquals(result.getCategories().get(0).getCats().get(0).getCatInfo().getCatId(), "6000"); + assertTrue(result.getCategories().get(0).getCats().get(0).getHasPermission()); + } + + @Test + public void shouldBeginTimingSale() throws WxErrorException { + RecordingChannelService channelService = response(okResponse()); + ProductTimingSaleParam param = new ProductTimingSaleParam(); + param.setProductId("10001"); + param.setTaskId(123L); + + WxChannelBaseResponse result = productService(channelService).beginTimingSale(param); + + assertRequest(channelService, "/channels/ec/product/begintimingsale", "{\"product_id\":\"10001\",\"task_id\":123}"); + assertTrue(result.isSuccess()); + } + + @Test + public void shouldCancelTimingSale() throws WxErrorException { + RecordingChannelService channelService = response(okResponse()); + + WxChannelBaseResponse result = productService(channelService).cancelTimingSale("10001"); + + assertRequest(channelService, "/channels/ec/product/canceltimingsale", "{\"product_id\":\"10001\"}"); + assertTrue(result.isSuccess()); + } + + @Test + public void shouldMapExternalProductAttribute() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"external_attribute_name\":\"材质\",\"external_attribute_value\":\"塑胶\",\"internal_attribute_name\":\"鞋面材质\",\"internal_attribute_value\":[\"塑胶\"]}"); + ExternalProductMappingParam param = new ExternalProductMappingParam(); + param.setCatId(6000L); + param.setExternalAttributeName("材质"); + param.setExternalAttributeValue("塑胶"); + param.setExternalCategoryName("母婴:童鞋"); + + ExternalProductMappingResponse result = productService(channelService).externalProductMapping(param); + + assertRequest(channelService, "/channels/ec/product/externalproductmapping", "{\"cat_id\":6000,\"external_attribute_name\":\"材质\",\"external_attribute_value\":\"塑胶\",\"external_category_name\":\"母婴:童鞋\"}"); + assertEquals(result.getInternalAttributeValue(), Arrays.asList("塑胶")); + } + + @Test + public void shouldPreCheckCategory() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"all_pass\":false,\"fail_reasons\":[\"缺少资质\"]}"); + ProductCategoryPreCheckParam param = new ProductCategoryPreCheckParam(); + param.setCatId(6000L); + + ProductCategoryPreCheckResponse result = productService(channelService).categoryPreCheck(param); + + assertRequest(channelService, "/channels/ec/product/categoryprecheck", "{\"cat_id\":6000}"); + assertEquals(result.getFailReasons(), Arrays.asList("缺少资质")); + } + + @Test + public void shouldGetProductAuditStrategy() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"audit_strategy\":{\"hide_err_field_flag\":1,\"hit_duplicated_flag\":0,\"hit_low_risk_rule_flag\":1}}"); + + ProductAuditStrategyResponse result = productService(channelService).getProductAuditStrategy(); + + assertRequest(channelService, "/channels/ec/product/auditstrategy/get", "{}"); + assertEquals(result.getAuditStrategy().getHideErrFieldFlag(), Integer.valueOf(1)); + } + + @Test + public void shouldSetProductAuditStrategy() throws WxErrorException { + RecordingChannelService channelService = response(okResponse()); + ProductAuditStrategyInfo strategy = new ProductAuditStrategyInfo(); + strategy.setHideErrFieldFlag(1); + ProductAuditStrategySetParam param = new ProductAuditStrategySetParam(); + param.setAuditStrategy(strategy); + + WxChannelBaseResponse result = productService(channelService).setProductAuditStrategy(param); + + assertRequest(channelService, "/channels/ec/product/auditstrategy/set", "{\"audit_strategy\":{\"hide_err_field_flag\":1}}"); + assertTrue(result.isSuccess()); + } + + @Test + public void shouldGetProductAuditQuota() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"audit_quota\":{\"block_status\":0,\"avail_quota\":20,\"total_quota\":100,\"unlimited_type\":0,\"audit_total_quota\":100,\"audit_total_remaining\":80,\"new_product_total_quota\":50,\"new_product_remaining\":40}}"); + + ProductAuditQuotaResponse result = productService(channelService).getProductAuditQuota(); + + assertRequest(channelService, "/channels/ec/product/getauditquota", "{}"); + assertEquals(result.getAuditQuota().getNewProductRemaining(), Integer.valueOf(40)); + } + + @Test + public void shouldMapAndRecommendExternalProductAttributes() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"attributes\":[{\"key\":\"鞋面材质\",\"value\":\"塑胶\"}]}"); + ExternalProductMappingNewParam.ExternalAttribute attribute = new ExternalProductMappingNewParam.ExternalAttribute(); + attribute.setKey("材质"); + attribute.setValue("塑胶"); + ExternalProductMappingNewParam param = new ExternalProductMappingNewParam(); + param.setCatId(6000L); + param.setExternalCategoryName("母婴:童鞋"); + param.setHeadImgs(Arrays.asList("https://example.com/head")); + param.setTitle("儿童雨鞋"); + param.setExternalAttributes(Arrays.asList(attribute)); + + ExternalProductMappingNewResponse result = productService(channelService).externalProductMappingNew(param); + + assertRequest(channelService, "/channels/ec/product/externalproductmappingnew", "{\"cat_id\":6000,\"external_category_name\":\"母婴:童鞋\",\"head_imgs\":[\"https://example.com/head\"],\"title\":\"儿童雨鞋\",\"external_attributes\":[{\"key\":\"材质\",\"value\":\"塑胶\"}]}"); + assertTrue(result.getAttributes().get(0) instanceof ExternalProductMappingNewResponse.Attribute); + assertEquals(result.getAttributes().get(0).getKey(), "鞋面材质"); + } + + @Test + public void shouldRecommendProductBrand() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"brand_id\":123,\"brand_name_chinese\":\"品牌\",\"brand_name_english\":\"Brand\"}"); + ProductBrandRecommendParam param = new ProductBrandRecommendParam(); + param.setCatId(6000L); + param.setHeadImgs(Arrays.asList("https://example.com/head")); + param.setTitle("商品"); + + ProductBrandRecommendResponse result = productService(channelService).productBrandRecommend(param); + + assertRequest(channelService, "/channels/ec/product/productbrandrecommend", "{\"cat_id\":6000,\"head_imgs\":[\"https://example.com/head\"],\"title\":\"商品\"}"); + assertEquals(result.getBrandId(), Long.valueOf(123)); + } + + @Test + public void shouldAddThirdPartyProductSource() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"third_party_source_id\":12345}"); + AddProductThirdPartySourceParam param = new AddProductThirdPartySourceParam(); + param.setSceneValue(1); + param.setPublishMethod(2); + + AddProductThirdPartySourceResponse result = productService(channelService).addProductThirdPartySource(param); + + assertRequest(channelService, "/channels/ec/product/addproductthirdpartysource", "{\"scene_value\":1,\"publish_method\":2}"); + assertEquals(result.getThirdPartySourceId(), Long.valueOf(12345)); + } + + @Test + public void shouldGetStockFlowWithRequiredPaginationAndStockTypeId() throws WxErrorException { + RecordingChannelService channelService = response("{\"errcode\":0,\"errmsg\":\"ok\",\"data\":{\"stock_flow_info_list\":[{\"amount\":2}],\"next_key\":\"next\"}}"); + ProductStockFlowParam param = new ProductStockFlowParam(); + param.setProductId("10001"); + param.setSkuId("10002"); + param.setStockType(2); + param.setStockTypeId("activity-1"); + param.setBeginTime(100L); + param.setEndTime(200L); + param.setPageSize(10); + + ProductStockFlowResponse result = productService(channelService).getStockFlow(param); + + assertRequest(channelService, "/channels/ec/product/stock/getflow", "{\"product_id\":\"10001\",\"sku_id\":\"10002\",\"stock_type\":2,\"begin_time\":100,\"end_time\":200,\"page_size\":10,\"stock_type_id\":\"activity-1\"}"); + assertEquals(result.getData().getStockFlowInfoList().size(), 1); + assertEquals(result.getData().getNextKey(), "next"); + } + + @Test + public void shouldSerializeNestedProductManagementResponses() throws IOException { + ProductAuditQuotaResponse.AuditQuota auditQuota = new ProductAuditQuotaResponse.AuditQuota(); + ProductCategoryClassifyResponse.Category category = new ProductCategoryClassifyResponse.Category(); + ProductCategoryClassifyResponse.CategoryLevel categoryLevel = new ProductCategoryClassifyResponse.CategoryLevel(); + ProductCategoryClassifyResponse.CategoryInfo categoryInfo = new ProductCategoryClassifyResponse.CategoryInfo(); + ProductStockFlowResponse.StockFlowData stockFlowData = new ProductStockFlowResponse.StockFlowData(); + + assertSerializable(auditQuota); + assertSerializable(category); + assertSerializable(categoryLevel); + assertSerializable(categoryInfo); + assertSerializable(stockFlowData); + } + + private static WxChannelProductServiceImpl productService(RecordingChannelService channelService) { + return new WxChannelProductServiceImpl(channelService); + } + + private static RecordingChannelService response(String response) { + return new RecordingChannelService(response); + } + + private static String okResponse() { + return "{\"errcode\":0,\"errmsg\":\"ok\"}"; + } + + private static void assertRequest(RecordingChannelService channelService, String path, String expectedJson) { + assertTrue(channelService.getUrl().endsWith(path)); + assertEquals(JsonUtils.decode(channelService.getRequestJson(), Object.class), JsonUtils.decode(expectedJson, Object.class)); + } + + private static void assertSerializable(Object value) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try (ObjectOutputStream objectOutput = new ObjectOutputStream(output)) { + objectOutput.writeObject(value); + } + } + + private static class RecordingChannelService extends WxChannelServiceHttpClientImpl { + private final String response; + private String url; + private String requestJson; + + RecordingChannelService(String response) { + this.response = response; + } + + @Override + public String post(String url, String postData) { + this.url = url; + this.requestJson = postData; + return response; + } + + String getUrl() { + return url; + } + + String getRequestJson() { + return requestJson; + } + } +} diff --git a/weixin-java-channel/src/test/resources/testng.xml b/weixin-java-channel/src/test/resources/testng.xml index caa7a2c54..4e7e2e951 100644 --- a/weixin-java-channel/src/test/resources/testng.xml +++ b/weixin-java-channel/src/test/resources/testng.xml @@ -3,6 +3,7 @@ +