招行接口对接

1、需要让招行提供商户号和收银员
招行提供的商户号和收银员
pom.xml

<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.69</version>
</dependency>

2、加密方式有两种:01:RSA2,02:SM2;第一种有时候请求不了,使用了第二种,SM2代码如下
SM2Util类

public class SM2Util {

    private static final String SM2_KEY_TITLE = "3059301306072a8648ce3d020106082a811ccf5501822d03420004";

    public static final String USER_ID = "1234567812345678";

    public static String sm2Sign(String content, String privateKey) {
        try {
            //init privateKey
            BCECPrivateKey bcecPrivateKey = BCUtil.getPrivatekeyFromD(new BigInteger(privateKey, 16));

            byte[] sign = BCUtil.signSm3WithSm2(content.getBytes(), USER_ID.getBytes(), bcecPrivateKey);

            return encodeBase64(signRawToAsn1(sign));
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    /**
     * @param content
     * @param rawSign
     * @param publicKey
     * @return
     */
    public static boolean sm2Check(String content, String rawSign, String publicKey) {
        try {
            //init PublicKey
            Sm2Vo sm2Vo = parseBase64TRawKey(publicKey);
            if (null == sm2Vo) {
                return false;
            }
            BCECPublicKey bcecPublicKey = BCUtil.getPublickeyFromXY(new BigInteger(sm2Vo.getSm2_x(), 16), new BigInteger(sm2Vo.getSm2_y(), 16));

            byte[] sign = signAsn12Raw(decodeBase64(rawSign));

            return BCUtil.verifySm3WithSm2(content.getBytes(), USER_ID.getBytes(), sign, bcecPublicKey);

        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }

    /**
     * BASE64格式公钥转换为裸公钥
     *
     * @param sm2Key
     * @return
     */
    private static Sm2Vo parseBase64TRawKey(String sm2Key) {
        if (null == sm2Key) {
            return null;
        }

        String sm2_asn1 = Hex.toHexString(decodeBase64(sm2Key));
        if (!sm2_asn1.startsWith(SM2_KEY_TITLE)) {
            return null;
        }

        String sm2_xy = sm2_asn1.substring(SM2_KEY_TITLE.length(), sm2_asn1.length());
        String sm2_x = sm2_xy.substring(0, sm2_xy.length() / 2);
        String sm2_y = sm2_xy.substring(sm2_xy.length() / 2, sm2_xy.length());

        return new Sm2Vo(SM2_KEY_TITLE, sm2_x, sm2_y);
    }

    /**
     * 将字节数组转换为Base64格式字符串
     *
     * @param data
     * @return
     */
    public static String encodeBase64(byte[] data) {
        return Base64.getEncoder().encodeToString(data);
    }

    /**
     * 将Base64格式字符串转为字节数组
     *
     * @param data
     * @return
     */
    public static byte[] decodeBase64(String data) {
        return Base64.getDecoder().decode(data);
    }

    /**
     * 将BC SM2 RAW签名值转化为ASN1格式签名值
     *
     * @param bcCipTxt
     * @return
     * @throws Exception
     */
    private static byte[] signRawToAsn1(byte[] bcCipTxt) throws Exception {

        byte[] netSignCipTxt = new byte[73];

        byte[] signR = new byte[32];
        byte[] signS = new byte[32];

        System.arraycopy(bcCipTxt, 0, signR, 0, 32);
        System.arraycopy(bcCipTxt, 32, signS, 0, 32);

        //signR补位
        int wPos = 4;
        netSignCipTxt[0] = 0x30;
        netSignCipTxt[2] = 0x02;
        if ((signR[0] & 0xFF) >= 128) {
            netSignCipTxt[wPos - 1] = 0x21;
            netSignCipTxt[wPos] = 0x00;
            wPos += 1;
        } else {
            netSignCipTxt[wPos - 1] = 0x20;
        }
        System.arraycopy(signR, 0, netSignCipTxt, wPos, 32);
        wPos += 32;

        //signS补位
        netSignCipTxt[wPos] = 0x02;
        wPos += 1;
        if ((signS[0] & 0xFF) >= 128) {
            netSignCipTxt[wPos] = 0x21;
            wPos += 1;
            netSignCipTxt[wPos] = 0x00;
            wPos += 1;
        } else {
            netSignCipTxt[wPos] = 0x20;
            wPos += 1;
        }
        System.arraycopy(signS, 0, netSignCipTxt, wPos, 32);
        wPos += 32;

        if (70 == wPos) {
            netSignCipTxt[1] = 0x44;
        } else if (71 == wPos) {
            netSignCipTxt[1] = 0x45;
        } else if (72 == wPos) {
            netSignCipTxt[1] = 0x46;
        } else {
            throw new Exception("signRawToAsn1 Error!");
        }

        byte[] resultBytes = new byte[wPos];
        System.arraycopy(netSignCipTxt, 0, resultBytes, 0, wPos);

        return resultBytes;
    }

    /**
     * 将ASN1格式签名值转化为BC SM2 RAW 签名值
     *
     * @param signature Asn1格式签名值
     * @return byte[] Raw签名值
     */
    private static byte[] signAsn12Raw(byte[] signature) throws Exception {

        byte[] resultBytes = new byte[64];

        //截取signR
        int wPos = 3;
        if ((signature[wPos] & 0xFF) == 32) {
            wPos += 1;
        } else if ((signature[wPos] & 0xFF) == 33) {
            wPos += 2;
        } else {
            throw new Exception("signR length Error!");
        }
        System.arraycopy(signature, wPos, resultBytes, 0, 32);
        wPos += 32;

        //截取signS
        wPos += 1;
        if ((signature[wPos] & 0xFF) == 32) {
            wPos += 1;
        } else if ((signature[wPos] & 0xFF) == 33) {
            wPos += 2;
        } else {
            throw new Exception("signS length Error!");
        }
        System.arraycopy(signature, wPos, resultBytes, 32, 32);

        //System.out.println("\nhhh:\n" + ByteToHex(resultBytes));

        return resultBytes;
    }

}

BCUtil类


public class BCUtil {
    private final static int RS_LEN = 32;
    private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    private static ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
    private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());

    static {
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * @param msg
     * @param userId
     * @param privateKey
     * @return r||s,直接拼接byte数组的rs
     */
    public static byte[] signSm3WithSm2(byte[] msg, byte[] userId, PrivateKey privateKey) {
        return rsAsn1ToPlainByteArray(signSm3WithSm2Asn1Rs(msg, userId, privateKey));
    }

    /**
     * @param msg
     * @param userId
     * @param privateKey
     * @return rs in <b>asn1 format</b>
     */
    public static byte[] signSm3WithSm2Asn1Rs(byte[] msg, byte[] userId, PrivateKey privateKey) {
        try {
            SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userId);
            Signature signer = Signature.getInstance("SM3withSM2", "BC");
            signer.setParameter(parameterSpec);
            signer.initSign(privateKey, new SecureRandom());
            signer.update(msg, 0, msg.length);
            byte[] sig = signer.sign();
            return sig;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @param msg
     * @param userId
     * @param rs        r||s,直接拼接byte数组的rs
     * @param publicKey
     * @return
     */
    public static boolean verifySm3WithSm2(byte[] msg, byte[] userId, byte[] rs, PublicKey publicKey) {
        return verifySm3WithSm2Asn1Rs(msg, userId, rsPlainByteArrayToAsn1(rs), publicKey);
    }

    /**
     * @param msg
     * @param userId
     * @param rs        in <b>asn1 format</b>
     * @param publicKey
     * @return
     */
    public static boolean verifySm3WithSm2Asn1Rs(byte[] msg, byte[] userId, byte[] rs, PublicKey publicKey) {
        try {
            SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userId);
            Signature verifier = Signature.getInstance("SM3withSM2", "BC");
            verifier.setParameter(parameterSpec);
            verifier.initVerify(publicKey);
            verifier.update(msg, 0, msg.length);
            return verifier.verify(rs);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * BC的SM3withSM2签名得到的结果的rs是asn1格式的,这个方法转化成直接拼接r||s
     *
     * @param rsDer rs in asn1 format
     * @return sign result in plain byte array
     */
    private static byte[] rsAsn1ToPlainByteArray(byte[] rsDer) {
        ASN1Sequence seq = ASN1Sequence.getInstance(rsDer);
        byte[] r = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue());
        byte[] s = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(1)).getValue());
        byte[] result = new byte[RS_LEN * 2];
        System.arraycopy(r, 0, result, 0, r.length);
        System.arraycopy(s, 0, result, RS_LEN, s.length);
        return result;
    }

    /**
     * BC的SM3withSM2验签需要的rs是asn1格式的,这个方法将直接拼接r||s的字节数组转化成asn1格式
     *
     * @param sign in plain byte array
     * @return rs result in asn1 format
     */
    private static byte[] rsPlainByteArrayToAsn1(byte[] sign) {
        if (sign.length != RS_LEN * 2) throw new RuntimeException("err rs. ");
        BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, RS_LEN));
        BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, RS_LEN, RS_LEN * 2));
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(r));
        v.add(new ASN1Integer(s));
        try {
            return new DERSequence(v).getEncoded("DER");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static byte[] bigIntToFixexLengthBytes(BigInteger rOrS) {
        // for sm2p256v1, n is 00fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123,
        // r and s are the result of mod n, so they should be less than n and have length<=32
        byte[] rs = rOrS.toByteArray();
        if (rs.length == RS_LEN) return rs;
        else if (rs.length == RS_LEN + 1 && rs[0] == 0) return Arrays.copyOfRange(rs, 1, RS_LEN + 1);
        else if (rs.length < RS_LEN) {
            byte[] result = new byte[RS_LEN];
            Arrays.fill(result, (byte) 0);
            System.arraycopy(rs, 0, result, RS_LEN - rs.length, rs.length);
            return result;
        } else {
            throw new RuntimeException("err rs: " + Hex.toHexString(rs));
        }
    }

    public static BCECPrivateKey getPrivatekeyFromD(BigInteger d) {
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecParameterSpec);
        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    public static BCECPublicKey getPublickeyFromXY(BigInteger x, BigInteger y) {
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecParameterSpec);
        return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
    }
}

