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

This commit is contained in:
GeXiangDong
2024-11-01 00:14:29 +08:00
committed by GitHub
parent f6e300b10a
commit cff5616463
35 changed files with 3632 additions and 580 deletions

View File

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

View File

@@ -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";
/** 获取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();
}

View File

@@ -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
@@ -240,34 +326,67 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
return this.post(url, obj.toJson());
}
@Override
public String upload(String url, CommonUploadParam param) throws WxErrorException {
RequestExecutor<String, CommonUploadParam> executor = CommonUploadRequestExecutor.create(getRequestHttp());
return this.execute(executor, url, param);
}
@Override
public String post(String url, JsonObject jsonObject) throws WxErrorException {
return this.post(url, jsonObject.toString());
}
/**
* 向微信端发送请求在这里执行的策略是当发生access_token过期时才去刷新然后重新执行请求而不是全局定时请求
*/
@Override
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
public String upload(String url, CommonUploadParam param) throws WxErrorException {
RequestExecutor<String, CommonUploadParam> executor =
CommonUploadRequestExecutor.create(getRequestHttp());
return this.execute(executor, url, param);
}
/** 向微信端发送请求在这里执行的策略是当发生access_token过期时才去刷新然后重新执行请求而不是全局定时请求 */
@Override
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);
}
@Override
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;
}
}

View File

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