diff --git a/nursing-unit-admin/nu-admin-biz/pom.xml b/nursing-unit-admin/nu-admin-biz/pom.xml index d76ec65..60bf3dd 100644 --- a/nursing-unit-admin/nu-admin-biz/pom.xml +++ b/nursing-unit-admin/nu-admin-biz/pom.xml @@ -10,6 +10,17 @@ nu-admin-biz + + + com.github.wechatpay-apiv3 + wechatpay-apache-httpclient + 0.4.9 + + + com.github.wxpay + wxpay-sdk + 0.0.3 + com.nursingunit.boot nu-admin-local-api diff --git a/nursing-unit-admin/nu-admin-biz/src/main/java/com/nu/modules/wechart/controller/WechatPay2Controller.java b/nursing-unit-admin/nu-admin-biz/src/main/java/com/nu/modules/wechart/controller/WechatPay2Controller.java new file mode 100644 index 0000000..f635384 --- /dev/null +++ b/nursing-unit-admin/nu-admin-biz/src/main/java/com/nu/modules/wechart/controller/WechatPay2Controller.java @@ -0,0 +1,76 @@ +package com.nu.modules.wechart.controller; + + +import cn.hutool.core.util.IdUtil; +import com.github.wxpay.sdk.WXPay; +import com.github.wxpay.sdk.WXPayConstants; +import com.github.wxpay.sdk.WXPayUtil; +import com.nu.modules.wechart.entity.WXConfig; +import com.nu.modules.wechart.entity.WechatpayConfig; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/weiXinPay2") +@Slf4j +public class WechatPay2Controller { + @Autowired + public WechatpayConfig wechatpayConfig; + + + /** + * Native下单 + * 调用统一下单API,生成支付二维码 + */ + @PostMapping("/native") + public Map nativePay() throws Exception { + log.info("生成订单"); + //生成订单 + log.info("存入数据库..."); + + log.info("调用统一下单API"); + // 订单号 + String orderNo = IdUtil.simpleUUID(); + // 请求body参数 看官方文档 + WXConfig config = new WXConfig(); + config.setAppId(wechatpayConfig.getAppid()); + config.setMchId(wechatpayConfig.getMchId()); + config.setKey(wechatpayConfig.getApiV3Key()); + + Map paramsMap = new HashMap<>(); + paramsMap.put("appid", wechatpayConfig.getAppid()); + paramsMap.put("mchid", wechatpayConfig.getMchId()); + paramsMap.put("description", "iPhone 15 Pro Max 5G"); + paramsMap.put("out_trade_no", orderNo); + // 回调的地址 + paramsMap.put("notify_url",wechatpayConfig.getNotifyDomain()+"/native/notify"); + + Map amountMap = new HashMap(); + //订单总金额,单位为分。 + amountMap.put("total", 1); + //CNY:人民币,境内商户号仅支持人民币。 + amountMap.put("currency", "CNY"); + paramsMap.put("total_fee", 1+""); + paramsMap.put("trade_type", "JSAPI"); + String generateNonceStr = WXPayUtil.generateNonceStr(); + paramsMap.put("nonce_str", generateNonceStr); + String body = "body"; + paramsMap.put("body", body); + paramsMap.put("sign", WXPayUtil.generateSignature(paramsMap, wechatpayConfig.getApiV3Key(), WXPayConstants.SignType.MD5)); + + log.info("请求参数:" + paramsMap); + + + + WXPay wxpay = new WXPay(config); + Map response = wxpay.unifiedOrder(paramsMap); + for (String key : response.keySet()) { + log.info("微信支付订单微信返回参数:keys:" + key + " value:" + response.get(key).toString()); + } + return null; + } +} diff --git a/nursing-unit-admin/nu-admin-biz/src/main/java/com/nu/modules/wechart/controller/WechatPayController.java b/nursing-unit-admin/nu-admin-biz/src/main/java/com/nu/modules/wechart/controller/WechatPayController.java new file mode 100644 index 0000000..c078de5 --- /dev/null +++ b/nursing-unit-admin/nu-admin-biz/src/main/java/com/nu/modules/wechart/controller/WechatPayController.java @@ -0,0 +1,345 @@ +package com.nu.modules.wechart.controller; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.json.JSONUtil; +import com.github.wxpay.sdk.WXPayUtil; +import com.nu.modules.wechart.entity.WechatpayConfig; +import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/weiXinPay") +@Slf4j +public class WechatPayController { + + + @Resource + public WechatpayConfig wechatpayConfig; + + + /** + * Native下单 + * 调用统一下单API,生成支付二维码 + */ + @PostMapping("/native") + public Map nativePay() throws Exception { + log.info("生成订单"); + //生成订单 + log.info("存入数据库..."); + + log.info("调用统一下单API"); + //调用统一下单API + HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native"); + + // 订单号 + String orderNo = IdUtil.simpleUUID(); + // 请求body参数 看官方文档 + Map paramsMap = new HashMap(); + paramsMap.put("appid", wechatpayConfig.getAppid()); + paramsMap.put("mchid", wechatpayConfig.getMchId()); + paramsMap.put("description", "iPhone 15 Pro Max 5G"); + paramsMap.put("out_trade_no", orderNo); + // 回调的地址 + paramsMap.put("notify_url",wechatpayConfig.getNotifyDomain()+"/native/notify"); + Map amountMap = new HashMap(); + //订单总金额,单位为分。 + amountMap.put("total", 1); + //CNY:人民币,境内商户号仅支持人民币。 + amountMap.put("currency", "CNY"); + + paramsMap.put("amount", amountMap); + + + + //将参数转换成json字符串 + String jsonParams = JSONUtil.toJsonStr(paramsMap); + log.info("请求参数:" + jsonParams); + + StringEntity entity = new StringEntity(jsonParams,"utf-8"); + entity.setContentType("application/json"); + httpPost.setEntity(entity); + httpPost.setHeader("Accept", "application/json"); + httpPost.setHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 "+ jsonParams); + //创建httpclient对象 + CloseableHttpClient wxPayClient = HttpClients.createDefault(); + //完成签名并执行请求 + CloseableHttpResponse response = wxPayClient.execute(httpPost); + try { + String bodyAsString = EntityUtils.toString(response.getEntity());//响应体 + int statusCode = response.getStatusLine().getStatusCode();//响应状态码 + if (statusCode == 200) { //处理成功 + log.info("成功, 返回结果 = " + bodyAsString); + } else if (statusCode == 204) { //处理成功,无返回Body + log.info("成功"); + } else { + log.info("Native下单失败,响应码 = " + statusCode+ ",返回结果 = " + + bodyAsString); + throw new IOException("request failed"); + } + //响应结果 + Map resultMap = JSONUtil.toBean(bodyAsString,HashMap.class); + //二维码 + String codeUrl = resultMap.get("code_url"); + Map map = new HashMap<>(); + map.put("codeUrl", codeUrl); + map.put("orderNo", orderNo.toString()); + return map; + } finally { + response.close(); + } + } + /** + * 支付通知 + * 微信支付通过支付通知接口将用户支付成功消息通知给商户 + */ + @PostMapping("/native/notify") + public Map nativeNotify(@RequestBody Map signalRes, HttpServletResponse response){ + Map map = new HashMap<>();//应答对象 + log.info("支付通知的完整数据 ===> {}", signalRes); + try { + //用密文解密出明文 + Map resource=(Map)signalRes.get("resource"); + String ciphertext=resource.get("ciphertext"); + String associatedData=resource.get("associated_data"); + String nonce=resource.get("nonce"); + // 拿到明文 + String plainText=new AesUtil(wechatpayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8)).decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),ciphertext); + //转换 + HashMap data= JSONUtil.toBean(plainText,HashMap.class); + log.info("解密后的完整数据:{}",data); + //处理订单 + log.info("处理订单..."); + //成功应答:成功应答必须为200或204,否则就是失败应答 + response.setStatus(200); + map.put("code", "SUCCESS"); + } catch (GeneralSecurityException e) { + response.setStatus(500); + map.put("code", "FAIL"); + map.put("message","失败"); + } + return map; + } + /** + * 查询订单 + * @param orderNo + * @return + * @throws Exception + */ + @GetMapping("/query/{orderNo}") + public String queryOrder(@PathVariable String orderNo) throws Exception { + log.info("查询订单"); + String url = String.format("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s", orderNo); + url = url+"?mchid="+wechatpayConfig.getMchId(); + HttpGet httpGet = new HttpGet(url); + httpGet.setHeader("Accept", "application/json"); + + //创建httpclient对象 + CloseableHttpClient wxPayClient = HttpClients.createDefault(); + //完成签名并执行请求 + CloseableHttpResponse response = wxPayClient.execute(httpGet); + try { + String bodyAsString = EntityUtils.toString(response.getEntity());//响应体 + int statusCode = response.getStatusLine().getStatusCode();//响应状态码 + if (statusCode == 200) { //处理成功 + log.info("成功, 返回结果 = " + bodyAsString); + } else if (statusCode == 204) { //处理成功,无返回Body + log.info("成功"); + } else { + log.info("查单接口调用,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString); + throw new IOException("request failed"); + } + return bodyAsString; + } finally { + response.close(); + } + + } + /** + * 用户取消订单 + * @param orderNo 订单id + */ + @PostMapping("/cancel/{orderNo}") + public String cancel(@PathVariable String orderNo) throws Exception { + log.info("用户取消订单"); + //创建远程请求对象 + String url = String.format("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s/close", orderNo); + HttpPost httpPost = new HttpPost(url); + + //组装json请求体 + Map paramsMap = new HashMap<>(); + paramsMap.put("mchid", wechatpayConfig.getMchId()); + String jsonParams = JSONUtil.toJsonStr(paramsMap); + log.info("请求参数 ===> {}", jsonParams); + + //将请求参数设置到请求对象中 + StringEntity entity = new StringEntity(jsonParams,"utf-8"); + entity.setContentType("application/json"); + httpPost.setEntity(entity); + httpPost.setHeader("Accept", "application/json"); + + //创建httpclient对象 + CloseableHttpClient wxPayClient = HttpClients.createDefault(); + //完成签名并执行请求 + CloseableHttpResponse response = wxPayClient.execute(httpPost); + try { + int statusCode = response.getStatusLine().getStatusCode();//响应状态码 + if (statusCode == 200) { //处理成功 + log.info("成功200"); + } else if (statusCode == 204) { //处理成功,无返回Body + log.info("成功204"); + } else { + log.info("Native下单失败,响应码 = " + statusCode); + throw new IOException("request failed"); + } + return "订单取消成功"; + } finally { + response.close(); + } + } + /** + * 申请退款 + * @param orderNo 订单编号 + */ + @PostMapping("/refunds/{orderNo}") + public Map refunds(@PathVariable String orderNo) throws Exception { + + log.info("创建退款单记录..."); + //根据订单编号创建退款单 + + log.info("调用退款API"); + + //调用统一下单API + HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"); + + // 请求body参数 + Map paramsMap = new HashMap(); + paramsMap.put("out_trade_no", orderNo);//订单编号 + + // 给它一个退款单号 + paramsMap.put("out_refund_no", "tk202412120001");//退款单编号 + paramsMap.put("reason", "买了不发货");//退款原因 + paramsMap.put("notify_url", wechatpayConfig.getNotifyDomain()+"/refunds/notify");//退款通知地址 + + Map amountMap = new HashMap(); + amountMap.put("refund", 1);//退款金额 + amountMap.put("total", 1);//原订单金额 + amountMap.put("currency", "CNY");//退款币种 + paramsMap.put("amount", amountMap); + + //将参数转换成json字符串 + String jsonParams = JSONUtil.toJsonStr(paramsMap); + log.info("请求参数 ===> {}" + jsonParams); + + StringEntity entity = new StringEntity(jsonParams, "utf-8"); + entity.setContentType("application/json");//设置请求报文格式 + httpPost.setEntity(entity);//将请求报文放入请求对象 + httpPost.setHeader("Accept", "application/json");//设置响应报文格式 + + //创建httpclient对象 + CloseableHttpClient wxPayClient = HttpClients.createDefault(); + //完成签名并执行请求,并完成验签 + CloseableHttpResponse response = wxPayClient.execute(httpPost); + + try { + //解析响应结果 + String bodyAsString = EntityUtils.toString(response.getEntity()); + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode == 200) { + log.info("成功, 退款返回结果 = " + bodyAsString); + } else if (statusCode == 204) { + log.info("成功"); + } else { + throw new RuntimeException("退款异常, 响应码 = " + statusCode + ", 退款返回结果 = " + bodyAsString); + } + log.info("更新订单状态......"); + log.info("更新退款单......"); + return JSONUtil.toBean(bodyAsString,Map.class); + } finally { + response.close(); + } + } + /** + * 退款结果通知 + * 退款状态改变后,微信会把相关退款结果发送给商户。 + */ + @PostMapping("/refunds/notify") + public String refundsNotify(@RequestBody Map signalRes, HttpServletResponse response){ + log.info("退款通知执行"); + Map map = new HashMap<>();//应答对象 + try { + Map resource=(Map)signalRes.get("resource"); + String ciphertext=resource.get("ciphertext"); + String associatedData=resource.get("associated_data"); + String nonce=resource.get("nonce"); + // 拿到明文 + String plainText=new AesUtil(wechatpayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8)).decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),ciphertext); + + //转换 + HashMap data= JSONUtil.toBean(plainText,HashMap.class); + log.info("解密后的完整数据:{}",data); + log.info("处理退款单................................"); + log.info("更新订单状态................................"); + + //成功应答 + response.setStatus(200); + map.put("code", "SUCCESS"); + map.put("message", "成功"); + return JSONUtil.toJsonStr(map); + } catch (Exception e) { + e.printStackTrace(); + //失败应答 + response.setStatus(500); + map.put("code", "ERROR"); + map.put("message", "失败"); + return JSONUtil.toJsonStr(map); + } + } + /** + * 查询退款 + * @param refundNo 退款订单 + */ + @GetMapping("/query-refund/{refundNo}") + public String queryRefund(@PathVariable String refundNo) throws Exception { + + log.info("查询退款接口调用 ===> {}", refundNo); + String url = String.format("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/%s", refundNo); + //创建远程Get 请求对象 + HttpGet httpGet = new HttpGet(url); + httpGet.setHeader("Accept", "application/json"); + //创建httpclient对象 + CloseableHttpClient wxPayClient = HttpClients.createDefault(); + //完成签名并执行请求 + CloseableHttpResponse response = wxPayClient.execute(httpGet); + try { + String bodyAsString = EntityUtils.toString(response.getEntity()); + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode == 200) { + log.info("成功, 查询退款返回结果 = " + bodyAsString); + } else if (statusCode == 204) { + log.info("成功"); + } else { + throw new RuntimeException("查询退款异常, 响应码 = " + statusCode+ ", 查询退款返回结果 = " + bodyAsString); + } + return bodyAsString; + } finally { + response.close(); + } + } + +} diff --git a/nursing-unit-admin/nu-admin-biz/src/main/java/com/nu/modules/wechart/entity/WXConfig.java b/nursing-unit-admin/nu-admin-biz/src/main/java/com/nu/modules/wechart/entity/WXConfig.java new file mode 100644 index 0000000..06224b1 --- /dev/null +++ b/nursing-unit-admin/nu-admin-biz/src/main/java/com/nu/modules/wechart/entity/WXConfig.java @@ -0,0 +1,89 @@ +package com.nu.modules.wechart.entity; + +import com.github.wxpay.sdk.WXPayConfig; +import lombok.Data; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +/** + * @author fang + * @date 2020/2/26 + */ +@Data +public class WXConfig implements WXPayConfig { + private byte[] certData; + + public String appId; + public String key; + public String mchId; + + /*public WXConfigUtil() throws Exception { + String certPath = ClassUtils.getDefaultClassLoader().getResource("").getPath()+"/weixin/apiclient_cert.p12";//从微信商户平台下载的安全证书存放的路径 + File file = new File(certPath); + InputStream certStream = new FileInputStream(file); + this.certData = new byte[(int) file.length()]; + certStream.read(this.certData); + certStream.close(); + }*/ + + public byte[] getCertData() { + return certData; + } + + public void setCertData(byte[] certData) { + this.certData = certData; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public void setKey(String key) { + this.key = key; + } + + public String getMchId() { + return mchId; + } + + public void setMchId(String mchId) { + this.mchId = mchId; + } + + @Override + public String getAppID() { + return this.appId; + } + + //parnerid,商户号 + @Override + public String getMchID() { + return this.mchId; + } + + @Override + public String getKey() { + return this.key; + } + + @Override + public InputStream getCertStream() { + ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData); + return certBis; + } + + @Override + public int getHttpConnectTimeoutMs() { + return 8000; + } + + @Override + public int getHttpReadTimeoutMs() { + return 10000; + } +} diff --git a/nursing-unit-admin/nu-admin-biz/src/main/java/com/nu/modules/wechart/entity/WechatpayConfig.java b/nursing-unit-admin/nu-admin-biz/src/main/java/com/nu/modules/wechart/entity/WechatpayConfig.java new file mode 100644 index 0000000..00bb7b0 --- /dev/null +++ b/nursing-unit-admin/nu-admin-biz/src/main/java/com/nu/modules/wechart/entity/WechatpayConfig.java @@ -0,0 +1,38 @@ +package com.nu.modules.wechart.entity; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Slf4j +@Data +public class WechatpayConfig { + // 商户ID + @Value("${wxpay.mch-id}") + private String mchId; + + // 商户API证书序列号 + @Value("${wxpay.mch-serial-no}") + private String mchSerialNo; + + // 商户私钥文件 + @Value("${wxpay.private-key-path}") + private String privateKeyPath; + + // APIv3密钥 + @Value("${wxpay.api-v3-key}") + private String apiV3Key; + + // APPID + @Value("${wxpay.appid}") + private String appid; + + // 接收结果通知地址 + @Value("${wxpay.notify-domain}") + private String notifyDomain; + + + +} diff --git a/nursing-unit-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java b/nursing-unit-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java index cbbaa42..e0937cd 100644 --- a/nursing-unit-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java +++ b/nursing-unit-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java @@ -113,6 +113,8 @@ public class ShiroConfig { filterChainDefinitionMap.put("/sys/checkAuth", "anon"); //授权接口排除 filterChainDefinitionMap.put("/h5Api/nuBizAdvisoryInfo/**", "anon"); //授权接口排除 filterChainDefinitionMap.put("/h5Api/nuBaseInfo/**", "anon"); //授权接口排除 + filterChainDefinitionMap.put("/weiXinPay/**", "anon"); //微信支付接口 + filterChainDefinitionMap.put("/weiXinPay2/**", "anon"); //微信支付接口 //update-begin--Author:scott Date:20221116 for:排除静态资源后缀 filterChainDefinitionMap.put("/", "anon"); diff --git a/nursing-unit-invoicing/nu-invoicing-biz/src/main/java/com/nu/modules/ConfigMaterial/controller/ConfigMaterialCategoryController.java b/nursing-unit-invoicing/nu-invoicing-biz/src/main/java/com/nu/modules/ConfigMaterial/controller/ConfigMaterialCategoryController.java index edf8292..5b8b39d 100644 --- a/nursing-unit-invoicing/nu-invoicing-biz/src/main/java/com/nu/modules/ConfigMaterial/controller/ConfigMaterialCategoryController.java +++ b/nursing-unit-invoicing/nu-invoicing-biz/src/main/java/com/nu/modules/ConfigMaterial/controller/ConfigMaterialCategoryController.java @@ -175,7 +175,6 @@ public class ConfigMaterialCategoryController extends JeecgController${commonmark.version} - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - 2021.0.5.0 - + + + + + - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - 2021.0.5.0 - + + + + + org.springframework.boot