package cc.mrbird.febs.pay.service.impl; import cc.mrbird.febs.common.enumerates.FlowTypeEnum; import cc.mrbird.febs.common.enumerates.OrderStatusEnum; import cc.mrbird.febs.mall.entity.MallMqRecord; import cc.mrbird.febs.mall.entity.MallOrderInfo; import cc.mrbird.febs.mall.mapper.MallMqRecordMapper; import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper; import cc.mrbird.febs.mall.service.ICommonService; import cc.mrbird.febs.pay.model.LaKaLaBasicReqDate; import cc.mrbird.febs.pay.model.LaKaLaCreateOrderReqDate; import cc.mrbird.febs.pay.model.LaKaLaQueryOrderReqDate; import cc.mrbird.febs.pay.service.LaKaLaService; import cc.mrbird.febs.rabbit.producter.AgentProducer; import cn.hutool.core.date.DateUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.*; import java.security.*; import java.security.cert.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Date; import java.util.HashMap; @Slf4j @Service public class LaKaLaServiceImpl implements LaKaLaService { @Autowired private ICommonService commonService; @Autowired private MallOrderInfoMapper orderInfoMapper; @Autowired private MallMqRecordMapper mallMqRecordMapper; @Autowired private AgentProducer agentProducer; @Autowired ResourceLoader resourceLoader; @Value("${spring.profiles.active}") private String active; /** * 字符集固定 utf-8 */ public static final String ENCODING = "utf-8"; /** * API schema ,固定 LKLAPI-SHA256withRSA */ public final static String SCHEMA = "LKLAPI-SHA256withRSA"; /** * 接入 appid */ public static final String appid = "OP00000418"; public static final String appid_prd = "OP00000671"; /** * 商户证书序列号,和商户私钥对应 */ public static final String mchSerialNo = "01848940fd41"; public static final String mchSerialNo_prd = "018804e5ecd9"; /** * 商户号 */ public static final String merchant_no = "8222900701107M5"; public static final String merchant_no_prd = "822301053110LXS"; /** * 商户证书私钥,用于请求签名 */ public static final String merchantPrivateKeyPathName = "classpath:lkl/api_private_key.pem"; public static final String merchantPrivateKeyPathName_prd = "classpath:lkl/prd/api_private_key.pem"; /** * 拉卡拉公钥证书,用于response签名验证,务必区分测试环境和生产环境证书 */ public static final String lklCertificatePathName = "classpath:lkl/lkl-apigw-v2.cer"; public static final String lklCertificatePathName_prd = "classpath:lkl/prd/lkl-apigw-v1.cer"; /** * api请求地址 */ public final static String apiUrlCreate = "https://test.wsmsd.cn/sit/api/v3/ccss/counter/order/create"; public final static String apiUrlCreate_prd = "https://s2.lakala.com/api/v3/ccss/counter/order/create"; public final static String apiUrlQuery = "https://test.wsmsd.cn/sit/api/v3/ccss/counter/order/query"; public final static String apiUrlQuery_prd = "https://s2.lakala.com/api/v3/ccss/counter/order/query"; /** * 支付回调地址 */ public final static String notify_url = "http://120.27.238.55:8185/api/pay/laKaLaPayCallBack"; public final static String notify_url_prd = "https://hlm.meiao.biz/api/pay/laKaLaPayCallBack"; private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final SecureRandom RANDOM = new SecureRandom(); @Override public String verifyCreateOrder(LaKaLaCreateOrderReqDate laKaLaCreateOrderReqDateInfo) { /** * {"req_time":"20221026100323", * "version":"1.0", * "req_data":{ * "out_order_no":"GHSNVDY8033038232443530", * "merchant_no":"8222900701107M5", * "vpos_id":"627872798014865408", * "total_amount":1, * "order_efficient_time":"20221118220823", * "notify_url":"", * "support_cancel":0, * "support_refund":1, * "support_repeat_pay":1, * "order_info":"测试"}} */ HashMap objectObjectHashMap = new HashMap<>(); String yyyyMMddHHmmss = DateUtil.format(new Date(), "yyyyMMddHHmmss"); //过期时间 String order_efficient_time = DateUtil.format(DateUtil.offsetHour(new Date(),1), "yyyyMMddHHmmss"); LaKaLaCreateOrderReqDate laKaLaCreateOrderReqDate = new LaKaLaCreateOrderReqDate(); laKaLaCreateOrderReqDate.setOut_order_no(laKaLaCreateOrderReqDateInfo.getOut_order_no()); laKaLaCreateOrderReqDate.setTotal_amount(laKaLaCreateOrderReqDateInfo.getTotal_amount()); laKaLaCreateOrderReqDate.setOrder_efficient_time(order_efficient_time); if ("dev".equals(active) || "test".equals(active)) { laKaLaCreateOrderReqDate.setNotify_url(notify_url); laKaLaCreateOrderReqDate.setMerchant_no(merchant_no); }else{ laKaLaCreateOrderReqDate.setNotify_url(notify_url_prd); laKaLaCreateOrderReqDate.setMerchant_no(merchant_no_prd); } laKaLaCreateOrderReqDate.setSupport_cancel(0); laKaLaCreateOrderReqDate.setSupport_refund(1); laKaLaCreateOrderReqDate.setSupport_repeat_pay(1); laKaLaCreateOrderReqDate.setOrder_info(laKaLaCreateOrderReqDateInfo.getOrder_info()); LaKaLaBasicReqDate laKaLaBasicReqDate = new LaKaLaBasicReqDate(); laKaLaBasicReqDate.setReq_time(yyyyMMddHHmmss); laKaLaBasicReqDate.setVersion("3.0"); JSONObject jsonObject = JSONUtil.parseObj(laKaLaCreateOrderReqDate); laKaLaBasicReqDate.setReq_data(jsonObject); try { String body = JSONUtil.parseObj(laKaLaBasicReqDate).toString(); log.info("LKL创建请求Body,{}",body); String authorization = getAuthorization(body); String apiUrlCreateHttp = ""; if ("dev".equals(active) || "test".equals(active)) { apiUrlCreateHttp = apiUrlCreate; }else{ apiUrlCreateHttp = apiUrlCreate_prd; } HttpResponse response = post(apiUrlCreateHttp , body, authorization); if (response.getStatusLine().getStatusCode() != 200) { objectObjectHashMap.put("code","false"); return JSONUtil.parseObj(objectObjectHashMap).toString(); } String appid = getHeadValue(response, "Lklapi-Appid"); String lklapiSerial = getHeadValue(response, "Lklapi-Serial"); String timestamp = getHeadValue(response, "Lklapi-Timestamp"); String nonce = getHeadValue(response, "Lklapi-Nonce"); String signature = getHeadValue(response, "Lklapi-Signature"); String responseStr = IOUtils.toString(response.getEntity().getContent(), ENCODING); System.out.println("responseStr " + responseStr); log.info("LKL创建请求返回的responseStr,{}",responseStr); String source = appid + "\n" + lklapiSerial + "\n" + timestamp + "\n" + nonce + "\n" + responseStr + "\n"; System.out.println("source " + source); String lklCertificatePathNameStr = ""; if ("dev".equals(active) || "test".equals(active)) { lklCertificatePathNameStr = lklCertificatePathName; }else{ lklCertificatePathNameStr = lklCertificatePathName_prd; } InputStream fileInputStream = resourceLoader.getResource(lklCertificatePathNameStr).getInputStream(); X509Certificate lklCertificate = loadCertificate(fileInputStream); boolean verify = verify(lklCertificate, source.getBytes(ENCODING), signature); if (verify) { System.out.println("验签成功"); JSONObject parseObj = JSONUtil.parseObj(responseStr); String code = parseObj.get("code").toString(); if("000000".equals(code)){ Object respData = parseObj.get("resp_data"); JSONObject respDataChild = JSONUtil.parseObj(respData); String counterUrl = respDataChild.get("counter_url").toString(); String payOrderNo = respDataChild.get("pay_order_no").toString(); objectObjectHashMap.put("code","true"); objectObjectHashMap.put("counterUrl",counterUrl); objectObjectHashMap.put("payOrderNo",payOrderNo); }else{ objectObjectHashMap.put("code","false"); } return JSONUtil.parseObj(objectObjectHashMap).toString(); } else { System.err.println("验签失败"); objectObjectHashMap.put("code","false"); return JSONUtil.parseObj(objectObjectHashMap).toString(); } } catch (Exception e) { e.printStackTrace(); } objectObjectHashMap.put("code","false"); return JSONUtil.parseObj(objectObjectHashMap).toString(); } @Override public String payCallback(JSONObject jsonObject) { boolean flag = false; String orderStatus = jsonObject.get("order_status").toString(); if(2 != Integer.parseInt(orderStatus)){ return "FAIL"; } String orderNo = jsonObject.get("out_order_no").toString(); /* {"req_time":"1660205634885","version":"3.0","req_data":{"out_order_no":"GHSNVDY8033038232443530","merchant_no":"8222900701107M5"}} */ LaKaLaQueryOrderReqDate laKaLaQueryOrderReqDate = new LaKaLaQueryOrderReqDate(); laKaLaQueryOrderReqDate.setOut_order_no(orderNo); if ("dev".equals(active) || "test".equals(active)) { laKaLaQueryOrderReqDate.setMerchant_no(merchant_no); }else{ laKaLaQueryOrderReqDate.setMerchant_no(merchant_no_prd); } String yyyyMMddHHmmss = DateUtil.format(new Date(), "yyyyMMddHHmmss"); LaKaLaBasicReqDate laKaLaBasicReqDate = new LaKaLaBasicReqDate(); laKaLaBasicReqDate.setReq_time(yyyyMMddHHmmss); laKaLaBasicReqDate.setVersion("3.0"); JSONObject jsonObjectQuery = JSONUtil.parseObj(laKaLaQueryOrderReqDate); laKaLaBasicReqDate.setReq_data(jsonObjectQuery); try { String body = JSONUtil.parseObj(laKaLaBasicReqDate).toString(); log.info("LKL查询请求Body,{}",body); String authorization = getAuthorization(body); String apiUrlCreateHttp = ""; if ("dev".equals(active) || "test".equals(active)) { apiUrlCreateHttp = apiUrlQuery; }else{ apiUrlCreateHttp = apiUrlQuery_prd; } HttpResponse response = post(apiUrlCreateHttp , body, authorization); if (response.getStatusLine().getStatusCode() != 200) { return "FAIL"; } String appid = getHeadValue(response, "Lklapi-Appid"); String lklapiSerial = getHeadValue(response, "Lklapi-Serial"); String timestamp = getHeadValue(response, "Lklapi-Timestamp"); String nonce = getHeadValue(response, "Lklapi-Nonce"); String signature = getHeadValue(response, "Lklapi-Signature"); String responseStr = IOUtils.toString(response.getEntity().getContent(), ENCODING); System.out.println("responseStr " + responseStr); log.info("LKL查询返回的responseStr,{}",responseStr); String source = appid + "\n" + lklapiSerial + "\n" + timestamp + "\n" + nonce + "\n" + responseStr + "\n"; System.out.println("source " + source); String lklCertificatePathNameStr = ""; if ("dev".equals(active) || "test".equals(active)) { lklCertificatePathNameStr = lklCertificatePathName; }else{ lklCertificatePathNameStr = lklCertificatePathName_prd; } InputStream fileInputStream = resourceLoader.getResource(lklCertificatePathNameStr).getInputStream(); X509Certificate lklCertificate = loadCertificate(fileInputStream); boolean verify = verify(lklCertificate, source.getBytes(ENCODING), signature); if (verify) { System.out.println("验签成功"); JSONObject parseObj = JSONUtil.parseObj(responseStr); String code = parseObj.get("code").toString(); if("000000".equals(code)){ Object respData = parseObj.get("resp_data"); JSONObject respDataChild = JSONUtil.parseObj(respData); String orderStatusQuery = respDataChild.get("order_status").toString(); if(2 != Integer.parseInt(orderStatusQuery)){ return "FAIL"; } String tradeStatus = respDataChild.getJSONArray("order_trade_info_list").getJSONObject(0).get("trade_status").toString(); if(!"S".equals(tradeStatus)){ return "FAIL"; } flag = true; } } } catch (Exception e) { e.printStackTrace(); } if(flag){ LambdaQueryWrapper query = new LambdaQueryWrapper<>(); query.eq(MallOrderInfo::getOrderNo, orderNo); MallOrderInfo orderInfo = this.orderInfoMapper.selectOne(query); if ("1".equals(orderInfo.getPayResult())) { return "SUCCESS"; } orderInfo.setStatus(OrderStatusEnum.WAIT_SHIPPING.getValue()); orderInfo.setPayResult("1"); orderInfo.setPayTime(new Date()); String payOrderNo = jsonObject.get("pay_order_no").toString(); orderInfo.setPayTradeNo(payOrderNo); orderInfoMapper.updateById(orderInfo); commonService.changeWallet(orderInfo.getId(), FlowTypeEnum.WECHAT.getValue()); /** * 插入一条待处理记录 * mq处理之后,更新状态 */ MallMqRecord mallMqRecord = new MallMqRecord(); mallMqRecord.setOrderId(orderInfo.getId()); mallMqRecord.setState(2); mallMqRecord.setRetryTimes(2); mallMqRecordMapper.insert(mallMqRecord); //发送补贴消息 agentProducer.sendPerkMoneyMsg(orderInfo.getId()); //发送代理自动升级消息 agentProducer.sendAutoLevelUpMsg(orderInfo.getMemberId()); return "SUCCESS"; }else{ return "FAIL"; } } public static void main(String[] args) { String s = "{\"code\":\"000000\"," + "\"msg\":\"操作成功\"," + "\"resp_time\":\"20230510154634\"," + "\"resp_data\":{\"pay_order_no\":\"23051011012001101011000969911\"," + "\"out_order_no\":\"2023051015455302379\"," + "\"channel_id\":\"95\"," + "\"trans_merchant_no\":\"8222900701107M5\"," + "\"trans_term_no\":\"A1062976\"," + "\"merchant_no\":\"8222900701107M5\"," + "\"term_no\":\"A1062976\"," + "\"order_status\":\"2\"," + "\"order_info\":\"礼包一\"," + "\"total_amount\":1," + "\"order_create_time\":\"20230510154556\"," + "\"order_efficient_time\":\"20230510164555\"," + "\"settle_type\":\"0\"," + "\"split_mark\":\"\"," + "\"counter_param\":null," + "\"counter_remark\":null," + "\"busi_type_param\":\"\"," + "\"sgn_info\":[]," + "\"goods_mark\":\"\"," + "\"goods_field\":\"\"," + "\"order_trade_info_list\":[{\"trade_no\":\"2023051066210308370172\"," + "\"log_No\":\"66210308370172\"," + "\"trade_ref_no\":\"\"," + "\"trade_type\":\"PAY\"," + "\"trade_status\":\"S\"," + "\"trade_amount\":1,\"payer_amount\":1,\"user_id1\":\"oLvoQ5W-MMaepM5fIf2XGOkatAzQ\",\"user_id2\":\"oUpF8uPxTeOlCA4eunIiiK6vZ9IA\",\"busi_type\":\"SCPAY\",\"trade_time\":\"20230510154610\",\"acc_trade_no\":\"4200001884202305101607494108\",\"payer_account_no\":\"\",\"payer_name\":\"\",\"payer_account_bank\":\"\",\"acc_type\":\"99\",\"pay_mode\":\"WECHAT\",\"client_batch_no\":\"\",\"client_seq_no\":\"\",\"settle_merchant_no\":\"8222900701107M5\",\"settle_term_no\":\"A1062976\",\"origin_trade_no\":\"\",\"auth_code\":\"\",\"bank_type\":\"OTHERS\"}]}}"; JSONObject parseObj = JSONUtil.parseObj(s); String code = parseObj.get("code").toString(); if("000000".equals(code)){ Object respData = parseObj.get("resp_data"); JSONObject respDataChild = JSONUtil.parseObj(respData); System.out.println(respDataChild); // String orderStatusQuery = respDataChild.get("order_status").toString(); String orderStatusQuery = respDataChild.get("order_trade_info_list").toString(); System.out.println(orderStatusQuery); String tradeStatus = respDataChild.getJSONArray("order_trade_info_list").getJSONObject(0).get("trade_status").toString(); System.out.println(tradeStatus); } } public HttpResponse post(String url, String message, String authorization) throws Exception { SSLContext ctx = SSLContext.getInstance("TLS"); X509TrustManager tm = new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] xcs, String str) {} public void checkServerTrusted(X509Certificate[] xcs, String str) {} }; HttpClient http = new DefaultHttpClient(); ClientConnectionManager ccm = http.getConnectionManager(); ctx.init(null, new TrustManager[] { tm }, null); SSLSocketFactory ssf = new SSLSocketFactory(ctx); ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); SchemeRegistry registry = ccm.getSchemeRegistry(); registry.register(new Scheme("https", ssf,443)); HttpPost post = new HttpPost(url); StringEntity myEntity = new StringEntity(message, ENCODING); post.setEntity(myEntity); post.setHeader("Authorization", SCHEMA + " " + authorization); post.setHeader("Accept", "application/json"); post.setHeader("Content-Type", "application/json"); return http.execute(post); } protected long generateTimestamp() { return System.currentTimeMillis() / 1000; } protected String generateNonceStr() { char[] nonceChars = new char[32]; for (int index = 0; index < nonceChars.length; ++index) { nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length())); } return new String(nonceChars); } public final String getAuthorization(String body) throws IOException { String nonceStr = generateNonceStr(); long timestamp = generateTimestamp(); String appidStr = ""; String mchSerialNoStr = ""; if ("dev".equals(active) || "test".equals(active)) { appidStr = appid; mchSerialNoStr = mchSerialNo; }else{ appidStr = appid_prd; mchSerialNoStr = mchSerialNo_prd; } String message = appidStr + "\n" + mchSerialNoStr + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n"; System.out.println("getToken message : " + message); String merchantPrivateKeyPathNameStr = ""; if ("dev".equals(active) || "test".equals(active)) { merchantPrivateKeyPathNameStr = merchantPrivateKeyPathName; }else{ merchantPrivateKeyPathNameStr = merchantPrivateKeyPathName_prd; } InputStream fileInputStream = resourceLoader.getResource(merchantPrivateKeyPathNameStr).getInputStream(); PrivateKey merchantPrivateKey = loadPrivateKey(fileInputStream); String signature = this.sign(message.getBytes(ENCODING), merchantPrivateKey); String authorization = "appid=\"" + appidStr + "\"," + "serial_no=\"" + mchSerialNoStr + "\"," + "timestamp=\"" + timestamp + "\"," + "nonce_str=\"" + nonceStr + "\"," + "signature=\"" + signature + "\""; System.out.println("authorization message :" + authorization); return authorization; } public String sign(byte[] message, PrivateKey privateKey) { try { Signature sign = Signature.getInstance("SHA256withRSA"); sign.initSign(privateKey); sign.update(message); return new String(Base64.encodeBase64(sign.sign())); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("当前Java环境不支持SHA256withRSA", e); } catch (SignatureException e) { throw new RuntimeException("签名计算失败", e); } catch (InvalidKeyException e) { throw new RuntimeException("无效的私钥", e); } } private static String getHeadValue(HttpResponse response, String key) { return (response.containsHeader(key)) ? response.getFirstHeader(key).getValue() : ""; } private static boolean verify(X509Certificate certificate, byte[] message, String signature) { try { Signature sign = Signature.getInstance("SHA256withRSA"); sign.initVerify(certificate); sign.update(message); byte[] signatureB = Base64.decodeBase64(signature); return sign.verify(signatureB); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("当前Java环境不支持SHA256withRSA", e); } catch (SignatureException e) { throw new RuntimeException("签名验证过程发生了错误", e); } catch (InvalidKeyException e) { throw new RuntimeException("无效的证书", e); } } 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.decodeBase64(privateKey))); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("当前Java环境不支持RSA", e); } catch (InvalidKeySpecException e) { throw new RuntimeException("无效的密钥格式"); } catch (IOException e) { throw new RuntimeException("无效的密钥"); } } 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 RuntimeException("证书已过期", e); } catch (CertificateNotYetValidException e) { throw new RuntimeException("证书尚未生效", e); } catch (CertificateException e) { throw new RuntimeException("无效的证书", e); } } }