|  |  | 
 |  |  | package com.dy.pipIrrSell.wechatpay; | 
 |  |  |  | 
 |  |  | import com.alibaba.fastjson2.JSON; | 
 |  |  | import com.alibaba.fastjson2.JSONObject; | 
 |  |  | import com.dy.common.aop.SsoAop; | 
 |  |  | import com.dy.common.webUtil.BaseResponse; | 
 |  |  | import com.dy.common.webUtil.BaseResponseUtils; | 
 |  |  | import com.dy.common.webUtil.ResultCodeMsg; | 
 |  |  | import com.dy.pipIrrGlobal.pojoSe.SeVcRefund; | 
 |  |  | import com.dy.pipIrrGlobal.pojoSe.SeVcRefundItem; | 
 |  |  | import com.dy.pipIrrGlobal.pojoSe.SeVirtualCard; | 
 |  |  | import com.dy.pipIrrGlobal.pojoSe.SeWebchatLogonState; | 
 |  |  | import com.dy.pipIrrGlobal.voSe.VoClient; | 
 |  |  | 
 |  |  | import com.dy.pipIrrSell.util.RestTemplateUtil; | 
 |  |  | import com.dy.pipIrrSell.virtualCard.VirtualCardSv; | 
 |  |  | import com.dy.pipIrrSell.virtualCard.dto.DtoVirtualCard; | 
 |  |  | import com.dy.pipIrrSell.wechatpay.dto.*; | 
 |  |  | import com.dy.pipIrrSell.virtualCard.enums.LastOperateENUM; | 
 |  |  | import com.dy.pipIrrSell.virtualCard.enums.RefundItemStateENUM; | 
 |  |  | import com.dy.pipIrrSell.wallet.enums.RefundStatusENUM; | 
 |  |  | import com.dy.pipIrrSell.wechatpay.dto.Code2Session; | 
 |  |  | import com.dy.pipIrrSell.wechatpay.dto.DtoOrder; | 
 |  |  | import com.dy.pipIrrSell.wechatpay.dto.OrderNotify; | 
 |  |  | import io.swagger.v3.oas.annotations.Operation; | 
 |  |  | import io.swagger.v3.oas.annotations.media.Content; | 
 |  |  | import io.swagger.v3.oas.annotations.media.Schema; | 
 |  |  | 
 |  |  |     private final VirtualCardSv virtualCardSv; | 
 |  |  |     private final ClientSv clientSv; | 
 |  |  |  | 
 |  |  |     private String privateCertFileName = PayInfo.privateCertFileName; | 
 |  |  |     private String appid = PayInfo.appid; | 
 |  |  |     private String mchid = PayInfo.mchid; | 
 |  |  |     private String schema = PayInfo.schema; | 
 |  |  |     private String signType = PayInfo.signType; | 
 |  |  |     private String description = PayInfo.description; | 
 |  |  |     private String loginUrl = PayInfo.loginUrl; | 
 |  |  |     private String notifyUrl = PayInfo.notifyUrl; | 
 |  |  |     private String grantType = PayInfo.grantType; | 
 |  |  |     private String refundUrl = PayInfo.refundUrl; | 
 |  |  |     private final String privateCertFileName = PayInfo.privateCertFileName; | 
 |  |  |     private final String appid = PayInfo.appid; | 
 |  |  |     private final String mchid = PayInfo.mchid; | 
 |  |  |     private final String schema = PayInfo.schema; | 
 |  |  |     private final String signType = PayInfo.signType; | 
 |  |  |     private final String description = PayInfo.description; | 
 |  |  |     private final String loginUrl = PayInfo.loginUrl; | 
 |  |  |     private final String notifyUrl = PayInfo.notifyUrl; | 
 |  |  |     private final String grantType = PayInfo.grantType; | 
 |  |  |  | 
 |  |  |     // 平台证书公钥 | 
 |  |  |     private Map CERTIFICATE_MAP = new HashMap(); | 
 |  |  |     private final Map CERTIFICATE_MAP = new HashMap(); | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  |      * 登录凭证校验 | 
 |  |  |      * @param appid 小程序 appId | 
 |  |  |      * @param secret 小程序 appSecret | 
 |  |  |      * @param js_code 临时登录凭证code | 
 |  |  |      * @param code2Session 登录凭证校验传入对象 | 
 |  |  |      * @param bindingResult | 
 |  |  |      * @return | 
 |  |  |      * @throws Exception | 
 |  |  |      */ | 
 |  |  | 
 |  |  |     @PostMapping(path = "getSessionId") | 
 |  |  |     @Transactional(rollbackFor = Exception.class) | 
 |  |  |     @SsoAop() | 
 |  |  |     public BaseResponse<Boolean> getSessionId(@RequestParam("appid")  String appid, @RequestParam("secret") String secret, @RequestParam("js_code") String js_code) throws Exception { | 
 |  |  |     public BaseResponse<Boolean> getSessionId(@RequestBody @Valid Code2Session code2Session, BindingResult bindingResult) throws Exception { | 
 |  |  |         if(bindingResult != null && bindingResult.hasErrors()){ | 
 |  |  |             return BaseResponseUtils.buildFail(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage()); | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         Map<String, Object> queryParams = new HashMap<>(); | 
 |  |  |         queryParams.put("appid", appid); | 
 |  |  |         queryParams.put("secret", secret); | 
 |  |  |         queryParams.put("js_code", js_code); | 
 |  |  |         queryParams.put("secret", code2Session.getSecret()); | 
 |  |  |         queryParams.put("js_code", code2Session.getJs_code()); | 
 |  |  |         queryParams.put("grant_type", grantType); | 
 |  |  |         Map<String, String> headerParams = new HashMap<>(); | 
 |  |  |         JSONObject job = restTemplateUtil.get(loginUrl, queryParams, headerParams); | 
 |  |  | 
 |  |  |         String sessionKey = job.getString("session_key"); | 
 |  |  |  | 
 |  |  |         // 检验登录态 | 
 |  |  |         JSONObject checkSessionKey = payHelper.checkSessionKey(appid, secret, openid, sessionKey); | 
 |  |  |         if(checkSessionKey != null) { | 
 |  |  |             Integer errcode = checkSessionKey.getInteger("errcode"); | 
 |  |  |             String errmsg = checkSessionKey.getString("errmsg"); | 
 |  |  |         } | 
 |  |  |         //JSONObject checkSessionKey = payHelper.checkSessionKey(appid, secret, openid, sessionKey); | 
 |  |  |         //if(checkSessionKey != null) { | 
 |  |  |         //    Integer errcode = checkSessionKey.getInteger("errcode"); | 
 |  |  |         //    String errmsg = checkSessionKey.getString("errmsg"); | 
 |  |  |         //} | 
 |  |  |  | 
 |  |  |         // 重置登录态 | 
 |  |  |         JSONObject resetUserSessionKey = payHelper.resetUserSessionKey(appid, secret, openid, sessionKey); | 
 |  |  |         if(resetUserSessionKey != null) { | 
 |  |  |             Integer errcode = checkSessionKey.getInteger("errcode"); | 
 |  |  |             String errmsg = checkSessionKey.getString("errmsg"); | 
 |  |  |             String openid_New = checkSessionKey.getString("openid"); | 
 |  |  |             String sessionKey_New = checkSessionKey.getString("session_key"); | 
 |  |  |         } | 
 |  |  |         //JSONObject resetUserSessionKey = payHelper.resetUserSessionKey(appid, secret, openid, sessionKey); | 
 |  |  |         //if(resetUserSessionKey != null) { | 
 |  |  |         //    Integer errcode = checkSessionKey.getInteger("errcode"); | 
 |  |  |         //    String errmsg = checkSessionKey.getString("errmsg"); | 
 |  |  |         //    String openid_New = checkSessionKey.getString("openid"); | 
 |  |  |         //    String sessionKey_New = checkSessionKey.getString("session_key"); | 
 |  |  |         //} | 
 |  |  |  | 
 |  |  |         // 添加登录态记录 | 
 |  |  |         SeWebchatLogonState po = new SeWebchatLogonState(); | 
 |  |  | 
 |  |  |         headers.put("Accept", "application/json"); | 
 |  |  |         headers.put("Content-Type", "application/json"); | 
 |  |  |  | 
 |  |  |         JSONObject job_result = restTemplateUtil.post(PayInfo.orderUrl, body, headers); | 
 |  |  |         if(job_result != null) { | 
 |  |  |             System.out.println(job_result.toString()); | 
 |  |  |             prepayId = job_result.getString("prepay_id"); | 
 |  |  |         } | 
 |  |  |         // 暂时注释掉,认证通过后再放开 | 
 |  |  |         //JSONObject job_result = restTemplateUtil.post(PayInfo.orderUrl, body, headers); | 
 |  |  |         //if(job_result != null) { | 
 |  |  |         //    System.out.println(job_result.toString()); | 
 |  |  |         //    prepayId = job_result.getString("prepay_id"); | 
 |  |  |         //} | 
 |  |  |  | 
 |  |  |         return BaseResponseUtils.buildSuccess(prepayId) ; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  |      * 申请退款 | 
 |  |  |      * @param po 退款请求对象 | 
 |  |  |      * @param bindingResult | 
 |  |  |      * @return | 
 |  |  |      * @throws NoSuchPaddingException | 
 |  |  |      * @throws NoSuchAlgorithmException | 
 |  |  |      * @throws InvalidKeySpecException | 
 |  |  |      * @throws IOException | 
 |  |  |      * @throws SignatureException | 
 |  |  |      * @throws InvalidKeyException | 
 |  |  |      */ | 
 |  |  |     @Operation(summary = "申请退款", description = "申请退款") | 
 |  |  |     @ApiResponses(value = { | 
 |  |  |             @ApiResponse( | 
 |  |  |                     responseCode = ResultCodeMsg.RsCode.SUCCESS_CODE, | 
 |  |  |                     description = "操作结果:true:成功,false:失败(BaseResponse.content)", | 
 |  |  |                     content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, | 
 |  |  |                             schema = @Schema(implementation = Boolean.class))} | 
 |  |  |             ) | 
 |  |  |     }) | 
 |  |  |     @PostMapping(path = "refunds") | 
 |  |  |     @Transactional(rollbackFor = Exception.class) | 
 |  |  |     @SsoAop() | 
 |  |  |     public BaseResponse<Boolean> refunds(@RequestBody @Valid Refund po, BindingResult bindingResult) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, SignatureException, InvalidKeyException { | 
 |  |  |         if(bindingResult != null && bindingResult.hasErrors()){ | 
 |  |  |             return BaseResponseUtils.buildFail(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage()); | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         /** | 
 |  |  |          * 1. 判断交易时间是否超过一年 | 
 |  |  |          * 2. 判断退款总金额是否超过订单金额 | 
 |  |  |          * 3. 判断当前订单退款次数是否超过50次 | 
 |  |  |          * 4. 判断与该订单上次退款是否相隔1分钟 | 
 |  |  |          */ | 
 |  |  |  | 
 |  |  |         String tradeNo = po.getTradeNo(); | 
 |  |  |         String refundNo = po.getRefundNo(); | 
 |  |  |         Integer refund = po.getRefund(); | 
 |  |  |         if(refundNo == null || refundNo.length() <= 0) { | 
 |  |  |             // 新提退款申请,生成退款单号 | 
 |  |  |             //refundNo = generateRefundNo(tradeNo); | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         // 根据订单号获取总支付金额和总退款金额 | 
 |  |  |         Integer totalTradeAmount = 0; | 
 |  |  |         Integer totalRefundAmount = 0; | 
 |  |  |         //Integer totalTradeAmount = getTotalTradeAmount(tradeNo); | 
 |  |  |         //Integer totalRefundAmount = getTotalRefundAmount(tradeNo); | 
 |  |  |         if(totalRefundAmount > totalTradeAmount) { | 
 |  |  |             return BaseResponseUtils.buildFail(SellResultCode.TOTAL_REFUND_EXCEED_TRADE.getMessage()); | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         // 生成body | 
 |  |  |         RefundRequest.Amount amount = new RefundRequest.Amount(); | 
 |  |  |         amount.setRefund(refund); | 
 |  |  |         amount.setTotal(totalTradeAmount); | 
 |  |  |         amount.setCurrency("CNY"); | 
 |  |  |  | 
 |  |  |         RefundRequest refundRequest = new RefundRequest(); | 
 |  |  |         refundRequest.setOut_trade_no(tradeNo); | 
 |  |  |         refundRequest.setOut_refund_no(refundNo); | 
 |  |  |         refundRequest.setNotify_url(notifyUrl); | 
 |  |  |         refundRequest.setAmount(amount); | 
 |  |  |  | 
 |  |  |         // 生成header | 
 |  |  |         String nonceStr = payHelper.generateRandomString(); | 
 |  |  |         Long timestamp = System.currentTimeMillis() / 1000; | 
 |  |  |  | 
 |  |  |         String method = "POST"; | 
 |  |  |         String httpUrl = "/v3/refund/domestic/refunds"; | 
 |  |  |  | 
 |  |  |         String body = JSONObject.toJSONString(refundRequest); | 
 |  |  |         String header = schema + " " + payHelper.getToken(method, httpUrl, body, nonceStr, timestamp, privateCertFileName); | 
 |  |  |  | 
 |  |  |         Map<String, String> headers = new HashMap<>(); | 
 |  |  |         headers.put("Authorization", header); | 
 |  |  |         headers.put("Accept", "application/json"); | 
 |  |  |         headers.put("Content-Type", "application/json"); | 
 |  |  |  | 
 |  |  |         JSONObject job_refundResponse = restTemplateUtil.post(PayInfo.orderUrl, body, headers); | 
 |  |  |         RefundResponse refundResponse = JSON.parseObject(job_refundResponse.toJSONString(), RefundResponse.class); | 
 |  |  |  | 
 |  |  |         String status = refundResponse.getStatus(); | 
 |  |  |         if(status != null && status.equals("SUCCESS")) { | 
 |  |  |             // 退款申请已受理 | 
 |  |  |             return BaseResponseUtils.buildSuccess(true) ; | 
 |  |  |         } else if(status != null && status.equals("PROCESSING")) { | 
 |  |  |             // 退款处理中 | 
 |  |  |             return BaseResponseUtils.buildFail(SellResultCode.PROCESSING.getMessage()); | 
 |  |  |         } else { | 
 |  |  |             // 退款异常 | 
 |  |  |             return BaseResponseUtils.buildError(SellResultCode.ABNORMAL.getMessage()); | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /** | 
 |  |  | 
 |  |  |             } | 
 |  |  |         } else if(eventType != null && eventType.equals("REFUND.SUCCESS")) { | 
 |  |  |             // 退款成功后回调 | 
 |  |  |             /** | 
 |  |  |              * 退款成功的回调 | 
 |  |  |              * 取出通知数据对象,继而取出解密所需的associatedData和nonce,以及密文ciphertext | 
 |  |  |              * 解密ciphertext得到 | 
 |  |  |              */ | 
 |  |  |             OrderNotify.NotifyResource notifyResource = orderNotify.getResource(); | 
 |  |  |             String associatedData = notifyResource.getAssociated_data(); | 
 |  |  |             String nonce = notifyResource.getNonce(); | 
 |  |  |             String ciphertext = notifyResource.getCiphertext(); | 
 |  |  |  | 
 |  |  |             String resource = AesUtil.decryptToString(PayInfo.key.getBytes("utf-8"), associatedData.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext); | 
 |  |  |             JSONObject job_resource = JSONObject.parseObject(resource); | 
 |  |  |  | 
 |  |  |             // 解密后取出:商户订单员、微信支付订单号、交易状态、支付完成时间 | 
 |  |  |             String out_trade_no = job_resource.getString("out_trade_no"); | 
 |  |  |             String transaction_id = job_resource.getString("transaction_id"); | 
 |  |  |             String out_refund_no = job_resource.getString("out_refund_no"); | 
 |  |  |             String refund_status = job_resource.getString("refund_status"); | 
 |  |  |             Date success_time = job_resource.getDate("success_time"); | 
 |  |  |             if(!refund_status.equals("SUCCESS")) { | 
 |  |  |                 response.setStatus(500); | 
 |  |  |                 result.put("code", "FAIL"); | 
 |  |  |                 result.put("message", "失败"); | 
 |  |  |                 return result; | 
 |  |  |             } | 
 |  |  |  | 
 |  |  |             // 更新虚拟卡表及充值表响应字段 | 
 |  |  |             SeVcRefundItem seVcRefundItem = new SeVcRefundItem(); | 
 |  |  |             seVcRefundItem.setRefundTime(success_time); | 
 |  |  |             seVcRefundItem.setRefundStatus(RefundItemStateENUM.REFUNDED.getCode()); | 
 |  |  |             Integer rec = virtualCardSv.updateRefundItem(seVcRefundItem); | 
 |  |  |             if(rec == null && rec <= 0) { | 
 |  |  |                 response.setStatus(500); | 
 |  |  |                 result.put("code", "FAIL"); | 
 |  |  |                 result.put("message", "失败"); | 
 |  |  |                 return result; | 
 |  |  |             } | 
 |  |  |  | 
 |  |  |             // 根据退款单号反查退款ID,根据退款ID获取退款状态是未退款的记录数量,如果是0则说明全部退款完成,更新退款表状态为已退款,将退款后金额更新到虚拟卡表 | 
 |  |  |             /** | 
 |  |  |              * 根据退款通知接口返回的退款单号反查退款ID,查询该退款ID下未退款记录数量 | 
 |  |  |              *      如果结果为0,则该退款已经完成 | 
 |  |  |              *      1. 更新退款表状态为已退款 | 
 |  |  |              *      2. 将退款后余额更新到虚拟卡表中 | 
 |  |  |              */ | 
 |  |  |             Integer noRefundedCount = virtualCardSv.getNoRefundedCount(out_refund_no); | 
 |  |  |             if(noRefundedCount != null && noRefundedCount == 0) { | 
 |  |  |                 // 获取退款对象并修改退款状态 | 
 |  |  |                 Long refundId = virtualCardSv.getRefundIdByRefundNumber(out_refund_no); | 
 |  |  |                 SeVcRefund seVcRefund = virtualCardSv.selectRefundByRefundId(refundId); | 
 |  |  |                 seVcRefund.setRefundStatus(RefundStatusENUM.REFUNDED.getCode()); | 
 |  |  |                 virtualCardSv.updateRefund(seVcRefund); | 
 |  |  |  | 
 |  |  |                 // 获取虚拟卡对象并修改余额、最后操作、最后操作时间 | 
 |  |  |                 Long vcId = seVcRefund.getVcId(); | 
 |  |  |                 Double afterRefund = seVcRefund.getAfterRefund(); | 
 |  |  |                 SeVirtualCard seVirtualCard = virtualCardSv.selectVirtuCardById(vcId); | 
 |  |  |                 seVirtualCard.setMoney(afterRefund); | 
 |  |  |                 seVirtualCard.setLastOperate(LastOperateENUM.REFUND.getCode()); | 
 |  |  |                 seVirtualCard.setLastOperateTime(new Date()); | 
 |  |  |                 virtualCardSv.updateVirtualCard(seVirtualCard); | 
 |  |  |             } | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         // 通知应答 |