<commit_before>/*
 * Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & <EMAIL>).
 * <p>
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/lgpl.html
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jeequan.jeepay.pay.channel.wxpay;

import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.v3.auth.AutoUpdateCertificatesVerifier;
import com.github.binarywang.wxpay.v3.auth.PrivateKeySigner;
import com.github.binarywang.wxpay.v3.auth.WxPayCredentials;
import com.github.binarywang.wxpay.v3.util.PemUtils;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ResponseException;
import com.jeequan.jeepay.pay.channel.AbstractChannelNoticeService;
import com.jeequan.jeepay.pay.model.MchAppConfigContext;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.service.ConfigContextService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.io.FileInputStream;
import java.math.BigDecimal;
import java.security.PrivateKey;

/*
* 微信回调
*
* @author zhuxiao
* @site https://www.jeepay.vip
* @date 2021/6/8 18:10
*/
@Service
@Slf4j
public class WxpayChannelNoticeService extends AbstractChannelNoticeService {

    @Autowired private ConfigContextService configContextService;

    @Autowired private PayOrderService payOrderService;

    @Override
    public String getIfCode() {
        return CS.IF_CODE.WXPAY;
    }

    @Override
    public MutablePair<String, Object> parseParams(HttpServletRequest request, String urlOrderId, NoticeTypeEnum noticeTypeEnum) {

        try {
            if(StringUtils.isNotBlank(urlOrderId)){     // V3接口回调

                // 获取订单信息
                PayOrder payOrder = payOrderService.getById(urlOrderId);
                if(payOrder == null){
                    throw new BizException("订单不存在");
                }

                //获取支付参数 (缓存数据) 和 商户信息
                MchAppConfigContext mchAppConfigContext = configContextService.getMchAppConfigContext(payOrder.getMchNo(), payOrder.getAppId());
                if(mchAppConfigContext == null){
                    throw new BizException("获取商户信息失败");
                }

                // 验签 && 获取订单回调数据
                WxPayOrderNotifyV3Result.DecryptNotifyResult result = parseOrderNotifyV3Result(request, mchAppConfigContext);

                return MutablePair.of(result.getOutTradeNo(), result);

            } else {     // V2接口回调
                String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
                if(StringUtils.isEmpty(xmlResult)) {
                    return null;
                }

                WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xmlResult);
                String payOrderId = result.getOutTradeNo();
                return MutablePair.of(payOrderId, result);
            }
        } catch (Exception e) {
            log.error("error", e);
            throw ResponseException.buildText("ERROR");
        }
    }

    @Override
    public ChannelRetMsg doNotice(HttpServletRequest request, Object params, PayOrder payOrder, MchAppConfigContext mchAppConfigContext, NoticeTypeEnum noticeTypeEnum) {
        try {
            ChannelRetMsg channelResult = new ChannelRetMsg();
            channelResult.setChannelState(ChannelRetMsg.ChannelState.WAITING); // 默认支付中

            if (CS.PAY_IF_VERSION.WX_V2.equals(mchAppConfigContext.getWxServiceWrapper().getApiVersion())) { // V2
                // 获取回调参数
                WxPayOrderNotifyResult result = (WxPayOrderNotifyResult) params;

                WxPayService wxPayService = mchAppConfigContext.getWxServiceWrapper().getWxPayService();

                // 验证参数
                verifyWxPayParams(wxPayService, result, payOrder);

                channelResult.setChannelOrderId(result.getTransactionId()); //渠道订单号
                channelResult.setChannelUserId(result.getOpenid()); //支付用户ID
                channelResult.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS);

            }else if (CS.PAY_IF_VERSION.WX_V3.equals(mchAppConfigContext.getWxServiceWrapper().getApiVersion())) { // V3
                // 获取回调参数
                WxPayOrderNotifyV3Result.DecryptNotifyResult result = (WxPayOrderNotifyV3Result.DecryptNotifyResult) params;

                // 验证参数
                verifyWxPayParams(result, payOrder);

                String channelState = result.getTradeState();
                if ("SUCCESS".equals(channelState)) {
                    channelResult.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS);
                }else if("CLOSED".equals(channelState)
                        || "REVOKED".equals(channelState)
                        || "PAYERROR".equals(channelState)){  //CLOSED—已关闭， REVOKED—已撤销, PAYERROR--支付失败
                    channelResult.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL); //支付失败
                }

                channelResult.setChannelOrderId(result.getTransactionId()); //渠道订单号
                WxPayOrderNotifyV3Result.Payer payer = result.getPayer();
                if (payer != null) {
                    channelResult.setChannelUserId(payer.getOpenid()); //支付用户ID
                }

            }else {
                throw ResponseException.buildText("API_VERSION ERROR");
            }

            JSONObject resJSON = new JSONObject();
            resJSON.put("code", "SUCCESS");
            resJSON.put("message", "成功");

            ResponseEntity okResponse = jsonResp(resJSON);
            channelResult.setResponseEntity(okResponse); //响应数据

            return channelResult;

        } catch (Exception e) {
            log.error("error", e);
            throw ResponseException.buildText("ERROR");
        }
    }

    /**
     * V2接口验证微信支付通知参数
     * @return
     */
    public void verifyWxPayParams(WxPayService wxPayService, WxPayOrderNotifyResult result, PayOrder payOrder) {

        try {
            result.checkResult(wxPayService, WxPayConstants.SignType.MD5, true);

            // 核对金额
            Integer total_fee = result.getTotalFee();   			// 总金额
            long wxPayAmt = new BigDecimal(total_fee).longValue();
            long dbPayAmt = payOrder.getAmount().longValue();
            if (dbPayAmt != wxPayAmt) {
                throw ResponseException.buildText("AMOUNT ERROR");
            }
        } catch (Exception e) {
            throw ResponseException.buildText("ERROR");<commit_msg>解决微信V2接口支付成功重复通知问题<commit_after>/*
 * Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & <EMAIL>).
 * <p>
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/lgpl.html
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jeequan.jeepay.pay.channel.wxpay;

import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.v3.auth.AutoUpdateCertificatesVerifier;
import com.github.binarywang.wxpay.v3.auth.PrivateKeySigner;
import com.github.binarywang.wxpay.v3.auth.WxPayCredentials;
import com.github.binarywang.wxpay.v3.util.PemUtils;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.ResponseException;
import com.jeequan.jeepay.pay.channel.AbstractChannelNoticeService;
import com.jeequan.jeepay.pay.model.MchAppConfigContext;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.service.ConfigContextService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.io.FileInputStream;
import java.math.BigDecimal;
import java.security.PrivateKey;

/*
* 微信回调
*
* @author zhuxiao
* @site https://www.jeepay.vip
* @date 2021/6/8 18:10
*/
@Service
@Slf4j
public class WxpayChannelNoticeService extends AbstractChannelNoticeService {

    @Autowired private ConfigContextService configContextService;

    @Autowired private PayOrderService payOrderService;

    @Override
    public String getIfCode() {
        return CS.IF_CODE.WXPAY;
    }

    @Override
    public MutablePair<String, Object> parseParams(HttpServletRequest request, String urlOrderId, NoticeTypeEnum noticeTypeEnum) {

        try {
            if(StringUtils.isNotBlank(urlOrderId)){     // V3接口回调

                // 获取订单信息
                PayOrder payOrder = payOrderService.getById(urlOrderId);
                if(payOrder == null){
                    throw new BizException("订单不存在");
                }

                //获取支付参数 (缓存数据) 和 商户信息
                MchAppConfigContext mchAppConfigContext = configContextService.getMchAppConfigContext(payOrder.getMchNo(), payOrder.getAppId());
                if(mchAppConfigContext == null){
                    throw new BizException("获取商户信息失败");
                }

                // 验签 && 获取订单回调数据
                WxPayOrderNotifyV3Result.DecryptNotifyResult result = parseOrderNotifyV3Result(request, mchAppConfigContext);

                return MutablePair.of(result.getOutTradeNo(), result);

            } else {     // V2接口回调
                String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
                if(StringUtils.isEmpty(xmlResult)) {
                    return null;
                }

                WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xmlResult);
                String payOrderId = result.getOutTradeNo();
                return MutablePair.of(payOrderId, result);
            }
        } catch (Exception e) {
            log.error("error", e);
            throw ResponseException.buildText("ERROR");
        }
    }

    @Override
    public ChannelRetMsg doNotice(HttpServletRequest request, Object params, PayOrder payOrder, MchAppConfigContext mchAppConfigContext, NoticeTypeEnum noticeTypeEnum) {
        try {
            ChannelRetMsg channelResult = new ChannelRetMsg();
            channelResult.setChannelState(ChannelRetMsg.ChannelState.WAITING); // 默认支付中

            if (CS.PAY_IF_VERSION.WX_V2.equals(mchAppConfigContext.getWxServiceWrapper().getApiVersion())) { // V2
                // 获取回调参数
                WxPayOrderNotifyResult result = (WxPayOrderNotifyResult) params;

                WxPayService wxPayService = mchAppConfigContext.getWxServiceWrapper().getWxPayService();

                // 验证参数
                verifyWxPayParams(wxPayService, result, payOrder);

                channelResult.setChannelOrderId(result.getTransactionId()); //渠道订单号
                channelResult.setChannelUserId(result.getOpenid()); //支付用户ID
                channelResult.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS);

            }else if (CS.PAY_IF_VERSION.WX_V3.equals(mchAppConfigContext.getWxServiceWrapper().getApiVersion())) { // V3
                // 获取回调参数
                WxPayOrderNotifyV3Result.DecryptNotifyResult result = (WxPayOrderNotifyV3Result.DecryptNotifyResult) params;

                // 验证参数
                verifyWxPayParams(result, payOrder);

                String channelState = result.getTradeState();
                if ("SUCCESS".equals(channelState)) {
                    channelResult.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS);
                }else if("CLOSED".equals(channelState)
                        || "REVOKED".equals(channelState)
                        || "PAYERROR".equals(channelState)){  //CLOSED—已关闭， REVOKED—已撤销, PAYERROR--支付失败
                    channelResult.setChannelState(ChannelRetMsg.ChannelState.CONFIRM_FAIL); //支付失败
                }

                channelResult.setChannelOrderId(result.getTransactionId()); //渠道订单号
                WxPayOrderNotifyV3Result.Payer payer = result.getPayer();
                if (payer != null) {
                    channelResult.setChannelUserId(payer.getOpenid()); //支付用户ID
                }

                JSONObject resJSON = new JSONObject();
                resJSON.put("code", "SUCCESS");
                resJSON.put("message", "成功");

                ResponseEntity okResponse = jsonResp(resJSON);
                channelResult.setResponseEntity(okResponse); //响应数据

            }else {
                throw ResponseException.buildText("API_VERSION ERROR");
            }

            return channelResult;

        } catch (Exception e) {
            log.error("error", e);
            throw ResponseException.buildText("ERROR");
        }
    }

    /**
     * V2接口验证微信支付通知参数
     * @return
     */
    public void verifyWxPayParams(WxPayService wxPayService, WxPayOrderNotifyResult result, PayOrder payOrder) {

        try {
            result.checkResult(wxPayService, WxPayConstants.SignType.MD5, true);

            // 核对金额
            Integer total_fee = result.getTotalFee();   			// 总金额
            long wxPayAmt = new BigDecimal(total_fee).longValue();
            long dbPayAmt = payOrder.getAmount().longValue();
            if (dbPayAmt != wxPayAmt) {
                throw ResponseException.buildText("AMOUNT ERROR");
            }
        } catch (Exception e) {
            throw ResponseException.buildText("ERROR");
{'fraction_non_alphanumeric': 0.09545192122650586, 'fraction_numerical': 0.0055808582975174804, 'mean_word_length': 2.698695136417556, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 13, 'https://': 4, 'lorem ipsum': 0, 'www.': 6, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4675017', 'n_tokens_mistral': 4731, 'n_tokens_neox': 4667, 'n_words': 779}