Browse Source

:new: #3404 【小程序】增加同城配送相关接口,同时为WxMaService增加了API签名支持

GeXiangDong 4 months ago
parent
commit
cff5616463
35 changed files with 3626 additions and 574 deletions
  1. BIN
      images/api-signature/api-signature-1.png
  2. BIN
      images/api-signature/api-signature-2.png
  3. 46 0
      weixin-java-miniapp/api-signature-readme.md
  4. 86 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaIntracityService.java
  5. 63 37
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
  6. 327 47
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
  7. 276 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaIntracityServiceImpl.java
  8. 34 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaApiResponse.java
  9. 128 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/BasicWxMaOrder.java
  10. 49 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/BasicWxMaStoreChargeRefundRequest.java
  11. 16 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/PayMode.java
  12. 133 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaAddOrderRequest.java
  13. 115 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaAddOrderResponse.java
  14. 67 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaCancelOrderResponse.java
  15. 42 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaGetPayModeResponse.java
  16. 344 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaOrder.java
  17. 22 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaPreAddOrderRequest.java
  18. 88 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaQueryFlowRequest.java
  19. 187 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaStore.java
  20. 115 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaStoreBalance.java
  21. 22 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaStoreChargeRequest.java
  22. 318 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaStoreFlowResponse.java
  23. 11 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaStoreRefundRequest.java
  24. 56 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaTransCity.java
  25. 41 26
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java
  26. 73 43
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java
  27. 306 323
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java
  28. 71 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheApiSignaturePostRequestExecutor.java
  29. 69 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApiSignaturePostRequestExecutor.java
  30. 59 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddApiSignaturePostRequestExecutor.java
  31. 51 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpApiSignaturePostRequestExecutor.java
  32. 234 0
      weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaIntracityServiceImpleTest.java
  33. 5 0
      weixin-java-miniapp/src/test/resources/test-config-sample.xml
  34. 44 21
      weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenConfigStorage.java
  35. 128 77
      weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java

BIN
images/api-signature/api-signature-1.png


BIN
images/api-signature/api-signature-2.png


+ 46 - 0
weixin-java-miniapp/api-signature-readme.md

@@ -0,0 +1,46 @@
+# 使用API签名
+
+如果对API签名不了解,可先阅读微信文档 [服务端API签名指南](https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/getting_started/api_signature.html)
+
+有API数据加密与签名两种功能,此处按照微信文档,简称为签名。
+
+## 程序内设置
+
+[WxMaConfig](src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java)类增加了几个属性,分别对应小程序内设置的签名密钥等。
+
+* apiSignatureAesKey
+* apiSignatureAesKeySn
+* apiSignatureRsaPrivateKey
+* apiSignatureRsaPrivateKeySn
+
+这4个属性需要按照小程序后台设置。
+
+## 小程序后台设置/查看签名用密钥
+
+在小程序后台,开发管理 -> 开发设置 -> API安全处,可以看到如下图界面。
+
+![图一](../images/api-signature/api-signature-1.png)
+
+上图中A处对应 apiSignatureAesKeySn; B处对应apiSignatureAesKey; C处对应apiSignatureRsaPrivateKeySn
+
+apiSignatureRsaPrivateKey 在上图中**无**对应,C处右侧是公钥,apiSignatureRsaPrivateKey 需要的是私钥。
+
+可点击图上右上角的修改,打开如下图的设置页面
+
+![图二](../images/api-signature/api-signature-2.png)
+
+首先确保对称密钥选中 AES256,非对称密钥选中RSA。不要选SM4和SM2。
+(如果需要支持SM4/SM2,可修改BaseWxMaServiceImpl.java中postWithSignature方法中相应部分实现)。
+
+在API非对称密钥中下方左侧有个「随机生成密钥对」,点击它,然后点它右侧的「下载私钥」,之后点击「确认」保存。
+下载得到的文件是PKCS1格式的私钥,用openssl可转成PKCS8格式。apiSignatureRsaPrivateKey 需要设置的是PKCS8格式的私钥。
+
+注意:
+
+1. 如果不先点击「随机生成密钥对」,直接点击「下载私钥」得到的是公钥,公钥在这里没有用途。
+2. 打开下载的文件,第一行是「-----BEGIN RSA PRIVATE KEY-----」说明是PKCS1格式私钥。
+3. PKCS8格式第一行是「-----BEGIN PRIVATE KEY-----」
+4. 转换命令 `openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in PKCS1格式密钥文件名 -out 新的PKCS8格式密钥文件名`
+5. 如果密钥文件有 PUBLIC KEY 字样,说明下载了公钥,重新生成密钥对,下载私钥
+
+

+ 86 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaIntracityService.java

@@ -0,0 +1,86 @@
+package cn.binarywang.wx.miniapp.api;
+
+import cn.binarywang.wx.miniapp.bean.intractiy.*;
+import java.util.List;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+/**
+ * 微信小程序 物流服务 同城配送服务API <br>
+ * *不是*即时配送接口,两个相近,容易混淆<br>
+ * 微信相关接口 <br>
+ * https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/intracity_service.html
+ */
+public interface WxMaIntracityService {
+
+  /** 申请开通门店权限 */
+  void apply() throws WxErrorException;
+
+  /** 创建门店 */
+  String createStore(WxMaStore store) throws WxErrorException;
+
+  /**
+   * 更新门店;只更新store中不为null的部分 wxStoreId和outStoreId至少要有一个不为null,根据这2个来更新。 仅支持更新 storeName orderPattern
+   * serviceTransPrefer addressInfo几个属性
+   */
+  void updateStore(WxMaStore store) throws WxErrorException;
+
+  /** 查询门店(列出所有门店) */
+  List<WxMaStore> listAllStores() throws WxErrorException;
+
+  /** 根据wx_store_id查询门店 */
+  WxMaStore queryStoreByWxStoreId(String wxStoreId) throws WxErrorException;
+
+  /** 根据 out_store_id 查询门店 */
+  List<WxMaStore> queryStoreByOutStoreId(String outStoreId) throws WxErrorException;
+
+  /** 门店运费充值,返回充值URL */
+  String storeCharge(WxMaStoreChargeRequest request) throws WxErrorException;
+
+  /** 门店运费退款,返回退款金额 */
+  int storeRefund(WxMaStoreRefundRequest request) throws WxErrorException;
+
+  /** 门店运费流水查询 */
+  WxMaStoreFlowResponse<? extends WxMaStoreFlowResponse.BasicFlowRecord> queryFlow(
+      WxMaQueryFlowRequest request) throws WxErrorException;
+
+  /** 查询门店余额 */
+  WxMaStoreBalance balanceQuery(String wxStoreId, String serviceTransId, PayMode payMode)
+      throws WxErrorException;
+
+  /**
+   * 设置扣费主体 <br>
+   * 接口调用成功后,小程序的管理员会收到模板消息,点击模板消息确认更改门店扣费主体后,修改生效。
+   */
+  void setPayMode(PayMode payMode) throws WxErrorException;
+
+  /** 查询扣费主体 */
+  WxMaGetPayModeResponse getPayMode() throws WxErrorException;
+
+  /** 查询运费 */
+  WxMaAddOrderResponse preAddOrder(WxMaPreAddOrderRequest request) throws WxErrorException;
+
+  /** 创建配送单 */
+  WxMaAddOrderResponse addOrder(WxMaAddOrderRequest order) throws WxErrorException;
+
+  /** 查询配送单 根据wxOrderId */
+  WxMaOrder queryOrderByWxOrderId(String wxOrderId) throws WxErrorException;
+
+  /** 依据商户订单号 查询配送单 */
+  WxMaOrder queryOrderByStoreOrderId(String wxStoreId, String storeOrderId) throws WxErrorException;
+
+  /** 依据微信订单号 查询配送单 */
+  WxMaCancelOrderResponse cancelOrderByWxOrderId(
+      String wxOrderId, int cancelReasonId, String cancelReason) throws WxErrorException;
+
+  /** 依据商户订单号 查询配送单 */
+  WxMaCancelOrderResponse cancelOrderByStoreOrderId(
+      String wxStoreId, String storeOrderId, int cancelReasonId, String cancelReason)
+      throws WxErrorException;
+
+  /**
+   * 查询支持同城配送的城市
+   *
+   * @param serviceTransId 运力ID,传NULL则返回所有
+   */
+  List<WxMaTransCity> getCity(String serviceTransId) throws WxErrorException;
+}

+ 63 - 37
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java

@@ -1,7 +1,11 @@
 package cn.binarywang.wx.miniapp.api;
 
+import cn.binarywang.wx.miniapp.bean.WxMaApiResponse;
 import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
+import cn.binarywang.wx.miniapp.executor.ApiSignaturePostRequestExecutor;
+import com.google.gson.JsonObject;
+import java.util.Map;
 import java.util.function.Function;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.service.WxImgProcService;