Sm2Vo类


public class Sm2Vo {

    //标准公钥头
    private String sm2_h;
    //裸公钥X
    private String sm2_x;
    //裸公钥Y
    private String sm2_y;

    public Sm2Vo(String sm2_h, String sm2_x, String sm2_y) {
        this.sm2_h = sm2_h;
        this.sm2_x = sm2_x;
        this.sm2_y = sm2_y;
    }

    public String getSm2_h() {
        return sm2_h;
    }

    public void setSm2_h(String sm2_h) {
        this.sm2_h = sm2_h;
    }

    public String getSm2_x() {
        return sm2_x;
    }

    public void setSm2_x(String sm2_x) {
        this.sm2_x = sm2_x;
    }

    public String getSm2_y() {
        return sm2_y;
    }

    public void setSm2_y(String sm2_y) {
        this.sm2_y = sm2_y;
    }
}

3、提供回调给招行notifyUrl,审核时间要2~3天,记得提前提供

4、业务代码嵌入,代码如下
controller(控制层)

/**
 * Description: 充值记录控制层
 *
 * @author: YJZ
 * @date: 2021/9/10 14:12
 */
@Slf4j
@RestController
@RequestMapping("/rechargeRecord")
public class RechargeRecordController {

    @Autowired
    private RechargeRecordService recordService;

