123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- package com.ylx.web.controller.massage;
- import cn.hutool.core.date.DatePattern;
- import cn.hutool.core.date.DateUtil;
- import cn.hutool.core.io.file.FileWriter;
- import cn.hutool.core.util.StrUtil;
- import cn.hutool.http.ContentType;
- import cn.hutool.json.JSONArray;
- import cn.hutool.json.JSONObject;
- import cn.hutool.json.JSONUtil;
- import com.ijpay.core.IJPayHttpResponse;
- import com.ijpay.core.enums.AuthTypeEnum;
- import com.ijpay.core.enums.RequestMethodEnum;
- import com.ijpay.core.kit.AesUtil;
- import com.ijpay.core.kit.HttpKit;
- import com.ijpay.core.kit.PayKit;
- import com.ijpay.core.kit.WxPayKit;
- import com.ijpay.core.utils.DateTimeZoneUtil;
- import com.ijpay.wxpay.WxPayApi;
- import com.ijpay.wxpay.WxPayApiConfigKit;
- import com.ijpay.wxpay.enums.WxDomainEnum;
- import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
- import com.ijpay.wxpay.enums.v3.CertAlgorithmTypeEnum;
- import com.ijpay.wxpay.enums.v3.TransferApiEnum;
- import com.ijpay.wxpay.model.v3.*;
- import com.ylx.common.config.WxPayConfig;
- import com.ylx.common.core.domain.R;
- import com.ylx.massage.domain.TRecharge;
- import com.ylx.massage.enums.BillTypeEnum;
- import com.ylx.massage.service.RefundVoucherService;
- import com.ylx.massage.service.TOrderService;
- import com.ylx.massage.service.TRechargeService;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.*;
- import javax.annotation.Resource;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.ByteArrayInputStream;
- import java.math.BigDecimal;
- import java.nio.charset.StandardCharsets;
- import java.security.cert.X509Certificate;
- import java.util.*;
- import static com.ylx.common.constant.HttpStatus.SUCCESS;
- /**
- * @author jianlong
- * @date 2024-04-03 15:27
- */
- @RestController
- @Slf4j
- @RequestMapping("/wx/pay")
- @Api(tags = {"微信支付"})
- public class PayController {
- @Autowired
- private WxPayConfig wxPayProperties;
- @Resource
- private TRechargeService rechargeService;
- @Resource
- private TOrderService orderService;
- @Resource
- private RefundVoucherService refundVoucherService;
- String serialNo;
- String platSerialNo;
- /**
- * 小程序微信支付的第一步,统一下单
- */
- @PostMapping("/pay")
- @ApiOperation("AIPV3微信支付充值")
- public R createUnifiedOrder(@RequestBody TRecharge recharge) throws Exception {
- TRecharge rechargeResp = rechargeService.recharge(recharge);
- return rechargeService.getPay(rechargeResp.getRechargeNo(), recharge.getdMoney(), recharge.getcOpenId(), BillTypeEnum.RECHARGE.getInfo(), BillTypeEnum.RECHARGE.getCode().toString());
- }
- public R<String> getPay(String setOutTradeNo, BigDecimal amount, String openId, String description) throws Exception {
- String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
- UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
- .setAppid(wxPayProperties.getAppId())
- .setMchid(wxPayProperties.getMchId())
- //商品描述
- .setDescription(description)
- //订单号
- .setOut_trade_no(setOutTradeNo)
- //交易结束时间
- .setTime_expire(timeExpire)
- //附加数据
- .setAttach("夜来香")
- //通知地址异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http
- //示例值:https://www.weixin.qq.com/wxpay/pay.php
- .setNotify_url(wxPayProperties.getNotifyUrl())
- //支付金额以分为单位
- .setAmount(new Amount().setTotal(amount.multiply(new BigDecimal(100)).intValue()))
- //交易人
- .setPayer(new Payer().setOpenid(openId));
- log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
- IJPayHttpResponse response = WxPayApi.v3(
- RequestMethodEnum.POST,
- WxDomainEnum.CHINA.toString(),
- BasePayApiEnum.JS_API_PAY.toString(),
- wxPayProperties.getMchId(),
- getSerialNumber(),
- null,
- wxPayProperties.getCertKeyPath(),
- JSONUtil.toJsonStr(unifiedOrderModel)
- );
- log.info("统一下单响应 {}", response);
- // 根据证书序列号查询对应的证书来验证签名结果
- boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
- log.info("verifySignature: {}", verifySignature);
- if (response.getStatus() == SUCCESS && verifySignature) {
- String body = response.getBody();
- JSONObject jsonObject = JSONUtil.parseObj(body);
- String prepayId = jsonObject.getStr("prepay_id");
- Map<String, String> map = WxPayKit.jsApiCreateSign(wxPayProperties.getAppId(), prepayId, wxPayProperties.getCertKeyPath());
- log.info("唤起支付参数:{}", map);
- return R.ok(JSONUtil.toJsonStr(map));
- }
- return R.ok(JSONUtil.toJsonStr(response));
- }
- private String getSerialNumber() {
- if (StrUtil.isEmpty(serialNo)) {
- // 获取证书序列号
- X509Certificate certificate = PayKit.getCertificate(wxPayProperties.getCertPath());
- if (null != certificate) {
- serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
- // 提前两天检查证书是否有效
- boolean isValid = PayKit.checkCertificateIsValid(certificate, wxPayProperties.getMchId(), -2);
- log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN));
- }
- // System.out.println("输出证书信息:\n" + certificate.toString());
- // // 输出关键信息,截取部分并进行标记
- // System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16));
- // System.out.println("版本号:" + certificate.getVersion());
- // System.out.println("签发者:" + certificate.getIssuerDN());
- // System.out.println("有效起始日期:" + certificate.getNotBefore());
- // System.out.println("有效终止日期:" + certificate.getNotAfter());
- // System.out.println("主体名:" + certificate.getSubjectDN());
- // System.out.println("签名算法:" + certificate.getSigAlgName());
- // System.out.println("签名:" + certificate.getSignature().toString());
- }
- System.out.println("serialNo:" + serialNo);
- return serialNo;
- }
- @RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
- @ResponseBody
- @ApiOperation("微信支付回调接口")
- public void payNotify(HttpServletRequest request, HttpServletResponse response) {
- log.info("微信支付回调接口====================================>>>>微信支付回调接口");
- Map<String, String> map = new HashMap<>(12);
- try {
- String timestamp = request.getHeader("Wechatpay-Timestamp");
- String nonce = request.getHeader("Wechatpay-Nonce");
- String serialNo = request.getHeader("Wechatpay-Serial");
- String signature = request.getHeader("Wechatpay-Signature");
- log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
- String result = HttpKit.readData(request);
- log.info("支付通知密文 {}", result);
- // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
- String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
- wxPayProperties.getMchKey(), wxPayProperties.getPlatFormPath());
- log.info("支付通知明文 {}", plainText);
- if (StrUtil.isNotEmpty(plainText)) {
- response.setStatus(200);
- map.put("code", "SUCCESS");
- map.put("message", "SUCCESS");
- // 处理业务逻辑
- JSONObject jsonObject = new JSONObject(plainText);
- if (jsonObject.get("attach").equals(BillTypeEnum.WX_PAY.getCode().toString())) {
- // 订单支付成功
- orderService.payNotifyOrder(jsonObject.get("out_trade_no").toString());
- } else {
- TRecharge outTradeNo = rechargeService.increaseAmount(jsonObject.get("out_trade_no").toString());
- }
- } else {
- response.setStatus(500);
- map.put("code", "ERROR");
- map.put("message", "签名错误");
- }
- response.setHeader("Content-type", ContentType.JSON.toString());
- response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
- response.flushBuffer();
- } catch (Exception e) {
- log.error("系统异常", e);
- }
- }
- @RequestMapping(value = "/test", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
- @ResponseBody
- @ApiOperation("测试")
- public void test(HttpServletRequest request, HttpServletResponse response) {
- System.out.println("test=======================>");
- }
- @RequestMapping("/get")
- @ResponseBody
- public String v3Get() throws Exception {
- // 获取平台证书列表
- try {
- IJPayHttpResponse response = WxPayApi.v3(
- RequestMethodEnum.GET,
- WxDomainEnum.CHINA.toString(),
- CertAlgorithmTypeEnum.getCertSuffixUrl(CertAlgorithmTypeEnum.RSA.getCode()),
- wxPayProperties.getMchId(),
- getSerialNumber(),
- null,
- wxPayProperties.getCertKeyPath(),
- "",
- AuthTypeEnum.RSA.getCode()
- );
- Map<String, List<String>> headers = response.getHeaders();
- log.info("请求头: {}", headers);
- String timestamp = response.getHeader("Wechatpay-Timestamp");
- String nonceStr = response.getHeader("Wechatpay-Nonce");
- String serialNumber = response.getHeader("Wechatpay-Serial");
- String signature = response.getHeader("Wechatpay-Signature");
- String body = response.getBody();
- int status = response.getStatus();
- log.info("serialNumber: {}", serialNumber);
- log.info("status: {}", status);
- log.info("body: {}", body);
- int isOk = 200;
- if (status == isOk) {
- JSONObject jsonObject = JSONUtil.parseObj(body);
- JSONArray dataArray = jsonObject.getJSONArray("data");
- // 默认认为只有一个平台证书
- JSONObject encryptObject = dataArray.getJSONObject(0);
- JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
- String associatedData = encryptCertificate.getStr("associated_data");
- String cipherText = encryptCertificate.getStr("ciphertext");
- String nonce = encryptCertificate.getStr("nonce");
- String algorithm = encryptCertificate.getStr("algorithm");
- String serialNo = encryptObject.getStr("serial_no");
- final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, algorithm, wxPayProperties.getPlatFormPath());
- log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
- // 根据证书序列号查询对应的证书来验证签名结果
- boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
- log.info("verifySignature:{}", verifySignature);
- }
- return body;
- } catch (Exception e) {
- log.error("获取平台证书列表异常", e);
- return null;
- }
- }
- private String savePlatformCert(String associatedData, String nonce, String cipherText, String algorithm, String certPath) {
- try {
- String key3 = wxPayProperties.getMchKey();
- String publicKey;
- if (StrUtil.equals(algorithm, AuthTypeEnum.SM2.getPlatformCertAlgorithm())) {
- publicKey = PayKit.sm4DecryptToString(key3, cipherText, nonce, associatedData);
- } else {
- AesUtil aesUtil = new AesUtil(wxPayProperties.getMchKey().getBytes(StandardCharsets.UTF_8));
- // 平台证书密文解密
- // encrypt_certificate 中的 associated_data nonce ciphertext
- publicKey = aesUtil.decryptToString(
- associatedData.getBytes(StandardCharsets.UTF_8),
- nonce.getBytes(StandardCharsets.UTF_8),
- cipherText
- );
- }
- if (StrUtil.isNotEmpty(publicKey)) {
- // 保存证书
- FileWriter writer = new FileWriter(certPath);
- writer.write(publicKey);
- // 获取平台证书序列号
- X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
- return certificate.getSerialNumber().toString(16).toUpperCase();
- }
- return "";
- } catch (Exception e) {
- log.error("保存平台证书异常", e);
- return e.getMessage();
- }
- }
- @RequestMapping("/batchTransfer")
- @ApiOperation("微信批量提现")
- @ResponseBody
- public String batchTransfer(@RequestParam(value = "openId", required = false, defaultValue = "o-_-itxuXeGW3O1cxJ7FXNmq8Wf8") String openId) {
- try {
- BatchTransferModel batchTransferModel = new BatchTransferModel()
- .setAppid(wxPayProperties.getAppId())
- .setOut_batch_no(PayKit.generateStr())
- .setBatch_name("IJPay 测试微信转账到零钱")
- .setBatch_remark("IJPay 测试微信转账到零钱")
- .setTotal_amount(1)
- .setTotal_num(1)
- .setTransfer_detail_list(Collections.singletonList(
- new TransferDetailInput()
- .setOut_detail_no(PayKit.generateStr())
- .setTransfer_amount(1)
- .setTransfer_remark("IJPay 测试微信转账到零钱")
- .setOpenid(openId)));
- log.info("发起商家转账请求参数 {}", JSONUtil.toJsonStr(batchTransferModel));
- IJPayHttpResponse response = WxPayApi.v3(
- RequestMethodEnum.POST,
- WxDomainEnum.CHINA.toString(),
- TransferApiEnum.TRANSFER_BATCHES.toString(),
- wxPayProperties.getMchId(),
- getSerialNumber(),
- null,
- wxPayProperties.getCertKeyPath(),
- JSONUtil.toJsonStr(batchTransferModel)
- );
- log.info("发起商家转账响应 {}", response);
- // 根据证书序列号查询对应的证书来验证签名结果
- boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
- log.info("verifySignature: {}", verifySignature);
- if (response.getStatus() == SUCCESS && verifySignature) {
- return response.getBody();
- }
- return JSONUtil.toJsonStr(response);
- } catch (Exception e) {
- log.error("系统异常", e);
- return e.getMessage();
- }
- }
- @RequestMapping("/refund")
- @ResponseBody
- public String refund(@RequestParam(required = false) String outRefundNo, @RequestParam(required = false) BigDecimal amount,@RequestParam(required = false) String transactionId, @RequestParam(required = false) String outTradeNo) {
- return rechargeService.refund(outRefundNo,transactionId,outTradeNo,amount);
- }
- // /**
- // * 退款通知
- // */
- // @RequestMapping(value = "/refundNotify", method = {RequestMethod.POST, RequestMethod.GET})
- // @ResponseBody
- // @ApiOperation("微信退款回调接口")
- // public String refundNotify(HttpServletRequest request) {
- // String xmlMsg = HttpKit.readData(request);
- // log.info("退款通知=" + xmlMsg);
- // Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
- // log.info("退款通知parms:{}",params);
- // String returnCode = params.get("event_type");
- // // 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态
- // if (returnCode.equals("REFUND.SUCCESS")) {
- // String reqInfo = params.get("resource");
- // String decryptData = WxPayKit.decryptData(reqInfo, WxPayApiConfigKit.getWxPayApiConfig().getPartnerKey());
- // log.info("退款通知解密后的数据=" + decryptData);
- //// refundVoucherService.
- // // 更新订单信息
- // // 发送通知等
- // Map<String, String> xml = new HashMap<String, String>(2);
- // xml.put("return_code", "SUCCESS");
- // xml.put("return_msg", "OK");
- // return WxPayKit.toXml(xml);
- // }
- // return null;
- // }
- @ApiOperation("微信退款回调接口")
- @RequestMapping(value = "/refundNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
- public void refundWechatCallback(HttpServletRequest request, HttpServletResponse response) {
- log.info("微信退款回调接口====================================>>>>微信退款回调接口");
- Map<String, String> map = new HashMap<>(12);
- try {
- String timestamp = request.getHeader("Wechatpay-Timestamp");
- String nonce = request.getHeader("Wechatpay-Nonce");
- String serialNo = request.getHeader("Wechatpay-Serial");
- String signature = request.getHeader("Wechatpay-Signature");
- log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
- String result = HttpKit.readData(request);
- log.info("退款通知密文 {}", result);
- // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
- String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
- wxPayProperties.getMchKey(), wxPayProperties.getPlatFormPath());
- log.info("退款通知明文 {}", plainText);
- if (StrUtil.isNotEmpty(plainText)) {
- response.setStatus(200);
- map.put("code", "SUCCESS");
- map.put("message", "SUCCESS");
- // 处理业务逻辑
- } else {
- response.setStatus(500);
- map.put("code", "ERROR");
- map.put("message", "签名错误");
- }
- response.setHeader("Content-type", ContentType.JSON.toString());
- response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
- response.flushBuffer();
- } catch (Exception e) {
- log.error("系统异常", e);
- }
- }
- }
|