| @@ -6,7 +6,6 @@ import java.security.NoSuchAlgorithmException; | |||||
| import java.util.*; | import java.util.*; | ||||
| import com.iformall.common.ErrorCode; | import com.iformall.common.ErrorCode; | ||||
| import com.iformall.douyin.pay.enums.AppAddSubMerchantUrlType; | |||||
| import com.iformall.douyin.pay.orderQuery.QueryMerchantResult; | import com.iformall.douyin.pay.orderQuery.QueryMerchantResult; | ||||
| import com.iformall.douyin.pay.orderQuery.QueryRefundResult; | import com.iformall.douyin.pay.orderQuery.QueryRefundResult; | ||||
| import com.iformall.douyin.pay.orderQuery.QuerySettleResult; | import com.iformall.douyin.pay.orderQuery.QuerySettleResult; | ||||
| @@ -22,7 +21,6 @@ import com.iformall.utils.HttpUtil; | |||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| import springfox.documentation.spring.web.json.Json; | |||||
| /** | /** | ||||
| * 抖音小程序支付 | * 抖音小程序支付 | ||||
| @@ -356,48 +354,52 @@ public class DouYinPayHelper { | |||||
| public static void main(String[] args) { | public static void main(String[] args) { | ||||
| //测试 | //测试 | ||||
| // String mcuid = "70777649963225930610";//商户号 | // String mcuid = "70777649963225930610";//商户号 | ||||
| String appid = "ttef6599d8705b49b101"; | |||||
| String salt = "G8DdHfRcEPXB8kHQsnpVWvhLfMC7MZQ72aweTei8";//支付密钥 | |||||
| // String appid = "ttef6599d8705b49b101"; | |||||
| // String salt = "G8DdHfRcEPXB8kHQsnpVWvhLfMC7MZQ72aweTei8";//支付密钥 | |||||
| //生产 | //生产 | ||||
| // String mcuid = "69739487619282066230"; | // String mcuid = "69739487619282066230"; | ||||
| // String appid = "tt8183f73e1ebb053701"; | |||||
| // String salt = "rtQwyvhZHMuMYrY6GBm8DnhvEu0fuJUVHYS2KP6P"; | |||||
| String appid = "ttc0cdb6705ff345f301"; | |||||
| String salt = "FhGHClzfdDEPW6Cq4hw2GyoynJbpeq6wLQyM5iub"; | |||||
| AppAddSubMerchant appAddSubMerchant = new AppAddSubMerchant(); | |||||
| appAddSubMerchant.setAppId("ttef6599d8705b49b101"); | |||||
| appAddSubMerchant.setSalt("G8DdHfRcEPXB8kHQsnpVWvhLfMC7MZQ72aweTei8"); | |||||
| appAddSubMerchant.setSubMerchantId("666438479039619072"); | |||||
| appAddSubMerchant.setUrlType(2); | |||||
| AppAddSubMerchantResult appAddSubMerchantResult = appAddSubMerchant(appAddSubMerchant); | |||||
| System.out.println(appAddSubMerchantResult.toString()); | |||||
| // AppAddSubMerchant appAddSubMerchant = new AppAddSubMerchant(); | |||||
| // appAddSubMerchant.setAppId("ttef6599d8705b49b101"); | |||||
| // appAddSubMerchant.setSalt("G8DdHfRcEPXB8kHQsnpVWvhLfMC7MZQ72aweTei8"); | |||||
| // appAddSubMerchant.setSubMerchantId("666438479039619072"); | |||||
| // appAddSubMerchant.setUrlType(2); | |||||
| // AppAddSubMerchantResult appAddSubMerchantResult = appAddSubMerchant(appAddSubMerchant); | |||||
| // System.out.println(appAddSubMerchantResult.toString()); | |||||
| // Settle settle = new Settle(); | // Settle settle = new Settle(); | ||||
| // settle.setAppId(appid); | // settle.setAppId(appid); | ||||
| // settle.setSalt(salt); | // settle.setSalt(salt); | ||||
| ///** | ///** | ||||
| // * {"settle_params":"[{\"amount\":19,\"merchant_uid\":\"69798058370993871733\"}]", | |||||
| // * "settle_desc":"客户提现。", | |||||
| // * "sign":"94fa5f559880870f2fda53c67b236a3f", | |||||
| // * "out_settle_no":"572578660822212608", | |||||
| // * "out_order_no":"572308600038809600", | |||||
| // * "app_id":"tt9da4eb19029c2bb301"} | |||||
| // * {settle_params=[{"amount":6859,"merchant_uid":"70878359464980830803"}], | |||||
| // * settle_desc=自主完结分账, sign=9acc782f9196bb45823fdbe7e14b24df, | |||||
| // * out_settle_no=687210710707761152, | |||||
| // * out_order_no=687209074036342784, app_id=ttc0cdb6705ff345f301} | |||||
| // * response:{"err_no":6006,"err_tips":"其他分账方分账比例超限","settle_no":""} | |||||
| // * | |||||
| // * request:{settle_params=[{"amount":6900,"merchant_uid":"70878359464980830803"}], | |||||
| // * settle_desc=自主完结分账, sign=d3541104ea0a6234f96ea753aa514f7c, | |||||
| // * out_settle_no=687210746524631040, out_order_no=687208992638238720, app_id=ttc0cdb6705ff345f301} | |||||
| // * response:{"err_no":6006,"err_tips":"其他分账方分账比例超限","settle_no":""} | |||||
| // */ | // */ | ||||
| // settle.setOutSettleNo("57257866081118888"); | |||||
| // settle.setOutOrderNo("613967802405908480"); | |||||
| // settle.setOutSettleNo("687210746524631040"); | |||||
| // settle.setOutOrderNo("687208992638238720"); | |||||
| // settle.setSettleDesc("手动处理。"); | // settle.setSettleDesc("手动处理。"); | ||||
| // List list = new ArrayList<>(); | // List list = new ArrayList<>(); | ||||
| // Map<String,Object> settleParams = new HashMap<>(); | // Map<String,Object> settleParams = new HashMap<>(); | ||||
| // settleParams.put("merchant_uid",mcuid); | |||||
| // settleParams.put("amount",5); | |||||
| // settleParams.put("merchant_uid","70878359464980830803"); | |||||
| // settleParams.put("amount",6859); | |||||
| // list.add(settleParams); | // list.add(settleParams); | ||||
| // settle.setSettleParams(JSON.toJSONString(list)); | // settle.setSettleParams(JSON.toJSONString(list)); | ||||
| // SettleResult settle1 = settle(settle); | // SettleResult settle1 = settle(settle); | ||||
| // System.out.println(settle1.toString()); | // System.out.println(settle1.toString()); | ||||
| // QuerySettleResult querySettleResult = settleQuery(appid, salt, "57257866081118888", null); | |||||
| // System.out.println(querySettleResult.toString()); | |||||
| QuerySettleResult querySettleResult = settleQuery(appid, salt, "687210746524631040", null); | |||||
| System.out.println(querySettleResult.toString()); | |||||
| // OrderQueryResult orderQueryResult = orderQuery(appid, salt, "613879951804915712", null); | // OrderQueryResult orderQueryResult = orderQuery(appid, salt, "613879951804915712", null); | ||||
| // System.out.println(orderQueryResult.toString()); | // System.out.println(orderQueryResult.toString()); | ||||
| @@ -0,0 +1,48 @@ | |||||
| package com.iformall.douyin.pay; | |||||
| import com.iformall.douyin.pay.exception.TtPayException; | |||||
| import com.iformall.douyin.payv2.request.ProfitSharingRequest; | |||||
| import com.iformall.douyin.payv2.result.ProfitSharingResult; | |||||
| /** | |||||
| * 支付V2-资金应用-分账 | |||||
| * https://bytedance.feishu.cn/docx/doxcnj21j74jHyXEq6Ybg3AzOxh | |||||
| * @author pg 2021-6-23 | |||||
| * @date 2021-6-23 | |||||
| */ | |||||
| public interface ProfitSharingV2Service { | |||||
| /** | |||||
| * <pre> | |||||
| * 请求分账API | |||||
| * 接口简介 | |||||
| * 一笔订单完成后,开发者可以通过分账接口将这笔订单产生的资金结算给各个分账方。 | |||||
| * - 结算规则统一为核销D+3; | |||||
| * - CPS订单比较特殊,统一结算规则是核销D+3 | |||||
| * - 结算规则为核销D+3,表示订单到达“终态”3天后可以分账,“终态”表示所有的券/商品已核销或已退款 | |||||
| * - 如对上述结算规则有特殊需求,请联系对应行业经理(餐饮@Xinru Chen 酒旅@Jie Zhou @Yuekai Zhao 到综@Yankai Ouyang ) | |||||
| * - 若分账失败,请参考文档进行自查分账失败如何自查 | |||||
| * 核销D+3 | |||||
| * - 例如用户的一笔订单买了5张团购券,其中3张先核销,剩余2张再退款,最终退款完成的时间就是订单到达终态的时间。订单到达终态3天后,则可以发起分账 | |||||
| * | |||||
| * 返回 小程序分账id,系统会确保不超过64个字节(数字+ASCII) | |||||
| * | |||||
| */ | |||||
| String profitSharing(ProfitSharingRequest request) throws TtPayException; | |||||
| /** | |||||
| * <pre> | |||||
| * 查询分账接口 | |||||
| * out_order_no string 否 开发者交易订单id,长度<64byte,数字ASCII字符 | |||||
| * out_settle_no string 否 开发者分账单id,长度<64byte,数字、ASCII字符 | |||||
| * order_id string 否 抖音开平侧订单id,长度<64byte,数字、ASCII | |||||
| * settle_id string 否 抖音开平侧分账单id,长度<64byte,数字、ASCII | |||||
| * - 查询优先级: settle_id > order_id > out_settle_no > out_order_no,4个参数选填一个 | |||||
| * - 例如:请求填写了settle_id和order_id,服务只会按settle_id来查询,忽略order_id | |||||
| * - 如果未查询到结果,会返回空数组 | |||||
| * - 注意:对于担保交易和交易1.0的订单,如果需要根据order_id/out_order_no查询订单的分账记录,建议使用order_id(抖音开平侧支付单id查询),若使用out_order_no可能存在查询不到的情况 | |||||
| * | |||||
| */ | |||||
| ProfitSharingResult getProfitSharingResult(String order_id) throws TtPayException; | |||||
| } | |||||
| @@ -0,0 +1,201 @@ | |||||
| package com.iformall.douyin.pay; | |||||
| import com.github.binarywang.wxpay.bean.WxPayApiData; | |||||
| import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader; | |||||
| import com.iformall.douyin.pay.exception.TtPayException; | |||||
| import com.iformall.douyin.payv2.config.TtPayConfig; | |||||
| import com.iformall.douyin.payv2.request.*; | |||||
| import com.iformall.douyin.payv2.result.*; | |||||
| import org.apache.http.client.methods.HttpPost; | |||||
| import org.apache.http.client.methods.HttpRequestBase; | |||||
| /** | |||||
| * <pre> | |||||
| * 支付相关接口. | |||||
| * Created by Binary Wang on 2016/7/28. | |||||
| * </pre> | |||||
| * | |||||
| */ | |||||
| public interface TtPayService { | |||||
| /** | |||||
| * 获取微信支付请求url前缀,沙箱环境可能不一样. | |||||
| * | |||||
| * @return the pay base url | |||||
| */ | |||||
| String getPayBaseUrl(); | |||||
| /** | |||||
| * 发送post请求,得到响应字符串. | |||||
| * | |||||
| * @param url 请求地址 | |||||
| * @param requestStr 请求信息 | |||||
| * @return 返回请求结果字符串 string | |||||
| * @throws TtPayException the tt pay exception | |||||
| */ | |||||
| String postV2(String url, String requestStr) throws TtPayException; | |||||
| /** | |||||
| * 发送post请求,得到响应字符串. | |||||
| * | |||||
| * @param url 请求地址 | |||||
| * @param httpPost 请求信息 | |||||
| * @return 返回请求结果字符串 string | |||||
| * @throws TtPayException the tt pay exception | |||||
| */ | |||||
| String postV2(String url, HttpPost httpPost) throws TtPayException; | |||||
| /** | |||||
| * 发送http请求,得到响应字符串. | |||||
| * | |||||
| * @param url 请求地址 | |||||
| * @param httpRequest 请求信息,可以是put,post,get,delete等请求 | |||||
| * @return 返回请求结果字符串 string | |||||
| * @throws TtPayException the tt pay exception | |||||
| */ | |||||
| String requestV2(String url, HttpRequestBase httpRequest) throws TtPayException; | |||||
| /** | |||||
| * 发送get V2请求,得到响应字符串. | |||||
| * | |||||
| * @param url 请求地址 | |||||
| * @return 返回请求结果字符串 string | |||||
| * @throws TtPayException the tt pay exception | |||||
| */ | |||||
| String getV2(String url) throws TtPayException; | |||||
| /** | |||||
| * 获取分账服务类. | |||||
| * | |||||
| * @return the ent pay service | |||||
| */ | |||||
| ProfitSharingV2Service getProfitSharingV2Service(); | |||||
| /** | |||||
| * 2.1 交易回调设置接口 | |||||
| */ | |||||
| boolean callbackSettings(CallBackSettingsRequest settings) throws TtPayException; | |||||
| /** | |||||
| * 2.2 查询交易回调设置接口 | |||||
| */ | |||||
| CallBackSettingsRequest querySettings() throws TtPayException; | |||||
| /** | |||||
| * 解析预下单回调 | |||||
| * | |||||
| */ | |||||
| CreateOrderCallback parseOrderNotifyV2Result(String notifyData, SignatureHeader header) throws TtPayException; | |||||
| /** | |||||
| * 交易系统2.0模版下单依赖入口前端js-api,如果用户没有更新抖音,就没法使用模版下单,开发者可用该接口替用户发起下单,开发者发起的下单流程则不会有预下单回调,当低版本减少后,会下掉该接口,请不要应用于其它场景,否则会影响业务(对于poi商品,目前只支持团购券) | |||||
| * @param request | |||||
| * @return | |||||
| * @throws TtPayException | |||||
| */ | |||||
| TtPayUnifiedOrderV2Result createOrderV2(TtPayUnifiedOrderV2Request request) throws TtPayException; | |||||
| /** | |||||
| * <pre> | |||||
| * 5. 订单信息接口 | |||||
| */ | |||||
| TtPayOrderQueryV2Result queryOrderV2(String orderId, String outOrderNo) throws TtPayException; | |||||
| /** | |||||
| * <pre> | |||||
| * 5. 订单信息接口 | |||||
| */ | |||||
| TtPayOrderQueryV2Result queryOrderV2(TtPayOrderQueryV2Request request) throws TtPayException; | |||||
| /** | |||||
| * <pre> | |||||
| * 5. cps订单信息接口 | |||||
| */ | |||||
| TtPayCpsOrderQueryV2Result queryCpsOrderV2(String orderId, String outOrderNo) throws TtPayException; | |||||
| /** | |||||
| * 推送核销状态(非闭环核销) | |||||
| */ | |||||
| boolean pushDelivery(TtOrderPushDeliveryRequest request) throws TtPayException; | |||||
| /** | |||||
| * 获取配置. | |||||
| * | |||||
| * @return the config | |||||
| */ | |||||
| TtPayConfig getConfig(); | |||||
| /** | |||||
| * 设置配置对象. | |||||
| * | |||||
| * @param config the config | |||||
| */ | |||||
| void setConfig(TtPayConfig config); | |||||
| /** | |||||
| * <pre> | |||||
| * 开发者可用该接口替用户发起退款,开发者发起的退款流程则不会有退款申请回调。 | |||||
| * - 过期自动退说明 | |||||
| * - 过期自动退的退款被拒绝退款后,不会再次发起。 | |||||
| * - 在交易系统2.0产生的订单,如果用户购买的商品是过期退商品,交易系统2.0会自动发起退款,创建退款单,退款流程和用户发起退款流程相同。 | |||||
| * - 发起过期自动退条件 | |||||
| * - 非POI 商品 | |||||
| * - 前端传入合法的商品过期时间 | |||||
| * - 前端传入的goodsLabels中包含过期退标签 | |||||
| * - POI 商品 | |||||
| * - POI 商品设置了过期时间 | |||||
| * - POI 商品的 sub_title包含过期退标签 | |||||
| * - 旧订单系统不会自动发起过期自动退,仍需开发者处理。 | |||||
| * - 过期自动退的退款单由系统创建,因此 退款申请回调和状态通知回调 cp_extra 会是空值。 | |||||
| * - 外部退款单号说明 | |||||
| * - 已经在担保支付或者交易1.0使用过的外部退款单号,请不要在交易2.0使用,当交易2.0系统发现该外部退款单号已经在担保支付或者交易1.0创建过退款单号时,会拒绝创建退款单。 | |||||
| * - 已经升级到交易2.0的接口之后,不支持再降级使用交易1.0,否则产生的后果由开发者承担。 | |||||
| */ | |||||
| TtPayRefundV2Result refundV2(TtPayRefundV2Request request) throws TtPayException; | |||||
| /** | |||||
| * 5. 退款申请回调 | |||||
| * 当用户从交易的模板组件发起退款后,抖音开平交易系统会给向开发者的服务发起请求,用于通知开发者用户发起退款,开发者响应时需要返回必填的信息。 | |||||
| * 注意 | |||||
| * - 退款单的创建是在退款申请回调之前,退款申请回调请求开发者服务失败,也不会在C端展示错误,退款申请回调请求失败后会进行重试。 | |||||
| * - 请务必确保退款申请回调请求能按照正确的格式和参数要求返回。 | |||||
| * - 请求返回的外部退款单号也务必确保在同一小程序内不会重复,否则也会被认为请求失败。 | |||||
| * - 开发者返回的err_no 不为 0 也会认为请求失败,会进行重试,请确保请求成功,有拒绝退款的场景请在退款审核阶段拒绝退款。 | |||||
| * - 如果一直请求开发者服务失败,退款单将无法被推进到退款审核阶段,请务必确保响应无问题。 | |||||
| * - 退款申请回调务必做好幂等处理,相同的系统退款单号重复请求应当被视为相同的一次退款。 | |||||
| * | |||||
| */ | |||||
| RefundOrderCallback refundOrderNotifyV2Result(String notifyData, SignatureHeader header) throws TtPayException; | |||||
| /** | |||||
| * 退款审核结果同步 | |||||
| * @param request | |||||
| * @return | |||||
| * @throws TtPayException | |||||
| */ | |||||
| boolean merchantAuditCallback(TtRefundAuditMsgRequest request) throws TtPayException; | |||||
| /** | |||||
| * <pre> | |||||
| * 查询退款 | |||||
| * | |||||
| */ | |||||
| TtPayRefundQueryV2Result refundQueryV2(TtPayRefundQueryV2Request request) throws TtPayException; | |||||
| /** | |||||
| * 获取微信请求数据,方便接口调用方获取处理. | |||||
| * | |||||
| * @return the wx api data | |||||
| */ | |||||
| WxPayApiData getWxApiData(); | |||||
| } | |||||
| @@ -0,0 +1,216 @@ | |||||
| package com.iformall.douyin.pay.exception; | |||||
| import com.github.binarywang.wxpay.bean.result.BaseWxPayResult; | |||||
| import com.google.common.base.Joiner; | |||||
| import lombok.Data; | |||||
| import lombok.EqualsAndHashCode; | |||||
| /** | |||||
| * <pre> | |||||
| * 支付异常结果类 | |||||
| * Created by Binary Wang on 2017-6-6. | |||||
| * </pre> | |||||
| * | |||||
| * @author BinaryWang | |||||
| */ | |||||
| @Data | |||||
| @EqualsAndHashCode(callSuper = false) | |||||
| public class TtPayException extends Exception { | |||||
| private static final long serialVersionUID = -1246675502834889819L; | |||||
| /** | |||||
| * 自定义错误讯息. | |||||
| */ | |||||
| private String customErrorMsg; | |||||
| /** | |||||
| * 返回状态码. | |||||
| */ | |||||
| private String returnCode; | |||||
| /** | |||||
| * 返回信息. | |||||
| */ | |||||
| private String returnMsg; | |||||
| /** | |||||
| * 业务结果. | |||||
| */ | |||||
| private String resultCode; | |||||
| /** | |||||
| * 错误代码. | |||||
| */ | |||||
| private String errCode; | |||||
| /** | |||||
| * 错误代码描述. | |||||
| */ | |||||
| private String errCodeDes; | |||||
| /** | |||||
| * 微信支付返回的结果xml字符串. | |||||
| */ | |||||
| private String xmlString; | |||||
| /** | |||||
| * Instantiates a new Wx pay exception. | |||||
| * | |||||
| * @param customErrorMsg the custom error msg | |||||
| */ | |||||
| public TtPayException(String customErrorMsg) { | |||||
| super(customErrorMsg); | |||||
| this.customErrorMsg = customErrorMsg; | |||||
| } | |||||
| /** | |||||
| * Instantiates a new Wx pay exception. | |||||
| * | |||||
| * @param customErrorMsg the custom error msg | |||||
| * @param tr the tr | |||||
| */ | |||||
| public TtPayException(String customErrorMsg, Throwable tr) { | |||||
| super(customErrorMsg, tr); | |||||
| this.customErrorMsg = customErrorMsg; | |||||
| } | |||||
| private TtPayException(Builder builder) { | |||||
| super(builder.buildErrorMsg()); | |||||
| returnCode = builder.returnCode; | |||||
| returnMsg = builder.returnMsg; | |||||
| resultCode = builder.resultCode; | |||||
| errCode = builder.errCode; | |||||
| errCodeDes = builder.errCodeDes; | |||||
| xmlString = builder.xmlString; | |||||
| } | |||||
| /** | |||||
| * 通过BaseWxPayResult生成异常对象. | |||||
| * | |||||
| * @param payBaseResult the pay base result | |||||
| * @return the wx pay exception | |||||
| */ | |||||
| public static TtPayException from(BaseWxPayResult payBaseResult) { | |||||
| return TtPayException.newBuilder() | |||||
| .xmlString(payBaseResult.getXmlString()) | |||||
| .returnMsg(payBaseResult.getReturnMsg()) | |||||
| .returnCode(payBaseResult.getReturnCode()) | |||||
| .resultCode(payBaseResult.getResultCode()) | |||||
| .errCode(payBaseResult.getErrCode()) | |||||
| .errCodeDes(payBaseResult.getErrCodeDes()) | |||||
| .build(); | |||||
| } | |||||
| /** | |||||
| * New builder builder. | |||||
| * | |||||
| * @return the builder | |||||
| */ | |||||
| public static Builder newBuilder() { | |||||
| return new Builder(); | |||||
| } | |||||
| /** | |||||
| * The type Builder. | |||||
| */ | |||||
| public static final class Builder { | |||||
| private String returnCode; | |||||
| private String returnMsg; | |||||
| private String resultCode; | |||||
| private String errCode; | |||||
| private String errCodeDes; | |||||
| private String xmlString; | |||||
| private Builder() { | |||||
| } | |||||
| /** | |||||
| * Return code builder. | |||||
| * | |||||
| * @param returnCode the return code | |||||
| * @return the builder | |||||
| */ | |||||
| public Builder returnCode(String returnCode) { | |||||
| this.returnCode = returnCode; | |||||
| return this; | |||||
| } | |||||
| /** | |||||
| * Return msg builder. | |||||
| * | |||||
| * @param returnMsg the return msg | |||||
| * @return the builder | |||||
| */ | |||||
| public Builder returnMsg(String returnMsg) { | |||||
| this.returnMsg = returnMsg; | |||||
| return this; | |||||
| } | |||||
| /** | |||||
| * Result code builder. | |||||
| * | |||||
| * @param resultCode the result code | |||||
| * @return the builder | |||||
| */ | |||||
| public Builder resultCode(String resultCode) { | |||||
| this.resultCode = resultCode; | |||||
| return this; | |||||
| } | |||||
| /** | |||||
| * Err code builder. | |||||
| * | |||||
| * @param errCode the err code | |||||
| * @return the builder | |||||
| */ | |||||
| public Builder errCode(String errCode) { | |||||
| this.errCode = errCode; | |||||
| return this; | |||||
| } | |||||
| /** | |||||
| * Err code des builder. | |||||
| * | |||||
| * @param errCodeDes the err code des | |||||
| * @return the builder | |||||
| */ | |||||
| public Builder errCodeDes(String errCodeDes) { | |||||
| this.errCodeDes = errCodeDes; | |||||
| return this; | |||||
| } | |||||
| /** | |||||
| * Xml string builder. | |||||
| * | |||||
| * @param xmlString the xml string | |||||
| * @return the builder | |||||
| */ | |||||
| public Builder xmlString(String xmlString) { | |||||
| this.xmlString = xmlString; | |||||
| return this; | |||||
| } | |||||
| /** | |||||
| * Build wx pay exception. | |||||
| * | |||||
| * @return the wx pay exception | |||||
| */ | |||||
| public TtPayException build() { | |||||
| return new TtPayException(this); | |||||
| } | |||||
| /** | |||||
| * Build error msg string. | |||||
| * | |||||
| * @return the string | |||||
| */ | |||||
| public String buildErrorMsg() { | |||||
| return Joiner.on(",").skipNulls().join( | |||||
| returnCode == null ? null : String.format("返回代码:[%s]", returnCode), | |||||
| returnMsg == null ? null : String.format("返回信息:[%s]", returnMsg), | |||||
| resultCode == null ? null : String.format("结果代码:[%s]", resultCode), | |||||
| errCode == null ? null : String.format("错误代码:[%s]", errCode), | |||||
| errCodeDes == null ? null : String.format("错误详情:[%s]", errCodeDes), | |||||
| xmlString == null ? null : "返回的原始报文:\n" + xmlString | |||||
| ); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,240 @@ | |||||
| package com.iformall.douyin.pay.impl; | |||||
| import com.alibaba.fastjson.TypeReference; | |||||
| import com.github.binarywang.wxpay.bean.WxPayApiData; | |||||
| import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader; | |||||
| import com.github.binarywang.wxpay.exception.WxPayException; | |||||
| import com.github.binarywang.wxpay.v3.util.AesUtils; | |||||
| import com.google.gson.Gson; | |||||
| import com.google.gson.GsonBuilder; | |||||
| import com.iformall.douyin.pay.ProfitSharingV2Service; | |||||
| import com.iformall.douyin.pay.TtPayService; | |||||
| import com.iformall.douyin.pay.exception.TtPayException; | |||||
| import com.iformall.douyin.payv2.config.TtPayConfig; | |||||
| import com.iformall.douyin.payv2.request.*; | |||||
| import com.iformall.douyin.payv2.result.*; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import java.io.IOException; | |||||
| import java.nio.charset.StandardCharsets; | |||||
| import java.security.GeneralSecurityException; | |||||
| import java.util.*; | |||||
| /** | |||||
| * <pre> | |||||
| * 支付接口请求抽象实现类 | |||||
| * Created by Binary Wang on 2017-7-8. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public abstract class BaseTtPayServiceImpl implements TtPayService { | |||||
| private static final String TOTAL_FUND_COUNT = "资金流水总笔数"; | |||||
| /** | |||||
| * The Log. | |||||
| */ | |||||
| final Logger log = LoggerFactory.getLogger(this.getClass()); | |||||
| private static final Gson GSON = new GsonBuilder().create(); | |||||
| /** | |||||
| * The constant wxApiData. | |||||
| */ | |||||
| static ThreadLocal<WxPayApiData> wxApiData = new ThreadLocal<>(); | |||||
| private ProfitSharingV2Service profitSharingV2Service = new ProfitSharingV2ServiceImpl(this); | |||||
| /** | |||||
| * The Config. | |||||
| */ | |||||
| protected TtPayConfig config; | |||||
| @Override | |||||
| public ProfitSharingV2Service getProfitSharingV2Service() { | |||||
| return profitSharingV2Service; | |||||
| } | |||||
| @Override | |||||
| public TtPayConfig getConfig() { | |||||
| return this.config; | |||||
| } | |||||
| @Override | |||||
| public void setConfig(TtPayConfig config) { | |||||
| this.config = config; | |||||
| } | |||||
| @Override | |||||
| public String getPayBaseUrl() { | |||||
| return this.getConfig().getPayBaseUrl(); | |||||
| } | |||||
| @Override | |||||
| public boolean callbackSettings(CallBackSettingsRequest request) throws TtPayException { | |||||
| String url = String.format("%s/api/apps/trade/v2/settings", this.getPayBaseUrl()); | |||||
| String response = this.postV2(url, GSON.toJson(request)); | |||||
| return GSON.fromJson(response, BaseTtPayResult.class).isSuccess(); | |||||
| } | |||||
| @Override | |||||
| public CallBackSettingsRequest querySettings() throws TtPayException { | |||||
| String url = String.format("%s/api/apps/trade/v2/query_settings", this.getPayBaseUrl()); | |||||
| String response = this.postV2(url,""); | |||||
| BaseTtPayResult<CallBackSettingsRequest> baseTtPayResult = GSON.fromJson(response, | |||||
| new TypeReference<BaseTtPayResult<CallBackSettingsRequest>>(){}.getType()); | |||||
| if(baseTtPayResult.isSuccess()){ | |||||
| return baseTtPayResult.getData(); | |||||
| }else{ | |||||
| throw new TtPayException(baseTtPayResult.getErrTips()); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public CreateOrderCallback parseOrderNotifyV2Result(String notifyData, SignatureHeader header) throws TtPayException { | |||||
| if (Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)) { | |||||
| throw new TtPayException("非法请求,头部信息验证失败"); | |||||
| } | |||||
| OriginNotifyResponse response = GSON.fromJson(notifyData, OriginNotifyResponse.class); | |||||
| try { | |||||
| CreateOrderCallback createOrderCallback = GSON.fromJson(response.getMsg(), CreateOrderCallback.class); | |||||
| return createOrderCallback; | |||||
| } catch (Exception e) { | |||||
| throw new TtPayException("解析报文异常!", e); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 校验通知签名 | |||||
| * | |||||
| * @param header 通知头信息 | |||||
| * @param data 通知数据 | |||||
| * @return true:校验通过 false:校验不通过 | |||||
| */ | |||||
| private boolean verifyNotifySign(SignatureHeader header, String data) { | |||||
| String beforeSign = String.format("%s\n%s\n%s\n", | |||||
| header.getTimeStamp(), | |||||
| header.getNonce(), | |||||
| data); | |||||
| return this.getConfig().getVerifier().verify( | |||||
| beforeSign.getBytes(StandardCharsets.UTF_8), header.getSigned()); | |||||
| } | |||||
| @Override | |||||
| public TtPayUnifiedOrderV2Result createOrderV2(TtPayUnifiedOrderV2Request request) throws TtPayException{ | |||||
| String url = String.format("%s/api/apps/trade/v2/create_order", this.getPayBaseUrl()); | |||||
| String response = this.postV2(url, GSON.toJson(request)); | |||||
| BaseTtPayResult<TtPayUnifiedOrderV2Result> baseTtPayResult = GSON.fromJson(response, | |||||
| new TypeReference<BaseTtPayResult<TtPayUnifiedOrderV2Result>>(){}.getType()); | |||||
| if(baseTtPayResult.isSuccess()){ | |||||
| return baseTtPayResult.getData(); | |||||
| }else{ | |||||
| throw new TtPayException(baseTtPayResult.getErrTips()); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public TtPayOrderQueryV2Result queryOrderV2(String orderId, String outOrderNo) throws TtPayException { | |||||
| TtPayOrderQueryV2Request request = new TtPayOrderQueryV2Request(); | |||||
| request.setOutOrderNo(StringUtils.trimToNull(outOrderNo)); | |||||
| request.setOrderId(StringUtils.trimToNull(orderId)); | |||||
| return this.queryOrderV2(request); | |||||
| } | |||||
| @Override | |||||
| public TtPayOrderQueryV2Result queryOrderV2(TtPayOrderQueryV2Request request) throws TtPayException { | |||||
| String url = String.format("%s/api/apps/trade/v2/query_order", this.getPayBaseUrl()); | |||||
| String response = this.postV2(url, GSON.toJson(request)); | |||||
| BaseTtPayResult<TtPayOrderQueryV2Result> baseTtPayResult = GSON.fromJson(response, | |||||
| new TypeReference<BaseTtPayResult<TtPayOrderQueryV2Result>>(){}.getType()); | |||||
| if(baseTtPayResult.isSuccess()){ | |||||
| return baseTtPayResult.getData(); | |||||
| }else{ | |||||
| throw new TtPayException(baseTtPayResult.getErrTips()); | |||||
| } | |||||
| } | |||||
| public TtPayCpsOrderQueryV2Result queryCpsOrderV2(String orderId, String outOrderNo) throws TtPayException{ | |||||
| String url = String.format("%s/api/apps/trade/v2/query_cps", this.getPayBaseUrl()); | |||||
| TtPayOrderQueryV2Request request = new TtPayOrderQueryV2Request(); | |||||
| request.setOutOrderNo(StringUtils.trimToNull(outOrderNo)); | |||||
| request.setOrderId(StringUtils.trimToNull(orderId)); | |||||
| String response = this.postV2(url, GSON.toJson(request)); | |||||
| BaseTtPayResult<TtPayCpsOrderQueryV2Result> baseTtPayResult = GSON.fromJson(response, | |||||
| new TypeReference<BaseTtPayResult<TtPayCpsOrderQueryV2Result>>(){}.getType()); | |||||
| if(baseTtPayResult.isSuccess()){ | |||||
| return baseTtPayResult.getData(); | |||||
| }else{ | |||||
| throw new TtPayException(baseTtPayResult.getErrTips()); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public boolean pushDelivery(TtOrderPushDeliveryRequest request) throws TtPayException{ | |||||
| String url = String.format("%s/api/apps/trade/v2/push_delivery", this.getPayBaseUrl()); | |||||
| String response = this.postV2(url, GSON.toJson(request)); | |||||
| return GSON.fromJson(response, BaseTtPayResult.class).isSuccess(); | |||||
| } | |||||
| @Override | |||||
| public TtPayRefundV2Result refundV2(TtPayRefundV2Request request) throws TtPayException { | |||||
| String url = String.format("%s/api/apps/trade/v2/create_refund", this.getPayBaseUrl()); | |||||
| String response = this.postV2(url, GSON.toJson(request)); | |||||
| BaseTtPayResult<TtPayRefundV2Result> baseTtPayResult = GSON.fromJson(response, | |||||
| new TypeReference<BaseTtPayResult<TtPayRefundV2Result>>(){}.getType()); | |||||
| if(baseTtPayResult.isSuccess()){ | |||||
| return baseTtPayResult.getData(); | |||||
| }else{ | |||||
| throw new TtPayException(baseTtPayResult.getErrTips()); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public RefundOrderCallback refundOrderNotifyV2Result(String notifyData, SignatureHeader header) throws TtPayException { | |||||
| if (Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)) { | |||||
| throw new TtPayException("非法请求,头部信息验证失败"); | |||||
| } | |||||
| OriginNotifyResponse response = GSON.fromJson(notifyData, OriginNotifyResponse.class); | |||||
| try { | |||||
| RefundOrderCallback refundOrderCallback = GSON.fromJson(response.getMsg(), RefundOrderCallback.class); | |||||
| return refundOrderCallback; | |||||
| } catch (Exception e) { | |||||
| throw new TtPayException("解析报文异常!", e); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public boolean merchantAuditCallback(TtRefundAuditMsgRequest request) throws TtPayException{ | |||||
| String url = String.format("%s/api/apps/trade/v2/merchant_audit_callback", this.getPayBaseUrl()); | |||||
| String response = this.postV2(url, GSON.toJson(request)); | |||||
| return GSON.fromJson(response, BaseTtPayResult.class).isSuccess(); | |||||
| } | |||||
| @Override | |||||
| public TtPayRefundQueryV2Result refundQueryV2(TtPayRefundQueryV2Request request) throws TtPayException{ | |||||
| String url = String.format("%s/api/apps/trade/v2/query_refund", this.getPayBaseUrl()); | |||||
| String response = this.postV2(url, GSON.toJson(request)); | |||||
| BaseTtPayResult<TtPayRefundQueryV2Result> baseTtPayResult = GSON.fromJson(response, | |||||
| new TypeReference<BaseTtPayResult<TtPayRefundQueryV2Result>>(){}.getType()); | |||||
| if(baseTtPayResult.isSuccess()){ | |||||
| return baseTtPayResult.getData(); | |||||
| }else{ | |||||
| throw new TtPayException(baseTtPayResult.getErrTips()); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public WxPayApiData getWxApiData() { | |||||
| try { | |||||
| return wxApiData.get(); | |||||
| } finally { | |||||
| //一般来说,接口请求会在一个线程内进行,这种情况下,每个线程get的会是之前所存入的数据, | |||||
| // 但以防万一有同一线程多次请求的问题,所以每次获取完数据后移除对应数据 | |||||
| wxApiData.remove(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,76 @@ | |||||
| package com.iformall.douyin.pay.impl; | |||||
| import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader; | |||||
| import com.google.gson.Gson; | |||||
| import com.google.gson.GsonBuilder; | |||||
| import com.google.gson.JsonObject; | |||||
| import com.iformall.douyin.pay.ProfitSharingV2Service; | |||||
| import com.iformall.douyin.pay.TtPayService; | |||||
| import com.iformall.douyin.pay.exception.TtPayException; | |||||
| import com.iformall.douyin.payv2.auth.Verifier; | |||||
| import com.iformall.douyin.payv2.request.ProfitSharingRequest; | |||||
| import com.iformall.douyin.payv2.result.BaseTtPayResult; | |||||
| import com.iformall.douyin.payv2.result.ProfitSharingResult; | |||||
| import lombok.RequiredArgsConstructor; | |||||
| import lombok.extern.slf4j.Slf4j; | |||||
| import me.chanjar.weixin.common.util.json.GsonHelper; | |||||
| import java.nio.charset.StandardCharsets; | |||||
| /** | |||||
| * 支付V2-资金应用-分账Service | |||||
| * | |||||
| * @author pg 2021-6-23 | |||||
| * @version 1.0 | |||||
| */ | |||||
| @Slf4j | |||||
| @RequiredArgsConstructor | |||||
| public class ProfitSharingV2ServiceImpl implements ProfitSharingV2Service { | |||||
| private static final Gson GSON = new GsonBuilder().create(); | |||||
| private final TtPayService payService; | |||||
| @Override | |||||
| public String profitSharing(ProfitSharingRequest request) throws TtPayException { | |||||
| String url = String.format("%s/api/apps/trade/v2/create_settle", this.payService.getPayBaseUrl()); | |||||
| String result = this.payService.postV2(url, GSON.toJson(request)); | |||||
| BaseTtPayResult baseTtPayResult = GSON.fromJson(result, BaseTtPayResult.class); | |||||
| if(baseTtPayResult.isSuccess()){ | |||||
| Object data = baseTtPayResult.getData(); | |||||
| JsonObject asJsonObject = GSON.toJsonTree(data).getAsJsonObject(); | |||||
| return GsonHelper.getString(asJsonObject,"settle_id"); | |||||
| }else{ | |||||
| throw new TtPayException(baseTtPayResult.getErrTips()); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public ProfitSharingResult getProfitSharingResult(String order_id) throws TtPayException { | |||||
| String url = String.format("%s/api/apps/trade/v2/query_settle", this.payService.getPayBaseUrl()); | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("order_id",order_id); | |||||
| String result = this.payService.postV2(url,jsonObject.toString()); | |||||
| return GSON.fromJson(result, ProfitSharingResult.class); | |||||
| } | |||||
| /** | |||||
| * 校验通知签名 | |||||
| * | |||||
| * @param header 通知头信息 | |||||
| * @param data 通知数据 | |||||
| * @return true:校验通过 false:校验不通过 | |||||
| */ | |||||
| private boolean verifyNotifySign(SignatureHeader header, String data) throws TtPayException { | |||||
| String beforeSign = String.format("%s\n%s\n%s\n", | |||||
| header.getTimeStamp(), | |||||
| header.getNonce(), | |||||
| data); | |||||
| Verifier verifier = this.payService.getConfig().getVerifier(); | |||||
| if (verifier == null) { | |||||
| throw new TtPayException("检验对象为空"); | |||||
| } | |||||
| return verifier.verify( | |||||
| beforeSign.getBytes(StandardCharsets.UTF_8), header.getSigned()); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,155 @@ | |||||
| package com.iformall.douyin.pay.impl; | |||||
| import com.google.gson.JsonElement; | |||||
| import com.google.gson.JsonObject; | |||||
| import com.iformall.douyin.pay.exception.TtPayException; | |||||
| import me.chanjar.weixin.common.util.json.GsonParser; | |||||
| import org.apache.http.HttpStatus; | |||||
| import org.apache.http.client.config.RequestConfig; | |||||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||||
| import org.apache.http.client.methods.HttpGet; | |||||
| import org.apache.http.client.methods.HttpPost; | |||||
| import org.apache.http.client.methods.HttpRequestBase; | |||||
| import org.apache.http.entity.StringEntity; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import org.apache.http.util.EntityUtils; | |||||
| import java.io.UnsupportedEncodingException; | |||||
| import java.nio.charset.StandardCharsets; | |||||
| /** | |||||
| * <pre> | |||||
| * 支付请求实现类,apache httpclient实现. | |||||
| * Created by Binary Wang on 2016/7/28. | |||||
| * </pre> | |||||
| * | |||||
| */ | |||||
| public class TtPayServiceApacheHttpImpl extends BaseTtPayServiceImpl { | |||||
| @Override | |||||
| public String postV2(String url, String requestStr) throws TtPayException { | |||||
| CloseableHttpClient httpClient = this.createApiV2HttpClient(); | |||||
| HttpPost httpPost = this.createHttpPost(url, requestStr); | |||||
| httpPost.addHeader("Accept", "application/json"); | |||||
| httpPost.addHeader("Content-Type", "application/json"); | |||||
| try (CloseableHttpResponse response = httpClient.execute(httpPost)) { | |||||
| //v3已经改为通过状态码判断200 204 成功 | |||||
| int statusCode = response.getStatusLine().getStatusCode(); | |||||
| //post方法有可能会没有返回值的情况 | |||||
| String responseString; | |||||
| if (response.getEntity() == null) { | |||||
| responseString = null; | |||||
| } else { | |||||
| responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); | |||||
| } | |||||
| if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) { | |||||
| this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString); | |||||
| return responseString; | |||||
| } else { | |||||
| //有错误提示信息返回 | |||||
| JsonObject jsonObject = GsonParser.parse(responseString); | |||||
| throw convertException(jsonObject); | |||||
| } | |||||
| } catch (Exception e) { | |||||
| this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage()); | |||||
| throw (e instanceof TtPayException) ? (TtPayException) e : new TtPayException(e.getMessage(), e); | |||||
| } finally { | |||||
| httpPost.releaseConnection(); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public String postV2(String url, HttpPost httpPost) throws TtPayException { | |||||
| return this.requestV2(url, httpPost); | |||||
| } | |||||
| @Override | |||||
| public String requestV2(String url, HttpRequestBase httpRequest) throws TtPayException { | |||||
| httpRequest.setConfig(RequestConfig.custom() | |||||
| .setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout()) | |||||
| .setConnectTimeout(this.getConfig().getHttpConnectionTimeout()) | |||||
| .setSocketTimeout(this.getConfig().getHttpTimeout()) | |||||
| .build()); | |||||
| CloseableHttpClient httpClient = this.createApiV2HttpClient(); | |||||
| try (CloseableHttpResponse response = httpClient.execute(httpRequest)) { | |||||
| //v3已经改为通过状态码判断200 204 成功 | |||||
| int statusCode = response.getStatusLine().getStatusCode(); | |||||
| //post方法有可能会没有返回值的情况 | |||||
| String responseString; | |||||
| if (response.getEntity() == null) { | |||||
| responseString = null; | |||||
| } else { | |||||
| responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); | |||||
| } | |||||
| if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) { | |||||
| this.log.info("\n【请求地址】:{}\n【响应数据】:{}", url, responseString); | |||||
| return responseString; | |||||
| } else { | |||||
| //有错误提示信息返回 | |||||
| JsonObject jsonObject = GsonParser.parse(responseString); | |||||
| throw convertException(jsonObject); | |||||
| } | |||||
| } catch (Exception e) { | |||||
| this.log.error("\n【请求地址】:{}\n【异常信息】:{}", url, e.getMessage()); | |||||
| throw (e instanceof TtPayException) ? (TtPayException) e : new TtPayException(e.getMessage(), e); | |||||
| } finally { | |||||
| httpRequest.releaseConnection(); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public String getV2(String url) throws TtPayException { | |||||
| HttpGet httpGet = new HttpGet(url); | |||||
| httpGet.addHeader("Accept", "application/json"); | |||||
| httpGet.addHeader("Content-Type", "application/json"); | |||||
| return this.requestV2(url.toString(), httpGet); | |||||
| } | |||||
| private CloseableHttpClient createApiV2HttpClient() throws TtPayException { | |||||
| CloseableHttpClient apiV3HttpClient = this.getConfig().getApiV2HttpClient(); | |||||
| if (null == apiV3HttpClient) { | |||||
| return this.getConfig().initApiV2HttpClient(); | |||||
| } | |||||
| return apiV3HttpClient; | |||||
| } | |||||
| private StringEntity createEntry(String requestStr) { | |||||
| try { | |||||
| return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)); | |||||
| } catch (UnsupportedEncodingException e) { | |||||
| //cannot happen | |||||
| this.log.error(e.getMessage(), e); | |||||
| return null; | |||||
| } | |||||
| } | |||||
| private HttpPost createHttpPost(String url, String requestStr) { | |||||
| HttpPost httpPost = new HttpPost(url); | |||||
| httpPost.setEntity(this.createEntry(requestStr)); | |||||
| httpPost.setConfig(RequestConfig.custom() | |||||
| .setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout()) | |||||
| .setConnectTimeout(this.getConfig().getHttpConnectionTimeout()) | |||||
| .setSocketTimeout(this.getConfig().getHttpTimeout()) | |||||
| .build()); | |||||
| return httpPost; | |||||
| } | |||||
| private TtPayException convertException(JsonObject jsonObject) { | |||||
| //todo 这里考虑使用新的适用于V2的异常 | |||||
| JsonElement codeElement = jsonObject.get("code"); | |||||
| String code = codeElement == null ? null : codeElement.getAsString(); | |||||
| String message = jsonObject.get("message").getAsString(); | |||||
| TtPayException ttPayException = new TtPayException(message); | |||||
| ttPayException.setErrCode(code); | |||||
| ttPayException.setErrCodeDes(message); | |||||
| return ttPayException; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,11 @@ | |||||
| package com.iformall.douyin.pay.impl; | |||||
| /** | |||||
| * <pre> | |||||
| * 支付接口请求实现类,默认使用Apache HttpClient实现 | |||||
| * Created by Binary Wang on 2017-7-8. | |||||
| * </pre> | |||||
| * | |||||
| */ | |||||
| public class TtPayServiceImpl extends TtPayServiceApacheHttpImpl { | |||||
| } | |||||
| @@ -0,0 +1,32 @@ | |||||
| package com.iformall.douyin.payv2.auth; | |||||
| import me.chanjar.weixin.common.error.WxRuntimeException; | |||||
| import java.security.*; | |||||
| import java.util.Base64; | |||||
| public class CertificatesVerifier implements Verifier { | |||||
| private PublicKey apiKey; | |||||
| public CertificatesVerifier(PublicKey apiKey) { | |||||
| this.apiKey = apiKey; | |||||
| } | |||||
| @Override | |||||
| public boolean verify(byte[] message, String signature) { | |||||
| try { | |||||
| Signature sign = Signature.getInstance("SHA256withRSA"); | |||||
| sign.initVerify(this.apiKey); | |||||
| sign.update(message); | |||||
| return sign.verify(Base64.getDecoder().decode(signature)); | |||||
| } catch (NoSuchAlgorithmException e) { | |||||
| throw new WxRuntimeException("当前Java环境不支持SHA256withRSA", e); | |||||
| } catch (SignatureException e) { | |||||
| throw new WxRuntimeException("签名验证过程发生了错误", e); | |||||
| } catch (InvalidKeyException e) { | |||||
| throw new WxRuntimeException("无效的证书", e); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| package com.iformall.douyin.payv2.auth; | |||||
| import me.chanjar.weixin.common.error.WxRuntimeException; | |||||
| import java.security.*; | |||||
| import java.util.Base64; | |||||
| public class PrivateKeySigner implements Signer { | |||||
| private String certificateSerialNumber; | |||||
| private PrivateKey privateKey; | |||||
| public PrivateKeySigner(String serialNumber, PrivateKey privateKey) { | |||||
| this.certificateSerialNumber = serialNumber; | |||||
| this.privateKey = privateKey; | |||||
| } | |||||
| @Override | |||||
| public SignatureResult sign(byte[] message) { | |||||
| try { | |||||
| Signature sign = Signature.getInstance("SHA256withRSA"); | |||||
| sign.initSign(privateKey); | |||||
| sign.update(message); | |||||
| return new SignatureResult( | |||||
| Base64.getEncoder().encodeToString(sign.sign()), certificateSerialNumber); | |||||
| } catch (NoSuchAlgorithmException e) { | |||||
| throw new WxRuntimeException("当前Java环境不支持SHA256withRSA", e); | |||||
| } catch (SignatureException e) { | |||||
| throw new WxRuntimeException("签名计算失败", e); | |||||
| } catch (InvalidKeyException e) { | |||||
| throw new WxRuntimeException("无效的私钥", e); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| package com.iformall.douyin.payv2.auth; | |||||
| public interface Signer { | |||||
| SignatureResult sign(byte[] message); | |||||
| class SignatureResult { | |||||
| String sign; | |||||
| String certificateSerialNumber; | |||||
| public SignatureResult(String sign, String serialNumber) { | |||||
| this.sign = sign; | |||||
| this.certificateSerialNumber = serialNumber; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,87 @@ | |||||
| package com.iformall.douyin.payv2.auth; | |||||
| import com.iformall.douyin.payv2.util.Credentials; | |||||
| import lombok.extern.slf4j.Slf4j; | |||||
| import org.apache.http.HttpEntityEnclosingRequest; | |||||
| import org.apache.http.client.methods.HttpRequestWrapper; | |||||
| import org.apache.http.util.EntityUtils; | |||||
| import java.io.IOException; | |||||
| import java.net.URI; | |||||
| import java.nio.charset.StandardCharsets; | |||||
| import java.security.SecureRandom; | |||||
| @Slf4j | |||||
| public class TtPayCredentials implements Credentials { | |||||
| private static final String SYMBOLS = | |||||
| "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; | |||||
| private static final SecureRandom RANDOM = new SecureRandom(); | |||||
| protected String appId; | |||||
| protected Signer signer; | |||||
| public TtPayCredentials(String appId, Signer signer) { | |||||
| this.appId = appId; | |||||
| this.signer = signer; | |||||
| } | |||||
| public String getAppId() { | |||||
| return appId; | |||||
| } | |||||
| protected long generateTimestamp() { | |||||
| return System.currentTimeMillis() / 1000; | |||||
| } | |||||
| protected String generateNonceStr() { | |||||
| char[] nonceChars = new char[32]; | |||||
| for (int index = 0; index < nonceChars.length; ++index) { | |||||
| nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length())); | |||||
| } | |||||
| return new String(nonceChars); | |||||
| } | |||||
| @Override | |||||
| public final String getSchema() { | |||||
| return "SHA256-RSA2048"; | |||||
| } | |||||
| @Override | |||||
| public final String getToken(HttpRequestWrapper request) throws IOException { | |||||
| String nonceStr = generateNonceStr(); | |||||
| long timestamp = generateTimestamp(); | |||||
| String message = buildMessage(nonceStr, timestamp, request); | |||||
| log.debug("authorization message=[{}]", message); | |||||
| Signer.SignatureResult signature = signer.sign(message.getBytes(StandardCharsets.UTF_8)); | |||||
| String token = "appid=\"" + getAppId() + "\"," | |||||
| + "nonce_str=\"" + nonceStr + "\"," | |||||
| + "timestamp=\"" + timestamp + "\"," | |||||
| + "key_version=\"" + signature.certificateSerialNumber + "\"," | |||||
| + "signature=\"" + signature.sign + "\""; | |||||
| log.debug("authorization token=[{}]", token); | |||||
| return token; | |||||
| } | |||||
| protected final String buildMessage(String nonce, long timestamp, HttpRequestWrapper request) | |||||
| throws IOException { | |||||
| URI uri = request.getURI(); | |||||
| String canonicalUrl = uri.getRawPath(); | |||||
| if (uri.getQuery() != null) { | |||||
| canonicalUrl += "?" + uri.getRawQuery(); | |||||
| } | |||||
| String body = ""; | |||||
| // PATCH,POST,PUT | |||||
| body = EntityUtils.toString(((HttpEntityEnclosingRequest) request).getEntity()); | |||||
| return request.getRequestLine().getMethod() + "\n" | |||||
| + canonicalUrl + "\n" | |||||
| + timestamp + "\n" | |||||
| + nonce + "\n" | |||||
| + body + "\n"; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,55 @@ | |||||
| package com.iformall.douyin.payv2.auth; | |||||
| import com.iformall.douyin.payv2.util.Validator; | |||||
| import lombok.extern.slf4j.Slf4j; | |||||
| import org.apache.http.Header; | |||||
| import org.apache.http.HttpEntity; | |||||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||||
| import org.apache.http.entity.ContentType; | |||||
| import org.apache.http.util.EntityUtils; | |||||
| import java.io.IOException; | |||||
| @Slf4j | |||||
| public class TtPayValidator implements Validator { | |||||
| private Verifier verifier; | |||||
| public TtPayValidator(Verifier verifier) { | |||||
| this.verifier = verifier; | |||||
| } | |||||
| @Override | |||||
| public final boolean validate(CloseableHttpResponse response) throws IOException { | |||||
| if (!ContentType.APPLICATION_JSON.getMimeType().equals(ContentType.parse(String.valueOf(response.getFirstHeader("Content-Type").getValue())).getMimeType())) { | |||||
| return true; | |||||
| } | |||||
| Header sign = response.getFirstHeader("Byte-Signature"); | |||||
| Header timestamp = response.getFirstHeader("Byte-Timestamp"); | |||||
| Header nonce = response.getFirstHeader("Byte-Nonce-Str"); | |||||
| // todo: check timestamp | |||||
| if (timestamp == null || nonce == null || sign == null) { | |||||
| return false; | |||||
| } | |||||
| String message = buildMessage(response); | |||||
| return verifier.verify(message.getBytes("utf-8"), sign.getValue()); | |||||
| } | |||||
| protected final String buildMessage(CloseableHttpResponse response) throws IOException { | |||||
| String timestamp = response.getFirstHeader("Byte-Timestamp").getValue(); | |||||
| String nonce = response.getFirstHeader("Byte-Nonce-Str").getValue(); | |||||
| String body = getResponseBody(response); | |||||
| return timestamp + "\n" | |||||
| + nonce + "\n" | |||||
| + body + "\n"; | |||||
| } | |||||
| protected final String getResponseBody(CloseableHttpResponse response) throws IOException { | |||||
| HttpEntity entity = response.getEntity(); | |||||
| return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : ""; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| package com.iformall.douyin.payv2.auth; | |||||
| public interface Verifier { | |||||
| boolean verify(byte[] message, String signature); | |||||
| } | |||||
| @@ -0,0 +1,179 @@ | |||||
| package com.iformall.douyin.payv2.config; | |||||
| import com.github.binarywang.wxpay.v3.util.PemUtils; | |||||
| import com.iformall.douyin.pay.exception.TtPayException; | |||||
| import com.iformall.douyin.payv2.auth.CertificatesVerifier; | |||||
| import com.iformall.douyin.payv2.auth.TtPayValidator; | |||||
| import com.iformall.douyin.payv2.auth.Verifier; | |||||
| import com.iformall.douyin.payv2.util.TtPayV2HttpClientBuilder; | |||||
| import jodd.util.ResourcesUtil; | |||||
| import lombok.Data; | |||||
| import lombok.SneakyThrows; | |||||
| import org.apache.commons.lang3.RegExUtils; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import java.io.*; | |||||
| import java.net.URL; | |||||
| import java.security.PrivateKey; | |||||
| import java.security.PublicKey; | |||||
| /** | |||||
| * 支付配置 | |||||
| * | |||||
| * @author Binary Wang | |||||
| */ | |||||
| @Data | |||||
| public class TtPayConfig { | |||||
| private static final String DEFAULT_PAY_BASE_URL = "https://developer.toutiao.com"; | |||||
| private static final String PROBLEM_MSG = "证书文件【%s】有问题,请核实!"; | |||||
| private static final String NOT_FOUND_MSG = "证书文件【%s】不存在,请核实!"; | |||||
| /** | |||||
| * 支付接口请求地址域名部分. | |||||
| */ | |||||
| private String payBaseUrl = DEFAULT_PAY_BASE_URL; | |||||
| /** | |||||
| * http请求连接超时时间. | |||||
| */ | |||||
| private int httpConnectionTimeout = 5000; | |||||
| /** | |||||
| * http请求数据读取等待时间. | |||||
| */ | |||||
| private int httpTimeout = 10000; | |||||
| /** | |||||
| * appid. | |||||
| */ | |||||
| private String appId; | |||||
| /** | |||||
| * apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径. | |||||
| */ | |||||
| private String privateKeyPath; | |||||
| /** | |||||
| * api 秘钥值. | |||||
| */ | |||||
| private String apiKey; | |||||
| private Verifier verifier; | |||||
| @SneakyThrows | |||||
| public Verifier getVerifier() { | |||||
| if (verifier == null) { | |||||
| //当改对象为null时,初始化api v3的请求头 | |||||
| initApiV2HttpClient(); | |||||
| } | |||||
| return verifier; | |||||
| } | |||||
| /** | |||||
| * apiV2 证书序列号值 | |||||
| */ | |||||
| private String certSerialNo; | |||||
| private CloseableHttpClient apiV2HttpClient; | |||||
| /** | |||||
| * 私钥信息 | |||||
| */ | |||||
| private PrivateKey privateKey; | |||||
| /** | |||||
| * 返回所设置的微信支付接口请求地址域名. | |||||
| * | |||||
| * @return 微信支付接口请求地址域名 | |||||
| */ | |||||
| public String getPayBaseUrl() { | |||||
| if (StringUtils.isEmpty(this.payBaseUrl)) { | |||||
| return DEFAULT_PAY_BASE_URL; | |||||
| } | |||||
| return this.payBaseUrl; | |||||
| } | |||||
| /** | |||||
| * 初始化api v2请求头 自动签名验签 | |||||
| * | |||||
| * @return org.apache.http.impl.client.CloseableHttpClient | |||||
| * @author doger.wang | |||||
| **/ | |||||
| public CloseableHttpClient initApiV2HttpClient() throws TtPayException { | |||||
| String privateKeyPath = this.getPrivateKeyPath(); | |||||
| String apiKey = this.getApiKey(); | |||||
| if (StringUtils.isBlank(privateKeyPath)) { | |||||
| throw new TtPayException("请确保privateKeyPath已设置"); | |||||
| } | |||||
| if (StringUtils.isBlank(apiKey)) { | |||||
| throw new TtPayException("请确保apiKey值已设置"); | |||||
| } | |||||
| InputStream keyInputStream = this.loadConfigInputStream(privateKeyPath); | |||||
| try { | |||||
| PrivateKey privateKey = PemUtils.loadPrivateKey(keyInputStream); | |||||
| PublicKey publicKey = PemUtils.loadPublicKey(new ByteArrayInputStream(apiKey.getBytes("utf-8"))); | |||||
| CertificatesVerifier certificatesVerifier = new CertificatesVerifier(publicKey); | |||||
| CloseableHttpClient httpClient = TtPayV2HttpClientBuilder.create() | |||||
| .withAppId(appId, certSerialNo, privateKey) | |||||
| .withValidator(new TtPayValidator(certificatesVerifier)) | |||||
| .build(); | |||||
| this.apiV2HttpClient = httpClient; | |||||
| this.privateKey = privateKey; | |||||
| this.verifier = certificatesVerifier; | |||||
| return httpClient; | |||||
| } catch (Exception e) { | |||||
| throw new TtPayException("v2请求构造异常!", e); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 从配置路径 加载配置 信息(支持 classpath、本地路径、网络url) | |||||
| * @param configPath 配置路径 | |||||
| * @return | |||||
| * @throws TtPayException | |||||
| */ | |||||
| private InputStream loadConfigInputStream(String configPath) throws TtPayException { | |||||
| InputStream inputStream; | |||||
| final String prefix = "classpath:"; | |||||
| String fileHasProblemMsg = String.format(PROBLEM_MSG, configPath); | |||||
| String fileNotFoundMsg = String.format(NOT_FOUND_MSG, configPath); | |||||
| if (configPath.startsWith(prefix)) { | |||||
| String path = RegExUtils.removeFirst(configPath, prefix); | |||||
| if (!path.startsWith("/")) { | |||||
| path = "/" + path; | |||||
| } | |||||
| try { | |||||
| inputStream = ResourcesUtil.getResourceAsStream(path); | |||||
| if (inputStream == null) { | |||||
| throw new TtPayException(fileNotFoundMsg); | |||||
| } | |||||
| } catch (Exception e) { | |||||
| throw new TtPayException(fileNotFoundMsg, e); | |||||
| } | |||||
| } else if (configPath.startsWith("http://") || configPath.startsWith("https://")) { | |||||
| try { | |||||
| inputStream = new URL(configPath).openStream(); | |||||
| if (inputStream == null) { | |||||
| throw new TtPayException(fileNotFoundMsg); | |||||
| } | |||||
| } catch (IOException e) { | |||||
| throw new TtPayException(fileNotFoundMsg, e); | |||||
| } | |||||
| } else { | |||||
| try { | |||||
| File file = new File(configPath); | |||||
| if (!file.exists()) { | |||||
| throw new TtPayException(fileNotFoundMsg); | |||||
| } | |||||
| inputStream = new FileInputStream(file); | |||||
| } catch (IOException e) { | |||||
| throw new TtPayException(fileHasProblemMsg, e); | |||||
| } | |||||
| } | |||||
| return inputStream; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| package com.iformall.douyin.payv2.request; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import lombok.experimental.Accessors; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| @Accessors(chain = true) | |||||
| public class CallBackSettingsRequest implements Serializable { | |||||
| private static final long serialVersionUID = -1L; | |||||
| /** | |||||
| * https回调地址,预下单请求地址,长度限制<=512字节 | |||||
| */ | |||||
| @SerializedName(value = "create_order_callback") | |||||
| private String createOrderCallback; | |||||
| /** | |||||
| * https回调地址,退款请求地址,长度限制<=512字节 | |||||
| */ | |||||
| @SerializedName(value = "refund_callback") | |||||
| private String refundCallback; | |||||
| /** | |||||
| * https回调地址,长度限制<=256字节。闭环核销方案核销组件上展示的二维码所对应的跳转地址。开发者可使用这个参数设置app_id维度的订单跳转链接。 | |||||
| */ | |||||
| @SerializedName(value = "delivery_qrcode_redirect") | |||||
| private String deliveryQrcodeRedirect; | |||||
| } | |||||
| @@ -0,0 +1,67 @@ | |||||
| package com.iformall.douyin.payv2.request; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.AllArgsConstructor; | |||||
| import lombok.Builder; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * 请求分账API请求实体 | |||||
| * | |||||
| * @author pg | |||||
| * @date 2021-6-24 | |||||
| */ | |||||
| @Data | |||||
| @Builder(builderMethodName = "newBuilder") | |||||
| @NoArgsConstructor | |||||
| @AllArgsConstructor | |||||
| public class ProfitSharingRequest implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 开发者侧订单id,长度限制<64字节 | |||||
| */ | |||||
| @SerializedName("out_order_no") | |||||
| private String outOrderNo; | |||||
| /** | |||||
| * 开发者侧分账单id,分账请求的唯一标识,长度限制<64字节 | |||||
| */ | |||||
| @SerializedName("out_settle_no") | |||||
| private String outSettleNo; | |||||
| /** | |||||
| * 分账描述,长度<512字节 | |||||
| */ | |||||
| @SerializedName("settle_desc") | |||||
| private String settleDesc; | |||||
| /** | |||||
| * 其他分账方(除卖家之外的),json字符串,长度<512字节 | |||||
| * - 示例,"[{\"merchant_uid\":\"分账方商户号1\",\"amount\":100}]" | |||||
| * merchant_uid string 是 | |||||
| * 进件商户id(除了卖家以外的其他分账方) | |||||
| * amount int64 是 | |||||
| * 分账金额,单位[分],amount>0 | |||||
| */ | |||||
| @SerializedName("settle_params") | |||||
| private String settleParams; | |||||
| /** | |||||
| * 透传字段,开发者自定义,<=2048字节,不支持二进制 | |||||
| */ | |||||
| @SerializedName("cp_extra") | |||||
| private String cpExtra; | |||||
| /** | |||||
| * 分账结果通知地址,必须是https类型, | |||||
| * 若不填,默认使用在开发者平台-担保交易配置页面设置的回调地址 | |||||
| */ | |||||
| @SerializedName("notify_url") | |||||
| private String notifyUrl; | |||||
| } | |||||
| @@ -0,0 +1,73 @@ | |||||
| package com.iformall.douyin.payv2.request; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import lombok.experimental.Accessors; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 订单推送核销状态请求对象 | |||||
| * </pre> | |||||
| * | |||||
| * @author thinsstar | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| @Accessors(chain = true) | |||||
| public class TtOrderPushDeliveryRequest implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * <pre> | |||||
| * 开发者服务交易单号 | |||||
| * </pre> | |||||
| */ | |||||
| @SerializedName(value = "out_order_no") | |||||
| private String outOrderNo; | |||||
| /** | |||||
| * <pre> | |||||
| * out_order_no所关联的需要核销的商品单信息,详细结构见下表。可使用此参数指定核销整单中的特定商品单。 | |||||
| * 长度<=100 | |||||
| * 注意:只有待使用状态的商品单才能核销,已核销、正退款或已退款的将返回错误。 | |||||
| * </pre> | |||||
| */ | |||||
| @SerializedName(value = "item_order_list") | |||||
| private List<ItemOrder> itemOrderList; | |||||
| /** | |||||
| * 是否整单核销。此参数设为true则将out_order_no的所有商品单核销。use_all和item_list必须有一个有效值,要么整单核销,要么指定商品单核销。 | |||||
| * 注意:此参数设为true时只有out_order_no的所有商品单都为待使用状态才能成功核销,如果有商品单状态不对将导致不能整单核销,会返回错误。 | |||||
| */ | |||||
| @SerializedName(value = "use_all") | |||||
| private Boolean useAll; | |||||
| /** | |||||
| * 核销的商铺poi信息,详细结构见下表,最多1024个字节。 | |||||
| * 注意:此字段为json string | |||||
| * poi_info 结构,poi商品必填所有字段(强校验),小程序商品也尽量传入(不校验) | |||||
| * shop_name string poi商品必填 | |||||
| * 核销店铺名称,参考商铺同步接口中的店铺名称(name) | |||||
| * ext_valid_shop_id string poi商品必填 | |||||
| * 核销门店的外部店铺id,参考商铺同步接口中的接入方店铺id(supplier_ext_id) | |||||
| * valid_poi_id_str string poi商品必填 | |||||
| * 核销门店对应的抖音poi_id,参考商铺同步接口中的抖音poi id(poi_id) | |||||
| */ | |||||
| @SerializedName(value = "poi_info") | |||||
| private String poiInfo; | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class ItemOrder implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| @SerializedName(value = "item_order_id") | |||||
| private String itemOrderId; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| package com.iformall.douyin.payv2.request; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import lombok.experimental.Accessors; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * <pre> | |||||
| * 订单查询请求对象 | |||||
| * </pre> | |||||
| * | |||||
| * @author thinsstar | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| @Accessors(chain = true) | |||||
| public class TtPayOrderQueryV2Request implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * <pre> | |||||
| * 抖音开平内部交易订单号,长度<64byte,开发者在接收到抖音开平发起的下单交易回调请求的时候,由抖音开平传递给开发者服务,开发者必须确保将该id与自己平台生成的交易单号进行唯一关联 | |||||
| * </pre> | |||||
| */ | |||||
| @SerializedName(value = "order_id") | |||||
| private String orderId; | |||||
| /** | |||||
| * <pre> | |||||
| * 开发者系统生成的订单号,与抖音开平交易单号order_id唯一关联,长度<64byte | |||||
| * </pre> | |||||
| */ | |||||
| @SerializedName(value = "out_order_no") | |||||
| private String outOrderNo; | |||||
| } | |||||
| @@ -0,0 +1,36 @@ | |||||
| package com.iformall.douyin.payv2.request; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import lombok.experimental.Accessors; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 微信支付-申请退款请求参数 | |||||
| * </pre> | |||||
| * | |||||
| * @author thinsstar | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| @Accessors(chain = true) | |||||
| public class TtPayRefundQueryV2Request implements Serializable { | |||||
| private static final long serialVersionUID = -1L; | |||||
| /** | |||||
| * 抖音开平内部交易退款单号,长度<= 64byte,开发者必须确保将该id与自己平台生成的交易单号进行唯一关联 | |||||
| */ | |||||
| @SerializedName(value = "refund_id") | |||||
| private String refund_id; | |||||
| /** | |||||
| * 开发者系统生成的退款单号,与抖音开平退款单号refund_id 唯一关联,长度<= 64byte | |||||
| */ | |||||
| @SerializedName(value = "out_refund_no") | |||||
| private String outRefundNo; | |||||
| } | |||||
| @@ -0,0 +1,107 @@ | |||||
| package com.iformall.douyin.payv2.request; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import lombok.experimental.Accessors; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 申请退款请求参数 | |||||
| * </pre> | |||||
| * | |||||
| * @author | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| @Accessors(chain = true) | |||||
| public class TtPayRefundV2Request implements Serializable { | |||||
| private static final long serialVersionUID = -1L; | |||||
| /** | |||||
| * 是 | |||||
| * 开发者侧订单号,长度 <= 64 byte | |||||
| */ | |||||
| @SerializedName(value = "out_order_no") | |||||
| private String outOrderNo; | |||||
| /** | |||||
| * 是 | |||||
| * 开发者侧退款单号,长度 <= 64 byte | |||||
| */ | |||||
| @SerializedName(value = "out_refund_no") | |||||
| private String outRefundNo; | |||||
| /** | |||||
| * 否 | |||||
| * 开发者自定义透传字段,不支持二进制,长度 <= 2048 byte | |||||
| */ | |||||
| @SerializedName(value = "cp_extra") | |||||
| private String cpExtra; | |||||
| /** | |||||
| * 是 | |||||
| * 退款单的跳转的schema | |||||
| */ | |||||
| @SerializedName(value = "order_entry_schema") | |||||
| private PageEntry orderEntrySchema; | |||||
| /** | |||||
| * 否 | |||||
| * 退款结果通知地址,必须是https类型, | |||||
| * 若不填,默认使用在开发者平台-担保交易配置页面设置的退款回调地址 | |||||
| */ | |||||
| @SerializedName(value = "notify_url") | |||||
| private String notifyUrl; | |||||
| /** | |||||
| * 否(新订单发起退款必传) | |||||
| * | |||||
| * 需要发起退款的商品单信息(新订单发起退款必传) | |||||
| */ | |||||
| @SerializedName(value = "item_order_detail") | |||||
| private List<OrderDetail> itemOrderDetail; | |||||
| /** | |||||
| * 否(旧订单发起退款必传) | |||||
| * 退款总金额 (旧订单发起退款必传) | |||||
| */ | |||||
| @SerializedName(value = "refund_total_amount") | |||||
| private Integer refundTotalAmount; | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class PageEntry implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 路径 没有前导的“/” | |||||
| */ | |||||
| @SerializedName(value = "path") | |||||
| private String path; | |||||
| /** | |||||
| * 参数 自定义的json结构 | |||||
| */ | |||||
| @SerializedName(value = "params") | |||||
| private String params; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class OrderDetail implements Serializable { | |||||
| private static final long serialVersionUID = -1L; | |||||
| /** | |||||
| * 商品单号,参见前文"对于重要id字段的说明" | |||||
| */ | |||||
| @SerializedName(value = "item_order_id") | |||||
| private String itemOrderId; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,456 @@ | |||||
| package com.iformall.douyin.payv2.request; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import lombok.experimental.Accessors; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 统一下单请求参数对象. | |||||
| * 参考文档:https://bytedance.feishu.cn/docx/doxcna1ALcy90AGMNdGLoALBeIH#doxcnUUemGYgUYECi8n92wMQQke | |||||
| * </pre> | |||||
| * | |||||
| * @author thinsstar | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| @Accessors(chain = true) | |||||
| public class TtPayUnifiedOrderV2Request implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 是 | |||||
| * 商品信息,详情见 goods_list说明 | |||||
| */ | |||||
| @SerializedName(value = "goods_list") | |||||
| protected List<Good> goodsList; | |||||
| /** | |||||
| * 是 | |||||
| * 订单总价,单位(分) | |||||
| */ | |||||
| @SerializedName(value = "total_amount") | |||||
| private Integer totalAmount; | |||||
| /** | |||||
| * 否 | |||||
| * 用户手机号 | |||||
| */ | |||||
| @SerializedName(value = "phone_num") | |||||
| private String phoneNum; | |||||
| /** | |||||
| * 否 | |||||
| * 用户姓名 | |||||
| */ | |||||
| @SerializedName(value = "contact_name") | |||||
| private String contactName; | |||||
| /** | |||||
| * 否 | |||||
| * 下单备注信息,长度 <= 2048byte | |||||
| */ | |||||
| @SerializedName(value = "extra") | |||||
| private String extra; | |||||
| /** | |||||
| * 是 | |||||
| * 用户OpenID | |||||
| */ | |||||
| @SerializedName(value = "open_id") | |||||
| private String openId; | |||||
| /** | |||||
| * 否 | |||||
| * 支付结果通知地址,必须是https类型。若不填,默认使用在开发者平台-担保交易配置页面设置的支付回调地址 | |||||
| */ | |||||
| @SerializedName(value = "pay_notify_url") | |||||
| private String payNotifyUrl; | |||||
| /** | |||||
| * 是 | |||||
| * 开发者的单号,长度 <= 64 byte | |||||
| */ | |||||
| @SerializedName(value = "out_order_no") | |||||
| private String outOrderNo; | |||||
| /** | |||||
| * 否 | |||||
| * 支付超时时间,单位秒,例如300表示300秒后过期;不传或传0会使用默认值300 | |||||
| */ | |||||
| @SerializedName(value = "pay_expire_seconds") | |||||
| private Integer payExpireSeconds; | |||||
| /** | |||||
| * 是 | |||||
| * 订单详情页 | |||||
| */ | |||||
| @SerializedName(value = "order_entry_schema") | |||||
| private PageEntry orderEntrySchema; | |||||
| /** | |||||
| * 否 | |||||
| * 开发者自定义透传字段,不支持二进制,长度 <= 2048 byte | |||||
| */ | |||||
| @SerializedName(value = "cp_extra") | |||||
| private PageEntry cpExtra; | |||||
| /** | |||||
| * 否 | |||||
| * 折扣金额,单位(分) | |||||
| */ | |||||
| @SerializedName(value = "discount_amount") | |||||
| private Integer discountAmount; | |||||
| /** | |||||
| * 否 | |||||
| * 营销算价结果信息, | |||||
| */ | |||||
| @SerializedName(value = "price_calculation_detail") | |||||
| private PriceDetail priceCalculationDetail; | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class Good implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 否 | |||||
| * - 非poi商品必传 | |||||
| * 商品图片链接,长度 <= 512 byte | |||||
| */ | |||||
| @SerializedName(value = "goods_image") | |||||
| private String goodsImage; | |||||
| /** | |||||
| * 否 | |||||
| * - 非poi商品必传 | |||||
| * 商品标题/商品名称,长度 <= 256 byte | |||||
| */ | |||||
| @SerializedName(value = "goods_title") | |||||
| private String goodsTitle; | |||||
| /** | |||||
| * 否 | |||||
| * - 非poi商品必传 | |||||
| * 商品标签,最多设置三个标签,例如:随时退|免预约|提前3日预约 (“|”是中文类型),详见type的合法值 | |||||
| */ | |||||
| @SerializedName(value = "labels") | |||||
| private String labels; | |||||
| /** | |||||
| * 否 | |||||
| * 使用规则,如 “周一至周日可用”、“周一至周五可用”、“非节假日可用”,默认“周一至周日可用” | |||||
| */ | |||||
| @SerializedName(value = "date_rule") | |||||
| private String dateRule; | |||||
| /** | |||||
| * 否 | |||||
| * - 非poi商品必传 | |||||
| * 商品价格,单位(分) | |||||
| */ | |||||
| @SerializedName(value = "price") | |||||
| private Integer price; | |||||
| /** | |||||
| * 是 | |||||
| * 商品数量 | |||||
| */ | |||||
| @SerializedName(value = "quantity") | |||||
| private Integer quantity; | |||||
| /** | |||||
| * 是 | |||||
| * 商品id | |||||
| */ | |||||
| @SerializedName(value = "goods_id") | |||||
| private String goodsId; | |||||
| /** | |||||
| * 是 | |||||
| * 商品id类别,poi商品传 1,非poi商品传 2 | |||||
| */ | |||||
| @SerializedName(value = "goods_id_type") | |||||
| private Integer goodsIdType; | |||||
| /** | |||||
| * 否 | |||||
| * 商品详情页 | |||||
| */ | |||||
| @SerializedName(value = "goods_page") | |||||
| private PageEntry goodsPage; | |||||
| /** | |||||
| * 否 | |||||
| * - 非poi商品必传 | |||||
| * 券的有效期,注意: | |||||
| * 1. 非poi商品必传,poi商品会从poi库里查询有效期信息,不会使用开发者传的数据 | |||||
| * 2. 如果是非poi商品,每个goods_id都要传券的有效期信息,否则会下单失败 | |||||
| */ | |||||
| @SerializedName(value = "order_valid_time") | |||||
| private OrderValid orderValidTime; | |||||
| /** | |||||
| * 否 | |||||
| * 折扣金额,单位(分) | |||||
| */ | |||||
| @SerializedName(value = "discount_amount") | |||||
| private Integer discountAmount; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class PageEntry implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 路径 没有前导的“/” | |||||
| */ | |||||
| @SerializedName(value = "path") | |||||
| private String path; | |||||
| /** | |||||
| * 参数 自定义的json结构 | |||||
| */ | |||||
| @SerializedName(value = "params") | |||||
| private String params; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class OrderValid implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 券的有效期开始时间,单位毫秒,须大于0 | |||||
| */ | |||||
| @SerializedName(value = "valid_start_time") | |||||
| private Long validStartTime; | |||||
| /** | |||||
| * 券的有效期结束时间,单位毫秒,须大于0,且须大于valid_start_time和当前时间 | |||||
| */ | |||||
| @SerializedName(value = "valid_end_time") | |||||
| private Long validEndTime; | |||||
| /** | |||||
| * 1. 券的相对有效时间,单位毫秒,须大于0 | |||||
| * 2. 与<valid_start_time,valid_end_time>组合,至少回传一个,否则会下单失败 | |||||
| * 3. 都合法优先使用<valid_start_time,valid_end_time>组合 | |||||
| * 4. 当valid_duration 有效时,券的有效期开始时间 S=订单支付完成时间,券的有效期结束时间 E=1天 + 向下按天截断(S+valid_duration) | |||||
| * eg:valid_duration = 86400000ms(一天) S = 2021.1.1 6:00 E = 2021.1.3 00:00 | |||||
| */ | |||||
| @SerializedName(value = "valid_duration") | |||||
| private Long validDuration; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class PriceDetail implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 是 | |||||
| * 算价类型,1-商户单维度,item单价格我们按分摊比例计算, | |||||
| * 2-item单维度,item单价格由开发者计算 | |||||
| */ | |||||
| @SerializedName(value = "calculation_type") | |||||
| private Integer calculationType; | |||||
| /** | |||||
| * 商品算价结果 | |||||
| */ | |||||
| @SerializedName(value = "goods_discount_detail") | |||||
| private List<GoodsDiscountDetail> goodsDiscountDetail; | |||||
| /** | |||||
| * 商品算价结果 | |||||
| */ | |||||
| @SerializedName(value = "order_discount_detail") | |||||
| private OrderDiscountDetail order_discount_detail; | |||||
| /** | |||||
| * 商品算价结果 | |||||
| */ | |||||
| @SerializedName(value = "item_discount_detail") | |||||
| private List<ItemDiscountDetail> item_discount_detail; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class GoodsDiscountDetail implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 是 | |||||
| * 商品id,此处为课程id,注意这里是string类型 | |||||
| */ | |||||
| @SerializedName(value = "goods_id") | |||||
| private String goodsId; | |||||
| /** | |||||
| * 是 | |||||
| * 购买数量 | |||||
| */ | |||||
| @SerializedName(value = "quantity") | |||||
| private Integer quantity; | |||||
| /** | |||||
| * 是 | |||||
| * 商品总价,单位:分 | |||||
| */ | |||||
| @SerializedName(value = "total_amount") | |||||
| private Integer totalAmount; | |||||
| /** | |||||
| * 是 | |||||
| * 该商品总优惠金额,该商品的实付金额=total_amount-discount_amount | |||||
| */ | |||||
| @SerializedName(value = "discountAmount") | |||||
| private Integer discountAmount; | |||||
| /** | |||||
| * 营销明细 | |||||
| */ | |||||
| @SerializedName(value = "marketing_detail") | |||||
| private List<MarketingDetail> marketingDetail; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class OrderDiscountDetail implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 是 | |||||
| * 订单维度总优惠金额,单位: 分 | |||||
| */ | |||||
| @SerializedName(value = "order_total_discount_amount") | |||||
| private Integer orderTotalDiscountAmount; | |||||
| /** | |||||
| * 是 | |||||
| * 商品(sku)维度总优惠金额,单位: 分 | |||||
| */ | |||||
| @SerializedName(value = "goods_total_discount_amount") | |||||
| private Integer goodsTotalDiscountAmount; | |||||
| /** | |||||
| * 营销明细 | |||||
| */ | |||||
| @SerializedName(value = "marketing_detail") | |||||
| private List<MarketingDetail> marketingDetail; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class ItemDiscountDetail implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 是 | |||||
| * 商品id,此处为课程id,注意这里是string类型 | |||||
| */ | |||||
| @SerializedName(value = "goods_id") | |||||
| private String goodsId; | |||||
| /** | |||||
| * 是 | |||||
| * 商品总价,单位:分 | |||||
| */ | |||||
| @SerializedName(value = "total_amount") | |||||
| private Integer totalAmount; | |||||
| /** | |||||
| * 是 | |||||
| * 该商品总优惠金额,该商品的实付金额=total_amount-discount_amount | |||||
| */ | |||||
| @SerializedName(value = "discountAmount") | |||||
| private Integer discountAmount; | |||||
| /** | |||||
| * 营销明细 | |||||
| */ | |||||
| @SerializedName(value = "marketing_detail") | |||||
| private List<MarketingDetail> marketingDetail; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class MarketingDetail implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 是 | |||||
| * 营销id(用户身份id,优惠券id,积分id或者活动id) | |||||
| */ | |||||
| @SerializedName(value = "id") | |||||
| private String id; | |||||
| /** | |||||
| * 是 | |||||
| * | |||||
| * 营销类型: | |||||
| * 1: 用户身份 | |||||
| * 2: 优惠券 | |||||
| * 3: 积分(暂不开放) | |||||
| * 4: 活动 | |||||
| */ | |||||
| @SerializedName(value = "type") | |||||
| private Integer type; | |||||
| /** | |||||
| * 是 | |||||
| * 该营销策略优惠金额,单位: 分 | |||||
| */ | |||||
| @SerializedName(value = "discountAmount") | |||||
| private Integer discountAmount; | |||||
| /** | |||||
| * 营销名称 | |||||
| */ | |||||
| @SerializedName(value = "title") | |||||
| private String title; | |||||
| /** | |||||
| * 营销备注 | |||||
| */ | |||||
| @SerializedName(value = "note") | |||||
| private String note; | |||||
| /** | |||||
| * 优惠范围 | |||||
| */ | |||||
| @SerializedName(value = "discount_range") | |||||
| private Integer discountRange; | |||||
| /** | |||||
| * 营销子类型 | |||||
| */ | |||||
| @SerializedName(value = "subtype") | |||||
| private String subtype; | |||||
| /** | |||||
| * 不同type含义不同,若type=3,value则为积分值 | |||||
| */ | |||||
| @SerializedName(value = "value") | |||||
| private Integer value; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,64 @@ | |||||
| package com.iformall.douyin.payv2.request; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import lombok.experimental.Accessors; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 订单推送核销状态请求对象 | |||||
| * </pre> | |||||
| * | |||||
| * @author thinsstar | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| @Accessors(chain = true) | |||||
| public class TtRefundAuditMsgRequest implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 开发者侧退款单号 | |||||
| */ | |||||
| @SerializedName(value = "out_refund_no") | |||||
| private String outRefundNo; | |||||
| /** | |||||
| * 审核状态,1-同意退款 2-不同意退款 | |||||
| */ | |||||
| @SerializedName(value = "refund_audit_status") | |||||
| private Integer refundAuditStatus; | |||||
| /** | |||||
| * 不同意退款信息(不同意退款时必填),长度 <= 512 byte | |||||
| */ | |||||
| @SerializedName(value = "deny_message") | |||||
| private String denyMessage; | |||||
| /** | |||||
| * 核销的商铺poi信息,详细结构见下表,最多1024个字节。 | |||||
| * shop_name string poi商品必填 | |||||
| * 核销店铺名称,参考商铺同步接口中的店铺名称(name) | |||||
| * ext_valid_shop_id string poi商品必填 | |||||
| * 核销门店的外部店铺id,参考商铺同步接口中的接入方店铺id(supplier_ext_id) | |||||
| * valid_poi_id_str string poi商品必填 | |||||
| * 核销门店对应的抖音poi_id,参考商铺同步接口中的抖音poi id(poi_id) | |||||
| */ | |||||
| @SerializedName(value = "poi_info") | |||||
| private Boolean poiInfo; | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class ItemOrder implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| @SerializedName(value = "item_order_id") | |||||
| private String itemOrderId; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,57 @@ | |||||
| package com.iformall.douyin.payv2.result; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.apache.poi.ss.formula.functions.T; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * <pre> | |||||
| * 结果共用属性类. | |||||
| * Created by Binary Wang on 2016-10-24. | |||||
| * </pre> | |||||
| * | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public abstract class BaseTtPayResult<T> implements Serializable { | |||||
| /** | |||||
| * 返回状态码. | |||||
| */ | |||||
| @SerializedName("err_no") | |||||
| protected Integer errNo; | |||||
| public boolean isSuccess() { | |||||
| return errNo.intValue() == 0; | |||||
| } | |||||
| /** | |||||
| * 返回信息. | |||||
| */ | |||||
| @SerializedName("err_tips") | |||||
| protected String errTips; | |||||
| @SerializedName("data") | |||||
| private T data; | |||||
| /** | |||||
| * Gets logger. | |||||
| * | |||||
| * @return the logger | |||||
| */ | |||||
| protected Logger getLogger() { | |||||
| return LoggerFactory.getLogger(this.getClass()); | |||||
| } | |||||
| @Override | |||||
| public String toString() { | |||||
| return WxGsonBuilder.create().toJson(this); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,200 @@ | |||||
| package com.iformall.douyin.payv2.result; | |||||
| import cn.binarywang.wx.miniapp.bean.WxMaGetLiveInfo; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * 结果通知. | |||||
| * | |||||
| * @author thinsstar | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public class CreateOrderCallback implements Serializable { | |||||
| private static final long serialVersionUID = -1L; | |||||
| /** | |||||
| * 是 | |||||
| * 抖音开平侧生成的订单号 | |||||
| */ | |||||
| @SerializedName(value = "order_id") | |||||
| private String orderId; | |||||
| /** | |||||
| * 是 | |||||
| * 商品信息,详情见 goods参数 部分 | |||||
| */ | |||||
| @SerializedName(value = "goods") | |||||
| private List<Good> goods; | |||||
| /** | |||||
| * 是 | |||||
| * 订单总价格,单位[分] | |||||
| * 用户实付金额=total_amount-discount | |||||
| */ | |||||
| @SerializedName(value = "total_amount") | |||||
| private Integer totalAmount; | |||||
| /** | |||||
| * 是 | |||||
| * 订单折扣,单位[分] | |||||
| */ | |||||
| @SerializedName(value = "discount") | |||||
| private Integer discount; | |||||
| /** | |||||
| * 否 | |||||
| * 预下单时开发者定义的透传信息 | |||||
| */ | |||||
| @SerializedName(value = "cp_extra") | |||||
| private String cpExtra; | |||||
| /** | |||||
| * 是 | |||||
| * 订单创建时间,13位时间戳,毫秒 | |||||
| */ | |||||
| @SerializedName(value = "create_order_time") | |||||
| private Long createOrderTime; | |||||
| /** | |||||
| * 是 | |||||
| * 用户OpenID | |||||
| */ | |||||
| @SerializedName(value = "open_id") | |||||
| private String openId; | |||||
| /** | |||||
| * 否 | |||||
| * 用户手机号 | |||||
| */ | |||||
| @SerializedName(value = "phone_num") | |||||
| private String phoneNum; | |||||
| /** | |||||
| * 否 | |||||
| * 联系人姓名 | |||||
| */ | |||||
| @SerializedName(value = "contact_name") | |||||
| private String contactName; | |||||
| /** | |||||
| * 是 | |||||
| * 小程序id | |||||
| */ | |||||
| @SerializedName(value = "app_id") | |||||
| private String appId; | |||||
| /** | |||||
| * 否 | |||||
| * 用户的 UnionID | |||||
| */ | |||||
| @SerializedName(value = "union_id") | |||||
| private String unionId; | |||||
| /** | |||||
| * 是 | |||||
| * 核销类型,开平告知此订单是否可走闭环核销判断结果。 | |||||
| * 0-非闭环核销,开发者自行处理券码生成及展示,通过push_delivery接口推送核销状态,1-闭环核销,开平负责生券,开发者使用核销组件展示,使用验券准备和验券接口核销。 | |||||
| */ | |||||
| @SerializedName(value = "delivery_type") | |||||
| private Integer deliveryType; | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class Good implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 是 | |||||
| * 商品图片链接 | |||||
| */ | |||||
| @SerializedName(value = "img_url") | |||||
| private String imgUrl; | |||||
| /** | |||||
| * 是 | |||||
| * 商品标题 | |||||
| */ | |||||
| @SerializedName(value = "title") | |||||
| private String title; | |||||
| /** | |||||
| * 是 | |||||
| * 商品副标题 | |||||
| */ | |||||
| @SerializedName(value = "sub_title") | |||||
| private String subTitle; | |||||
| /** | |||||
| * 是 | |||||
| * | |||||
| * 商品标签,对应poi多门店SPU同步的sub_title,例如:随时退|免预约|提前3日预约 (“|”是中文类型) | |||||
| */ | |||||
| @SerializedName(value = "labels") | |||||
| private String labels; | |||||
| /** | |||||
| * 是 | |||||
| * 券的可用时间(目前仅用于展示),例如:“周一至周五可用”、“非节假日可用” | |||||
| */ | |||||
| @SerializedName(value = "date_rule") | |||||
| private String dateRule; | |||||
| /** | |||||
| * 是 | |||||
| * 商品原价,单位[分] | |||||
| */ | |||||
| @SerializedName(value = "origin_price") | |||||
| private Integer originPrice; | |||||
| /** | |||||
| * 是 | |||||
| * 商品去掉折扣后的单价,单位[分] | |||||
| */ | |||||
| @SerializedName(value = "price") | |||||
| private Integer price; | |||||
| /** | |||||
| * 是 | |||||
| * 购买的商品数量 | |||||
| */ | |||||
| @SerializedName(value = "quantity") | |||||
| private Integer quantity; | |||||
| /** | |||||
| * 否 | |||||
| * 对应门店的Poi ID | |||||
| */ | |||||
| @SerializedName(value = "poi_id") | |||||
| private String poiId; | |||||
| /** | |||||
| * 是 | |||||
| * 商品id | |||||
| */ | |||||
| @SerializedName(value = "goods_id") | |||||
| private String goodsId; | |||||
| /** | |||||
| * 是 | |||||
| * item_order_id列表,id个数与quantity一致 | |||||
| */ | |||||
| @SerializedName(value = "item_order_id_list") | |||||
| private List<String> itemOrderIdList; | |||||
| /** | |||||
| * 是 | |||||
| * 商品id类别,1:poi商品,2:非poi商品 | |||||
| */ | |||||
| @SerializedName(value = "goods_id_type") | |||||
| private Integer goodsIdType; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,32 @@ | |||||
| package com.iformall.douyin.payv2.result; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import java.io.Serializable; | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public class OriginNotifyResponse implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| @SerializedName(value = "msg") | |||||
| private String msg; | |||||
| /** | |||||
| * 枚举值: | |||||
| * pre_create_order 预下单回调 | |||||
| * pre_create_refund 退款审核回调 | |||||
| */ | |||||
| @SerializedName(value = "type") | |||||
| private String type; | |||||
| /** | |||||
| * 固定值:"2.0" callback版本,用于开发者识别回调参数的变更 | |||||
| */ | |||||
| @SerializedName(value = "version") | |||||
| private String version; | |||||
| } | |||||
| @@ -0,0 +1,112 @@ | |||||
| package com.iformall.douyin.payv2.result; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * 请求分账API返回的分账结果实体 | |||||
| * | |||||
| * @author pg | |||||
| * @date 2021-6-24 | |||||
| */ | |||||
| @Data | |||||
| public class ProfitSharingResult implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 返回状态码. | |||||
| */ | |||||
| @SerializedName("err_no") | |||||
| protected Integer errNo; | |||||
| public boolean isSuccess() { | |||||
| return errNo.intValue() == 0; | |||||
| } | |||||
| /** | |||||
| * 返回信息. | |||||
| */ | |||||
| @SerializedName("err_tips") | |||||
| protected String errTips; | |||||
| /** | |||||
| * 订单的分账结果 | |||||
| */ | |||||
| @SerializedName("data") | |||||
| private List<Receiver> data; | |||||
| @Data | |||||
| public static class Receiver implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 开发者交易订单id,长度<=64byte,数字、ASCII字符组成 | |||||
| */ | |||||
| @SerializedName("out_order_no") | |||||
| private String outOrderNo; | |||||
| /** | |||||
| * 开发者分账单id,长度<=64byte,数字、ASCII字符组成 | |||||
| */ | |||||
| @SerializedName("out_settle_no") | |||||
| private String outSettleNo; | |||||
| /** | |||||
| * 小程序侧交易订单id,长度<=64byte,数字、ASCII字符组成 | |||||
| */ | |||||
| @SerializedName("order_id") | |||||
| private String orderId; | |||||
| /** | |||||
| * 小程序侧分账单id,长度<=64byte,数字、ASCII字符组成 | |||||
| */ | |||||
| @SerializedName("settle_id") | |||||
| private String settleId; | |||||
| /** | |||||
| * 分账金额,单位[分] | |||||
| */ | |||||
| @SerializedName("settle_amount") | |||||
| private Integer settleAmount; | |||||
| /** | |||||
| * 分账状态:INIT,PROCESSING,SUCCESS,FAIL | |||||
| */ | |||||
| @SerializedName("settle_status") | |||||
| private String settleStatus; | |||||
| /** | |||||
| * 分账详情,如“商户号ABC-分成金额(分)100” | |||||
| */ | |||||
| @SerializedName("settle_detail") | |||||
| private String settleDetail; | |||||
| /** | |||||
| * 分账时间,13位时间戳,毫秒 | |||||
| */ | |||||
| @SerializedName("settle_time") | |||||
| private Long settleTime; | |||||
| /** | |||||
| * 手续费,单位[分] | |||||
| */ | |||||
| @SerializedName("rake") | |||||
| private Integer rake; | |||||
| /** | |||||
| * 佣金,单位[分] | |||||
| */ | |||||
| @SerializedName("commission") | |||||
| private Integer commission; | |||||
| /** | |||||
| * 透传字段,开发者自定义,<=2048字节,不支持二进制数据 | |||||
| */ | |||||
| @SerializedName("cp_extra") | |||||
| private String cpExtra; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,138 @@ | |||||
| package com.iformall.douyin.payv2.result; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * 结果通知. | |||||
| * | |||||
| * @author | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public class RefundOrderCallback implements Serializable { | |||||
| private static final long serialVersionUID = -1L; | |||||
| /** | |||||
| * 小程序的appid | |||||
| */ | |||||
| @SerializedName(value = "app_id") | |||||
| private String appId; | |||||
| /** | |||||
| * 用户openid | |||||
| */ | |||||
| @SerializedName(value = "open_id") | |||||
| private String openId; | |||||
| /** | |||||
| * 抖音开平侧退款单号 | |||||
| */ | |||||
| @SerializedName(value = "refund_id") | |||||
| private String refundId; | |||||
| /** | |||||
| * 抖音开平侧订单号 | |||||
| */ | |||||
| @SerializedName(value = "order_id") | |||||
| private String orderId; | |||||
| /** | |||||
| * 开发者侧订单号 | |||||
| */ | |||||
| @SerializedName(value = "out_order_no") | |||||
| private String outOrderNo; | |||||
| /** | |||||
| * 退款总金额,单位[分] | |||||
| */ | |||||
| @SerializedName(value = "refund_total_amount") | |||||
| private Integer refundTotalAmount; | |||||
| /** | |||||
| * 退款审核的最后期限,超过该期限无需商家审核,自动退款,13位时间戳,毫秒 | |||||
| */ | |||||
| @SerializedName(value = "refund_audit_deadline") | |||||
| private Long refundAuditDeadline; | |||||
| /** | |||||
| * 退款创建时间,13位时间戳,毫秒 | |||||
| */ | |||||
| @SerializedName(value = "create_refund_time") | |||||
| private Long createRefundTime; | |||||
| /** | |||||
| * 退款来源:1-用户发起退款 3-过期自动退 | |||||
| */ | |||||
| @SerializedName(value = "refund_source") | |||||
| private Integer refundSource; | |||||
| /** | |||||
| * cp 自定义字段,不支持二进制,长度 <= 2048byte | |||||
| */ | |||||
| @SerializedName(value = "cp_extra") | |||||
| private String cpExtra; | |||||
| /** | |||||
| * 退款原因,退款原因有多个 | |||||
| */ | |||||
| @SerializedName(value = "refund_reason") | |||||
| private List<String> refundReason; | |||||
| /** | |||||
| *退款补充说明 | |||||
| */ | |||||
| @SerializedName(value = "refund_description") | |||||
| private String refundDescription; | |||||
| /** | |||||
| * 退款商品单信息 (新系统订单退款必填) | |||||
| */ | |||||
| @SerializedName(value = "refund_item_detail") | |||||
| private RefundItemDetail refundItemDetail; | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class RefundItemDetail implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 用户需要退款多少个商品单 | |||||
| */ | |||||
| @SerializedName(value = "item_order_quantity") | |||||
| private Integer itemOrderQuantity; | |||||
| /** | |||||
| * 本次退款的商品单列表 | |||||
| */ | |||||
| @SerializedName(value = "item_order_detail") | |||||
| private List<ItemOrderDetail> itemOrderDetail; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class ItemOrderDetail implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 用户需要退款多少个商品单 | |||||
| */ | |||||
| @SerializedName(value = "item_order_id") | |||||
| private String itemOrderId; | |||||
| /** | |||||
| * 用户需要退款多少个商品单 | |||||
| */ | |||||
| @SerializedName(value = "refund_amount") | |||||
| private Integer refundAmount; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,304 @@ | |||||
| package com.iformall.douyin.payv2.result; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 查询CPS订单 返回结果对象 | |||||
| * </pre> | |||||
| * | |||||
| * @author thinsstar | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public class TtPayCpsOrderQueryV2Result implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 抖音开平侧订单号 | |||||
| */ | |||||
| @SerializedName(value = "order_id") | |||||
| private String orderId; | |||||
| /** | |||||
| * 开发者侧订单号,与order_id一一对应 | |||||
| */ | |||||
| @SerializedName(value = "out_order_no") | |||||
| private String outOrderNo; | |||||
| /** | |||||
| *cps订单支付相关信息 | |||||
| */ | |||||
| @SerializedName(value = "payment_info") | |||||
| private PaymentInfo paymentInfo; | |||||
| /** | |||||
| *cps相关信息 | |||||
| */ | |||||
| @SerializedName(value = "cps_info") | |||||
| private CpsInfo cpsInfo; | |||||
| /** | |||||
| *cps订单已退款记录信息 | |||||
| * - refund_info字段结构(只展示已退款记录,退款失败和退款中不展示) | |||||
| */ | |||||
| @SerializedName(value = "refund_info") | |||||
| private RefundInfo refundInfo; | |||||
| /** | |||||
| *cps订单已核销记录信息 | |||||
| * - delivery_info字段结构(只展示已核销记录) | |||||
| */ | |||||
| @SerializedName(value = "delivery_info") | |||||
| private DeliveryInfo deliveryInfo; | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class PaymentInfo implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 订单实际支付金额,单位[分] | |||||
| */ | |||||
| @SerializedName(value = "total_fee") | |||||
| private Integer totalFee; | |||||
| /** | |||||
| * 订单状态, | |||||
| * INIT 初始状态 | |||||
| * PROCESS 订单处理中 | |||||
| * SUCCESS:成功 | |||||
| * FAIL:失败 | |||||
| * TIMEOUT:用户超时未支付 | |||||
| */ | |||||
| @SerializedName(value = "order_status") | |||||
| private String orderStatus; | |||||
| /** | |||||
| * 支付时间,格式:2021-12-12 00:00:00 | |||||
| */ | |||||
| @SerializedName(value = "pay_time") | |||||
| private String payTime; | |||||
| /** | |||||
| * 支付渠道枚举,1:微信,2:支付宝 | |||||
| */ | |||||
| @SerializedName(value = "pay_channel") | |||||
| private Integer payChannel; | |||||
| /** | |||||
| * 渠道支付单号,如微信的支付单号 | |||||
| */ | |||||
| @SerializedName(value = "channel_pay_id") | |||||
| private String channelPayId; | |||||
| /** | |||||
| * 卖家商户号id | |||||
| */ | |||||
| @SerializedName(value = "seller_uid") | |||||
| private String sellerUid; | |||||
| /** | |||||
| * 预下单时开发者定义的透传信息 | |||||
| */ | |||||
| @SerializedName(value = "cp_extra") | |||||
| private String cpExtra; | |||||
| /** | |||||
| * 结果描述信息,如失败原因 | |||||
| */ | |||||
| @SerializedName(value = "message") | |||||
| private String message; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class CpsInfo implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 订单总佣金,单位分 | |||||
| */ | |||||
| @SerializedName(value = "total_commission_amount") | |||||
| private Integer totalCommissionAmount; | |||||
| /** | |||||
| * 订单状态, | |||||
| * INIT 初始状态 | |||||
| * PROCESS 订单处理中 | |||||
| * SUCCESS:成功 | |||||
| * FAIL:失败 | |||||
| * TIMEOUT:用户超时未支付 | |||||
| */ | |||||
| @SerializedName(value = "cps_item_list") | |||||
| private List<CpsItem> cps_item_list; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class CpsItem implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 分佣比例,万分位 | |||||
| */ | |||||
| @SerializedName(value = "commission_rate") | |||||
| private Integer commissionRate; | |||||
| /** | |||||
| * 分佣用户ID | |||||
| */ | |||||
| @SerializedName(value = "commission_user_id") | |||||
| private Long commissionUserId; | |||||
| /** | |||||
| * 佣金,单位分 | |||||
| */ | |||||
| @SerializedName(value = "commission_amount") | |||||
| private Integer commissionAmount; | |||||
| /** | |||||
| * 分佣类型,1 短视频, 2 直播间 | |||||
| */ | |||||
| @SerializedName(value = "source_type") | |||||
| private Integer sourceType; | |||||
| /** | |||||
| * 短视频/直播间ID | |||||
| */ | |||||
| @SerializedName(value = "item_id") | |||||
| private Long itemId; | |||||
| /** | |||||
| * 售价,单位分 | |||||
| */ | |||||
| @SerializedName(value = "sell_amount") | |||||
| private Integer sellAmount; | |||||
| /** | |||||
| * cps订单状态,交易系统2.0为子单状态 | |||||
| * 0-未支付, | |||||
| * 1-已支付(待使用); | |||||
| * 2-已退款(不分佣); | |||||
| * 3-已核销(已分佣) | |||||
| */ | |||||
| @SerializedName(value = "status") | |||||
| private Integer status; | |||||
| @SerializedName(value = "item_order_id") | |||||
| private String itemOrderId; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class RefundInfo implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 订单退款总金额,单位分,没有已退款记录,为0 | |||||
| */ | |||||
| @SerializedName(value = "total_refund_amount") | |||||
| private Integer total_refund_amount; | |||||
| /** | |||||
| * 分佣用户ID | |||||
| */ | |||||
| @SerializedName(value = "refund_items") | |||||
| private List<RefundItem> refund_items; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class RefundItem implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 开发者系统生成的退款单号,与抖音开平退款单号refund_id 唯一关联 | |||||
| */ | |||||
| @SerializedName(value = "out_refund_no") | |||||
| private String outRefundNo; | |||||
| /** | |||||
| * 退款金额,单位[分] | |||||
| */ | |||||
| @SerializedName(value = "refund_amount") | |||||
| private Integer refundAmount; | |||||
| /** | |||||
| * 显示退款状态,默认“已退款” | |||||
| */ | |||||
| @SerializedName(value = "refundStatus") | |||||
| private String refundStatus; | |||||
| /** | |||||
| * 退款时间,13位毫秒时间戳,只有已退款才有退款时间 | |||||
| */ | |||||
| @SerializedName(value = "refunded_at") | |||||
| private Long refunded_at; | |||||
| /** | |||||
| * 抖音开平侧的商品单号,只存在交易2.0系统 | |||||
| */ | |||||
| @SerializedName(value = "item_order_id") | |||||
| private String itemOrderId; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class DeliveryInfo implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 订单核销总金额,单位分,没有核销记录,为0 | |||||
| */ | |||||
| @SerializedName(value = "total_delivery_amount") | |||||
| private Integer totalDeliveryAmount; | |||||
| /** | |||||
| * 分佣用户ID | |||||
| */ | |||||
| @SerializedName(value = "delivery_items") | |||||
| private List<DeliveryItem> deliveryItems; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class DeliveryItem implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 核销金额,单位[分] | |||||
| */ | |||||
| @SerializedName(value = "delivery_amount") | |||||
| private Integer deliveryAmount; | |||||
| /** | |||||
| * 显示核销状态,默认“已核销” | |||||
| */ | |||||
| @SerializedName(value = "delivery_status") | |||||
| private String deliveryStatus; | |||||
| /** | |||||
| * 退款时间,13位毫秒时间戳,只有已退款才有退款时间 | |||||
| */ | |||||
| @SerializedName(value = "delivery_at") | |||||
| private Long deliveryAt; | |||||
| /** | |||||
| * 抖音开平侧的商品单号,只存在交易2.0系统 | |||||
| */ | |||||
| @SerializedName(value = "item_order_id") | |||||
| private String itemOrderId; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,119 @@ | |||||
| package com.iformall.douyin.payv2.result; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import io.swagger.models.auth.In; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 查询订单 返回结果对象 | |||||
| * </pre> | |||||
| * | |||||
| * @author thinsstar | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public class TtPayOrderQueryV2Result implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 抖音开平侧订单号 | |||||
| */ | |||||
| @SerializedName(value = "order_id") | |||||
| private String orderId; | |||||
| /** | |||||
| * 开发者侧订单号,与order_id一一对应 | |||||
| */ | |||||
| @SerializedName(value = "out_order_no") | |||||
| private String outOrderNo; | |||||
| /** | |||||
| * 已退款金额,单位[分] | |||||
| */ | |||||
| @SerializedName(value = "refund_amount") | |||||
| private Integer refundAmount; | |||||
| /** | |||||
| * 已分账金额,单位[分] | |||||
| */ | |||||
| @SerializedName(value = "settle_amount") | |||||
| private Integer settleAmount; | |||||
| /** | |||||
| * 订单实际支付金额,单位[分] | |||||
| */ | |||||
| @SerializedName(value = "total_fee") | |||||
| private Integer totalFee; | |||||
| /** | |||||
| * 订单状态, | |||||
| * INIT 初始状态 | |||||
| * PROCESS 订单处理中 | |||||
| * SUCCESS:成功 | |||||
| * FAIL:失败 | |||||
| * TIMEOUT:用户超时未支付 | |||||
| */ | |||||
| @SerializedName(value = "order_status") | |||||
| private String orderStatus; | |||||
| /** | |||||
| * 支付时间,格式:2021-12-12 00:00:00 | |||||
| */ | |||||
| @SerializedName(value = "pay_time") | |||||
| private String payTime; | |||||
| /** | |||||
| * 支付渠道枚举,1:微信,2:支付宝 | |||||
| */ | |||||
| @SerializedName(value = "pay_channel") | |||||
| private Integer payChannel; | |||||
| /** | |||||
| * 渠道支付单号,如微信的支付单号 | |||||
| */ | |||||
| @SerializedName(value = "channel_pay_id") | |||||
| private String channelPayId; | |||||
| /** | |||||
| * 卖家商户号id | |||||
| */ | |||||
| @SerializedName(value = "seller_uid") | |||||
| private String sellerUid; | |||||
| /** | |||||
| * 视频id | |||||
| */ | |||||
| @SerializedName(value = "item_id") | |||||
| private String itemId; | |||||
| /** | |||||
| * 预下单时开发者定义的透传信息 | |||||
| */ | |||||
| @SerializedName(value = "cp_extra") | |||||
| private String cpExtra; | |||||
| /** | |||||
| * 结果描述信息,如失败原因 | |||||
| */ | |||||
| @SerializedName(value = "message") | |||||
| private String message; | |||||
| /** | |||||
| * 担保支付单id | |||||
| */ | |||||
| @SerializedName(value = "payment_order_id") | |||||
| private Integer paymentOrderId; | |||||
| /** | |||||
| * 订单核销类型: | |||||
| * 0-非闭环核销,开发者自行处理券码生成及展示,通过push_delivery接口推送核销状态。 | |||||
| * 1-闭环核销,开平负责生券,开发者使用核销组件展示,使用验券准备和验券接口核销。 | |||||
| */ | |||||
| @SerializedName(value = "delivery_type") | |||||
| private Integer deliveryType; | |||||
| } | |||||
| @@ -0,0 +1,121 @@ | |||||
| package com.iformall.douyin.payv2.result; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 退款返回结果. | |||||
| * </pre> | |||||
| * | |||||
| * @author | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public class TtPayRefundQueryV2Result implements Serializable { | |||||
| private static final long serialVersionUID = -1L; | |||||
| /** | |||||
| * 系统退款单号,开放平台生成的退款单号 | |||||
| */ | |||||
| @SerializedName(value = "refund_id") | |||||
| private String refundId; | |||||
| /** | |||||
| * 开发者系统生成的退款单号,与抖音开平退款单号refund_id 唯一关联 | |||||
| */ | |||||
| @SerializedName(value = "out_refund_no") | |||||
| private String outRefundNo; | |||||
| /** | |||||
| * 退款金额,单位[分] | |||||
| */ | |||||
| @SerializedName(value = "refund_total_amount") | |||||
| private Integer refundTotalAmount; | |||||
| /** | |||||
| * 退款状态 | |||||
| * 退款中- PROCESSING,已退款- SUCCESS,退款失败- FAIL | |||||
| */ | |||||
| @SerializedName(value = "refund_status") | |||||
| private String refundStatus; | |||||
| /** | |||||
| * 退款时间,13位毫秒时间戳,只有已退款才有退款时间 | |||||
| */ | |||||
| @SerializedName(value = "refund_at") | |||||
| private Long refundAt; | |||||
| /** | |||||
| * 退款结果信息,非商家拒绝退款导致的退款失败,可以通过该字段了解退款失败原因 | |||||
| */ | |||||
| @SerializedName(value = "message") | |||||
| private String message; | |||||
| /** | |||||
| * 系统订单信息,开放平台生成的订单号 | |||||
| */ | |||||
| @SerializedName(value = "order_id") | |||||
| private String orderId; | |||||
| /** | |||||
| * 商品单信息 (新系统订单退款才有的信息) | |||||
| */ | |||||
| @SerializedName(value = "item_order_detail") | |||||
| private List<itemOrderDetail> itemOrderDetail; | |||||
| /** | |||||
| * 退款审核信息(在新系统发起的退款单才有) | |||||
| */ | |||||
| @SerializedName(value = "merchant_audit_detail") | |||||
| private MerchantAuditDetail merchantAuditDetail; | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class itemOrderDetail implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 抖音开平侧的商品单号 | |||||
| */ | |||||
| @SerializedName(value = "item_order_id") | |||||
| private String itemOrderId; | |||||
| /** | |||||
| * 该商品单退款金额,单位[分] | |||||
| */ | |||||
| @SerializedName(value = "refund_amount") | |||||
| private Integer refundAmount; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class MerchantAuditDetail implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 退款审核的最后期限,过期无需审核,自动退款,13位unix时间戳,精度:毫秒 | |||||
| */ | |||||
| @SerializedName(value = "refund_audit_deadline") | |||||
| private Long refundAuditDeadline; | |||||
| /** | |||||
| * 退款审核状态 | |||||
| * INIT-初始化 TOAUDIT-待审核 AGREE-同意 DENY-拒绝 OVERTIME-超时未审核自动同意 | |||||
| */ | |||||
| @SerializedName(value = "audit_status") | |||||
| private String auditStatus; | |||||
| /** | |||||
| * 不同意退款信息(不同意退款时必填),长度 <= 512 byte | |||||
| */ | |||||
| @SerializedName(value = "deny_message") | |||||
| private String denyMessage; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,33 @@ | |||||
| package com.iformall.douyin.payv2.result; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * <pre> | |||||
| * 申请退款返回结果. | |||||
| * </pre> | |||||
| * | |||||
| * @author thinsstar | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public class TtPayRefundV2Result implements Serializable { | |||||
| private static final long serialVersionUID = -1L; | |||||
| /** | |||||
| * 抖音开放平台交易系统内部退款单号 | |||||
| */ | |||||
| @SerializedName(value = "refund_id") | |||||
| private String refundId; | |||||
| /** | |||||
| * 退款审核的最后期限,过期无需审核,自动退款,13位unix时间戳,精度:毫秒 | |||||
| */ | |||||
| @SerializedName(value = "refund_audit_deadline") | |||||
| private Long refundAuditDeadline; | |||||
| } | |||||
| @@ -0,0 +1,99 @@ | |||||
| package com.iformall.douyin.payv2.result; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import lombok.experimental.Accessors; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 统一下单请求参数对象. | |||||
| * </pre> | |||||
| * | |||||
| * @author thinsstar | |||||
| */ | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| @Accessors(chain = true) | |||||
| public class TtPayUnifiedOrderV2Result implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| /** | |||||
| * 是 | |||||
| * 抖音开平侧生成的订单号 | |||||
| */ | |||||
| @SerializedName(value = "order_id") | |||||
| protected String orderId; | |||||
| /** | |||||
| * 是 | |||||
| * 开发者系统生成的订单号 | |||||
| */ | |||||
| @SerializedName(value = "out_order_no") | |||||
| protected String outOrderNo; | |||||
| /** | |||||
| * 是 | |||||
| * 吊起收银台的支付订单号 | |||||
| */ | |||||
| @SerializedName(value = "pay_order_id") | |||||
| protected String payOrderId; | |||||
| /** | |||||
| * 是 | |||||
| * 吊起收银台的token | |||||
| */ | |||||
| @SerializedName(value = "pay_order_token") | |||||
| protected String payOrderToken; | |||||
| /** | |||||
| * 是 | |||||
| * 商品item_order信息,详情见 item_order_info_list部分 | |||||
| */ | |||||
| @SerializedName(value = "item_order_info_list") | |||||
| protected List<ItemOrderInfo> itemOrderInfoList; | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class ItemOrderInfo implements Serializable { | |||||
| private static final long serialVersionUID = 1L; | |||||
| @SerializedName(value = "goods_id") | |||||
| private String goodsId; | |||||
| /** | |||||
| * 是 | |||||
| * item_order_id列表,id个数与下单时对应goods_id的quantity一致 | |||||
| */ | |||||
| @SerializedName(value = "item_order_id_list") | |||||
| private List<String> itemOrderIdList; | |||||
| /** | |||||
| * 是 | |||||
| * 商品item_order详细信息,详情见 item_order_detail部分 | |||||
| */ | |||||
| @SerializedName(value = "item_order_detail") | |||||
| private List<String> itemOrderDetail; | |||||
| } | |||||
| @Data | |||||
| @NoArgsConstructor | |||||
| public static class ItemOrderDetail implements Serializable { | |||||
| private static final long serialVersionUID = -1L; | |||||
| /** | |||||
| * item单id | |||||
| */ | |||||
| @SerializedName(value = "item_order_id") | |||||
| private String itemOrderId; | |||||
| /** | |||||
| * 商品优惠后价格 | |||||
| */ | |||||
| @SerializedName(value = "price") | |||||
| private Integer price; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,12 @@ | |||||
| package com.iformall.douyin.payv2.util; | |||||
| import org.apache.http.client.methods.HttpRequestWrapper; | |||||
| import java.io.IOException; | |||||
| public interface Credentials { | |||||
| String getSchema(); | |||||
| String getToken(HttpRequestWrapper request) throws IOException; | |||||
| } | |||||
| @@ -0,0 +1,81 @@ | |||||
| package com.iformall.douyin.payv2.util; | |||||
| import org.apache.http.HttpEntity; | |||||
| import org.apache.http.HttpEntityEnclosingRequest; | |||||
| import org.apache.http.HttpException; | |||||
| import org.apache.http.StatusLine; | |||||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||||
| import org.apache.http.client.methods.HttpExecutionAware; | |||||
| import org.apache.http.client.methods.HttpRequestWrapper; | |||||
| import org.apache.http.client.protocol.HttpClientContext; | |||||
| import org.apache.http.conn.routing.HttpRoute; | |||||
| import org.apache.http.entity.BufferedHttpEntity; | |||||
| import org.apache.http.entity.ByteArrayEntity; | |||||
| import org.apache.http.impl.execchain.ClientExecChain; | |||||
| import org.apache.http.util.EntityUtils; | |||||
| import java.io.IOException; | |||||
| public class SignatureExec implements ClientExecChain { | |||||
| final ClientExecChain mainExec; | |||||
| final Credentials credentials; | |||||
| final Validator validator; | |||||
| SignatureExec(Credentials credentials, Validator validator, ClientExecChain mainExec) { | |||||
| this.credentials = credentials; | |||||
| this.validator = validator; | |||||
| this.mainExec = mainExec; | |||||
| } | |||||
| protected HttpEntity newRepeatableEntity(HttpEntity entity) throws IOException { | |||||
| byte[] content = EntityUtils.toByteArray(entity); | |||||
| ByteArrayEntity newEntity = new ByteArrayEntity(content); | |||||
| newEntity.setContentEncoding(entity.getContentEncoding()); | |||||
| newEntity.setContentType(entity.getContentType()); | |||||
| return newEntity; | |||||
| } | |||||
| protected void convertToRepeatableResponseEntity(CloseableHttpResponse response) throws IOException { | |||||
| HttpEntity entity = response.getEntity(); | |||||
| if (entity != null && !entity.isRepeatable()) { | |||||
| response.setEntity(newRepeatableEntity(entity)); | |||||
| } | |||||
| } | |||||
| protected void convertToRepeatableRequestEntity(HttpRequestWrapper request) throws IOException { | |||||
| if (request instanceof HttpEntityEnclosingRequest) { | |||||
| HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity(); | |||||
| if (entity != null) { | |||||
| ((HttpEntityEnclosingRequest) request).setEntity(new BufferedHttpEntity(entity)); | |||||
| } | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request, | |||||
| HttpClientContext context, HttpExecutionAware execAware) throws IOException, HttpException { | |||||
| return executeWithSignature(route, request, context, execAware); | |||||
| } | |||||
| private CloseableHttpResponse executeWithSignature(HttpRoute route, HttpRequestWrapper request, | |||||
| HttpClientContext context, HttpExecutionAware execAware) throws IOException, HttpException { | |||||
| // 添加认证信息 | |||||
| request.addHeader("Byte-Authorization", | |||||
| credentials.getSchema() + " " + credentials.getToken(request)); | |||||
| // 执行 | |||||
| CloseableHttpResponse response = mainExec.execute(route, request, context, execAware); | |||||
| // 对成功应答验签 | |||||
| StatusLine statusLine = response.getStatusLine(); | |||||
| if (statusLine.getStatusCode() >= 200 && statusLine.getStatusCode() < 300) { | |||||
| convertToRepeatableResponseEntity(response); | |||||
| if (!validator.validate(response)) { | |||||
| throw new HttpException("应答的支付签名验证失败"); | |||||
| } | |||||
| } | |||||
| return response; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,70 @@ | |||||
| package com.iformall.douyin.payv2.util; | |||||
| import com.iformall.douyin.payv2.auth.*; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import org.apache.http.impl.client.HttpClientBuilder; | |||||
| import org.apache.http.impl.execchain.ClientExecChain; | |||||
| import java.security.PrivateKey; | |||||
| import java.security.PublicKey; | |||||
| public class TtPayV2HttpClientBuilder extends HttpClientBuilder { | |||||
| private Credentials credentials; | |||||
| private Validator validator; | |||||
| static final String OS = System.getProperty("os.name") + "/" + System.getProperty("os.version"); | |||||
| static final String VERSION = System.getProperty("java.version"); | |||||
| private TtPayV2HttpClientBuilder() { | |||||
| super(); | |||||
| String userAgent = String.format( | |||||
| "WechatPay-Apache-HttpClient/%s (%s) Java/%s", | |||||
| getClass().getPackage().getImplementationVersion(), | |||||
| OS, | |||||
| VERSION == null ? "Unknown" : VERSION); | |||||
| setUserAgent(userAgent); | |||||
| } | |||||
| public static TtPayV2HttpClientBuilder create() { | |||||
| return new TtPayV2HttpClientBuilder(); | |||||
| } | |||||
| public TtPayV2HttpClientBuilder withAppId(String appId, String serialNo, PrivateKey privateKey) { | |||||
| this.credentials = | |||||
| new TtPayCredentials(appId, new PrivateKeySigner(serialNo, privateKey)); | |||||
| return this; | |||||
| } | |||||
| public TtPayV2HttpClientBuilder withCredentials(Credentials credentials) { | |||||
| this.credentials = credentials; | |||||
| return this; | |||||
| } | |||||
| public TtPayV2HttpClientBuilder withPublicKey(PublicKey publicKey) { | |||||
| this.validator = new TtPayValidator(new CertificatesVerifier(publicKey)); | |||||
| return this; | |||||
| } | |||||
| public TtPayV2HttpClientBuilder withValidator(Validator validator) { | |||||
| this.validator = validator; | |||||
| return this; | |||||
| } | |||||
| @Override | |||||
| public CloseableHttpClient build() { | |||||
| if (credentials == null) { | |||||
| throw new IllegalArgumentException("缺少身份认证信息"); | |||||
| } | |||||
| if (validator == null) { | |||||
| throw new IllegalArgumentException("缺少签名验证信息"); | |||||
| } | |||||
| return super.build(); | |||||
| } | |||||
| @Override | |||||
| protected ClientExecChain decorateProtocolExec(final ClientExecChain requestExecutor) { | |||||
| return new SignatureExec(this.credentials, this.validator, requestExecutor); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| package com.iformall.douyin.payv2.util; | |||||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||||
| import java.io.IOException; | |||||
| public interface Validator { | |||||
| boolean validate(CloseableHttpResponse response) throws IOException; | |||||
| } | |||||
| @@ -8,8 +8,11 @@ import com.iformall.domain.vo.WxMerchantVo; | |||||
| import com.iformall.domain.vo.WxOrderCouponVo; | import com.iformall.domain.vo.WxOrderCouponVo; | ||||
| import com.iformall.douyin.miniapp.api.TtMaService; | import com.iformall.douyin.miniapp.api.TtMaService; | ||||
| import com.iformall.douyin.pay.DouYinPayHelper; | import com.iformall.douyin.pay.DouYinPayHelper; | ||||
| import com.iformall.douyin.pay.TtPayService; | |||||
| import com.iformall.douyin.pay.preOrder.CreatePreOrderResult; | import com.iformall.douyin.pay.preOrder.CreatePreOrderResult; | ||||
| import com.iformall.douyin.pay.preOrder.DouYinCreatePreOrder; | import com.iformall.douyin.pay.preOrder.DouYinCreatePreOrder; | ||||
| import com.iformall.douyin.payv2.request.TtPayUnifiedOrderV2Request; | |||||
| import com.iformall.douyin.payv2.result.TtPayUnifiedOrderV2Result; | |||||
| import com.iformall.enums.*; | import com.iformall.enums.*; | ||||
| import com.iformall.mapper.TtMerchantPoiMapper; | import com.iformall.mapper.TtMerchantPoiMapper; | ||||
| import com.iformall.mapper.WxAppinfoMapper; | import com.iformall.mapper.WxAppinfoMapper; | ||||
| @@ -21,6 +24,7 @@ import com.iformall.service.pay.service.pay.entity.PayAdapterResult; | |||||
| import com.iformall.service.pay.service.pay.entity.PayQueryAdapterResult; | import com.iformall.service.pay.service.pay.entity.PayQueryAdapterResult; | ||||
| import com.iformall.utils.Constant; | import com.iformall.utils.Constant; | ||||
| import com.iformall.utils.DateUtils; | import com.iformall.utils.DateUtils; | ||||
| import com.iformall.utils.MaUtil; | |||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
| import me.chanjar.weixin.common.error.WxErrorException; | import me.chanjar.weixin.common.error.WxErrorException; | ||||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | import me.chanjar.weixin.common.util.json.WxGsonBuilder; | ||||
| @@ -42,9 +46,19 @@ public class TtMiniAppPayAdapterService extends BaseTtPayAdapterService implemen | |||||
| @Autowired | @Autowired | ||||
| private TtMerchantPoiMapper ttMerchantPoiMapper; | private TtMerchantPoiMapper ttMerchantPoiMapper; | ||||
| @Autowired | |||||
| private MaUtil maUtil; | |||||
| @Override | @Override | ||||
| public PayAdapterResult pay(WxPayAccount payAccount, WxPayOrder record,WxComposeOrder composeOrder,String productName,EnumPayShare isShare,WxAppinfo appInfo,Date currentDate, PayExtraParam params) throws Exception { | public PayAdapterResult pay(WxPayAccount payAccount, WxPayOrder record,WxComposeOrder composeOrder,String productName,EnumPayShare isShare,WxAppinfo appInfo,Date currentDate, PayExtraParam params) throws Exception { | ||||
| DouYinCreatePreOrder preOrder = new DouYinCreatePreOrder(); | |||||
| // 新下单接口 | |||||
| // TtPayService ttPayService = maUtil.getTtPayService(appInfo, payAccount); | |||||
| // TtPayUnifiedOrderV2Request request = new TtPayUnifiedOrderV2Request(); | |||||
| // | |||||
| // | |||||
| // TtPayUnifiedOrderV2Result result = ttPayService.createOrderV2(request); | |||||
| DouYinCreatePreOrder preOrder = new DouYinCreatePreOrder(); | |||||
| preOrder.setAppId(appInfo.getAppId()); | preOrder.setAppId(appInfo.getAppId()); | ||||
| preOrder.setSercrect(appInfo.getSecret()); | preOrder.setSercrect(appInfo.getSecret()); | ||||
| preOrder.setSalt(payAccount.getApiKey()); | preOrder.setSalt(payAccount.getApiKey()); | ||||
| @@ -16,6 +16,9 @@ import java.util.Date; | |||||
| import java.util.Map; | import java.util.Map; | ||||
| import java.util.concurrent.ConcurrentHashMap; | import java.util.concurrent.ConcurrentHashMap; | ||||
| import com.iformall.douyin.pay.TtPayService; | |||||
| import com.iformall.douyin.pay.impl.TtPayServiceImpl; | |||||
| import com.iformall.douyin.payv2.config.TtPayConfig; | |||||
| import com.iformall.douyin.web.api.TtWebService; | import com.iformall.douyin.web.api.TtWebService; | ||||
| import com.iformall.douyin.web.api.impl.TtWebServiceImpl; | import com.iformall.douyin.web.api.impl.TtWebServiceImpl; | ||||
| import com.iformall.douyin.web.config.TtWebDefaultConfigImpl; | import com.iformall.douyin.web.config.TtWebDefaultConfigImpl; | ||||
| @@ -42,6 +45,7 @@ public class MaUtil { | |||||
| private static Map<String,WxPayService> payServiceMap = new ConcurrentHashMap<String,WxPayService>(); | private static Map<String,WxPayService> payServiceMap = new ConcurrentHashMap<String,WxPayService>(); | ||||
| private static Map<String,TtPayService> ttpayServiceMap = new ConcurrentHashMap<String,TtPayService>(); | |||||
| private static Map<String,WxMaService> wxMaServiceMap = new ConcurrentHashMap<String,WxMaService>(); | private static Map<String,WxMaService> wxMaServiceMap = new ConcurrentHashMap<String,WxMaService>(); | ||||
| private static Map<String,String> wxMaAppServiceKeyMap = new ConcurrentHashMap<String,String>(); | private static Map<String,String> wxMaAppServiceKeyMap = new ConcurrentHashMap<String,String>(); | ||||
| @@ -119,6 +123,28 @@ public class MaUtil { | |||||
| return service; | return service; | ||||
| } | } | ||||
| public TtPayService getTtPayService(WxAppinfo appinfo, WxPayAccount payAccount) { | |||||
| TtPayConfig config = new TtPayConfig(); | |||||
| config.setAppId(appinfo.getAppId()); | |||||
| config.setPrivateKeyPath(payAccount.getPrivateKeyPath());//apiclient_key.pem 平台私钥 | |||||
| config.setCertSerialNo(payAccount.getCertSerialNo());//平台私钥序列号 | |||||
| config.setApiKey(payAccount.getApiV3Key());//平台私钥 | |||||
| TtPayService service = ttpayServiceMap.get(appinfo.getAppId()); | |||||
| if ( null == service ) { | |||||
| synchronized("getTtPayServiceBlock"+appinfo.getAppId()) { | |||||
| service = ttpayServiceMap.get(appinfo.getAppId()); | |||||
| if (null == service) { | |||||
| service = new TtPayServiceImpl(); | |||||
| service.setConfig(config); | |||||
| ttpayServiceMap.put(appinfo.getAppId(), service); | |||||
| } | |||||
| } | |||||
| } | |||||
| return service; | |||||
| } | |||||
| private TtMaService getTtappService(WxAppinfo appinfo) { | private TtMaService getTtappService(WxAppinfo appinfo) { | ||||
| WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); | WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); | ||||
| config.setAppid(appinfo.getAppId()); | config.setAppid(appinfo.getAppId()); | ||||