    /**
     * 招商充值
     *
     * @param orderNo 订单号
     * @return 支付二维码
     */
    @ApiOperation("招商充值")
    @PostMapping("/recharge")
    public RetResult recharge(@RequestParam String orderNo) {
        String result = recordService.recharge(orderNo);
        return RetResult.success(result);
    }

    /**
     * 支付成功回调接口
     *
     * @param requestBodyString 银行回调对象
     * @return 返回是否回调成功
     */
    @ApiOperation("支付成功回调接口")
    @PostMapping("/notifyUrl")
    public Map<String, String> notifyUrl(@RequestBody String requestBodyString) {
        log.info(String.format("回调开始,请求报文:%s", requestBodyString));
        Map<String, String> result = recordService.notifyUrl(requestBodyString);
        return result;
    }
}

service(服务层)

@Slf4j
@Service
public class RechargeRecordService {

    @Autowired
    private RechargeRecordMapper rechargeRecordMapper;
    @Autowired
    private RechargeRecordConfig recordConfig;

    public String recharge(String orderNo) {
        // 查询支付信息是否存在
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("ORDER_NO", orderNo);
        RechargeRecord rechargeRecord = rechargeRecordMapper.selectOne(queryWrapper);
        if (rechargeRecord == null) {
            throw new GlobalException("支付信息不存在");
        }
        String appId = recordConfig.getAppId();
        String appSecret = recordConfig.getAppSecret();
        try {
            String signResult = signMethod(rechargeRecord);
            ObjectMapper mapper = new ObjectMapper();
            Map<String, String> signResultMap = mapper.readValue(signResult, Map.class);

            // 组apiSign加密Map
            Map<String, String> apiSign = new TreeMap<>();
            apiSign.put("appid", appId);
            apiSign.put("secret", appSecret);
            apiSign.put("sign", signResultMap.get("sign"));
            apiSign.put("timestamp", "" + System.currentTimeMillis() / 1000);

            // MD5加密
            String MD5Content = SignatureUtil.getSignContent(apiSign);
            String apiSignString = MD5Utils.getMD5Content(MD5Content).toLowerCase();

            // 组request头部Map
            Map<String, String> apiHeader = new HashMap<>();
            apiHeader.put("appid", appId);
            apiHeader.put("timestamp", "" + System.currentTimeMillis() / 1000);
            apiHeader.put("apisign", apiSignString);
            // 发送HTTP post请求
            Map<String, String> response = HttpUtils.postForEntity(recordConfig.getReceivingCodeUrl(), signResult, apiHeader);
            log.info(String.format("二维码-%s二维码报文返回结果:%s", orderNo, JSONObject.toJSONString(response)));
            // 返回结果验签
            Boolean checkResult1 = checkSign(mapper.writeValueAsString(response), recordConfig.getSm2PublicKey());
            if (!checkResult1) {
                throw new GlobalException("验签失败");
            }
            if ("FAIL".equals(response.get("returnCode"))) {
                throw new GlobalException(response.get("respMsg"));
            }
            String bizContent = response.get("biz_content");
            JSONObject object = JSONObject.parseObject(bizContent);
            String qrCode = object.getString("qrCode");
            if (!StringUtils.isEmpty(qrCode)) {
                qrCode.replace("https://qr.95516.com", "http://payment-uat.cs.cmburl.cn");
            }
            return qrCode;
        } catch (Exception e) {
            throw new GlobalException(e.getMessage());
        }
    }