@@ -11,33 +15,25 @@ import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 
-import java.util.Map;
-
 /**
  * The interface Wx ma service.
  *
  * @author <a href="https://github.com/binarywang">Binary Wang</a>
  */
 public interface WxMaService extends WxService {
-  /**
-   * 获取access_token.
-   */
-  String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
-  String GET_STABLE_ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/stable_token";
+  /** 获取access_token. */
+  String GET_ACCESS_TOKEN_URL =
+      "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
 
+  String GET_STABLE_ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/stable_token";
 
-  /**
-   * The constant JSCODE_TO_SESSION_URL.
-   */
+  /** The constant JSCODE_TO_SESSION_URL. */
   String JSCODE_TO_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session";
-  /**
-   * getPaidUnionId
-   */
+
+  /** getPaidUnionId */
   String GET_PAID_UNION_ID_URL = "https://api.weixin.qq.com/wxa/getpaidunionid";
 
-  /**
-   * 导入抽样数据
-   */
+  /** 导入抽样数据 */
   String SET_DYNAMIC_DATA_URL = "https://api.weixin.qq.com/wxa/setdynamicdata";
 
   /**
@@ -51,6 +47,7 @@ public interface WxMaService extends WxService {
 
   /**
    * 导入抽样数据
+   *
    * <pre>
    * 第三方通过调用微信API,将数据写入到setdynamicdata这个API。每个Post数据包不超过5K,若数据过多可开多进(线)程并发导入数据(例如:数据量为十万量级可以开50个线程并行导数据)。
    * 文档地址:https://wsad.weixin.qq.com/wsad/zh_CN/htmledition/widget-docs-v3/html/custom/quickstart/implement/import/index.html
@@ -58,21 +55,23 @@ public interface WxMaService extends WxService {
    * </pre>
    *
    * @param lifespan 数据有效时间,秒为单位,一般为86400,一天一次导入的频率
-   * @param type     用于标识数据所属的服务类目
-   * @param scene    1代表用于搜索的数据
-   * @param data     推送到微信后台的数据列表,该数据被微信用于流量分配,注意该字段为string类型而不是object
+   * @param type 用于标识数据所属的服务类目
+   * @param scene 1代表用于搜索的数据
+   * @param data 推送到微信后台的数据列表,该数据被微信用于流量分配,注意该字段为string类型而不是object
    * @throws WxErrorException .
    */
   void setDynamicData(int lifespan, String type, int scene, String data) throws WxErrorException;
 
   /**
+   *
+   *
    * <pre>
    * 验证消息的确来自微信服务器.
    * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN
    * </pre>
    *
    * @param timestamp the timestamp
-   * @param nonce     the nonce
+   * @param nonce the nonce
    * @param signature the signature
    * @return the boolean
    */
@@ -88,6 +87,8 @@ public interface WxMaService extends WxService {
   String getAccessToken() throws WxErrorException;
 
   /**
+   *
+   *
    * <pre>
    * 获取access_token,本方法线程安全.
    * 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限
@@ -106,6 +107,8 @@ public interface WxMaService extends WxService {
   String getAccessToken(boolean forceRefresh) throws WxErrorException;
 
   /**
+   *
+   *
    * <pre>
    * 用户支付完成后,获取该用户的 UnionId,无需用户授权。本接口支持第三方平台代理查询。
    *
@@ -114,33 +117,45 @@ public interface WxMaService extends WxService {
    * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/api/getPaidUnionId.html
    * </pre>
    *
-   * @param openid        必填 支付用户唯一标识
+   * @param openid 必填 支付用户唯一标识
    * @param transactionId 非必填 微信支付订单号
-   * @param mchId         非必填 微信支付分配的商户号,和商户订单号配合使用
-   * @param outTradeNo    非必填  微信支付商户订单号,和商户号配合使用
+   * @param mchId 非必填 微信支付分配的商户号,和商户订单号配合使用
+   * @param outTradeNo 非必填 微信支付商户订单号,和商户号配合使用
    * @return UnionId. paid union id
    * @throws WxErrorException .
    */
-  String getPaidUnionId(String openid, String transactionId, String mchId, String outTradeNo) throws WxErrorException;
+  String getPaidUnionId(String openid, String transactionId, String mchId, String outTradeNo)
+      throws WxErrorException;
 
   /**
+   *
+   *
    * <pre>
    * Service没有实现某个API的时候,可以用这个,
    * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。
    * 可以参考,{@link MediaUploadRequestExecutor}的实现方法
    * </pre>
    *
-   * @param <T>      .
-   * @param <E>      .
+   * @param <T> .
+   * @param <E> .
    * @param executor 执行器
-   * @param uri      接口请求地址
-   * @param data     参数或请求数据
+   * @param uri 接口请求地址
+   * @param data 参数或请求数据
    * @return . t
    * @throws WxErrorException the wx error exception
    */
   <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException;
 
+  WxMaApiResponse execute(
+      ApiSignaturePostRequestExecutor executor,
+      String uri,
+      Map<String, String> headers,
+      String data)
+      throws WxErrorException;
+
   /**
+   *
+   *
    * <pre>
    * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试.
    * 默认:1000ms
@@ -151,6 +166,8 @@ public interface WxMaService extends WxService {
   void setRetrySleepMillis(int retrySleepMillis);
 
   /**
+   *
+   *
    * <pre>
    * 设置当微信系统响应系统繁忙时,最大重试次数.
    * 默认:5次
@@ -177,7 +194,7 @@ public interface WxMaService extends WxService {
   /**
    * Map里 加入新的 {@link WxMaConfig},适用于动态添加新的微信公众号配置.
    *
-   * @param miniappId     小程序标识
+   * @param miniappId 小程序标识
    * @param configStorage 新的微信配置
    */
   void addConfig(String miniappId, WxMaConfig configStorage);
@@ -190,8 +207,8 @@ public interface WxMaService extends WxService {
   void removeConfig(String miniappId);
 
   /**
-   * 注入多个 {@link WxMaConfig} 的实现. 并为每个 {@link WxMaConfig} 赋予不同的 {@link String mpId} 值
-   * 随机采用一个{@link String mpId}进行Http初始化操作
+   * 注入多个 {@link WxMaConfig} 的实现. 并为每个 {@link WxMaConfig} 赋予不同的 {@link String mpId} 值 随机采用一个{@link
+   * String mpId}进行Http初始化操作
    *
    * @param configs WxMaConfig map
    */
@@ -200,7 +217,7 @@ public interface WxMaService extends WxService {
   /**
    * 注入多个 {@link WxMaConfig} 的实现. 并为每个 {@link WxMaConfig} 赋予不同的 {@link String label} 值
    *
-   * @param configs          WxMaConfig map
+   * @param configs WxMaConfig map
    * @param defaultMiniappId 设置一个{@link WxMaConfig} 所对应的{@link String defaultMiniappId}进行Http初始化
    */
   void setMultiConfigs(Map<String, WxMaConfig> configs, String defaultMiniappId);
@@ -328,9 +345,7 @@ public interface WxMaService extends WxService {
    */
   WxMaPluginService getPluginService();
 
-  /**
-   * 初始化http请求对象.
-   */
+  /** 初始化http请求对象. */
   void initHttp();
 
   /**
@@ -403,7 +418,6 @@ public interface WxMaService extends WxService {
    */
   WxMaShopAfterSaleService getShopAfterSaleService();
 
-
   /**
    * 返回小程序交易组件-物流服务接口
    *
@@ -411,7 +425,6 @@ public interface WxMaService extends WxService {
    */
   WxMaShopDeliveryService getShopDeliveryService();
 
-
   /**
    * 返回小程序交易组件-订单服务接口
    *
@@ -544,18 +557,21 @@ public interface WxMaService extends WxService {
    * @return getWxMaOpenApiService
    */
   WxMaOpenApiService getWxMaOpenApiService();
+
   /**
    * 小程序短剧管理
    *
    * @return getWxMaVodService
    */
   WxMaVodService getWxMaVodService();
+
   /**
    * 小程序虚拟支付
    *
    * @return getWxMaXPayService
    */
   WxMaXPayService getWxMaXPayService();
+
   WxMaExpressDeliveryReturnService getWxMaExpressDeliveryReturnService();
 
   /**
@@ -564,4 +580,14 @@ public interface WxMaService extends WxService {
    * @return WxMaPromotionService
    */
   WxMaPromotionService getWxMaPromotionService();
+
+  String postWithSignature(String url, Object obj) throws WxErrorException;
+
+  String postWithSignature(String url, JsonObject jsonObject) throws WxErrorException;
+
+  /**
+   * 微信物流服务 -- 同城配送
+   * https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/intracity_service.html
+   */
+  WxMaIntracityService getIntracityService();
 }

+ 327 - 47
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java

@@ -1,16 +1,35 @@
 package cn.binarywang.wx.miniapp.api.impl;
 
 import cn.binarywang.wx.miniapp.api.*;
+import cn.binarywang.wx.miniapp.bean.WxMaApiResponse;
 import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
+import cn.binarywang.wx.miniapp.executor.ApiSignaturePostRequestExecutor;
 import cn.binarywang.wx.miniapp.util.WxMaConfigHolder;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
+import com.google.gson.FieldNamingPolicy;
 import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
 import com.google.gson.JsonObject;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.PSSParameterSpec;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
 import java.util.function.Function;
+import javax.crypto.Cipher;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.api.WxConsts;
 import me.chanjar.weixin.common.bean.CommonUploadParam;
@@ -25,26 +44,65 @@ import me.chanjar.weixin.common.service.WxImgProcService;
 import me.chanjar.weixin.common.service.WxOcrService;
 import me.chanjar.weixin.common.util.DataUtils;
 import me.chanjar.weixin.common.util.crypto.SHA1;
-import me.chanjar.weixin.common.util.http.RequestExecutor;
-import me.chanjar.weixin.common.util.http.RequestHttp;
-import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
-import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
+import me.chanjar.weixin.common.util.http.*;
 import me.chanjar.weixin.common.util.json.GsonParser;
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 import org.apache.commons.lang3.StringUtils;
 
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
-
 /**
  * @author <a href="https://github.com/binarywang">Binary Wang</a>
  * @see #doGetAccessTokenRequest
  */
 @Slf4j
 public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestHttp<H, P> {
+  /**
+   * 开启API签名验证后需要API签名的接口,根据 https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/
+   * 整理,uri包含下这些字符串且配置了api signature aes ras key 自动用签名接口
+   */
+  protected static final String[] urlPathSupportApiSignature =
+      new String[] {
+        "cgi-bin/clear_quota",
+        "cgi-bin/openapi/quota/get",
+        "cgi-bin/openapi/rid/get",
+        "wxa/getpluginopenpid",
+        "wxa/business/checkencryptedmsg",
+        "wxa/business/getuserencryptkey",
+        "wxa/business/getuserphonenumber",
+        "wxa/getwxacode",
+        "wxa/getwxacodeunlimit",
+        "cgi-bin/wxaapp/createwxaqrcode",
+        "cgi-bin/message/custom/send",
+        "cgi-bin/message/wxopen/updatablemsg/send",
+        "wxaapi/newtmpl/deltemplate",
+        "cgi-bin/message/subscribe/send",
+        "wxaapi/newtmpl/addtemplate",
+        "wxa/msg_sec_check",
+        "wxa/media_check_async",
+        "wxa/getuserriskrank",
+        "datacube/getweanalysisappidweeklyretaininfo",
+        "datacube/getweanalysisappidmonthlyretaininfo",
+        "datacube/getweanalysisappiddailyretaininfo",
+        "datacube/getweanalysisappidmonthlyvisittrend",
+        "datacube/getweanalysisappiddailyvisittrend",
+        "datacube/getweanalysisappidweeklyvisittrend",
+        "datacube/getweanalysisappiddailysummarytrend",
+        "datacube/getweanalysisappidvisitpage",
+        "datacube/getweanalysisappiduserportrait",
+        "wxa/business/performance/boot",
+        "datacube/getweanalysisappidvisitdistribution",
+        "wxa/getwxadevinfo",
+        "wxaapi/log/get_performance",
+        "wxaapi/log/jserr_detail",
+        "wxaapi/log/jserr_list",
+        "wxa/devplugin",
+        "wxa/plugin",
+        "cgi-bin/express/business/account/getall",
+        "cgi-bin/express/business/delivery/getall",
+        "cgi-bin/express/business/printer/getall",
+        "wxa/servicemarket",
+        "cgi-bin/soter/verify_signature"
+      };
+
   protected static final Gson GSON = new Gson();
   private final WxMaMsgService kefuService = new WxMaMsgServiceImpl(this);
   private final WxMaMediaService materialService = new WxMaMediaServiceImpl(this);
@@ -75,26 +133,33 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
   private final WxMaShopCatService shopCatService = new WxMaShopCatServiceImpl(this);
   private final WxMaShopImgService shopImgService = new WxMaShopImgServiceImpl(this);
   private final WxMaShopAuditService shopAuditService = new WxMaShopAuditServiceImpl(this);
-  private final WxMaShopAfterSaleService shopAfterSaleService = new WxMaShopAfterSaleServiceImpl(this);
+  private final WxMaShopAfterSaleService shopAfterSaleService =
+      new WxMaShopAfterSaleServiceImpl(this);
   private final WxMaShopDeliveryService shopDeliveryService = new WxMaShopDeliveryServiceImpl(this);
   private final WxMaLinkService linkService = new WxMaLinkServiceImpl(this);
-  private final WxMaReimburseInvoiceService reimburseInvoiceService = new WxMaReimburseInvoiceServiceImpl(this);
-  private final WxMaDeviceSubscribeService deviceSubscribeService = new WxMaDeviceSubscribeServiceImpl(this);
+  private final WxMaReimburseInvoiceService reimburseInvoiceService =
+      new WxMaReimburseInvoiceServiceImpl(this);
+  private final WxMaDeviceSubscribeService deviceSubscribeService =
+      new WxMaDeviceSubscribeServiceImpl(this);
   private final WxMaMarketingService marketingService = new WxMaMarketingServiceImpl(this);
-  private final WxMaImmediateDeliveryService immediateDeliveryService = new WxMaImmediateDeliveryServiceImpl(this);
+  private final WxMaImmediateDeliveryService immediateDeliveryService =
+      new WxMaImmediateDeliveryServiceImpl(this);
   private final WxMaShopSharerService shopSharerService = new WxMaShopSharerServiceImpl(this);
   private final WxMaProductService productService = new WxMaProductServiceImpl(this);
   private final WxMaProductOrderService productOrderService = new WxMaProductOrderServiceImpl(this);
   private final WxMaShopCouponService wxMaShopCouponService = new WxMaShopCouponServiceImpl(this);
   private final WxMaShopPayService wxMaShopPayService = new WxMaShopPayServiceImpl(this);
 
-  private final WxMaOrderShippingService wxMaOrderShippingService = new WxMaOrderShippingServiceImpl(this);
+  private final WxMaOrderShippingService wxMaOrderShippingService =
+      new WxMaOrderShippingServiceImpl(this);
 
   private final WxMaOpenApiService wxMaOpenApiService = new WxMaOpenApiServiceImpl(this);
   private final WxMaVodService wxMaVodService = new WxMaVodServiceImpl(this);
   private final WxMaXPayService wxMaXPayService = new WxMaXPayServiceImpl(this);
-  private final WxMaExpressDeliveryReturnService wxMaExpressDeliveryReturnService = new WxMaExpressDeliveryReturnServiceImpl(this);
+  private final WxMaExpressDeliveryReturnService wxMaExpressDeliveryReturnService =
+      new WxMaExpressDeliveryReturnServiceImpl(this);
   private final WxMaPromotionService wxMaPromotionService = new WxMaPromotionServiceImpl(this);
+  private final WxMaIntracityService intracityService = new WxMaIntracityServiceImpl(this);
 
   private Map<String, WxMaConfig> configMap = new HashMap<>();
   private int retrySleepMillis = 1000;
@@ -107,7 +172,7 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
 
   @Override
   public String getPaidUnionId(String openid, String transactionId, String mchId, String outTradeNo)
-    throws WxErrorException {
+      throws WxErrorException {
     Map<String, String> params = new HashMap<>(8);
     params.put("openid", openid);
 
@@ -123,7 +188,8 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
       params.put("out_trade_no", outTradeNo);
     }
 
-    String responseContent = this.get(GET_PAID_UNION_ID_URL, Joiner.on("&").withKeyValueSeparator("=").join(params));
+    String responseContent =
+        this.get(GET_PAID_UNION_ID_URL, Joiner.on("&").withKeyValueSeparator("=").join(params));
     WxError error = WxError.fromJson(responseContent, WxType.MiniApp);
     if (error.getErrorCode() != 0) {
       throw new WxErrorException(error);
@@ -141,12 +207,14 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
     params.put("js_code", jsCode);
     params.put("grant_type", "authorization_code");
 
-    String result = get(JSCODE_TO_SESSION_URL, Joiner.on("&").withKeyValueSeparator("=").join(params));
+    String result =
+        get(JSCODE_TO_SESSION_URL, Joiner.on("&").withKeyValueSeparator("=").join(params));
     return WxMaJscode2SessionResult.fromJson(result);
   }
 
   @Override
-  public void setDynamicData(int lifespan, String type, int scene, String data) throws WxErrorException {
+  public void setDynamicData(int lifespan, String type, int scene, String data)
+      throws WxErrorException {
     JsonObject jsonObject = new JsonObject();
     jsonObject.addProperty("lifespan", lifespan);
     jsonObject.addProperty("query", WxGsonBuilder.create().toJson(ImmutableMap.of("type", type)));
@@ -211,7 +279,6 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
    */
   protected abstract String doGetAccessTokenRequest() throws IOException;
 
-
   /**
    * 通过网络请求获取稳定版接口调用凭据
    *
@@ -225,14 +292,33 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
     return execute(SimpleGetRequestExecutor.create(this), url, queryParam);
   }
 
+  private boolean isApiSignatureRequired(String url) {
+    return this.getWxMaConfig().getApiSignatureAesKey() != null
+        && Arrays.stream(urlPathSupportApiSignature).anyMatch(part -> url.contains(part));
+  }
+
   @Override
   public String post(String url, String postData) throws WxErrorException {
-    return execute(SimplePostRequestExecutor.create(this), url, postData);
+    if (isApiSignatureRequired(url)) {
+      // 接口需要签名
+      log.debug("已经配置接口需要签名,接口{}将走加密访问路径", url);
+      JsonObject jsonObject = GSON.fromJson(postData, JsonObject.class);
+      return postWithSignature(url, jsonObject);
+    } else {
+      return execute(SimplePostRequestExecutor.create(this), url, postData);
+    }
   }
 
   @Override
   public String post(String url, Object obj) throws WxErrorException {
-    return this.execute(SimplePostRequestExecutor.create(this), url, WxGsonBuilder.create().toJson(obj));
+    if (isApiSignatureRequired(url)) {
+      // 接口需要签名
+      log.debug("已经配置接口需要签名,接口{}将走加密访问路径", url);
+      return postWithSignature(url, obj);
+    } else {
+      return this.execute(
+          SimplePostRequestExecutor.create(this), url, WxGsonBuilder.create().toJson(obj));
+    }
   }
 
   @Override
@@ -241,33 +327,66 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
   }
 
   @Override
+  public String post(String url, JsonObject jsonObject) throws WxErrorException {
+    return this.post(url, jsonObject.toString());
+  }
+
+  @Override
   public String upload(String url, CommonUploadParam param) throws WxErrorException {
-    RequestExecutor<String, CommonUploadParam> executor = CommonUploadRequestExecutor.create(getRequestHttp());
+    RequestExecutor<String, CommonUploadParam> executor =
+        CommonUploadRequestExecutor.create(getRequestHttp());
     return this.execute(executor, url, param);
   }
 
+  /** 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 */
   @Override
-  public String post(String url, JsonObject jsonObject) throws WxErrorException {
-    return this.post(url, jsonObject.toString());
+  public <R, T> R execute(RequestExecutor<R, T> executor, String uri, T data)
+      throws WxErrorException {
+    String dataForLog;
+    if (data instanceof String) {
+      dataForLog = DataUtils.handleDataWithSecret((String) data);
+    } else {
+      dataForLog = data.toString();
+    }
+    return excuteWithRetry(
+        (uriWithAccessToken) -> executor.execute(uriWithAccessToken, data, WxType.MiniApp),
+        uri,
+        dataForLog);
   }
 
-  /**
-   * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
-   */
   @Override
-  public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
+  public WxMaApiResponse execute(
+      ApiSignaturePostRequestExecutor executor,
+      String uri,
+      Map<String, String> headers,
+      String data)
+      throws WxErrorException {
+    String dataForLog = "Headers: " + headers.toString() + " Body: " + data;
+    return excuteWithRetry(
+        (uriWithAccessToken) -> executor.execute(uriWithAccessToken, headers, data, WxType.MiniApp),
+        uri,
+        dataForLog);
+  }
+
+  private static interface ExecutorAction<R> {
+    R execute(String urlWithAccessToken) throws IOException, WxErrorException;
+  }
+
+  private <R, T> R excuteWithRetry(ExecutorAction<R> executor, String uri, String dataForLog)
+      throws WxErrorException {
     int retryTimes = 0;
     do {
       try {
-        return this.executeInternal(executor, uri, data, false);
+        return this.executeInternal(executor, uri, dataForLog, false);
       } catch (WxErrorException e) {
         if (retryTimes + 1 > this.maxRetryTimes) {
           log.warn("重试达到最大次数【{}】", maxRetryTimes);
-          //最后一次重试失败后,直接抛出异常,不再等待
-          throw new WxErrorException(WxError.builder()
-            .errorCode(e.getError().getErrorCode())
-            .errorMsg("微信服务端异常,超出重试次数!")
-            .build());
+          // 最后一次重试失败后,直接抛出异常,不再等待
+          throw new WxErrorException(
+              WxError.builder()
+                  .errorCode(e.getError().getErrorCode())
+                  .errorMsg("微信服务端异常,超出重试次数!")
+                  .build());
         }
 
         WxError error = e.getError();
@@ -290,8 +409,9 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
     throw new WxRuntimeException("微信服务端异常,超出重试次数");
   }
 
-  private <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data, boolean doNotAutoRefreshToken) throws WxErrorException {
-    E dataForLog = DataUtils.handleDataWithSecret(data);
+  private <R, T> R executeInternal(
+      ExecutorAction<R> executor, String uri, String dataForLog, boolean doNotAutoRefreshToken)
+      throws WxErrorException {
 
     if (uri.contains("access_token=")) {
       throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri);
@@ -302,10 +422,10 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
       uri = uri.replace("https://api.weixin.qq.com", this.getWxMaConfig().getApiHostUrl());
     }
 
-    String uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "access_token=" + accessToken;
-
+    String uriWithAccessToken =
+        uri + (uri.contains("?") ? "&" : "?") + "access_token=" + accessToken;
     try {
-      T result = executor.execute(uriWithAccessToken, data, WxType.MiniApp);
+      R result = executor.execute(uriWithAccessToken);
       log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, dataForLog, result);
       return result;
     } catch (WxErrorException e) {
@@ -324,10 +444,11 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
           lock.unlock();
         }
         if (this.getWxMaConfig().autoRefreshToken() && !doNotAutoRefreshToken) {
-          log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg());
-          //下一次不再自动重试
-          //当小程序误调用第三方平台专属接口时,第三方无法使用小程序的access token,如果可以继续自动获取token会导致无限循环重试,直到栈溢出
-          return this.executeInternal(executor, uri, data, true);
+          log.warn(
+              "即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg());
+          // 下一次不再自动重试
+          // 当小程序误调用第三方平台专属接口时,第三方无法使用小程序的access token,如果可以继续自动获取token会导致无限循环重试,直到栈溢出
+          return this.executeInternal(executor, uri, dataForLog, true);
         }
       }
 
@@ -337,7 +458,8 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
       }
       return null;
     } catch (IOException e) {
-      log.warn("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage());
+      log.warn(
+          "\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage());
       throw new WxRuntimeException(e);
     }
   }
@@ -712,6 +834,164 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
 
   @Override
   public WxMaPromotionService getWxMaPromotionService() {
-      return this.wxMaPromotionService;
+    return this.wxMaPromotionService;
+  }
+
+  @Override
+  public String postWithSignature(String url, Object obj) throws WxErrorException {
+    Gson gson =
+        new GsonBuilder()
+            .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+            .create();
+    JsonObject jsonObject = gson.toJsonTree(obj).getAsJsonObject();
+    return this.postWithSignature(url, jsonObject);
+  }
+
+  private String generateNonce() {
+    byte[] nonce = generateRandomBytes(16);
+    return base64Encode(nonce).replace("=", "");
+  }
+
+  private byte[] generateRandomBytes(int length) {
+    byte[] bytes = new byte[length];
+    new SecureRandom().nextBytes(bytes);
+    return bytes;
+  }
+
+  private String base64Encode(byte[] data) {
+    return Base64.getEncoder().encodeToString(data);
+  }
+
+  @Override
+  public String postWithSignature(String url, JsonObject jsonObject) throws WxErrorException {
+    long timestamp = System.currentTimeMillis() / 1000;
+    String appId = this.getWxMaConfig().getWechatMpAppid();
+    String rndStr = UUID.randomUUID().toString().replace("-", "").substring(0, 30);
+    String aesKey = this.getWxMaConfig().getApiSignatureAesKey();
+    String aesKeySn = this.getWxMaConfig().getApiSignatureAesKeySn();
+
+    jsonObject.addProperty("_n", rndStr);
+    jsonObject.addProperty("_appid", appId);
+    jsonObject.addProperty("_timestamp", timestamp);
+
+    String plainText = jsonObject.toString();
+    String urlPath;
+    if (url.contains("?")) {
+      urlPath = url.substring(0, url.indexOf("?"));
+    } else {
+      urlPath = url;
+    }
+    String aad = urlPath + "|" + appId + "|" + timestamp + "|" + aesKeySn;
+    byte[] realKey;
+    try {
+      realKey = Base64.getDecoder().decode(aesKey);
+    } catch (Exception ex) {
+      log.error("解析AESKEY失败 {}", aesKey, ex);
+      throw new SecurityException("解析AES KEY失败,请检查ApiSignatureAesKey是否正确", ex);
+    }
+    byte[] realIv = generateRandomBytes(12);
+    byte[] realAad = aad.getBytes(StandardCharsets.UTF_8);
+    byte[] realPlainText = plainText.getBytes(StandardCharsets.UTF_8);
+
+    try {
+      // 加密内容 AES
+      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+      SecretKeySpec aesKeySpec = new SecretKeySpec(realKey, "AES");
+      GCMParameterSpec parameterSpec = new GCMParameterSpec(128, realIv);
+      cipher.init(Cipher.ENCRYPT_MODE, aesKeySpec, parameterSpec);
+      cipher.updateAAD(realAad);
+
+      byte[] ciphertext = cipher.doFinal(realPlainText);
+      byte[] encryptedData = Arrays.copyOfRange(ciphertext, 0, ciphertext.length - 16);
+      byte[] authTag = Arrays.copyOfRange(ciphertext, ciphertext.length - 16, ciphertext.length);
+
+      JsonObject reqData = new JsonObject();
+      reqData.addProperty("iv", base64Encode(realIv));
+      reqData.addProperty("data", base64Encode(encryptedData));
+      reqData.addProperty("authtag", base64Encode(authTag));
+      String requestJson = reqData.toString();
+
+      // 计算签名 RSA
+      String payload = urlPath + "\n" + appId + "\n" + timestamp + "\n" + requestJson;
+      byte[] dataBuffer = payload.getBytes(StandardCharsets.UTF_8);
+      RSAPrivateKey priKey;
+      try {
+        String rsaPrivateKey = this.getWxMaConfig().getApiSignatureRsaPrivateKey();
+        rsaPrivateKey = rsaPrivateKey.replace("-----BEGIN PRIVATE KEY-----", "");
+        rsaPrivateKey = rsaPrivateKey.replace("-----END PRIVATE KEY-----", "");
+        rsaPrivateKey = rsaPrivateKey.replaceAll("\\s+", "");
+        byte[] decoded = Base64.getDecoder().decode(rsaPrivateKey.getBytes(StandardCharsets.UTF_8));
+        PKCS8EncodedKeySpec rsaKeySpec = new PKCS8EncodedKeySpec(decoded);
+        priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(rsaKeySpec);
+      } catch (Exception ex) {
+        log.error("解析RSA KEY失败 {}", aesKey, ex);
+        throw new SecurityException("解析RSA KEY失败,请检查ApiSignatureRsaPrivateKey是否正确,需要PKCS8格式私钥", ex);
+      }
+      Signature signature = Signature.getInstance("RSASSA-PSS");
+      // salt长度,需与SHA256结果长度(32)一致
+      PSSParameterSpec pssParameterSpec =
+          new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1);
+      signature.setParameter(pssParameterSpec);
+      signature.initSign(priKey);
+      signature.update(dataBuffer);
+      byte[] sigBuffer = signature.sign();
+      String signatureString = base64Encode(sigBuffer);
+
+      Map<String, String> header = new HashMap<>();
+      header.put("Wechatmp-Signature", signatureString);
+      header.put("Wechatmp-Appid", appId);
+      header.put("Wechatmp-TimeStamp", String.valueOf(timestamp));
+      log.debug("发送请求uri:{}, headers:{}, postData:{}", url, header, requestJson);
+      WxMaApiResponse response =
+          this.execute(ApiSignaturePostRequestExecutor.create(this), url, header, requestJson);
+      String respTs = response.getHeaders().get("Wechatmp-TimeStamp");
+      String respAad = urlPath + "|" + appId + "|" + respTs + "|" + aesKeySn;
+      if (!appId.equals(response.getHeaders().get("Wechatmp-Appid"))) {
+        throw new RuntimeException("响应的appId不符 " + response.getHeaders().get("Wechatmp-Appid"));
+      }
+      // 省略验证平台签名部分,直接解密内容,返回明文
+      String decryptedData = aesDecodeResponse(response, respAad, aesKeySpec);
+      log.debug("解密后的响应:{}", decryptedData);
+      WxError error = WxError.fromJson(decryptedData, WxType.MiniApp);
+      if (error.getErrorCode() != 0) {
+        log.debug("调用API出错, uri:{}, postData:{}, response:{}", url, plainText, error);
+        throw new WxErrorException(error);
+      }
+      return decryptedData;
+    } catch (WxErrorException | SecurityException ex) {
+      throw ex;
+    } catch (Exception e) {
+      log.error("postWithSignature", e);
+      throw new RuntimeException(e);
+    }
+  }
+
+  private String aesDecodeResponse(WxMaApiResponse response, String aad, SecretKeySpec aesKeySpec)
+      throws Exception {
+    Map<?, ?> map = GSON.fromJson(response.getContent(), Map.class);
+    String iv = (String) map.get("iv");
+    String data = (String) map.get("data");
+    String authTag = (String) map.get("authtag");
+
+    byte[] dataBytes = Base64.getDecoder().decode(data);
+    byte[] authTagBytes = Base64.getDecoder().decode(authTag);
+    byte[] newDataBytes = new byte[dataBytes.length + authTagBytes.length];
+    System.arraycopy(dataBytes, 0, newDataBytes, 0, dataBytes.length);
+    System.arraycopy(authTagBytes, 0, newDataBytes, dataBytes.length, authTagBytes.length);
+    byte[] aadBytes = aad.getBytes(StandardCharsets.UTF_8);
+    byte[] ivBytes = Base64.getDecoder().decode(iv);
+
+    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, ivBytes);
+    cipher.init(Cipher.DECRYPT_MODE, aesKeySpec, gcmParameterSpec);
+    cipher.updateAAD(aadBytes);
+    byte[] decryptedBytes = cipher.doFinal(newDataBytes);
+
+    return new String(decryptedBytes, StandardCharsets.UTF_8);
+  }
+
+  @Override
+  public WxMaIntracityService getIntracityService() {
+    return this.intracityService;
   }
 }

+ 276 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaIntracityServiceImpl.java

@@ -0,0 +1,276 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.Intracity;
+import static me.chanjar.weixin.common.api.WxConsts.ERR_CODE;
+
+import cn.binarywang.wx.miniapp.api.WxMaIntracityService;
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.intractiy.*;
+import com.google.gson.*;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import java.util.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.json.GsonParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@RequiredArgsConstructor
+@Slf4j
+public class WxMaIntracityServiceImpl implements WxMaIntracityService {
+  private final WxMaService wxMaService;
+  private static final Logger logger = LoggerFactory.getLogger(WxMaIntracityServiceImpl.class);
+
+  private final Gson gson =
+      new GsonBuilder()
+          .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+          .create();
+
+  private void checkStringResponse(String response) throws WxErrorException {
+    JsonObject respObj = GsonParser.parse(response);
+    if (respObj.get(ERR_CODE).getAsInt() != 0) {
+      throw new WxErrorException(WxError.fromJson(response, WxType.MiniApp));
+    }
+  }
+
+  @Override
+  public void apply() throws WxErrorException {
+    String response = this.wxMaService.post(Intracity.APPLY_URL, "{}");
+    checkStringResponse(response);
+  }
+
+  @Override
+  public String createStore(WxMaStore store) throws WxErrorException {
+    if (store.getOutStoreId() == null) {
+      throw new IllegalArgumentException("创建门店时outStoreId不能为空");
+    }
+    if (store.getWxStoreId() != null) {
+      throw new IllegalArgumentException("创建门店时wxStoreId只能是null");
+    }
+    String response = this.wxMaService.postWithSignature(Intracity.CREATE_STORE_URL, store);
+    Map<?, ?> map = gson.fromJson(response, Map.class);
+    return (String) map.get("wx_store_id");
+  }
+
+  @Override
+  public void updateStore(WxMaStore store) throws WxErrorException {
+    if (store.getWxStoreId() == null && store.getOutStoreId() == null) {
+      throw new IllegalArgumentException("更新门店时wxStoreId 或 outStoreId 至少要有一个不为null");
+    }
+    JsonObject request = new JsonObject();
+    Map<String, String> keys = new HashMap<>();
+    if (store.getWxStoreId() != null) {
+      keys.put("wx_store_id", store.getWxStoreId());
+    } else {
+      keys.put("out_store_id", store.getOutStoreId());
+    }
+    request.add("keys", gson.toJsonTree(keys));
+    Map<String, Object> updateContent = new HashMap<>();
+    if (store.getStoreName() != null) {
+      updateContent.put("store_name", store.getStoreName());
+    }
+    if (store.getOrderPattern() == 1 || store.getOrderPattern() == 2) {
+      updateContent.put("order_pattern", store.getOrderPattern());
+    }
+    if (store.getServiceTransPrefer() != null) {
+      updateContent.put("service_trans_prefer", store.getServiceTransPrefer());
+    }
+    if (store.getAddressInfo() != null) {
+      updateContent.put("address_info", store.getAddressInfo());
+    }
+    request.add("content", gson.toJsonTree(updateContent));
+    String response = this.wxMaService.postWithSignature(Intracity.UPDATE_STORE_URL, request);
+    checkStringResponse(response);
+  }
+
+  @Override
+  public List<WxMaStore> listAllStores() throws WxErrorException {
+    return queryStore(null, null);
+  }
+
+  @Override
+  public WxMaStore queryStoreByWxStoreId(String wxStoreId) throws WxErrorException {
+    List<WxMaStore> list = queryStore(wxStoreId, null);
+    return list.isEmpty() ? null : list.get(0);
+  }
+
+  @Override
+  public List<WxMaStore> queryStoreByOutStoreId(String outStoreId) throws WxErrorException {
+    return queryStore(null, outStoreId);
+  }
+
+  private List<WxMaStore> queryStore(String wxStoreId, String outStoreId) throws WxErrorException {
+    Map<String, String> map = new HashMap<>();
+    if (wxStoreId != null) {
+      map.put("wx_store_id", wxStoreId);
+    } else if (outStoreId != null) {
+      map.put("out_store_id", outStoreId);
+    }
+    String response = this.wxMaService.postWithSignature(Intracity.QUERY_STORE_URL, map);
+    JsonObject jsonObject = gson.fromJson(response, JsonObject.class);
+    Type listType = new TypeToken<List<WxMaStore>>() {}.getType();
+    return gson.fromJson(jsonObject.getAsJsonArray("store_list"), listType);
+  }
+
+  @Override
+  public String storeCharge(WxMaStoreChargeRequest request) throws WxErrorException {
+    String response = this.wxMaService.postWithSignature(Intracity.STORE_CHARGE, request);
+    Map<?, ?> map = gson.fromJson(response, Map.class);
+    return (String) map.get("payurl");
+  }
+
+  @Override
+  public int storeRefund(WxMaStoreRefundRequest request) throws WxErrorException {
+    String response = this.wxMaService.postWithSignature(Intracity.STORE_REFUND, request);
+    Map<?, ?> map = gson.fromJson(response, Map.class);
+    return ((Number) map.get("refund_amount")).intValue();
+  }
+
+  @Override
+  public WxMaStoreFlowResponse<? extends WxMaStoreFlowResponse.BasicFlowRecord> queryFlow(
+      WxMaQueryFlowRequest request) throws WxErrorException {
+    if (request == null || request.getWxStoreId() == null) {
+      throw new IllegalArgumentException("查询请求 wxStoreId 不可为空");
+    }
+    WxMaStoreFlowResponse<? extends WxMaStoreFlowResponse.BasicFlowRecord> inst =
+        getFlowInstanceByType(request.getFlowType());
+    if (inst == null) {
+      throw new IllegalArgumentException("查询请求 flowType 不正确,只能是1、2、3之一");
+    }
+
+    String response = this.wxMaService.postWithSignature(Intracity.QUERY_FLOW, request);
+
+    WxMaStoreFlowResponse<? extends WxMaStoreFlowResponse.BasicFlowRecord> flowResponse;
+    flowResponse =
+        (WxMaStoreFlowResponse<? extends WxMaStoreFlowResponse.BasicFlowRecord>)
+            gson.fromJson(response, inst.getClass());
+    logger.debug("queryFlow: {}", flowResponse);
+    return flowResponse;
+  }
+
+  private WxMaStoreFlowResponse<? extends WxMaStoreFlowResponse.BasicFlowRecord>
+      getFlowInstanceByType(int flowType) {
+    WxMaStoreFlowResponse<? extends WxMaStoreFlowResponse.BasicFlowRecord> inst;
+    if (flowType == 1) {
+      inst = new WxMaStoreFlowResponse<WxMaStoreFlowResponse.ChargeFlowRecord>();
+    } else if (flowType == 2) {
+      inst = new WxMaStoreFlowResponse<WxMaStoreFlowResponse.RefundFlowRecord>();
+    } else if (flowType == 3) {
+      inst = new WxMaStoreFlowResponse<WxMaStoreFlowResponse.ConsumeFlowRecord>();
+    } else {
+      return null;
+    }
+    return inst;
+  }
+
+  @Override
+  public WxMaStoreBalance balanceQuery(String wxStoreId, String serviceTransId, PayMode payMode)
+      throws WxErrorException {
+    if (wxStoreId == null && (payMode != null && payMode != PayMode.STORE)) {
+      throw new IllegalArgumentException("payMode是PAY_MODE_STORE或null时,必须传递wxStoreId");
+    }
+    Map<String, Object> request = new HashMap<>();
+    if (wxStoreId != null) {
+      request.put("wx_store_id", wxStoreId);
+    }
+    if (serviceTransId != null) {
+      request.put("service_trans_id", serviceTransId);
+    }
+    if (payMode != null) {
+      request.put("pay_mode", payMode);
+    }
+    String response = this.wxMaService.postWithSignature(Intracity.BALANCE_QUERY, request);
+    WxMaStoreBalance balance = gson.fromJson(response, WxMaStoreBalance.class);
+    logger.debug("balance: {}", balance);
+    return balance;
+  }
+
+  public void setPayMode(PayMode payMode) throws WxErrorException {
+    Map<String, Object> request = new HashMap<>();
+    request.put("pay_mode", payMode);
+    request.put("appid", wxMaService.getWxMaConfig().getAppid());
+    String response = this.wxMaService.postWithSignature(Intracity.SET_PAY_MODE, request);
+    checkStringResponse(response);
+  }
+
+  public WxMaGetPayModeResponse getPayMode() throws WxErrorException {
+    Map<String, Object> request = new HashMap<>();
+    request.put("appid", wxMaService.getWxMaConfig().getAppid());
+    String response = this.wxMaService.postWithSignature(Intracity.GET_PAY_MODE, request);
+    return gson.fromJson(response, WxMaGetPayModeResponse.class);
+  }
+
+  @Override
+  public WxMaAddOrderResponse preAddOrder(WxMaPreAddOrderRequest request) throws WxErrorException {
+    String response = this.wxMaService.postWithSignature(Intracity.PRE_ADD_ORDER, request);
+    return gson.fromJson(response, WxMaAddOrderResponse.class);
+  }
+
+  @Override
+  public WxMaAddOrderResponse addOrder(WxMaAddOrderRequest request) throws WxErrorException {
+    String response = this.wxMaService.postWithSignature(Intracity.ADD_ORDER, request);
+    return gson.fromJson(response, WxMaAddOrderResponse.class);
+  }
+
+  @Override
+  public WxMaOrder queryOrderByWxOrderId(String wxOrderId) throws WxErrorException {
+    Map<String, Object> map = new HashMap<>();
+    map.put("wx_order_id", wxOrderId);
+    String response = this.wxMaService.postWithSignature(Intracity.QUERY_ORDER, map);
+    return gson.fromJson(response, WxMaOrder.class);
+  }
+
+  @Override
+  public WxMaOrder queryOrderByStoreOrderId(String wxStoreId, String storeOrderId)
+      throws WxErrorException {
+    Map<String, Object> map = new HashMap<>();
+    map.put("wx_store_id", wxStoreId);
+    map.put("store_order_id", storeOrderId);
+    String response = this.wxMaService.postWithSignature(Intracity.QUERY_ORDER, map);
+    return gson.fromJson(response, WxMaOrder.class);
+  }
+
+  @Override
+  public WxMaCancelOrderResponse cancelOrderByWxOrderId(
+      String wxOrderId, int cancelReasonId, String cancelReason) throws WxErrorException {
+    Map<String, Object> map = new HashMap<>();
+    map.put("wx_order_id", wxOrderId);
+    map.put("cancel_reason_id", cancelReasonId);
+    if (cancelReason != null) {
+      map.put("cancel_reason", cancelReason);
+    }
+    String response = this.wxMaService.postWithSignature(Intracity.CANCEL_ORDER, map);
+    return gson.fromJson(response, WxMaCancelOrderResponse.class);
+  }
+
+  @Override
+  public WxMaCancelOrderResponse cancelOrderByStoreOrderId(
+      String wxStoreId, String storeOrderId, int cancelReasonId, String cancelReason)
+      throws WxErrorException {
+    Map<String, Object> map = new HashMap<>();
+    map.put("wx_store_id", wxStoreId);
+    map.put("store_order_id", storeOrderId);
+    map.put("cancel_reason_id", cancelReasonId);
+    if (cancelReason != null) {
+      map.put("cancel_reason", cancelReason);
+    }
+    String response = this.wxMaService.postWithSignature(Intracity.CANCEL_ORDER, map);
+    return gson.fromJson(response, WxMaCancelOrderResponse.class);
+  }
+
+  @Override
+  public List<WxMaTransCity> getCity(String serviceTransId) throws WxErrorException {
+    Map<String, Object> map = new HashMap<>();
+    if (serviceTransId != null) {
+      map.put("service_trans_id", serviceTransId);
+    }
+    String response = this.wxMaService.postWithSignature(Intracity.GET_CITY, map);
+    JsonObject jsonObject = gson.fromJson(response, JsonObject.class);
+    Type listType = new TypeToken<List<WxMaTransCity>>() {}.getType();
+    return gson.fromJson(jsonObject.getAsJsonArray("support_list"), listType);
+  }
+}

+ 34 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaApiResponse.java

@@ -0,0 +1,34 @@
+package cn.binarywang.wx.miniapp.bean;
+
+import java.util.Map;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WxMaApiResponse {
+  private static final Logger logger = LoggerFactory.getLogger(WxMaApiResponse.class);
+
+  private String content;
+  private Map<String, String> headers;
+
+  public String getContent() {
+    return content;
+  }
+
+  public void setContent(String content) {
+    this.content = content;
+  }
+
+  public Map<String, String> getHeaders() {
+    return headers;
+  }
+
+  public void setHeaders(Map<String, String> headers) {
+    this.headers = headers;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+}

+ 128 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/BasicWxMaOrder.java

@@ -0,0 +1,128 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+class BasicWxMaOrder {
+
+  private String wxStoreId;
+  private String userName;
+  private String userPhone;
+  private double userLng;
+  private double userLat;
+  private String userAddress;
+  private int useSandbox;
+
+  public String getWxStoreId() {
+    return wxStoreId;
+  }
+
+  public void setWxStoreId(String wxStoreId) {
+    this.wxStoreId = wxStoreId;
+  }
+
+  public String getUserName() {
+    return userName;
+  }
+
+  public void setUserName(String userName) {
+    this.userName = userName;
+  }
+
+  public String getUserPhone() {
+    return userPhone;
+  }
+
+  public void setUserPhone(String userPhone) {
+    this.userPhone = userPhone;
+  }
+
+  public double getUserLng() {
+    return userLng;
+  }
+
+  public void setUserLng(double userLng) {
+    this.userLng = userLng;
+  }
+
+  public double getUserLat() {
+    return userLat;
+  }
+
+  public void setUserLat(double userLat) {
+    this.userLat = userLat;
+  }
+
+  public String getUserAddress() {
+    return userAddress;
+  }
+
+  public void setUserAddress(String userAddress) {
+    this.userAddress = userAddress;
+  }
+
+  public int isUseSandbox() {
+    return useSandbox;
+  }
+
+  public void setUseSandbox(int useSandbox) {
+    this.useSandbox = useSandbox;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+
+  static class Cargo {
+    private String cargoName;
+    private int cargoWeight;
+    private int cargoType;
+    private int cargoNum;
+    private int cargoPrice;
+
+    public String getCargoName() {
+      return cargoName;
+    }
+
+    public void setCargoName(String cargoName) {
+      this.cargoName = cargoName;
+    }
+
+    public int getCargoWeight() {
+      return cargoWeight;
+    }
+
+    public void setCargoWeight(int cargoWeight) {
+      this.cargoWeight = cargoWeight;
+    }
+
+    public int getCargoType() {
+      return cargoType;
+    }
+
+    public void setCargoType(int cargoType) {
+      this.cargoType = cargoType;
+    }
+
+    public int getCargoNum() {
+      return cargoNum;
+    }
+
+    public void setCargoNum(int cargoNum) {
+      this.cargoNum = cargoNum;
+    }
+
+    public int getCargoPrice() {
+      return cargoPrice;
+    }
+
+    public void setCargoPrice(int cargoPrice) {
+      this.cargoPrice = cargoPrice;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+}

+ 49 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/BasicWxMaStoreChargeRefundRequest.java

@@ -0,0 +1,49 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+class BasicWxMaStoreChargeRefundRequest {
+
+  /** 微信门店编号 pay_mode = PAY_MODE_STORE时必传,不传pay_mode时必传wx_store_id */
+  private String wxStoreId;
+
+  /**
+   * 充值/扣费主体 <br>
+   * 门店:PAY_MODE_STORE;小程序:PAY_MODE_APP;服务商:PAY_MODE_COMPONENT,不传pay_mode默认pay_mode=PAY_MODE_STORE
+   */
+  private PayMode payMode;
+
+  /**
+   * 运力Id,必填。运力ID请参考:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/intracity_service.html#_6-%E8%BF%90%E5%8A%9B%E5%88%97%E8%A1%A8
+   */
+  private String serviceTransId;
+
+  public String getWxStoreId() {
+    return wxStoreId;
+  }
+
+  public void setWxStoreId(String wxStoreId) {
+    this.wxStoreId = wxStoreId;
+  }
+
+  public PayMode getPayMode() {
+    return payMode;
+  }
+
+  public void setPayMode(PayMode payMode) {
+    this.payMode = payMode;
+  }
+
+  public String getServiceTransId() {
+    return serviceTransId;
+  }
+
+  public void setServiceTransId(String serviceTransId) {
+    this.serviceTransId = serviceTransId;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+}

+ 16 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/PayMode.java

@@ -0,0 +1,16 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import com.google.gson.annotations.SerializedName;
+
+/** 充值、扣费主体 */
+public enum PayMode {
+  /** 门店 */
+  @SerializedName("PAY_MODE_STORE")
+  STORE,
+  /** 小程序 */
+  @SerializedName("PAY_MODE_APP")
+  APP,
+  /** 服务商 */
+  @SerializedName("PAY_MODE_COMPONENT")
+  COMPONENT;
+}

+ 133 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaAddOrderRequest.java

@@ -0,0 +1,133 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import java.util.List;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WxMaAddOrderRequest extends BasicWxMaOrder {
+  private static final Logger logger = LoggerFactory.getLogger(WxMaAddOrderRequest.class);
+  private String storeOrderId;
+  private String userOpenid;
+  private String orderSeq;
+
+  /** 验证码类型 0:不生成 1:生成取货码 2:生成收货码 3:两者都生成 */
+  private int verifyCodeType;
+
+  private String orderDetailPath;
+  private String callbackUrl;
+  private Cargo cargo;
+
+  public String getStoreOrderId() {
+    return storeOrderId;
+  }
+
+  public void setStoreOrderId(String storeOrderId) {
+    this.storeOrderId = storeOrderId;
+  }
+
+  public String getUserOpenid() {
+    return userOpenid;
+  }
+
+  public void setUserOpenid(String userOpenid) {
+    this.userOpenid = userOpenid;
+  }
+
+  public String getOrderSeq() {
+    return orderSeq;
+  }
+
+  public void setOrderSeq(String orderSeq) {
+    this.orderSeq = orderSeq;
+  }
+
+  public int getVerifyCodeType() {
+    return verifyCodeType;
+  }
+
+  public void setVerifyCodeType(int verifyCodeType) {
+    this.verifyCodeType = verifyCodeType;
+  }
+
+  public String getOrderDetailPath() {
+    return orderDetailPath;
+  }
+
+  public void setOrderDetailPath(String orderDetailPath) {
+    this.orderDetailPath = orderDetailPath;
+  }
+
+  public String getCallbackUrl() {
+    return callbackUrl;
+  }
+
+  public void setCallbackUrl(String callbackUrl) {
+    this.callbackUrl = callbackUrl;
+  }
+
+  public Cargo getCargo() {
+    return cargo;
+  }
+
+  public void setCargo(Cargo cargo) {
+    this.cargo = cargo;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+
+  public static class Cargo extends BasicWxMaOrder.Cargo {
+    private List<ItemDetail> itemList;
+
+    public List<ItemDetail> getItemList() {
+      return itemList;
+    }
+
+    public void setItemList(List<ItemDetail> itemList) {
+      this.itemList = itemList;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+
+  public static class ItemDetail {
+    private String itemName;
+    private String itemPicUrl;
+    private int count;
+
+    public String getItemName() {
+      return itemName;
+    }
+
+    public void setItemName(String itemName) {
+      this.itemName = itemName;
+    }
+
+    public String getItemPicUrl() {
+      return itemPicUrl;
+    }
+
+    public void setItemPicUrl(String itemPicUrl) {
+      this.itemPicUrl = itemPicUrl;
+    }
+
+    public int getCount() {
+      return count;
+    }
+
+    public void setCount(int count) {
+      this.count = count;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+}

+ 115 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaAddOrderResponse.java

@@ -0,0 +1,115 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+public class WxMaAddOrderResponse {
+  private String wxOrderId;
+  private String storeOrderId;
+  private String wxStoreId;
+
+  /** 配送运力 */
+  private String serviceTransId;
+
+  /** 配送距离 单位:米 */
+  private int distance;
+
+  /** 运力订单号 */
+  private String transOrderId;
+
+  /** 运力配送单号 */
+  private String waybillId;
+
+  /** 配送费 */
+  private int fee;
+
+  /** 取货码 */
+  private String fetchCode;
+
+  /** 取货序号 */
+  private String orderSeq;
+
+  public String getWxOrderId() {
+    return wxOrderId;
+  }
+
+  public void setWxOrderId(String wxOrderId) {
+    this.wxOrderId = wxOrderId;
+  }
+
+  public String getStoreOrderId() {
+    return storeOrderId;
+  }
+
+  public void setStoreOrderId(String storeOrderId) {
+    this.storeOrderId = storeOrderId;
+  }
+
+  public String getWxStoreId() {
+    return wxStoreId;
+  }
+
+  public void setWxStoreId(String wxStoreId) {
+    this.wxStoreId = wxStoreId;
+  }
+
+  public String getServiceTransId() {
+    return serviceTransId;
+  }
+
+  public void setServiceTransId(String serviceTransId) {
+    this.serviceTransId = serviceTransId;
+  }
+
+  public int getDistance() {
+    return distance;
+  }
+
+  public void setDistance(int distance) {
+    this.distance = distance;
+  }
+
+  public String getTransOrderId() {
+    return transOrderId;
+  }
+
+  public void setTransOrderId(String transOrderId) {
+    this.transOrderId = transOrderId;
+  }
+
+  public String getWaybillId() {
+    return waybillId;
+  }
+
+  public void setWaybillId(String waybillId) {
+    this.waybillId = waybillId;
+  }
+
+  public int getFee() {
+    return fee;
+  }
+
+  public void setFee(int fee) {
+    this.fee = fee;
+  }
+
+  public String getFetchCode() {
+    return fetchCode;
+  }
+
+  public void setFetchCode(String fetchCode) {
+    this.fetchCode = fetchCode;
+  }
+
+  public String getOrderSeq() {
+    return orderSeq;
+  }
+
+  public void setOrderSeq(String orderSeq) {
+    this.orderSeq = orderSeq;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+}

+ 67 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaCancelOrderResponse.java

@@ -0,0 +1,67 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+public class WxMaCancelOrderResponse {
+  private String wxOrderId;
+  private String storeOrderId;
+  private String wxStoreId;
+  private String orderStatus;
+  private String appid;
+
+  /** 违约金 */
+  private int deductfee;
+
+  public String getWxOrderId() {
+    return wxOrderId;
+  }
+
+  public void setWxOrderId(String wxOrderId) {
+    this.wxOrderId = wxOrderId;
+  }
+
+  public String getStoreOrderId() {
+    return storeOrderId;
+  }
+
+  public void setStoreOrderId(String storeOrderId) {
+    this.storeOrderId = storeOrderId;
+  }
+
+  public String getWxStoreId() {
+    return wxStoreId;
+  }
+
+  public void setWxStoreId(String wxStoreId) {
+    this.wxStoreId = wxStoreId;
+  }
+
+  public String getOrderStatus() {
+    return orderStatus;
+  }
+
+  public void setOrderStatus(String orderStatus) {
+    this.orderStatus = orderStatus;
+  }
+
+  public String getAppid() {
+    return appid;
+  }
+
+  public void setAppid(String appid) {
+    this.appid = appid;
+  }
+
+  public int getDeductfee() {
+    return deductfee;
+  }
+
+  public void setDeductfee(int deductfee) {
+    this.deductfee = deductfee;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+}

+ 42 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaGetPayModeResponse.java

@@ -0,0 +1,42 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WxMaGetPayModeResponse {
+  private static final Logger logger = LoggerFactory.getLogger(WxMaGetPayModeResponse.class);
+
+  private PayMode payMode;
+  private String payAppid;
+  private String componentAppid;
+
+  public PayMode getPayMode() {
+    return payMode;
+  }
+
+  public void setPayMode(PayMode payMode) {
+    this.payMode = payMode;
+  }
+
+  public String getPayAppid() {
+    return payAppid;
+  }
+
+  public void setPayAppid(String payAppid) {
+    this.payAppid = payAppid;
+  }
+
+  public String getComponentAppid() {
+    return componentAppid;
+  }
+
+  public void setComponentAppid(String componentAppid) {
+    this.componentAppid = componentAppid;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+}

+ 344 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaOrder.java

@@ -0,0 +1,344 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import java.util.Date;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+public class WxMaOrder extends WxMaAddOrderRequest {
+  private String wxOrderId;
+  private int orderStatus;
+  private String appid;
+  private String serviceTransId;
+  private String deliveryNo;
+  private int actualfee;
+  private int deductfee;
+  private long createTime;
+  private long acceptTime;
+  private long fetchTime;
+  private long finishTime;
+  private long cancelTime;
+  private long expectedFinishTime;
+  private String fetchCode;
+  private String recvCode;
+  private TransporterInfo transporterInfo;
+  private StoreInfo storeInfo;
+  private ReceiverInfo receiverInfo;
+  private Cargo cargoInfo;
+
+  public String getWxOrderId() {
+    return wxOrderId;
+  }
+
+  public void setWxOrderId(String wxOrderId) {
+    this.wxOrderId = wxOrderId;
+  }
+
+  public int getOrderStatus() {
+    return orderStatus;
+  }
+
+  public void setOrderStatus(int orderStatus) {
+    this.orderStatus = orderStatus;
+  }
+
+  public String getAppid() {
+    return appid;
+  }
+
+  public void setAppid(String appid) {
+    this.appid = appid;
+  }
+
+  public String getServiceTransId() {
+    return serviceTransId;
+  }
+
+  public void setServiceTransId(String serviceTransId) {
+    this.serviceTransId = serviceTransId;
+  }
+
+  public String getDeliveryNo() {
+    return deliveryNo;
+  }
+
+  public void setDeliveryNo(String deliveryNo) {
+    this.deliveryNo = deliveryNo;
+  }
+
+  public int getActualfee() {
+    return actualfee;
+  }
+
+  public void setActualfee(int actualfee) {
+    this.actualfee = actualfee;
+  }
+
+  public int getDeductfee() {
+    return deductfee;
+  }
+
+  public void setDeductfee(int deductfee) {
+    this.deductfee = deductfee;
+  }
+
+  public long getCreateTime() {
+    return createTime;
+  }
+
+  public void setCreateTime(long createTime) {
+    this.createTime = createTime;
+  }
+
+  public long getAcceptTime() {
+    return acceptTime;
+  }
+
+  public void setAcceptTime(long acceptTime) {
+    this.acceptTime = acceptTime;
+  }
+
+  public long getFetchTime() {
+    return fetchTime;
+  }
+
+  public void setFetchTime(long fetchTime) {
+    this.fetchTime = fetchTime;
+  }
+
+  public long getFinishTime() {
+    return finishTime;
+  }
+
+  public void setFinishTime(long finishTime) {
+    this.finishTime = finishTime;
+  }
+
+  public long getCancelTime() {
+    return cancelTime;
+  }
+
+  public void setCancelTime(long cancelTime) {
+    this.cancelTime = cancelTime;
+  }
+
+  public long getExpectedFinishTime() {
+    return expectedFinishTime;
+  }
+
+  public void setExpectedFinishTime(long expectedFinishTime) {
+    this.expectedFinishTime = expectedFinishTime;
+  }
+
+  public String getFetchCode() {
+    return fetchCode;
+  }
+
+  public void setFetchCode(String fetchCode) {
+    this.fetchCode = fetchCode;
+  }
+
+  public String getRecvCode() {
+    return recvCode;
+  }
+
+  public void setRecvCode(String recvCode) {
+    this.recvCode = recvCode;
+  }
+
+  public TransporterInfo getTransporterInfo() {
+    return transporterInfo;
+  }
+
+  public void setTransporterInfo(TransporterInfo transporterInfo) {
+    this.transporterInfo = transporterInfo;
+  }
+
+  public StoreInfo getStoreInfo() {
+    return storeInfo;
+  }
+
+  public void setStoreInfo(StoreInfo storeInfo) {
+    this.storeInfo = storeInfo;
+  }
+
+  public ReceiverInfo getReceiverInfo() {
+    return receiverInfo;
+  }
+
+  public void setReceiverInfo(ReceiverInfo receiverInfo) {
+    this.receiverInfo = receiverInfo;
+  }
+
+  public Cargo getCargoInfo() {
+    return cargoInfo;
+  }
+
+  public void setCargoInfo(Cargo cargoInfo) {
+    this.cargoInfo = cargoInfo;
+  }
+
+  public Date getCreateDate() {
+    return createTime == 0 ? null : new Date(createTime * 1000);
+  }
+
+  public Date getAcceptDate() {
+    return acceptTime == 0 ? null : new Date(acceptTime * 1000);
+  }
+
+  public Date getFetchDate() {
+    return fetchTime == 0 ? null : new Date(fetchTime * 1000);
+  }
+
+  public Date getFinishDate() {
+    return finishTime == 0 ? null : new Date(finishTime * 1000);
+  }
+
+  public Date getCancelDate() {
+    return cancelTime == 0 ? null : new Date(cancelTime * 1000);
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+
+  public static class TransporterInfo {
+    private String transporterName;
+    private String transporterPhone;
+
+    public String getTransporterName() {
+      return transporterName;
+    }
+
+    public void setTransporterName(String transporterName) {
+      this.transporterName = transporterName;
+    }
+
+    public String getTransporterPhone() {
+      return transporterPhone;
+    }
+
+    public void setTransporterPhone(String transporterPhone) {
+      this.transporterPhone = transporterPhone;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+
+  public static class StoreInfo {
+    private String storeName;
+    private String wxStoreId;
+    private String address;
+    private double lng;
+    private double lat;
+    private String phoneNum;
+
+    public String getStoreName() {
+      return storeName;
+    }
+
+    public void setStoreName(String storeName) {
+      this.storeName = storeName;
+    }
+
+    public String getWxStoreId() {
+      return wxStoreId;
+    }
+
+    public void setWxStoreId(String wxStoreId) {
+      this.wxStoreId = wxStoreId;
+    }
+
+    public String getAddress() {
+      return address;
+    }
+
+    public void setAddress(String address) {
+      this.address = address;
+    }
+
+    public double getLng() {
+      return lng;
+    }
+
+    public void setLng(double lng) {
+      this.lng = lng;
+    }
+
+    public double getLat() {
+      return lat;
+    }
+
+    public void setLat(double lat) {
+      this.lat = lat;
+    }
+
+    public String getPhoneNum() {
+      return phoneNum;
+    }
+
+    public void setPhoneNum(String phoneNum) {
+      this.phoneNum = phoneNum;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+
+  public static class ReceiverInfo {
+    private String receiverName;
+    private String address;
+    private String phoneNum;
+    private double lng;
+    private double lat;
+
+    public String getReceiverName() {
+      return receiverName;
+    }
+
+    public void setReceiverName(String receiverName) {
+      this.receiverName = receiverName;
+    }
+
+    public String getAddress() {
+      return address;
+    }
+
+    public void setAddress(String address) {
+      this.address = address;
+    }
+
+    public String getPhoneNum() {
+      return phoneNum;
+    }
+
+    public void setPhoneNum(String phoneNum) {
+      this.phoneNum = phoneNum;
+    }
+
+    public double getLng() {
+      return lng;
+    }
+
+    public void setLng(double lng) {
+      this.lng = lng;
+    }
+
+    public double getLat() {
+      return lat;
+    }
+
+    public void setLat(double lat) {
+      this.lat = lat;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+}

+ 22 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaPreAddOrderRequest.java

@@ -0,0 +1,22 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+public class WxMaPreAddOrderRequest extends BasicWxMaOrder {
+  private Cargo cargo;
+
+  public Cargo getCargo() {
+    return cargo;
+  }
+
+  public void setCargo(Cargo cargo) {
+    this.cargo = cargo;
+  }
+
+  public static class Cargo extends BasicWxMaOrder.Cargo {}
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+}

+ 88 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaQueryFlowRequest.java

@@ -0,0 +1,88 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import java.util.Date;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WxMaQueryFlowRequest {
+  private static final Logger logger = LoggerFactory.getLogger(WxMaQueryFlowRequest.class);
+
+  private String wxStoreId;
+
+  /** 流水类型: 1:充值流水, 2:消费流水,3:退款流水 */
+  private int flowType = 1;
+
+  /** 运力ID */
+  private String serviceTransId;
+
+  private transient Date beginDate;
+  private transient Date endDate;
+  private long beginTime;
+  private long endTime;
+
+  public String getWxStoreId() {
+    return wxStoreId;
+  }
+
+  public void setWxStoreId(String wxStoreId) {
+    this.wxStoreId = wxStoreId;
+  }
+
+  public int getFlowType() {
+    return flowType;
+  }
+
+  public void setFlowType(int flowType) {
+    this.flowType = flowType;
+  }
+
+  public String getServiceTransId() {
+    return serviceTransId;
+  }
+
+  public void setServiceTransId(String serviceTransId) {
+    this.serviceTransId = serviceTransId;
+  }
+
+  public Date getBeginDate() {
+    return beginDate;
+  }
+
+  public void setBeginDate(Date beginDate) {
+    this.beginDate = beginDate;
+    this.beginTime = beginDate.getTime() / 1000;
+  }
+
+  public Date getEndDate() {
+    return endDate;
+  }
+
+  public void setEndDate(Date endDate) {
+    this.endDate = endDate;
+    this.endTime = endDate.getTime() / 1000;
+  }
+
+  public long getBeginTime() {
+    return beginTime;
+  }
+
+  public void setBeginTime(long beginTime) {
+    this.beginTime = beginTime;
+    this.beginDate = new Date(beginTime * 1000);
+  }
+
+  public long getEndTime() {
+    return endTime;
+  }
+
+  public void setEndTime(long endTime) {
+    this.endTime = endTime;
+    this.endDate = new Date(endTime * 1000);
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+}

+ 187 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaStore.java

@@ -0,0 +1,187 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WxMaStore {
+  private static final Logger logger = LoggerFactory.getLogger(WxMaStore.class);
+
+  /** 微信分配的ID,创建时不用填写,查询时返回,根据此ID下单等 */
+  private String wxStoreId;
+
+  /** 自己设置的门店ID,创建时填写,查询时返回,不可修改 */
+  private String outStoreId;
+
+  /** 门店名称,创建时需要,可修改;查询结果微信不返回此字段 */
+  private String storeName;
+
+  /** 创建时不用设置,查询时返回,微信自动设置 */
+  private String cityId;
+
+  /** 1:价格优先,2:运力优先 */
+  private int orderPattern = 1;
+
+  /**
+   * 运力优先时优先使用的运力。运力ID请参考:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/express/business/intracity_service.html#_6-%E8%BF%90%E5%8A%9B%E5%88%97%E8%A1%A8
+   */
+  private String ServiceTransPrefer;
+
+  private AddressInfo addressInfo;
+
+  public String getWxStoreId() {
+    return wxStoreId;
+  }
+
+  public void setWxStoreId(String wxStoreId) {
+    this.wxStoreId = wxStoreId;
+  }
+
+  public String getOutStoreId() {
+    return outStoreId;
+  }
+
+  public void setOutStoreId(String outStoreId) {
+    this.outStoreId = outStoreId;
+  }
+
+  public String getStoreName() {
+    return storeName;
+  }
+
+  public void setStoreName(String storeName) {
+    this.storeName = storeName;
+  }
+
+  public String getCityId() {
+    return cityId;
+  }
+
+  public void setCityId(String cityId) {
+    this.cityId = cityId;
+  }
+
+  public int getOrderPattern() {
+    return orderPattern;
+  }
+
+  public void setOrderPattern(int orderPattern) {
+    this.orderPattern = orderPattern;
+  }
+
+  public String getServiceTransPrefer() {
+    return ServiceTransPrefer;
+  }
+
+  public void setServiceTransPrefer(String serviceTransPrefer) {
+    ServiceTransPrefer = serviceTransPrefer;
+  }
+
+  public AddressInfo getAddressInfo() {
+    return addressInfo;
+  }
+
+  public void setAddressInfo(AddressInfo addressInfo) {
+    this.addressInfo = addressInfo;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+
+  public static class AddressInfo {
+    /** 省、自治区、直辖市 必填 */
+    private String province;
+
+    /** 地级市 必填 */
+    private String city;
+
+    /** 区/县/县级市 必填 */
+    private String area;
+
+    /** 街道/镇 选填 */
+    private String street;
+
+    /** 路名和门牌号 必填 */
+    private String house;
+
+    /** 门店电话号码 必填 */
+    private String phone;
+
+    /** 纬度 必填 */
+    private double lat;
+
+    /** 经度 必填 */
+    private double lng;
+
+    public String getProvince() {
+      return province;
+    }
+
+    public void setProvince(String province) {
+      this.province = province;
+    }
+
+    public String getCity() {
+      return city;
+    }
+
+    public void setCity(String city) {
+      this.city = city;
+    }
+
+    public String getArea() {
+      return area;
+    }
+
+    public void setArea(String area) {
+      this.area = area;
+    }
+
+    public String getStreet() {
+      return street;
+    }
+
+    public void setStreet(String street) {
+      this.street = street;
+    }
+
+    public String getHouse() {
+      return house;
+    }
+
+    public void setHouse(String house) {
+      this.house = house;
+    }
+
+    public String getPhone() {
+      return phone;
+    }
+
+    public void setPhone(String phone) {
+      this.phone = phone;
+    }
+
+    public double getLat() {
+      return lat;
+    }
+
+    public void setLat(double lat) {
+      this.lat = lat;
+    }
+
+    public double getLng() {
+      return lng;
+    }
+
+    public void setLng(double lng) {
+      this.lng = lng;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+}

+ 115 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaStoreBalance.java

@@ -0,0 +1,115 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import java.util.Date;
+import java.util.List;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WxMaStoreBalance {
+  private static final Logger logger = LoggerFactory.getLogger(WxMaStoreBalance.class);
+
+  private String wxStoreId;
+  private String appid;
+  private int allBalance;
+
+  private List<Detail> balanceDetail;
+
+  public String getWxStoreId() {
+    return wxStoreId;
+  }
+
+  public void setWxStoreId(String wxStoreId) {
+    this.wxStoreId = wxStoreId;
+  }
+
+  public String getAppid() {
+    return appid;
+  }
+
+  public void setAppid(String appid) {
+    this.appid = appid;
+  }
+
+  public int getAllBalance() {
+    return allBalance;
+  }
+
+  public void setAllBalance(int allBalance) {
+    this.allBalance = allBalance;
+  }
+
+  public List<Detail> getBalanceDetail() {
+    return balanceDetail;
+  }
+
+  public void setBalanceDetail(List<Detail> balanceDetail) {
+    this.balanceDetail = balanceDetail;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+
+  public static class Detail {
+    private String payorderId;
+    private int chargeAmt;
+    private int unusedAmt;
+    private long beginTime;
+    private long endTime;
+
+    public String getPayorderId() {
+      return payorderId;
+    }
+
+    public void setPayorderId(String payorderId) {
+      this.payorderId = payorderId;
+    }
+
+    public int getChargeAmt() {
+      return chargeAmt;
+    }
+
+    public void setChargeAmt(int chargeAmt) {
+      this.chargeAmt = chargeAmt;
+    }
+
+    public int getUnusedAmt() {
+      return unusedAmt;
+    }
+
+    public void setUnusedAmt(int unusedAmt) {
+      this.unusedAmt = unusedAmt;
+    }
+
+    public Date getBeginDate() {
+      return this.beginTime == 0 ? null : new Date(this.beginTime * 1000);
+    }
+
+    public Date getEndDate() {
+      return this.endTime == 0 ? null : new Date(this.endTime * 1000);
+    }
+
+    public long getBeginTime() {
+      return beginTime;
+    }
+
+    public void setBeginTime(long beginTime) {
+      this.beginTime = beginTime;
+    }
+
+    public long getEndTime() {
+      return endTime;
+    }
+
+    public void setEndTime(long endTime) {
+      this.endTime = endTime;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+}

+ 22 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaStoreChargeRequest.java

@@ -0,0 +1,22 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+public class WxMaStoreChargeRequest extends BasicWxMaStoreChargeRefundRequest {
+
+  /** 充值金额 单位:分, 50元起充 */
+  private int amount;
+
+  public int getAmount() {
+    return amount;
+  }
+
+  public void setAmount(int amount) {
+    this.amount = amount;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+}

+ 318 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaStoreFlowResponse.java

@@ -0,0 +1,318 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import java.util.Date;
+import java.util.List;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WxMaStoreFlowResponse<T extends WxMaStoreFlowResponse.BasicFlowRecord> {
+  private static final Logger logger = LoggerFactory.getLogger(WxMaStoreFlowResponse.class);
+
+  /** 总支付金额 */
+  private Long totalPayAmt;
+
+  /** 总退款金额 */
+  private Long totalRefundAmt;
+
+  /** 总违约金 查询消费流水才返回 */
+  private Long totalDeductAmt;
+
+  /** 流水 */
+  private List<T> flowList;
+
+  public List<T> getFlowList() {
+    return flowList;
+  }
+
+  public void setFlowList(List<T> flowList) {
+    this.flowList = flowList;
+  }
+
+  public Long getTotalPayAmt() {
+    return totalPayAmt;
+  }
+
+  public void setTotalPayAmt(Long totalPayAmt) {
+    this.totalPayAmt = totalPayAmt;
+  }
+
+  public Long getTotalRefundAmt() {
+    return totalRefundAmt;
+  }
+
+  public void setTotalRefundAmt(Long totalRefundAmt) {
+    this.totalRefundAmt = totalRefundAmt;
+  }
+
+  public Long getTotalDeductAmt() {
+    return totalDeductAmt;
+  }
+
+  public void setTotalDeductAmt(Long totalDeductAmt) {
+    this.totalDeductAmt = totalDeductAmt;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+
+  public static class BasicFlowRecord {
+    private int flowType;
+    private String appid;
+    private String wxStoreId;
+    private String payOrderId;
+    private String serviceTransId;
+    private int payAmount;
+    private long payTime;
+    private long createTime;
+
+    public int getFlowType() {
+      return flowType;
+    }
+
+    public void setFlowType(int flowType) {
+      this.flowType = flowType;
+    }
+
+    public String getAppid() {
+      return appid;
+    }
+
+    public void setAppid(String appid) {
+      this.appid = appid;
+    }
+
+    public String getWxStoreId() {
+      return wxStoreId;
+    }
+
+    public void setWxStoreId(String wxStoreId) {
+      this.wxStoreId = wxStoreId;
+    }
+
+    public String getPayOrderId() {
+      return payOrderId;
+    }
+
+    public void setPayOrderId(String payOrderId) {
+      this.payOrderId = payOrderId;
+    }
+
+    public String getServiceTransId() {
+      return serviceTransId;
+    }
+
+    public void setServiceTransId(String serviceTransId) {
+      this.serviceTransId = serviceTransId;
+    }
+
+    public int getPayAmount() {
+      return payAmount;
+    }
+
+    public void setPayAmount(int payAmount) {
+      this.payAmount = payAmount;
+    }
+
+    public Date getPayDate() {
+      return this.payTime == 0 ? null : new Date(this.payTime * 1000);
+    }
+
+    public long getPayTime() {
+      return payTime;
+    }
+
+    public void setPayTime(long payTime) {
+      this.payTime = payTime;
+    }
+
+    public Date getCreateDate() {
+      return this.createTime == 0 ? null : new Date(this.createTime * 1000);
+    }
+
+    public long getCreateTime() {
+      return createTime;
+    }
+
+    public void setCreateTime(long createTime) {
+      this.createTime = createTime;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+
+  /** 充值流水 */
+  public static class ChargeFlowRecord extends BasicFlowRecord {
+    private String payStatus;
+
+    private long consumeDeadline;
+
+    public String getPayStatus() {
+      return payStatus;
+    }
+
+    public void setPayStatus(String payStatus) {
+      this.payStatus = payStatus;
+    }
+
+    public Date getConsumeDeadlineDate() {
+      return this.consumeDeadline == 0 ? null : new Date(this.consumeDeadline * 1000);
+    }
+
+    public long getConsumeDeadline() {
+      return consumeDeadline;
+    }
+
+    public void setConsumeDeadline(long consumeDeadline) {
+      this.consumeDeadline = consumeDeadline;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+
+  /** 退款流水 */
+  public static class RefundFlowRecord extends BasicFlowRecord {
+    private int refundAmount;
+    private long refundTime;
+    private long consumeDeadline;
+
+    public int getRefundAmount() {
+      return refundAmount;
+    }
+
+    public void setRefundAmount(int refundAmount) {
+      this.refundAmount = refundAmount;
+    }
+
+    public Date getRefundDate() {
+      return this.refundTime == 0 ? null : new Date(this.refundTime * 1000);
+    }
+
+    public long getRefundTime() {
+      return refundTime;
+    }
+
+    public void setRefundTime(long refundTime) {
+      this.refundTime = refundTime;
+    }
+
+    public Date getConsumeDeadlineDate() {
+      return this.consumeDeadline == 0 ? null : new Date(this.consumeDeadline * 1000);
+    }
+
+    public long getConsumeDeadline() {
+      return consumeDeadline;
+    }
+
+    public void setConsumeDeadline(long consumeDeadline) {
+      this.consumeDeadline = consumeDeadline;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+
+  /** 消费流水 */
+  public static class ConsumeFlowRecord extends BasicFlowRecord {
+    private String wxOrderId;
+    private String openid;
+    private String deliveryStatus;
+    private String payStatus;
+    private String refundStatus;
+    private int refundAmount;
+    private int deductAmount;
+    private String billId;
+    private long deliveryFinishedTime;
+
+    public String getWxOrderId() {
+      return wxOrderId;
+    }
+
+    public void setWxOrderId(String wxOrderId) {
+      this.wxOrderId = wxOrderId;
+    }
+
+    public String getOpenid() {
+      return openid;
+    }
+
+    public void setOpenid(String openid) {
+      this.openid = openid;
+    }
+
+    public String getDeliveryStatus() {
+      return deliveryStatus;
+    }
+
+    public void setDeliveryStatus(String deliveryStatus) {
+      this.deliveryStatus = deliveryStatus;
+    }
+
+    public String getPayStatus() {
+      return payStatus;
+    }
+
+    public void setPayStatus(String payStatus) {
+      this.payStatus = payStatus;
+    }
+
+    public String getRefundStatus() {
+      return refundStatus;
+    }
+
+    public void setRefundStatus(String refundStatus) {
+      this.refundStatus = refundStatus;
+    }
+
+    public int getRefundAmount() {
+      return refundAmount;
+    }
+
+    public void setRefundAmount(int refundAmount) {
+      this.refundAmount = refundAmount;
+    }
+
+    public int getDeductAmount() {
+      return deductAmount;
+    }
+
+    public void setDeductAmount(int deductAmount) {
+      this.deductAmount = deductAmount;
+    }
+
+    public String getBillId() {
+      return billId;
+    }
+
+    public void setBillId(String billId) {
+      this.billId = billId;
+    }
+
+    public Date getDeliveryFinishedDate() {
+      return this.deliveryFinishedTime == 0 ? null : new Date(this.deliveryFinishedTime * 1000);
+    }
+
+    public long getDeliveryFinishedTime() {
+      return deliveryFinishedTime;
+    }
+
+    public void setDeliveryFinishedTime(long deliveryFinishedTime) {
+      this.deliveryFinishedTime = deliveryFinishedTime;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+}

+ 11 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaStoreRefundRequest.java

@@ -0,0 +1,11 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+public class WxMaStoreRefundRequest extends BasicWxMaStoreChargeRefundRequest {
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+}

+ 56 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/intractiy/WxMaTransCity.java

@@ -0,0 +1,56 @@
+package cn.binarywang.wx.miniapp.bean.intractiy;
+
+import java.util.List;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+public class WxMaTransCity {
+  private String serviceTransId;
+  private List<City> cityList;
+
+  public String getServiceTransId() {
+    return serviceTransId;
+  }
+
+  public void setServiceTransId(String serviceTransId) {
+    this.serviceTransId = serviceTransId;
+  }
+
+  public List<City> getCityList() {
+    return cityList;
+  }
+
+  public void setCityList(List<City> cityList) {
+    this.cityList = cityList;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+
+  public static class City {
+    private String cityName;
+    private String cityCode;
+
+    public String getCityName() {
+      return cityName;
+    }
+
+    public void setCityName(String cityName) {
+      this.cityName = cityName;
+    }
+
+    public String getCityCode() {
+      return cityCode;
+    }
+
+    public void setCityCode(String cityCode) {
+      this.cityCode = cityCode;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+}

+ 41 - 26
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaConfig.java

@@ -1,12 +1,11 @@
 package cn.binarywang.wx.miniapp.config;
 
+import java.util.concurrent.locks.Lock;
+import java.util.function.Consumer;
 import me.chanjar.weixin.common.bean.WxAccessToken;
 import me.chanjar.weixin.common.bean.WxAccessTokenEntity;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 
-import java.util.concurrent.locks.Lock;
-import java.util.function.Consumer;
-
 /**
  * 小程序配置
  *
@@ -14,9 +13,7 @@ import java.util.function.Consumer;
  */
 public interface WxMaConfig {
 
-  default void setUpdateAccessTokenBefore(Consumer<WxAccessTokenEntity> updateAccessTokenBefore) {
-
-  }
+  default void setUpdateAccessTokenBefore(Consumer<WxAccessTokenEntity> updateAccessTokenBefore) {}
 
   /**
    * Gets access token.
@@ -25,12 +22,12 @@ public interface WxMaConfig {
    */
   String getAccessToken();
 
-  //region 稳定版access token
+  // region 稳定版access token
   boolean isStableAccessToken();
 
   void useStableAccessToken(boolean useStableAccessToken);
-  //endregion
 
+  // endregion
 
   /**
    * Gets access token lock.
@@ -46,9 +43,7 @@ public interface WxMaConfig {
    */
   boolean isAccessTokenExpired();
 
-  /**
-   * 强制将access token过期掉
-   */
+  /** 强制将access token过期掉 */
   void expireAccessToken();
 
   /**
@@ -63,7 +58,7 @@ public interface WxMaConfig {
   /**
    * 应该是线程安全的
    *
-   * @param accessToken      新的accessToken值
+   * @param accessToken 新的accessToken值
    * @param expiresInSeconds 过期时间,以秒为单位
    */
   void updateAccessToken(String accessToken, int expiresInSeconds);
@@ -77,10 +72,7 @@ public interface WxMaConfig {
     updateAccessToken(accessToken, expiresInSeconds);
   }
 
-  default void updateAccessTokenBefore(WxAccessTokenEntity wxAccessTokenEntity) {
-
-  }
-
+  default void updateAccessTokenBefore(WxAccessTokenEntity wxAccessTokenEntity) {}
 
   /**
    * Gets jsapi ticket.
@@ -103,15 +95,13 @@ public interface WxMaConfig {
    */
   boolean isJsapiTicketExpired();
 
-  /**
-   * 强制将jsapi ticket过期掉
-   */
+  /** 强制将jsapi ticket过期掉 */
   void expireJsapiTicket();
 
   /**
    * 应该是线程安全的
    *
-   * @param jsapiTicket      新的jsapi ticket值
+   * @param jsapiTicket 新的jsapi ticket值
    * @param expiresInSeconds 过期时间,以秒为单位
    */
   void updateJsapiTicket(String jsapiTicket, int expiresInSeconds);
@@ -137,15 +127,13 @@ public interface WxMaConfig {
    */
   boolean isCardApiTicketExpired();
 
-  /**
-   * 强制将卡券api ticket过期掉.
-   */
+  /** 强制将卡券api ticket过期掉. */
   void expireCardApiTicket();
 
   /**
    * 应该是线程安全的.
    *
-   * @param apiTicket        新的卡券api ticket值
+   * @param apiTicket 新的卡券api ticket值
    * @param expiresInSeconds 过期时间,以秒为单位
    */
   void updateCardApiTicket(String apiTicket, int expiresInSeconds);
@@ -236,6 +224,7 @@ public interface WxMaConfig {
 
   /**
    * http 请求重试间隔
+   *
    * <pre>
    *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
    * </pre>
@@ -244,6 +233,7 @@ public interface WxMaConfig {
 
   /**
    * http 请求最大重试次数
+   *
    * <pre>
    *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
    * </pre>
@@ -288,10 +278,35 @@ public interface WxMaConfig {
   String getAccessTokenUrl();
 
   /**
-   * 设置自定义的获取accessToken地址
-   * 可用于设置获取accessToken的自定义服务
+   * 设置自定义的获取accessToken地址 可用于设置获取accessToken的自定义服务
    *
    * @param accessTokenUrl 自定义的获取accessToken地址
    */
   void setAccessTokenUrl(String accessTokenUrl);
+
+  /**
+   * 服务端API签名用到的RSA私钥【pkcs8格式,会以 -----BEGIN PRIVATE KEY-----开头, 'BEGIN RSA PRIVATE
+   * KEY'的是pkcs1格式,需要转换(可用openssl转换)。 设置参考:
+   * https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/getting_started/api_signature.html
+   *
+   * @return rsa private key string
+   */
+  String getApiSignatureRsaPrivateKey();
+
+  /**
+   * 服务端API签名用到的AES密钥
+   * https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/getting_started/api_signature.html
+   *
+   * @return aes key string
+   */
+  String getApiSignatureAesKey();
+
+  /** 密钥对应的序号 */
+  String getApiSignatureAesKeySn();
+
+  /** 密钥对应的序号 */
+  String getApiSignatureRsaPrivateKeySn();
+
+  /** 密钥对应的小程序ID (普通小程序同 appId, 托管第三方平台的是 componentAppId) */
+  String getWechatMpAppid();
 }

+ 73 - 43
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java

@@ -2,17 +2,16 @@ package cn.binarywang.wx.miniapp.config.impl;
 
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
 import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import java.io.File;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Consumer;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.Setter;
 import me.chanjar.weixin.common.bean.WxAccessTokenEntity;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 
-import java.io.File;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.Consumer;
-
 /**
  * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化
  *
@@ -23,30 +22,32 @@ public class WxMaDefaultConfigImpl implements WxMaConfig {
   protected volatile String appid;
   protected volatile String token;
 
-  /**
-   * 是否使用稳定版获取accessToken接口
-   */
+  /** 是否使用稳定版获取accessToken接口 */
   @Getter(value = AccessLevel.NONE)
   private boolean useStableAccessToken;
 
-  /**
-   * 小程序原始ID
-   */
+  /** 小程序原始ID */
   protected volatile String originalId;
+
   protected Lock accessTokenLock = new ReentrantLock();
-  /**
-   * 临时文件目录.
-   */
+
+  /** 临时文件目录. */
   protected volatile File tmpDirFile;
+
   private volatile String msgDataFormat;
   private volatile String secret;
   private volatile String accessToken;
   private volatile String aesKey;
   private volatile long expiresTime;
-  /**
-   * 云环境ID
-   */
+  private volatile String apiSignatureRsaPrivateKey;
+  private volatile String apiSignatureAesKey;
+  private volatile String apiSignatureRsaPrivateKeySn;
+  private volatile String apiSignatureAesKeySn;
+  private volatile String wechatMpAppid;
+
+  /** 云环境ID */
   private volatile String cloudEnv;
+
   private volatile String httpProxyHost;
   private volatile int httpProxyPort;
   private volatile String httpProxyUsername;
@@ -57,10 +58,10 @@ public class WxMaDefaultConfigImpl implements WxMaConfig {
 
   private volatile String jsapiTicket;
   private volatile long jsapiTicketExpiresTime;
-  /**
-   * 微信卡券的ticket单独缓存.
-   */
+
+  /** 微信卡券的ticket单独缓存. */
   private volatile String cardApiTicket;
+
   private volatile long cardApiTicketExpiresTime;
   protected volatile Lock jsapiTicketLock = new ReentrantLock();
   protected volatile Lock cardApiTicketLock = new ReentrantLock();
@@ -68,35 +69,24 @@ public class WxMaDefaultConfigImpl implements WxMaConfig {
   private String apiHostUrl;
   private String accessTokenUrl;
 
-  /**
-   * 自定义配置token的消费者
-   */
-  @Setter
-  private Consumer<WxAccessTokenEntity> updateAccessTokenBefore;
+  /** 自定义配置token的消费者 */
+  @Setter private Consumer<WxAccessTokenEntity> updateAccessTokenBefore;
 
-  /**
-   * 开启回调
-   */
+  /** 开启回调 */
   @Getter(AccessLevel.NONE)
   private boolean enableUpdateAccessTokenBefore = true;
 
-  /**
-   * 可临时关闭更新token回调,主要用于其他介质初始化数据时,可不进行回调
-   */
+  /** 可临时关闭更新token回调,主要用于其他介质初始化数据时,可不进行回调 */
   public void enableUpdateAccessTokenBefore(boolean enableUpdateAccessTokenBefore) {
     this.enableUpdateAccessTokenBefore = enableUpdateAccessTokenBefore;
   }
 
-  /**
-   * 会过期的数据提前过期时间,默认预留200秒的时间
-   */
+  /** 会过期的数据提前过期时间,默认预留200秒的时间 */
   protected long expiresAheadInMillis(int expiresInSeconds) {
     return System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
   }
 
-  /**
-   * 判断 expiresTime 是否已经过期
-   */
+  /** 判断 expiresTime 是否已经过期 */
   protected boolean isExpired(long expiresTime) {
     return System.currentTimeMillis() > expiresTime;
   }
@@ -110,7 +100,7 @@ public class WxMaDefaultConfigImpl implements WxMaConfig {
     this.accessToken = accessToken;
   }
 
-  //region 使用稳定版接口获取accessToken
+  // region 使用稳定版接口获取accessToken
   @Override
   public boolean isStableAccessToken() {
     return this.useStableAccessToken;
@@ -120,8 +110,8 @@ public class WxMaDefaultConfigImpl implements WxMaConfig {
   public void useStableAccessToken(boolean useStableAccessToken) {
     this.useStableAccessToken = useStableAccessToken;
   }
-  //endregion
 
+  // endregion
 
   @Override
   public Lock getAccessTokenLock() {
@@ -137,10 +127,10 @@ public class WxMaDefaultConfigImpl implements WxMaConfig {
     return isExpired(this.expiresTime);
   }
 
-//  @Override
-//  public synchronized void updateAccessToken(WxAccessToken accessToken) {
-//    updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
-//  }
+  //  @Override
+  //  public synchronized void updateAccessToken(WxAccessToken accessToken) {
+  //    updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
+  //  }
 
   @Override
   public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) {
@@ -248,6 +238,46 @@ public class WxMaDefaultConfigImpl implements WxMaConfig {
     this.aesKey = aesKey;
   }
 
+  public String getApiSignatureRsaPrivateKey() {
+    return apiSignatureRsaPrivateKey;
+  }
+
+  public void setApiSignatureRsaPrivateKey(String apiSignatureRsaPrivateKey) {
+    this.apiSignatureRsaPrivateKey = apiSignatureRsaPrivateKey;
+  }
+
+  public String getApiSignatureAesKey() {
+    return apiSignatureAesKey;
+  }
+
+  public void setApiSignatureAesKey(String apiSignatureAesKey) {
+    this.apiSignatureAesKey = apiSignatureAesKey;
+  }
+
+  public String getApiSignatureRsaPrivateKeySn() {
+    return apiSignatureRsaPrivateKeySn;
+  }
+
+  public void setApiSignatureRsaPrivateKeySn(String apiSignatureRsaPrivateKeySn) {
+    this.apiSignatureRsaPrivateKeySn = apiSignatureRsaPrivateKeySn;
+  }
+
+  public String getApiSignatureAesKeySn() {
+    return apiSignatureAesKeySn;
+  }
+
+  public void setApiSignatureAesKeySn(String apiSignatureAesKeySn) {
+    this.apiSignatureAesKeySn = apiSignatureAesKeySn;
+  }
+
+  public String getWechatMpAppid() {
+    return wechatMpAppid == null ? appid : wechatMpAppid;
+  }
+
+  public void setWechatMpAppid(String wechatMpAppid) {
+    this.wechatMpAppid = wechatMpAppid;
+  }
+
   @Override
   public String getOriginalId() {
     return originalId;

File diff suppressed because it is too large
+ 306 - 323
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaApiUrlConstants.java


+ 71 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheApiSignaturePostRequestExecutor.java

@@ -0,0 +1,71 @@
+package cn.binarywang.wx.miniapp.executor;
+
+import cn.binarywang.wx.miniapp.bean.WxMaApiResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler;
+import org.apache.http.Consts;
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ApacheApiSignaturePostRequestExecutor
+    extends ApiSignaturePostRequestExecutor<CloseableHttpClient, HttpHost> {
+  private static final Logger logger =
+      LoggerFactory.getLogger(ApacheApiSignaturePostRequestExecutor.class);
+
+  public ApacheApiSignaturePostRequestExecutor(RequestHttp<?, ?> requestHttp) {
+    super(requestHttp);
+  }
+
+  @Override
+  public WxMaApiResponse execute(
+      String uri, Map<String, String> headers, String postEntity, WxType wxType)
+      throws WxErrorException, IOException {
+    //    logger.debug(
+    //        "ApacheApiSignaturePostRequestExecutor.execute uri:{}, headers:{}, postData:{}",
+    //        uri,
+    //        headers,
+    //        postEntity);
+    HttpPost httpPost = new HttpPost(uri);
+    if (requestHttp.getRequestHttpProxy() != null) {
+      RequestConfig config =
+          RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
+      httpPost.setConfig(config);
+    }
+
+    if (headers != null) {
+      headers.forEach(httpPost::addHeader);
+    }
+
+    if (postEntity != null) {
+      StringEntity entity = new StringEntity(postEntity, Consts.UTF_8);
+      entity.setContentType("application/json; charset=utf-8");
+      httpPost.setEntity(entity);
+    }
+
+    try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) {
+      String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
+      Map<String, String> respHeaders = new HashMap<>();
+      Header[] rHeaders = response.getAllHeaders();
+      if (rHeaders != null) {
+        for (Header h : rHeaders) {
+          respHeaders.putIfAbsent(h.getName(), h.getValue());
+        }
+      }
+      return this.handleResponse(wxType, responseContent, respHeaders);
+    } finally {
+      httpPost.releaseConnection();
+    }
+  }
+}

+ 69 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApiSignaturePostRequestExecutor.java

@@ -0,0 +1,69 @@
+package cn.binarywang.wx.miniapp.executor;
+
+import cn.binarywang.wx.miniapp.bean.WxMaApiResponse;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.util.Map;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestExecutor;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.ResponseHandler;
+import org.jetbrains.annotations.NotNull;
+
+public abstract class ApiSignaturePostRequestExecutor<H, P>
+    implements RequestExecutor<WxMaApiResponse, WxMaApiResponse> {
+
+  protected RequestHttp<H, P> requestHttp;
+
+  public ApiSignaturePostRequestExecutor(RequestHttp requestHttp) {
+    this.requestHttp = requestHttp;
+  }
+
+  @Override
+  public WxMaApiResponse execute(String uri, WxMaApiResponse data, WxType wxType)
+      throws WxErrorException, IOException {
+    throw new RemoteException("method not implemented yet.");
+  }
+
+  @Override
+  public void execute(
+      String uri, WxMaApiResponse data, ResponseHandler<WxMaApiResponse> handler, WxType wxType)
+      throws WxErrorException, IOException {
+    throw new RemoteException("method not implemented yet.");
+  }
+
+  public abstract WxMaApiResponse execute(
+      String uri, Map<String, String> headers, String data, WxType wxType)
+      throws WxErrorException, IOException;
+
+  @NotNull
+  public WxMaApiResponse handleResponse(
+      WxType wxType, String responseContent, Map<String, String> headers) throws WxErrorException {
+    if (responseContent.isEmpty()) {
+      throw new WxErrorException("无响应内容");
+    }
+    WxError error = WxError.fromJson(responseContent, wxType);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
+    }
+    WxMaApiResponse response = new WxMaApiResponse();
+    response.setContent(responseContent);
+    response.setHeaders(headers);
+    return response;
+  }
+
+  public static ApiSignaturePostRequestExecutor create(RequestHttp requestHttp) {
+    switch (requestHttp.getRequestType()) {
+      case APACHE_HTTP:
+        return new ApacheApiSignaturePostRequestExecutor(requestHttp);
+      case JODD_HTTP:
+        return new JoddApiSignaturePostRequestExecutor(requestHttp);
+      case OK_HTTP:
+        return new OkHttpApiSignaturePostRequestExecutor(requestHttp);
+      default:
+        throw new IllegalArgumentException("非法请求参数");
+    }
+  }
+}

+ 59 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddApiSignaturePostRequestExecutor.java

@@ -0,0 +1,59 @@
+package cn.binarywang.wx.miniapp.executor;
+
+import cn.binarywang.wx.miniapp.bean.WxMaApiResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import jodd.http.HttpConnectionProvider;
+import jodd.http.HttpRequest;
+import jodd.http.HttpResponse;
+import jodd.http.ProxyInfo;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JoddApiSignaturePostRequestExecutor
+    extends ApiSignaturePostRequestExecutor<HttpConnectionProvider, ProxyInfo> {
+  private static final Logger logger =
+      LoggerFactory.getLogger(JoddApiSignaturePostRequestExecutor.class);
+
+  public JoddApiSignaturePostRequestExecutor(RequestHttp<?, ?> requestHttp) {
+    super(requestHttp);
+  }
+
+  @Override
+  public WxMaApiResponse execute(
+      String uri, Map<String, String> headers, String postEntity, WxType wxType)
+      throws WxErrorException, IOException {
+    //    logger.debug(
+    //        "JoddApiSignaturePostRequestExecutor.execute uri:{}, headers:{}, postData:{}",
+    //        uri,
+    //        headers,
+    //        postEntity);
+    HttpConnectionProvider provider = requestHttp.getRequestHttpClient();
+    ProxyInfo proxyInfo = requestHttp.getRequestHttpProxy();
+
+    HttpRequest request = HttpRequest.post(uri);
+    if (proxyInfo != null) {
+      provider.useProxy(proxyInfo);
+    }
+    if (headers != null) {
+      headers.forEach(request::header);
+    }
+    request.withConnectionProvider(provider);
+    if (postEntity != null) {
+      request.contentType("application/json", "utf-8");
+      request.bodyText(postEntity);
+    }
+    HttpResponse response = request.send();
+    response.charset(StandardCharsets.UTF_8.name());
+    Map<String, String> respHeaders = new HashMap<>();
+    for (String n : response.headerNames()) {
+      respHeaders.putIfAbsent(n, response.header(n));
+    }
+    return this.handleResponse(wxType, response.bodyText(), respHeaders);
+  }
+}

+ 51 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpApiSignaturePostRequestExecutor.java

@@ -0,0 +1,51 @@
+package cn.binarywang.wx.miniapp.executor;
+
+import cn.binarywang.wx.miniapp.bean.WxMaApiResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
+import okhttp3.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OkHttpApiSignaturePostRequestExecutor
+    extends ApiSignaturePostRequestExecutor<OkHttpClient, OkHttpProxyInfo> {
+  private static final Logger logger =
+      LoggerFactory.getLogger(OkHttpApiSignaturePostRequestExecutor.class);
+
+  public OkHttpApiSignaturePostRequestExecutor(RequestHttp<?, ?> requestHttp) {
+    super(requestHttp);
+  }
+
+  @Override
+  public WxMaApiResponse execute(
+      String uri, Map<String, String> headers, String postEntity, WxType wxType)
+      throws WxErrorException, IOException {
+    //    logger.debug(
+    //        "OkHttpApiSignaturePostRequestExecutor.execute uri:{}, headers:{}, postData:{}",
+    //        uri,
+    //        headers,
+    //        postEntity);
+    RequestBody body =
+        RequestBody.Companion.create(
+            postEntity, MediaType.parse("application/json; charset=utf-8"));
+    Request.Builder builder = new Request.Builder();
+    if (headers != null) {
+      headers.forEach(builder::addHeader);
+    }
+    Request request = builder.url(uri).post(body).build();
+    Response response = requestHttp.getRequestHttpClient().newCall(request).execute();
+    Map<String, String> respHeaders = new HashMap<>();
+    Headers rHeaders = response.headers();
+    for (String n : rHeaders.names()) {
+      respHeaders.put(n, rHeaders.get(n));
+    }
+    return this.handleResponse(
+        wxType, Objects.requireNonNull(response.body()).string(), respHeaders);
+  }
+}

+ 234 - 0
weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaIntracityServiceImpleTest.java

@@ -0,0 +1,234 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import static org.testng.AssertJUnit.*;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.intractiy.*;
+import cn.binarywang.wx.miniapp.bean.openapi.WxMiniGetApiQuotaResult;
+import cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants;
+import cn.binarywang.wx.miniapp.test.ApiTestModule;
+import cn.binarywang.wx.miniapp.test.TestConfig;
+import com.google.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+@Test
+@Guice(modules = ApiTestModule.class)
+public class WxMaIntracityServiceImpleTest {
+  private static final Logger logger = LoggerFactory.getLogger(WxMaIntracityServiceImpleTest.class);
+
+  @Inject private WxMaService wxService;
+
+  @Test
+  public void testApiSignature() throws Exception {
+    WxMiniGetApiQuotaResult result =
+        wxService
+            .getWxMaOpenApiService()
+            .getApiQuota(
+                WxMaApiUrlConstants.Intracity.APPLY_URL.substring(
+                    "https://api.weixin.qq.com".length()));
+    logger.info("apply 额度剩余 :{}", result.getQuota());
+  }
+
+  @Test
+  public void testApply() throws Exception {
+    logger.debug("testApply");
+    try {
+      wxService.getIntracityService().apply();
+    } catch (WxErrorException wxEx) {
+      if (wxEx.getError().getErrorCode() == 45009) {
+        // 调用分钟频率受限
+      } else {
+        throw wxEx;
+      }
+    }
+  }
+
+  @Test
+  public void testStoreRelatedApis() throws Exception {
+    WxMaStore store = new WxMaStore();
+    store.setStoreName("南京东路店");
+    store.setOutStoreId("njdl-001");
+    WxMaStore.AddressInfo addr = new WxMaStore.AddressInfo();
+    addr.setProvince("上海市");
+    addr.setCity("上海市");
+    addr.setArea("黄浦区");
+    addr.setStreet("");
+    addr.setHouse("南京东路690号");
+    addr.setLat(31.235318);
+    addr.setLng(121.477284);
+    addr.setPhone("021-23456789");
+    store.setAddressInfo(addr);
+    String wxStoreId;
+    List<WxMaStore> result =
+        wxService.getIntracityService().queryStoreByOutStoreId(store.getOutStoreId());
+    if (result.isEmpty()) {
+      wxStoreId = wxService.getIntracityService().createStore(store);
+      logger.debug("create store result:{}", wxStoreId);
+    } else {
+      wxStoreId = result.get(0).getWxStoreId();
+    }
+    store.setWxStoreId(wxStoreId);
+    addr.setPhone("021-23450000");
+    store.setStoreName(null);
+    wxService.getIntracityService().updateStore(store);
+    List<WxMaStore> stores = wxService.getIntracityService().listAllStores();
+    logger.info("listAllStores 查询到 {} 个门店 {}", stores.size(), stores);
+    if (stores.size() > 0) {
+      WxMaStore s =
+          wxService.getIntracityService().queryStoreByWxStoreId(stores.get(0).getWxStoreId());
+      assertNotNull(s);
+      List<WxMaStore> list =
+          wxService.getIntracityService().queryStoreByOutStoreId(stores.get(0).getOutStoreId());
+      logger.info("queryStoreByOutStoreId 查询到 {} 个门店 {}", list.size(), list);
+    }
+  }
+
+  @Test
+  public void testStoreChargeRelated() throws Exception {
+    List<WxMaStore> stores = wxService.getIntracityService().listAllStores();
+    if (stores.isEmpty()) {
+      logger.warn("没有门店,无法测试");
+      return;
+    }
+    WxMaStore store = stores.get(0);
+
+    WxMaGetPayModeResponse resp = wxService.getIntracityService().getPayMode();
+    logger.debug("查询付费主体 {}", resp);
+    PayMode currentPayMode = resp.getPayMode();
+    // 只能用当前付费模式充值;否则微信接口会返回 错误代码:934025, 错误信息:pay_mode not match
+    WxMaStoreChargeRequest request = new WxMaStoreChargeRequest();
+    request.setPayMode(currentPayMode);
+    request.setWxStoreId(store.getWxStoreId());
+    request.setServiceTransId("DADA");
+    request.setAmount(5000);
+    String payUrl = wxService.getIntracityService().storeCharge(request);
+    logger.debug("充值URL:{}", payUrl);
+
+    // 查询余额
+    WxMaStoreBalance balance =
+        wxService.getIntracityService().balanceQuery(store.getWxStoreId(), null, PayMode.STORE);
+    logger.debug("余额 {}", balance);
+
+    // 退款
+    WxMaStoreRefundRequest rr = new WxMaStoreRefundRequest();
+    rr.setPayMode(PayMode.STORE);
+    rr.setWxStoreId(store.getWxStoreId());
+    rr.setServiceTransId("DADA");
+    int refundAmount = wxService.getIntracityService().storeRefund(rr);
+    logger.debug("退款:{}", refundAmount);
+
+    // 查询流水
+    WxMaQueryFlowRequest qfr = new WxMaQueryFlowRequest();
+    qfr.setWxStoreId(store.getWxStoreId());
+    WxMaStoreFlowResponse flowResponse = wxService.getIntracityService().queryFlow(qfr);
+    logger.debug("查询流水 {}", flowResponse);
+  }
+
+  @Test
+  public void testPayMode() throws Exception {
+    WxMaGetPayModeResponse resp = wxService.getIntracityService().getPayMode();
+    logger.debug("查询付费主体 {}", resp);
+    PayMode newMode = resp.getPayMode() == PayMode.APP ? PayMode.STORE : PayMode.APP;
+    logger.debug("set pay mode to {}", newMode);
+    wxService.getIntracityService().setPayMode(newMode);
+    WxMaGetPayModeResponse resp2 = wxService.getIntracityService().getPayMode();
+    logger.debug("查询付费主体 {}", resp2);
+  }
+
+  @Test
+  public void testGetCity() throws Exception {
+    List<WxMaTransCity> list = wxService.getIntracityService().getCity(null);
+    logger.debug("支持的城市 {}", list);
+    List<WxMaTransCity> list2 = wxService.getIntracityService().getCity("SFTC");
+    logger.debug("SFTC支持的城市有{}个", list2.get(0).getCityList().size());
+  }
+
+  @Test
+  public void testOrderRelatived() throws Exception {
+    List<WxMaStore> stores = wxService.getIntracityService().listAllStores();
+    if (stores.isEmpty()) {
+      logger.warn("没有门店,无法测试");
+      return;
+    }
+    String wxStoreId = stores.get(0).getWxStoreId();
+    {
+      WxMaPreAddOrderRequest request = new WxMaPreAddOrderRequest();
+      request.setWxStoreId(wxStoreId);
+      request.setUseSandbox(1);
+      request.setUserName("顺丰同城");
+      request.setUserPhone("13800000138");
+      request.setUserAddress("北京市海淀区学清嘉创大厦A座15层");
+      request.setUserLat(40.01496);
+      request.setUserLng(116.353093);
+      WxMaPreAddOrderRequest.Cargo cargo = new WxMaPreAddOrderRequest.Cargo();
+      cargo.setCargoName("蛋糕");
+      cargo.setCargoType(13);
+      cargo.setCargoNum(1);
+      cargo.setCargoPrice(10000);
+      cargo.setCargoWeight(1000);
+      request.setCargo(cargo);
+      WxMaAddOrderResponse response = wxService.getIntracityService().preAddOrder(request);
+      logger.debug("查询运费返回 {}, 预估运费{}元", response, response.getFee() / 100.0);
+    }
+    String wxOrderId = null;
+    {
+      TestConfig config = (TestConfig) this.wxService.getWxMaConfig();
+      WxMaAddOrderRequest request = new WxMaAddOrderRequest();
+      request.setWxStoreId(wxStoreId);
+      request.setStoreOrderId("store-order-" + System.currentTimeMillis());
+      request.setOrderSeq("0001");
+      request.setUserOpenid(config.getOpenid());
+      request.setUseSandbox(1);
+      request.setUserName("顺丰同城");
+      request.setUserPhone("13800000138");
+      request.setUserAddress("北京市海淀区学清嘉创大厦A座15层");
+      request.setUserLat(40.01496);
+      request.setUserLng(116.353093);
+      request.setOrderDetailPath("/pages/user-center/order/detail/detail?id=xxx");
+      WxMaAddOrderRequest.Cargo cargo = new WxMaAddOrderRequest.Cargo();
+      cargo.setCargoName("蛋糕");
+      cargo.setCargoType(13);
+      cargo.setCargoNum(1);
+      cargo.setCargoPrice(10000);
+      cargo.setCargoWeight(1000);
+      WxMaAddOrderRequest.ItemDetail detail = new WxMaAddOrderRequest.ItemDetail();
+      detail.setItemName("蛋糕A");
+      detail.setItemPicUrl("https://www.somehost.com/aaa.jpg");
+      detail.setCount(1);
+      List<WxMaAddOrderRequest.ItemDetail> itemList = new ArrayList<>();
+      itemList.add(detail);
+      cargo.setItemList(itemList);
+      request.setCargo(cargo);
+      WxMaAddOrderResponse response = wxService.getIntracityService().addOrder(request);
+      wxOrderId = response.getWxOrderId();
+      logger.debug("创建订单返回 {}, wxOrderId:{}", response, wxOrderId);
+    }
+    WxMaOrder order = wxService.getIntracityService().queryOrderByWxOrderId(wxOrderId);
+    logger.debug("查询订单返回 {}, storeOrderId:{} ", order, order.getStoreOrderId());
+    WxMaOrder order2 =
+        wxService
+            .getIntracityService()
+            .queryOrderByStoreOrderId(wxStoreId, order.getStoreOrderId());
+    logger.debug("查询订单返回 {},  ", order);
+    assertEquals(order2.getWxOrderId(), wxOrderId);
+
+    WxMaCancelOrderResponse cancelOrderResp =
+        wxService.getIntracityService().cancelOrderByWxOrderId(wxOrderId, 1, "不再需要");
+    logger.debug("取消订单返回 {}, 扣费:{} ", cancelOrderResp, cancelOrderResp.getDeductfee());
+
+    try {
+      wxService
+          .getIntracityService()
+          .cancelOrderByStoreOrderId(wxStoreId, order.getStoreOrderId(), 1, "不再需要");
+      fail("重复取消未抛异常,疑似第一次取消未成功");
+    } catch (WxErrorException wxErrorException) {
+      // 订单已经被取消了,重复取消会报错,这里才正常
+    }
+  }
+}

+ 5 - 0
weixin-java-miniapp/src/test/resources/test-config-sample.xml

@@ -9,4 +9,9 @@
   <expiresTime>可以不填写</expiresTime>
   <openid>某个用户的openId</openid>
   <templateId>模版消息的模版ID</templateId>
+  <componentApiSignatureAesKey>API签名AES密钥【没有开启API签名不要这条】</componentApiSignatureAesKey>
+  <componentApiSignatureAesKeySn>API签名AES密钥的序号【没有开启API签名不要这条】</componentApiSignatureAesKeySn>
+  <componentApiSignatureRsaPrivateKey>API签名RSA私钥的【没有开启API签名不要这条】</componentApiSignatureRsaPrivateKey>
+  <componentApiSignatureRsaPrivateKeySn>API签名RSA私钥的序【没有开启API签名不要这条】
+  </componentApiSignatureRsaPrivateKeySn>
 </xml>

+ 44 - 21
weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenConfigStorage.java

@@ -1,13 +1,12 @@
 package me.chanjar.weixin.open.api;
 
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
+import java.util.concurrent.locks.Lock;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 import me.chanjar.weixin.mp.config.WxMpConfigStorage;
 import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken;
 import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken;
 
-import java.util.concurrent.locks.Lock;
-
 /**
  * The interface Wx open config storage.
  *
@@ -99,9 +98,7 @@ public interface WxOpenConfigStorage {
    */
   boolean isComponentAccessTokenExpired();
 
-  /**
-   * Expire component access token.
-   */
+  /** Expire component access token. */
   void expireComponentAccessToken();
 
   /**
@@ -141,6 +138,7 @@ public interface WxOpenConfigStorage {
 
   /**
    * http 请求重试间隔
+   *
    * <pre>
    *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setRetrySleepMillis(int)}
    *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
@@ -150,6 +148,7 @@ public interface WxOpenConfigStorage {
 
   /**
    * http 请求最大重试次数
+   *
    * <pre>
    *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setMaxRetryTimes(int)}
    *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
@@ -199,7 +198,7 @@ public interface WxOpenConfigStorage {
    * 应该是线程安全的
    *
    * @param componentAccessToken 新的accessToken值
-   * @param expiresInSeconds     过期时间,以秒为单位
+   * @param expiresInSeconds 过期时间,以秒为单位
    */
   void updateComponentAccessToken(String componentAccessToken, int expiresInSeconds);
 
@@ -221,7 +220,7 @@ public interface WxOpenConfigStorage {
   /**
    * Sets authorizer refresh token.
    *
-   * @param appId                  the app id
+   * @param appId the app id
    * @param authorizerRefreshToken the authorizer refresh token
    */
   void setAuthorizerRefreshToken(String appId, String authorizerRefreshToken);
@@ -229,7 +228,7 @@ public interface WxOpenConfigStorage {
   /**
    * setAuthorizerRefreshToken(String appId, String authorizerRefreshToken) 方法重载方法
    *
-   * @param appId                  the app id
+   * @param appId the app id
    * @param authorizerRefreshToken the authorizer refresh token
    */
   void updateAuthorizerRefreshToken(String appId, String authorizerRefreshToken);
@@ -260,7 +259,7 @@ public interface WxOpenConfigStorage {
   /**
    * 应该是线程安全的
    *
-   * @param appId                 the app id
+   * @param appId the app id
    * @param authorizerAccessToken 要更新的WxAccessToken对象
    */
   void updateAuthorizerAccessToken(String appId, WxOpenAuthorizerAccessToken authorizerAccessToken);
@@ -268,11 +267,12 @@ public interface WxOpenConfigStorage {
   /**
    * 应该是线程安全的
    *
-   * @param appId                 the app id
+   * @param appId the app id
    * @param authorizerAccessToken 新的accessToken值
-   * @param expiresInSeconds      过期时间,以秒为单位
+   * @param expiresInSeconds 过期时间,以秒为单位
    */
-  void updateAuthorizerAccessToken(String appId, String authorizerAccessToken, int expiresInSeconds);
+  void updateAuthorizerAccessToken(
+      String appId, String authorizerAccessToken, int expiresInSeconds);
 
   /**
    * Gets jsapi ticket.
@@ -300,8 +300,8 @@ public interface WxOpenConfigStorage {
   /**
    * 应该是线程安全的
    *
-   * @param appId            the app id
-   * @param jsapiTicket      新的jsapi ticket值
+   * @param appId the app id
+   * @param jsapiTicket 新的jsapi ticket值
    * @param expiresInSeconds 过期时间,以秒为单位
    */
   void updateJsapiTicket(String appId, String jsapiTicket, int expiresInSeconds);
@@ -314,7 +314,6 @@ public interface WxOpenConfigStorage {
    */
   String getCardApiTicket(String appId);
 
-
   /**
    * Is card api ticket expired boolean.
    *
@@ -333,8 +332,8 @@ public interface WxOpenConfigStorage {
   /**
    * 应该是线程安全的
    *
-   * @param appId            the app id
-   * @param cardApiTicket    新的cardApi ticket值
+   * @param appId the app id
+   * @param cardApiTicket 新的cardApi ticket值
    * @param expiresInSeconds 过期时间,以秒为单位
    */
   void updateCardApiTicket(String appId, String cardApiTicket, int expiresInSeconds);
@@ -342,10 +341,34 @@ public interface WxOpenConfigStorage {
   /**
    * 设置第三方平台基础信息
    *
-   * @param componentAppId     第三方平台 appid
+   * @param componentAppId 第三方平台 appid
    * @param componentAppSecret 第三方平台 appsecret
-   * @param componentToken     消息校验Token
-   * @param componentAesKey    消息加解密Key
+   * @param componentToken 消息校验Token
+   * @param componentAesKey 消息加解密Key
    */
-  void setWxOpenInfo(String componentAppId, String componentAppSecret, String componentToken, String componentAesKey);
+  void setWxOpenInfo(
+      String componentAppId,
+      String componentAppSecret,
+      String componentToken,
+      String componentAesKey);
+
+  /** 第三方平台设置API签名 RSA 私钥 */
+  String getComponentApiSignatureRsaPrivateKey();
+
+  void setComponentApiSignatureRsaPrivateKey(String apiSignatureRsaPrivateKey);
+
+  /** 第三方平台设置API签名 AES KEY */
+  String getComponentApiSignatureAesKey();
+
+  void setComponentApiSignatureAesKey(String apiSignatureAesKey);
+
+  /** 第三方平台设置API签名 RSA 私钥 序号 */
+  String getComponentApiSignatureRsaPrivateKeySn();
+
+  void setComponentApiSignatureRsaPrivateKeySn(String apiSignatureRsaPrivateKeySn);
+
+  /** 第三方平台设置API签名 AES key 序号 */
+  String getComponentApiSignatureAesKeySn();
+
+  void setComponentApiSignatureAesKeySn(String apiSignatureAesKeySn);
 }

+ 128 - 77
weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java

@@ -1,7 +1,11 @@
 package me.chanjar.weixin.open.api.impl;
 
-
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
+import java.io.File;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 import lombok.AccessLevel;
 import lombok.Data;
 import lombok.Getter;
@@ -16,12 +20,6 @@ import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken;
 import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken;
 import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder;
 
-import java.io.File;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
 /**
  * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化
  *
@@ -37,26 +35,36 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
   private String componentAccessToken;
   private long componentExpiresTime;
 
+  private String componentApiSignatureRsaPrivateKey;
+  private String componentApiSignatureAesKey;
+  private String componentApiSignatureRsaPrivateKeySn;
+  private String componentApiSignatureAesKeySn;
+
   private String httpProxyHost;
   private int httpProxyPort;
   private String httpProxyUsername;
   private String httpProxyPassword;
+
   /**
    * http 请求重试间隔
+   *
    * <pre>
    *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setRetrySleepMillis(int)}
    *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
    * </pre>
    */
   private int retrySleepMillis = 1000;
+
   /**
    * http 请求最大重试次数
+   *
    * <pre>
    *   {@link me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl#setMaxRetryTimes(int)}
    *   {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
    * </pre>
    */
   private int maxRetryTimes = 5;
+
   private ApacheHttpClientBuilder apacheHttpClientBuilder;
 
   private Map<String, Token> authorizerRefreshTokens = new ConcurrentHashMap<>();
@@ -77,7 +85,8 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
 
   @Override
   public void updateComponentAccessToken(WxOpenComponentAccessToken componentAccessToken) {
-    updateComponentAccessToken(componentAccessToken.getComponentAccessToken(), componentAccessToken.getExpiresIn());
+    updateComponentAccessToken(
+        componentAccessToken.getComponentAccessToken(), componentAccessToken.getExpiresIn());
   }
 
   private Lock accessTokenLockInstance;
@@ -126,8 +135,11 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
   }
 
   @Override
-  public void setWxOpenInfo(String componentAppId, String componentAppSecret, String componentToken,
-                            String componentAesKey) {
+  public void setWxOpenInfo(
+      String componentAppId,
+      String componentAppSecret,
+      String componentToken,
+      String componentAesKey) {
     setComponentAppId(componentAppId);
     setComponentAppSecret(componentAppSecret);
     setComponentToken(componentToken);
@@ -141,7 +153,8 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
 
   private String getTokenString(Map<String, Token> map, String key) {
     Token token = map.get(key);
-    if (token == null || (token.expiresTime != null && System.currentTimeMillis() > token.expiresTime)) {
+    if (token == null
+        || (token.expiresTime != null && System.currentTimeMillis() > token.expiresTime)) {
       return null;
     }
     return token.token;
@@ -154,7 +167,8 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
     }
   }
 
-  private void updateToken(Map<String, Token> map, String key, String tokenString, Integer expiresInSeconds) {
+  private void updateToken(
+      Map<String, Token> map, String key, String tokenString, Integer expiresInSeconds) {
     Token token = map.get(key);
     if (token == null) {
       token = new Token();
@@ -186,7 +200,6 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
     return getTokenString(authorizerAccessTokens, appId);
   }
 
-
   @Override
   public boolean isAuthorizerAccessTokenExpired(String appId) {
     return getTokenString(authorizerAccessTokens, appId) == null;
@@ -198,13 +211,17 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
   }
 
   @Override
-  public void updateAuthorizerAccessToken(String appId, WxOpenAuthorizerAccessToken authorizerAccessToken) {
-    updateAuthorizerAccessToken(appId, authorizerAccessToken.getAuthorizerAccessToken(),
-      authorizerAccessToken.getExpiresIn());
+  public void updateAuthorizerAccessToken(
+      String appId, WxOpenAuthorizerAccessToken authorizerAccessToken) {
+    updateAuthorizerAccessToken(
+        appId,
+        authorizerAccessToken.getAuthorizerAccessToken(),
+        authorizerAccessToken.getExpiresIn());
   }
 
   @Override
-  public void updateAuthorizerAccessToken(String appId, String authorizerAccessToken, int expiresInSeconds) {
+  public void updateAuthorizerAccessToken(
+      String appId, String authorizerAccessToken, int expiresInSeconds) {
     updateToken(authorizerAccessTokens, appId, authorizerAccessToken, expiresInSeconds);
   }
 
@@ -261,21 +278,18 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
     private WxMpHostConfig hostConfig;
     private String apiHostUrl;
     private String accessTokenUrl;
-    /**
-     * 是否使用稳定版获取accessToken接口
-     */
+
+    /** 是否使用稳定版获取accessToken接口 */
     @Getter(value = AccessLevel.NONE)
     @Setter(value = AccessLevel.NONE)
     private boolean useStableAccessToken;
 
-    /**
-     * 小程序原始ID
-     */
+    /** 小程序原始ID */
     private volatile String originalId;
-    /**
-     * 云环境ID
-     */
+
+    /** 云环境ID */
     private volatile String cloudEnv;
+
     private final Lock accessTokenLock;
     private final Lock jsapiTicketLock;
     private final Lock cardApiTicketLock;
@@ -326,15 +340,18 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
     @Override
     public String getTicket(TicketType type) {
       switch (type) {
-        case JSAPI: {
-          return wxOpenConfigStorage.getJsapiTicket(appId);
-        }
-        case WX_CARD: {
-          return wxOpenConfigStorage.getCardApiTicket(appId);
-        }
-        default: {
-          // do nothing
-        }
+        case JSAPI:
+          {
+            return wxOpenConfigStorage.getJsapiTicket(appId);
+          }
+        case WX_CARD:
+          {
+            return wxOpenConfigStorage.getCardApiTicket(appId);
+          }
+        default:
+          {
+            // do nothing
+          }
       }
       return null;
     }
@@ -342,15 +359,18 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
     @Override
     public Lock getTicketLock(TicketType type) {
       switch (type) {
-        case JSAPI: {
-          return this.jsapiTicketLock;
-        }
-        case WX_CARD: {
-          return this.cardApiTicketLock;
-        }
-        default: {
-          // do nothing
-        }
+        case JSAPI:
+          {
+            return this.jsapiTicketLock;
+          }
+        case WX_CARD:
+          {
+            return this.cardApiTicketLock;
+          }
+        default:
+          {
+            // do nothing
+          }
       }
       return null;
     }
@@ -358,15 +378,18 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
     @Override
     public boolean isTicketExpired(TicketType type) {
       switch (type) {
-        case JSAPI: {
-          return wxOpenConfigStorage.isJsapiTicketExpired(appId);
-        }
-        case WX_CARD: {
-          return wxOpenConfigStorage.isCardApiTicketExpired(appId);
-        }
-        default: {
-          // do nothing
-        }
+        case JSAPI:
+          {
+            return wxOpenConfigStorage.isJsapiTicketExpired(appId);
+          }
+        case WX_CARD:
+          {
+            return wxOpenConfigStorage.isCardApiTicketExpired(appId);
+          }
+        default:
+          {
+            // do nothing
+          }
       }
 
       return false;
@@ -375,36 +398,41 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
     @Override
     public void expireTicket(TicketType type) {
       switch (type) {
-        case JSAPI: {
-          wxOpenConfigStorage.expireJsapiTicket(appId);
-          break;
-        }
-        case WX_CARD: {
-          wxOpenConfigStorage.expireCardApiTicket(appId);
-          break;
-        }
-        default: {
-          // do nothing
-        }
+        case JSAPI:
+          {
+            wxOpenConfigStorage.expireJsapiTicket(appId);
+            break;
+          }
+        case WX_CARD:
+          {
+            wxOpenConfigStorage.expireCardApiTicket(appId);
+            break;
+          }
+        default:
+          {
+            // do nothing
+          }
       }
     }
 
     @Override
     public void updateTicket(TicketType type, String ticket, int expiresInSeconds) {
       switch (type) {
-        case JSAPI: {
-          wxOpenConfigStorage.updateJsapiTicket(appId, ticket, expiresInSeconds);
-          break;
-        }
-        case WX_CARD: {
-          wxOpenConfigStorage.updateCardApiTicket(appId, ticket, expiresInSeconds);
-          break;
-        }
-        default: {
-          // do nothing
-        }
+        case JSAPI:
+          {
+            wxOpenConfigStorage.updateJsapiTicket(appId, ticket, expiresInSeconds);
+            break;
+          }
+        case WX_CARD:
+          {
+            wxOpenConfigStorage.updateCardApiTicket(appId, ticket, expiresInSeconds);
+            break;
+          }
+        default:
+          {
+            // do nothing
+          }
       }
-
     }
 
     @Override
@@ -510,13 +538,36 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage {
       return 0;
     }
 
-
     @Override
     public String getAesKey() {
       return wxOpenConfigStorage.getComponentAesKey();
     }
 
     @Override
+    public String getApiSignatureRsaPrivateKey() {
+      return wxOpenConfigStorage.getComponentApiSignatureRsaPrivateKey();
+    }
+
+    @Override
+    public String getApiSignatureAesKey() {
+      return wxOpenConfigStorage.getComponentApiSignatureAesKey();
+    }
+
+    public String getApiSignatureRsaPrivateKeySn() {
+      return wxOpenConfigStorage.getComponentApiSignatureRsaPrivateKeySn();
+    }
+
+    @Override
+    public String getApiSignatureAesKeySn() {
+      return wxOpenConfigStorage.getComponentApiSignatureAesKeySn();
+    }
+
+    @Override
+    public String getWechatMpAppid() {
+      return wxOpenConfigStorage.getComponentAppId();
+    }
+
+    @Override
     public String getMsgDataFormat() {
       return null;
     }