diff --git a/mallinkAdmin/src/main/java/com/iformall/config/ShiroConfig.java b/mallinkAdmin/src/main/java/com/iformall/config/ShiroConfig.java index ae5031ad3..b4d7b4d5b 100644 --- a/mallinkAdmin/src/main/java/com/iformall/config/ShiroConfig.java +++ b/mallinkAdmin/src/main/java/com/iformall/config/ShiroConfig.java @@ -147,7 +147,7 @@ public class ShiroConfig { filterChainDefinitionMap.put("/wxDeviceScreenAd/**", "anon"); // filterChainDefinitionMap.put("/role/**", "corsFilter,token"); //商场初始化init -// filterChainDefinitionMap.put("/wxProjectConfig/**", "anon"); + filterChainDefinitionMap.put("/wxProjectConfig/**", "anon"); // //test // filterChainDefinitionMap.put("/merchantPoi/spuSync", "anon"); diff --git a/mallinkAdmin/src/main/java/com/iformall/controller/basic/WxProjectConfigController.java b/mallinkAdmin/src/main/java/com/iformall/controller/basic/WxProjectConfigController.java index e66bf133d..02ef82d4a 100644 --- a/mallinkAdmin/src/main/java/com/iformall/controller/basic/WxProjectConfigController.java +++ b/mallinkAdmin/src/main/java/com/iformall/controller/basic/WxProjectConfigController.java @@ -10,10 +10,13 @@ import com.iformall.controller.base.BaseController; import com.iformall.domain.po.*; import com.iformall.domain.po.base.TenantEntity; import com.iformall.domain.vo.WxWeappInfo; +import com.iformall.douyin.pay.TtPayService; +import com.iformall.douyin.payv2.request.CallBackSettingsRequest; import com.iformall.enums.*; import com.iformall.service.*; import com.iformall.shiro.PasswordHelper; import com.iformall.utils.Constant; +import com.iformall.utils.MaUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.apache.commons.lang3.StringUtils; @@ -94,6 +97,9 @@ public class WxProjectConfigController extends BaseController { @Autowired private WechatWebProperties wechatWebProperties; + @Autowired + private MaUtil maUtil; + @Autowired @Qualifier("openRedisTemplate") RedisTemplate openRedisTemplate; @@ -774,4 +780,74 @@ public class WxProjectConfigController extends BaseController { } } + @ApiOperation("获取抖音支付2.0 回调接口配置") + @GetMapping("/ttcallback/query/settings") + @SystemControllerLog(description = "") + @TenantIgnore + public ResultData ttcallbackQuerySettings(String appid) { + logger.debug("[" + getIpAddr() + "] WxProjectConfigController::ttcallbackQuerySettings"); + try { + WxAppinfo appinfo = wxAppinfoService.getByAppId(appid); + if(appinfo == null){ + return new ResultData(ErrorCode.SYS_PARAMETER_NOT_NULL.getCode(),"未找到对应的小程序"); + } + if(!EnumAppPlat.TOUTIAO.getCode().equals(appinfo.getPlat()) || !EnumAppType.C.getCode().equals(appinfo.getType())){ + return new ResultData(ErrorCode.SYS_PARAMETER_NOT_NULL.getCode(),"该小程序不支持"); + } + WxPayAccount payAccount = wxPayAccountService.getById(appinfo.getPayId()); + if(payAccount == null){ + return new ResultData(ErrorCode.SYS_PARAMETER_NOT_NULL.getCode(),"未找到对应的支付数据"); + } + TtPayService ttPayService = maUtil.getTtPayService(appinfo, payAccount); + CallBackSettingsRequest callBackSettingsRequest = ttPayService.querySettings(); + return new ResultData(callBackSettingsRequest); + }catch (Exception e){ + logger.error(e.getMessage(),e); + return new ResultData(ErrorCode.SYS_SERVER_ERROR); + } + } + + @ApiOperation("配置抖音支付2.0 回调接口") + @PostMapping("/ttcallback/settings") + @SystemControllerLog(description = "商场集团-更新") + @TenantIgnore + public ResultData ttcallbackSettings(@RequestBody Map map) { + logger.debug("[" + getIpAddr() + "] WxProjectConfigController::ttcallbackSettings"); + String appid = map.get("appid"); + String create_order_callback = map.get("create_order_callback"); + String refund_callback = map.get("refund_callback"); + String delivery_qrcode_redirect = map.get("delivery_qrcode_redirect"); + if(StringUtils.isBlank(appid) || StringUtils.isBlank(create_order_callback) + || StringUtils.isBlank(refund_callback)){ + return new ResultData(ErrorCode.SYS_PARAMETER_NOT_NULL); + } + try { + WxAppinfo appinfo = wxAppinfoService.getByAppId(appid); + if(appinfo == null){ + return new ResultData(ErrorCode.SYS_PARAMETER_NOT_NULL.getCode(),"未找到对应的小程序"); + } + if(!EnumAppPlat.TOUTIAO.getCode().equals(appinfo.getPlat()) || !EnumAppType.C.getCode().equals(appinfo.getType())){ + return new ResultData(ErrorCode.SYS_PARAMETER_NOT_NULL.getCode(),"该小程序不支持"); + } + WxPayAccount payAccount = wxPayAccountService.getById(appinfo.getPayId()); + if(payAccount == null){ + return new ResultData(ErrorCode.SYS_PARAMETER_NOT_NULL.getCode(),"未找到对应的支付数据"); + } + TtPayService ttPayService = maUtil.getTtPayService(appinfo, payAccount); + CallBackSettingsRequest request = new CallBackSettingsRequest(); + request.setCreateOrderCallback(create_order_callback); + request.setRefundCallback(refund_callback); + request.setDeliveryQrcodeRedirect(delivery_qrcode_redirect); + boolean b = ttPayService.callbackSettings(request); + if(b){ + return new ResultData(); + }else{ + return new ResultData(ErrorCode.SYS_SERVER_ERROR); + } + }catch (Exception e){ + logger.error(e.getMessage(),e); + return new ResultData(ErrorCode.SYS_SERVER_ERROR); + } + } + } diff --git a/mallinkCApi/src/main/java/com/iformall/controller/WxOrderController.java b/mallinkCApi/src/main/java/com/iformall/controller/WxOrderController.java index 3b18d9a03..833fb49f0 100644 --- a/mallinkCApi/src/main/java/com/iformall/controller/WxOrderController.java +++ b/mallinkCApi/src/main/java/com/iformall/controller/WxOrderController.java @@ -1,7 +1,9 @@ package com.iformall.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader; import com.github.pagehelper.PageInfo; +import com.iformall.annotation.AuthIgnore; import com.iformall.common.ErrorCode; import com.iformall.common.IdWorker; import com.iformall.common.Result; @@ -16,6 +18,7 @@ import com.iformall.domain.vo.WxOrderCouponPressVo; import com.iformall.domain.vo.WxOrderCouponVo; import com.iformall.enums.*; import com.iformall.exception.MallinkException; +import com.iformall.interceptor.BodyReaderHttpServletRequestWrapper; import com.iformall.service.*; import com.iformall.service.order.OrderFactory; import com.iformall.service.order.entity.WxComposeOrder; @@ -34,10 +37,8 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.*; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import java.util.*; @RestController @RequestMapping("/api/order/") @@ -138,27 +139,42 @@ public class WxOrderController extends BaseController { list.add(dto); return wxOrderService.composeSaveOrder(false,EnumComposeOrder.SINGLE,list,memberId,getPayWay(),getTenantInfo()); } - + + @AuthIgnore @ApiOperation(value = "抖音支付2.0与下单回调推送订单", notes = "{\"couponChannelId\":\"String\",\"couponId\":\"String\",\"press\":\"String\",\"orderGroupId\":\"String\",\"formId\":\"String\"}") @PostMapping("douyinPushOrder") - public ResultData douyinPushOrder(@RequestBody Map param) { - logger.info("save douyinPushOrder:"+param); - Long memberId; - try { - memberId = getMemberId(); - } catch (Exception e) { - return new ResultData(Result.ERROR,e.getMessage()); - } - - List list = new ArrayList<>(); - PlatPushOrderSaveDto dto = new PlatPushOrderSaveDto(); - /** - * TODO 从抖音推送的订单中取信息,每个订单里面有extParam,用来放第三方平台匹配的参数信息 - * 注意,wx_batch_order中要存allExtParam, wx_order,wx_coupon_order中存储每个订单的参数信息,需要自己根据总参数信息进行拆分 - */ - list.add(dto); - String allExtParam = "[]"; - return wxOrderService.platPushSaveOrder(false,EnumComposeOrder.ONE_NUMBER_ORDER_BATCH,allExtParam,list,memberId,getPayWay(),getTenantInfo()); + public Map douyinPushOrder(HttpServletRequest request) { + SignatureHeader header = new SignatureHeader(); + header.setTimeStamp(request.getHeader("Byte-Timestamp")); + header.setNonce(request.getHeader("Byte-Nonce-Str")); + header.setSigned(request.getHeader("Byte-Signature")); + + logger.info("支付2.0预下单回调---header{}"+header.toString()); + + String body = ((BodyReaderHttpServletRequestWrapper) request).getBody(); + logger.info("支付2.0预下单回调---body{}"+body); + + Map map = new HashMap(); + map.put("err_no",1000); + map.put("err_tips","库存不足"); + return map; + +// Long memberId; +// try { +// memberId = getMemberId(); +// } catch (Exception e) { +// return new ResultData(Result.ERROR,e.getMessage()); +// } +// +// List list = new ArrayList<>(); +// PlatPushOrderSaveDto dto = new PlatPushOrderSaveDto(); +// /** +// * TODO 从抖音推送的订单中取信息,每个订单里面有extParam,用来放第三方平台匹配的参数信息 +// * 注意,wx_batch_order中要存allExtParam, wx_order,wx_coupon_order中存储每个订单的参数信息,需要自己根据总参数信息进行拆分 +// */ +// list.add(dto); +// String allExtParam = "[]"; +// return wxOrderService.platPushSaveOrder(false,EnumComposeOrder.ONE_NUMBER_ORDER_BATCH,allExtParam,list,memberId,getPayWay(),getTenantInfo()); } @@ -700,4 +716,25 @@ public class WxOrderController extends BaseController { return new ResultData(refundOrder); } + @AuthIgnore + @ApiOperation(value = "抖音支付2.0与退款回调推送订单", notes = "{\"couponChannelId\":\"String\",\"couponId\":\"String\",\"press\":\"String\",\"orderGroupId\":\"String\",\"formId\":\"String\"}") + @PostMapping("douyinRefundOrder") + public Map douyinRefundOrder(HttpServletRequest request) { + SignatureHeader header = new SignatureHeader(); + header.setTimeStamp(request.getHeader("Byte-Timestamp")); + header.setNonce(request.getHeader("Byte-Nonce-Str")); + header.setSigned(request.getHeader("Byte-Signature")); + + logger.info("支付2.0预下单回调---header{}"+header.toString()); + + String body = ((BodyReaderHttpServletRequestWrapper) request).getBody(); + logger.info("支付2.0预下单回调---body{}"+body); + + Map map = new HashMap(); + map.put("err_no",1001); + map.put("err_tips","未找到支付信息"); + return map; + + } + } diff --git a/mallinkService/src/main/java/com/iformall/douyin/pay/impl/BaseTtPayServiceImpl.java b/mallinkService/src/main/java/com/iformall/douyin/pay/impl/BaseTtPayServiceImpl.java index cd189ff05..c7a8779bc 100644 --- a/mallinkService/src/main/java/com/iformall/douyin/pay/impl/BaseTtPayServiceImpl.java +++ b/mallinkService/src/main/java/com/iformall/douyin/pay/impl/BaseTtPayServiceImpl.java @@ -74,7 +74,12 @@ public abstract class BaseTtPayServiceImpl implements TtPayService { 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(); + BaseTtPayResult baseTtPayResult = GSON.fromJson(response, BaseTtPayResult.class); + if(baseTtPayResult.isSuccess()){ + return true; + }else{ + throw new TtPayException(baseTtPayResult.getErrTips()); + } } @Override @@ -174,7 +179,12 @@ public abstract class BaseTtPayServiceImpl implements TtPayService { 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(); + BaseTtPayResult baseTtPayResult = GSON.fromJson(response, BaseTtPayResult.class); + if(baseTtPayResult.isSuccess()){ + return true; + }else{ + throw new TtPayException(baseTtPayResult.getErrTips()); + } } @@ -209,7 +219,12 @@ public abstract class BaseTtPayServiceImpl implements TtPayService { 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(); + BaseTtPayResult baseTtPayResult = GSON.fromJson(response, BaseTtPayResult.class); + if(baseTtPayResult.isSuccess()){ + return true; + }else{ + throw new TtPayException(baseTtPayResult.getErrTips()); + } } @Override diff --git a/mallinkService/src/main/java/com/iformall/douyin/payv2/auth/CertificatesVerifier.java b/mallinkService/src/main/java/com/iformall/douyin/payv2/auth/CertificatesVerifier.java index 874ff2b6d..dcbfd0389 100644 --- a/mallinkService/src/main/java/com/iformall/douyin/payv2/auth/CertificatesVerifier.java +++ b/mallinkService/src/main/java/com/iformall/douyin/payv2/auth/CertificatesVerifier.java @@ -1,7 +1,11 @@ package com.iformall.douyin.payv2.auth; +import com.iformall.douyin.payv2.util.PemUtils; import me.chanjar.weixin.common.error.WxRuntimeException; +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.security.*; import java.util.Base64; @@ -19,7 +23,7 @@ public class CertificatesVerifier implements Verifier { Signature sign = Signature.getInstance("SHA256withRSA"); sign.initVerify(this.apiKey); sign.update(message); - return sign.verify(Base64.getDecoder().decode(signature)); + return sign.verify(Base64.getDecoder().decode(signature.getBytes(StandardCharsets.UTF_8))); } catch (NoSuchAlgorithmException e) { throw new WxRuntimeException("当前Java环境不支持SHA256withRSA", e); } catch (SignatureException e) { @@ -29,4 +33,40 @@ public class CertificatesVerifier implements Verifier { } } +// public static void main(String[] args) { +// String pubKey = "-----BEGIN PUBLIC KEY-----\n" + +// "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv8jSFriZ6l5vv0xVcpFJ\n" + +// "NxihPh3j6tVKrU2FBythCstyr/CwdpAp9AGXvcaW2x2kKiOMXXaRDo6OysjggsxK\n" + +// "5hh1fWDE3ML4Wi5tfAV64pvj8wn5XeHXGKtXcKD/ASQmUGnMKI0g8i2REm3Q7gzy\n" + +// "JH35URh+tU8wI2v8mDY9OCZ7Hmu8ImW3pibyYRYK0CEpa31+65gVaTeoaPlqTKR0\n" + +// "rwgxZM5nFcXxGtiTsH0O1GAJBqWpyVeOqH19vEVB/bROEb21bf65ZYhac2Z1lzWq\n" + +// "MW+/4knIDg6EH3/xZWFPPwAFZgpfkSQES/twPkN/59cZ8SfTwJ84k/KUOApvkihM\n" + +// "uQIDAQAB\n" + +// "-----END PUBLIC KEY-----"; +// String timestamp = "1653906762"; +// String nonce = "pztVmu9EhPgCv9Dvw31t68U9WhMcLXCb"; +// String httpBody = "{\"err_no\":0,\"data\":{\"create_order_callback\":\"https://ctest.malls.iformall.com/C/api/order/douyinPushOrder\",\"refund_callback\":\"https://ctest.malls.iformall.com/C/api/order/douyinRefundOrder\",\"delivery_qrcode_redirect\":\"\"},\"resp_extra\":{\"logid\":\"202205301832420101581160381B3C09C0\"},\"err_tips\":\"success\"}"; +// String signStr = "pZEQga7EpGI+NXEznLNZROViIBxwWaofnlstthQsu8MZiuTRUtool2SQhfLHkXwYzj5O8yj+luTbFUrhTfX+BzvcR3+fGrqMtgqGWp86a1hAg4e4pvoTHd5PAxUGRDb/gGxPoxLDgrS6cLKusdGi6mmwJbERveD9ThNV1RQGQo/BM3BeImr3fYdJ/ZU67iVkP6SEWkqRiEJhnQMaavy/B4LK3xI0A5BhWlGbhGUIqumLT903tIOhofXGA6PQJbDiU38J5EtMj/Z7l6+IXtbA/KO0IWbIZ96KFs6e8ZE6H18Xn7b0J7KDTjTelMt7iXczrLU2/e3QEcfn2/7+7DIRMQ=="; +// +// StringBuffer buffer = new StringBuffer(); +// buffer.append(timestamp).append("\n"); +// buffer.append(nonce).append("\n"); +// buffer.append(httpBody).append("\n"); +// try { +// Signature sign = Signature.getInstance("SHA256withRSA"); +// sign.initVerify(PemUtils.loadPublicKey(new ByteArrayInputStream(pubKey.getBytes("utf-8")))); +// sign.update(buffer.toString().getBytes(StandardCharsets.UTF_8)); +// boolean verify = sign.verify(Base64.getDecoder().decode(signStr.getBytes(StandardCharsets.UTF_8))); +// System.out.println("-------"+verify); +// } catch (NoSuchAlgorithmException e) { +// e.printStackTrace(); +// } catch (UnsupportedEncodingException e) { +// e.printStackTrace(); +// } catch (SignatureException e) { +// e.printStackTrace(); +// } catch (InvalidKeyException e) { +// e.printStackTrace(); +// } +// } + } diff --git a/mallinkService/src/main/java/com/iformall/douyin/payv2/config/TtPayConfig.java b/mallinkService/src/main/java/com/iformall/douyin/payv2/config/TtPayConfig.java index e4b6f3fa2..2a130ef35 100644 --- a/mallinkService/src/main/java/com/iformall/douyin/payv2/config/TtPayConfig.java +++ b/mallinkService/src/main/java/com/iformall/douyin/payv2/config/TtPayConfig.java @@ -1,10 +1,10 @@ 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.PemUtils; import com.iformall.douyin.payv2.util.TtPayV2HttpClientBuilder; import jodd.util.ResourcesUtil; import lombok.Data; diff --git a/mallinkService/src/main/java/com/iformall/douyin/payv2/result/BaseTtPayResult.java b/mallinkService/src/main/java/com/iformall/douyin/payv2/result/BaseTtPayResult.java index 3eeb545d6..06ed10e24 100644 --- a/mallinkService/src/main/java/com/iformall/douyin/payv2/result/BaseTtPayResult.java +++ b/mallinkService/src/main/java/com/iformall/douyin/payv2/result/BaseTtPayResult.java @@ -20,7 +20,7 @@ import java.io.Serializable; */ @Data @NoArgsConstructor -public abstract class BaseTtPayResult implements Serializable { +public class BaseTtPayResult implements Serializable { /** * 返回状态码. diff --git a/mallinkService/src/main/java/com/iformall/douyin/payv2/util/PemUtils.java b/mallinkService/src/main/java/com/iformall/douyin/payv2/util/PemUtils.java new file mode 100644 index 000000000..3a217eeea --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/douyin/payv2/util/PemUtils.java @@ -0,0 +1,89 @@ +package com.iformall.douyin.payv2.util; + +import me.chanjar.weixin.common.error.WxRuntimeException; +import sun.misc.BASE64Decoder; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +public class PemUtils { + + public static PrivateKey loadPrivateKey(InputStream inputStream) { + try { + ByteArrayOutputStream array = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length; + while ((length = inputStream.read(buffer)) != -1) { + array.write(buffer, 0, length); + } + + String privateKey = array.toString("utf-8") + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s+", ""); + + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePrivate( + new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))); + } catch (NoSuchAlgorithmException e) { + throw new WxRuntimeException("当前Java环境不支持RSA", e); + } catch (InvalidKeySpecException e) { + throw new WxRuntimeException("无效的密钥格式"); + } catch (IOException e) { + throw new WxRuntimeException("无效的密钥"); + } + } + + public static X509Certificate loadCertificate(InputStream inputStream) { + try { + CertificateFactory cf = CertificateFactory.getInstance("X509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream); + cert.checkValidity(); + return cert; + } catch (CertificateExpiredException e) { + throw new WxRuntimeException("证书已过期", e); + } catch (CertificateNotYetValidException e) { + throw new WxRuntimeException("证书尚未生效", e); + } catch (CertificateException e) { + throw new WxRuntimeException("无效的证书", e); + } + } + + public static PublicKey loadPublicKey(InputStream inputStream) { + + try { + ByteArrayOutputStream array = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length; + while ((length = inputStream.read(buffer)) != -1) { + array.write(buffer, 0, length); + } + + String publicKey = array.toString("utf-8") + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll("\\s+", ""); + + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePublic( + new X509EncodedKeySpec(new BASE64Decoder().decodeBuffer(publicKey))); + + } catch (NoSuchAlgorithmException e) { + throw new WxRuntimeException("当前Java环境不支持RSA", e); + } catch (InvalidKeySpecException e) { + throw new WxRuntimeException("无效的密钥格式"); + } catch (IOException e) { + throw new WxRuntimeException("无效的密钥"); + } + } +}