    public Map<String, String> notifyUrl(String requestBodyString) {
        Map<String, String> respData = new HashMap<>();
        //设置响应数据
        respData.put("version", "0.0.1");//版本号,固定为0.0.1(必传)
        respData.put("encoding", "UTF-8");//编码方式,固定为UTF-8(必传)
        respData.put("signMethod", "02");//签名方法,固定为01,表示签名方法为RSA2(必传)
        String orderId = "";
        try {
            //非空校验
            if (requestBodyString == null || "".equals(requestBodyString.trim())) {
                respData.put("returnCode", "FAIL");
                return respData;
            }
            Map<String, String> requestBodyMap = str2Map(requestBodyString);
            Map<String, String> resultMap = requestBodyMap.entrySet().stream().collect(Collectors.toMap(e -> SignatureUtil.decode(e.getKey()), e -> SignatureUtil.decode(e.getValue())));
            if (resultMap == null) {
                respData.put("returnCode", "FAIL");
                return respData;
            }
            //验证签名-使用招行公钥进行验签
            ObjectMapper mapper = new ObjectMapper();
            Boolean flag = checkSign(mapper.writeValueAsString(resultMap), recordConfig.getSm2PublicKey());
            if (!flag) {
                respData.put("returnCode", "FAIL");
                respData.put("respMsg", "验签失败");
                return respData;
            }
            String bizContent = resultMap.get("biz_content");
            JSONObject object = JSONObject.parseObject(bizContent);
            orderId = object.getString("orderId");
            // 查询支付信息是否存在
            QueryWrapper queryWrapper = new QueryWrapper();
            queryWrapper.eq("ORDER_NO", orderId);
            RechargeRecord rechargeRecord = rechargeRecordMapper.selectOne(queryWrapper);
            if (rechargeRecord == null) {
                respData.put("returnCode", "FAIL");
                respData.put("respCode", "FAIL");
                respData.put("respMsg", "支付信息不存在");
                return respData;
            }
            if (rechargeRecord.getBlStatus() == 1) {
                respData.put("returnCode", "SUCCESS");
                respData.put("respCode", "SUCCESS");
                respData.put("respMsg", "该订单已支付成功");
                return respData;
            }
            // 3、验证充值金额是否相同
            BigDecimal txnAmt = new BigDecimal(object.getString("txnAmt"));
            txnAmt = txnAmt.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
            BigDecimal arrivalAmount = rechargeRecord.getArrivalAmount();
            if (txnAmt.compareTo(arrivalAmount) == -1) {
                respData.put("returnCode", "FAIL");
                respData.put("respCode", "FAIL");
                respData.put("respMsg", "支付金额与实际支付金额不符");
                return respData;
            }
            // 4、更新数据
            rechargeRecord.setBlStatus(1);
            rechargeRecord.setTradeNo(object.getString("cmbOrderId"));
            Integer result = rechargeRecordMapper.updateById(rechargeRecord);
            if (result <= 0) {
                respData.put("returnCode", "FAIL");
                respData.put("respCode", "FAIL");
                respData.put("respMsg", "更新失败");
                return respData;
            }
            respData.put("returnCode", "SUCCESS");
            respData.put("respCode", "SUCCESS");
            respData.put("respMsg", "更新成功");
            //对待加签内容进行排序拼接
            String signContent = SignatureUtil.getSignContent(respData);
            //加签-使用商户私钥加签
            String sign = SM2Util.sm2Sign(signContent, recordConfig.getSm2PrivateKey());
            respData.put("sign", sign);
            return respData;
        } catch (Exception e) {
            log.error(String.format("回调-单号%s;回调失败;失败原因:%s", orderId, e.getMessage()));
            respData.put("returnCode", "FAIL");
            return respData;
        }
    }

