| | |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | // 通知应答 |