mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2026-03-10 00:13:40 +08:00
🆕 #3404 【小程序】增加同城配送相关接口,同时为WxMaService增加了API签名支持
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user