    private String signMethod(RechargeRecord rechargeRecord) {
        Map<String, String> requestPublicParams = new TreeMap<>();
        String requestStr = "";
        try {
            BigDecimal arrivalAmount = rechargeRecord.getArrivalAmount();
            BigDecimal txnAmt = arrivalAmount.multiply(new BigDecimal(100));// 转分

            //公共请求参数
            requestPublicParams.put("version", "0.0.1");    //版本号,固定为0.0.1(必传字段)
            requestPublicParams.put("encoding", "UTF-8");   //编码方式,固定为UTF-8(必传)
            requestPublicParams.put("signMethod", "02");    //签名方法,固定为01,表示签名方式为RSA2(必传)
            //业务要素
            Map<String, String> requestTransactionParams = new HashMap<>();
            requestTransactionParams.put("body", "聚合支付测试");   //支付描述
            requestTransactionParams.put("currencyCode", "156");    //交易币种,默认156,目前只支持人民币(156)
            requestTransactionParams.put("merId", recordConfig.getMerId());   //商户号(必传)
            requestTransactionParams.put("notifyUrl", recordConfig.getNotifyUrl());  //交易通知地址(必传)
            requestTransactionParams.put("orderId", rechargeRecord.getOrderNo()); //商户订单号(必传)
            requestTransactionParams.put("payValidTime", "1200"); //支付有效时间
            requestTransactionParams.put("txnAmt", txnAmt.stripTrailingZeros().toPlainString());  //交易金额,单位为分(必传)
            requestTransactionParams.put("userId", "N003399854");   //收银员
            requestTransactionParams.put("tradeScene", "OFFLINE");   //支付场景
            ObjectMapper mapper = new ObjectMapper();
            requestPublicParams.put("biz_content", mapper.writeValueAsString(requestTransactionParams));

            //私钥
            String privateKey = recordConfig.getSm2PrivateKey();
            //对待加签内容进行排序拼接
            String signContent = SignatureUtil.getSignContent(requestPublicParams);
            //加签
            String sign = SM2Util.sm2Sign(signContent, privateKey);
            requestPublicParams.put("sign", sign);

            requestStr = mapper.writeValueAsString(requestPublicParams);
            return requestStr;
        } catch (Exception e) {
            log.error(String.format("二维码-加签发生异常!单号:%s", rechargeRecord.getOrderNo()));
            e.printStackTrace();
            return requestStr;
        }
    }

