| @@ -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"); | |||
| @@ -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<String, String> 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<String, String> 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<PlatPushOrderSaveDto> 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<String,Object> 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<String, Object> 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<PlatPushOrderSaveDto> 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<String,Object> 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<String, Object> map = new HashMap(); | |||
| map.put("err_no",1001); | |||
| map.put("err_tips","未找到支付信息"); | |||
| return map; | |||
| } | |||
| } | |||
| @@ -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 | |||
| @@ -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(); | |||
| // } | |||
| // } | |||
| } | |||
| @@ -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; | |||
| @@ -20,7 +20,7 @@ import java.io.Serializable; | |||
| */ | |||
| @Data | |||
| @NoArgsConstructor | |||
| public abstract class BaseTtPayResult<T> implements Serializable { | |||
| public class BaseTtPayResult<T> implements Serializable { | |||
| /** | |||
| * 返回状态码. | |||
| @@ -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("无效的密钥"); | |||
| } | |||
| } | |||
| } | |||