    public static Boolean checkSign(String string, String publicKey) {
        try {
            //验签
            ObjectMapper objectMapper = new ObjectMapper();
            Map<String, String> responseBodyMap = objectMapper.readValue(string, Map.class);
            String sign = responseBodyMap.remove("sign");
            String contentStr = SignatureUtil.getSignContent(responseBodyMap);
            boolean result = SM2Util.sm2Check(contentStr, sign, publicKey);
            if (result) {
                log.info("报文验签成功!");
            } else {
                log.error("报文验签失败!");
            }
            return result;
        } catch (Exception e) {
            log.error("验签发生异常!");
            e.printStackTrace();
            return false;
        }
    }

    private Map<String, String> str2Map(String str) {
        Map<String, String> result = new HashMap<>();
        String[] results = str.split("&");
        if (results != null && results.length > 0) {
            for (int var = 0; var < results.length; ++var) {
                String pair = results[var];
                String[] kv = pair.split("=", 2);
                if (kv != null && kv.length == 2) {
                    result.put(kv[0], kv[1]);
                }
            }
        }
        return result;
    }

}

5、定时任务

/**
 * 定时任务推送模板消息(物流轨迹)
 *
 * @author langao_q
 * @since 2021-03-01 15:27
 */
@Slf4j
@Component
public class RechargeRecordTask {

    @Autowired
    private RechargeRecordMapper rechargeRecordMapper;
    @Autowired
    private RechargeRecordConfig recordConfig;

    /**
     * 支付结果查询
     * 1.先查出所有数据
     * 2.回写支付信息
     */
    @Scheduled(cron = "0 */1 * * * ?")
    public void paymentResultQuery() {
        List<RechargeRecord> rechargeRecordList = rechargeRecordMapper.getRechargeRecordList();
        if (rechargeRecordList.size() == 0) {
            log.error("没有拉到支付信息数据");
            return;
        }
        for (RechargeRecord record : rechargeRecordList) {
            try {
                String appId = recordConfig.getAppId();
                String appSecret = recordConfig.getAppSecret();
                String signResult = signMethod(record);
                ObjectMapper mapper = new ObjectMapper();
                Map<String, String> signResultMap = mapper.readValue(signResult, Map.class);
                // 组apiSign加密Map
                Map<String, String> apiSign = new TreeMap<>();
                apiSign.put("appid", appId);
                apiSign.put("secret", appSecret);
                apiSign.put("sign", signResultMap.get("sign"));
                apiSign.put("timestamp", "" + System.currentTimeMillis() / 1000);

                // MD5加密
                String MD5Content = SignatureUtil.getSignContent(apiSign);
                String apiSignString = MD5Utils.getMD5Content(MD5Content).toLowerCase();

                // 组request头部Map
                Map<String, String> apiHeader = new HashMap<>();
                apiHeader.put("appid", appId);
                apiHeader.put("timestamp", "" + System.currentTimeMillis() / 1000);
                apiHeader.put("apisign", apiSignString);
                // 发送HTTP post请求
                Map<String, String> response = HttpUtils.postForEntity(recordConfig.getPaymentResultUrl(), signResult, apiHeader);
                log.info(String.format("支付结果查询返回结果:%s;单号:%s", JSONObject.toJSONString(response), record.getOrderNo()));
                // 返回结果验签
                Boolean checkResult1 = RechargeRecordService.checkSign(mapper.writeValueAsString(response), recordConfig.getSm2PublicKey());
                if (!checkResult1) {
                    log.error("验签失败");
                    return;
                }
                if ("FAIL".equals(response.get("returnCode"))) {
                    log.error(response.get("respMsg"));
                    return;
                }
                String bizContent = response.get("biz_content");
                JSONObject object = JSONObject.parseObject(bizContent);

                String respCode = response.get("respCode");
                String tradeState = object.getString("tradeState");
                if ("SUCCESS".equals(respCode) && "S".equals(tradeState)) {// 交易是否成功
                    BigDecimal txnAmt = new BigDecimal(object.getString("txnAmt"));
                    txnAmt = txnAmt.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
                    BigDecimal arrivalAmount = record.getArrivalAmount();
                    if (txnAmt.compareTo(arrivalAmount) == -1) {
                        log.error(String.format("定时任务-支付金额与实际支付金额不符;单号:%s", record.getOrderNo()));
                        return;
                    } else {
                        record.setBlStatus(1);
                        record.setTradeNo(object.getString("cmbOrderId"));
                        Integer result = rechargeRecordMapper.updateById(record);
                        if (result > 0) {
                            log.info(String.format("定时任务-处理成功;单号:%s", record.getOrderNo()));
                        } else {
                            log.error(String.format("定时任务-处理失败;单号:%s", record.getOrderNo()));
                        }
                    }
                }
            } catch (Exception e) {
                log.error(String.format("定时任务-支付结果查询异常;单号:%s, 原因:%s", record.getOrderNo(), e.getMessage()));
                return;
            }
        }
    }

    private String signMethod(RechargeRecord rechargeRecord) {
        Map<String, String> requestPublicParams = new TreeMap<>();
        String requestStr = "";
        try {
            //公共请求参数
            requestPublicParams.put("version", "0.0.1");    //版本号,固定为0.0.1(必传字段)
            requestPublicParams.put("encoding", "UTF-8");   //编码方式,固定为UTF-8(必传)
            requestPublicParams.put("signMethod", "02");    //签名方法,固定为01,表示签名方式为RSA2(必传)
            //业务要素
            Map<String, String> requestTransactionParams = new HashMap<>();
            requestTransactionParams.put("merId", recordConfig.getMerId());   //商户号(必传)
            requestTransactionParams.put("orderId", rechargeRecord.getOrderNo()); //商户订单号(必传)
            requestTransactionParams.put("userId", "N003399854");   //收银员
            ObjectMapper mapper = new ObjectMapper();
            requestPublicParams.put("biz_content", mapper.writeValueAsString(requestTransactionParams));

            //私钥
            String privateKey = recordConfig.getSm2PrivateKey();
            //对待加签内容进行排序拼接
            String signContent = SignatureUtil.getSignContent(requestPublicParams);
            //加签
            String sign = SM2Util.sm2Sign(signContent, privateKey);
            requestPublicParams.put("sign", sign);

            requestStr = mapper.writeValueAsString(requestPublicParams);
            return requestStr;
        } catch (Exception e) {
            log.error(String.format("定时任务-加签发生异常!单号:%s;原因:%s", rechargeRecord.getOrderNo(), e.getMessage()));
            return requestStr;
        }
    }

}

HttpUtils类


@Slf4j
public class HttpUtils {

    public static Map<String, String> postForEntity(String url,String requestBody, Map<String, String> apiHeader){
        RestTemplate client = getRestTemplate();
        client.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
        HttpHeaders headers = getHttpHeaders();

        // 以json的方式提交
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.add("appid", apiHeader.get("appid"));
        headers.add("timestamp", apiHeader.get("timestamp"));
        headers.add("apisign", apiHeader.get("apisign"));

        // 将请求头部和参数合成一个请求
        HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, headers);
        // 执行HTTP请求
        Map<String,String> response = client.postForEntity(url,requestEntity,Map.class).getBody();

        return response;
    }

    public static RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

    public static HttpHeaders getHttpHeaders(){
        return new HttpHeaders();
    }

}

以上便是对接招行二维码申请、支付结果回调、支付结果反查的全部内容

如有侵权联系我删除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值