diff --git a/src/main/java/com/dpkj/modules/scanface/wx/config/WechatUrlConfig.java b/src/main/java/com/dpkj/modules/scanface/wx/config/WechatUrlConfig.java new file mode 100644 index 0000000..8758d58 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/config/WechatUrlConfig.java @@ -0,0 +1,81 @@ +package com.dpkj.modules.scanface.wx.config; + +/** + * @description: + * @author: Zhangxue + * @time: 2024/11/28 9:38 + */ + +public class WechatUrlConfig { + + /** + * 获取证书 + */ + public static final String CERTIFICATESURL = "https://api.mch.weixin.qq.com/v3/certificates"; + + + /** + * 退款地址 + */ + public static final String REFUNDSURL = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"; + + /** + * 创建支付分订单 + */ + public static final String SERVICEORDER = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder"; + + + /** + * 发起订单支付 + */ + public static final String CREATEORDER = "https://api.mch.weixin.qq.com/pay/micropay"; + + /** + * 查询订单状态 + */ + public static final String ORDERQUERY = "https://api.mch.weixin.qq.com/pay/orderquery"; + + + /** + * 撤销交易 + */ + public static final String REVERSE = "https://api.mch.weixin.qq.com/secapi/pay/reverse"; + + /** + * 取消支付分订单前缀 + */ + public static final String ORDERCANCELPREFIX = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder/"; + + /** + * 取消支付分订单后缀 + */ + public static final String ORDERCANCELSUFFIX = "/cancel"; + + /** + * 完结支付分订单前缀 + */ + public static final String SERVICEORDERCOMPLETEPREFIX = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder/"; + + + /** + * 完结支付分订单后缀 + */ + public static final String SERVICEORDERCOMPLETESUFFIX = "/complete"; + + /** + * 获取调用凭证 + */ + public static final String GET_WXPAYFACE_AUTHINFO = "https://payapp.weixin.qq.com/face/get_wxpayface_authinfo"; + + + /** + * 查询订单 + */ + public static final String FINDBYID = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder"; + + /** + * 查询退款订单 + */ + public static final String FINDREFUND = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/"; + +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/config/WxMpProperties.java b/src/main/java/com/dpkj/modules/scanface/wx/config/WxMpProperties.java new file mode 100644 index 0000000..00cf735 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/config/WxMpProperties.java @@ -0,0 +1,253 @@ +package com.dpkj.modules.scanface.wx.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 公众号配置 + */ +@Data +@Component +@ConfigurationProperties(prefix = "wx.mp") +public class WxMpProperties { + /** + * 是否使用redis存储access token + */ + private Boolean useRedis = false; + + /** + * redis 配置 + */ + private RedisConfig redisConfig; + + @Data + public static class RedisConfig { + /** + * redis服务器 主机地址 + */ + private String host; + + /** + * redis服务器 端口号 + */ + private Integer port; + + /** + * redis服务器 密码 + */ + private String password; + + /** + * redis 服务连接超时时间 + */ + private Integer timeout; + } + + /** + * 多个公众号配置信息 + */ + private List configs; + + @Data + public static class MpConfig { + /** + * 设置微信公众号的appid + */ + private String appId; + + /** + * 设置微信公众号的app secret + */ + private String secret; + + /** + * 设置微信公众号的token + */ + private String token; + + /** + * 设置微信公众号的EncodingAESKey + */ + private String aesKey; + + /** + * 微信:调用接口所需service_id + */ + private String serviceId; + + /** + * 微信:商户号 + */ + private String mchId; + + /** + * 微信支付商户密钥 + */ + private String mchKey; + + /** + * 微信:报文解密V3密钥key + */ + private String v3Key; + + /** + * 微信: API密钥 + */ + private String keyApi; + + /** + * 微信:密钥【apiclient_key.pem】地址 + p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath;开头) + */ + private String keyPath; + + /** + * 微信:商家api序列号 + */ + private String mchSerialNo; + + /** + * #JSAPI--公众号支付 NATIVE--原生扫码支付 APP--app支付 + */ + private String tradeType; + + /** + * 微信:支付回调地址 + */ + private String payNotifyUrl; + + /** + * 微信:退款回调地址 + */ + private String refundNotifyUrl; + } + + + /** + * 访问地址 + */ + private MpUrl url; + + @Data + public static class MpUrl { + /** + * H5地址 + */ + private String h5; + + /** + * 接口地址 + */ + private String server; + } + + /** + * 模板消息id + */ + private MpTemplate template; + + @Data + public static class MpTemplate { + /** + * 缴费通知 + */ + private String payMsg; + + /** + * 流程待办 + */ + private String test; + + /** + * 流程待办 + */ + private String test2; + + } + + /** + * 微信商户支付配置 + */ + private MchConfig mchConfig; + @Data + public static class MchConfig{ + /** + * 微信:APPID + */ + private String appId; + private String secret; + + /** + * 微信:商户号 + */ + private String mchId; + //微信支付商户密钥 + private String mchKey; + + /** + * 微信:报文解密V3密钥key + */ + private String v3Key; + + /** + * 微信: API密钥 + */ + private String keyApi; + + /** + * 微信:密钥【apiclient_key.pem】地址 + p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath;开头) + */ + private String keyPath; + + /** + * 退款证书 private-key-path V3 + * apiclient_key.pem 证书文件的绝对路径或者以classpath:开头的类路径. + */ + private String privateKeyPath; + + /** + * 退款证书 private-cert-path + * apiclient_cert.pem 证书文件的绝对路径或者以classpath:开头的类路径. + */ + private String privateCertPath; + + + /** + * 微信:商家api序列号 + */ + private String mchSerialNo; + + /** + * 微信:调用接口所需service_id + */ + private String serviceId; + + + /** + * 微信:支付回调地址 + */ + private String payNotifyUrl; + + + /** + * 微信:退款回调地址 + */ + private String refundNotifyUrl; + + /** + * #JSAPI--公众号支付 NATIVE--原生扫码支付 APP--app支付 + */ + private String tradeType; + + /** + * NATIVE--原生扫码支付 + */ + private String tradeTypeNative; + + } + +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/controller/WxpayFaceTestController.java b/src/main/java/com/dpkj/modules/scanface/wx/controller/WxpayFaceTestController.java new file mode 100644 index 0000000..7d05979 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/controller/WxpayFaceTestController.java @@ -0,0 +1,156 @@ +package com.dpkj.modules.scanface.wx.controller; + +import com.dpkj.common.vo.Result; +import com.dpkj.modules.scanface.wx.dll.WxpayFaceSDKDll; +import com.dpkj.modules.scanface.wx.service.CallWxpayFaceService; +import com.dpkj.modules.scanface.wx.service.WeChatPayFaceService; +import com.dpkj.modules.scanface.wx.service.impl.CallWxpayFaceServiceImpl; +import com.dpkj.modules.scanface.wx.service.impl.WeChatPayFaceServiceImpl; +import com.dpkj.modules.scanface.wx.vo.WxFacePayAuthinfoResp; +import com.dpkj.modules.scanface.wx.vo.WxFacePayReq; +import com.dpkj.modules.scanface.wx.vo.WxFacePayResp; +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.io.UnsupportedEncodingException; +import java.security.Provider; +import java.security.Security; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.Map; +import java.util.TreeSet; + +/** + * @description: + * @author: Zhangxue + * @time: 2024/11/28 16:43 + */ +@Slf4j +@RestController +@RequestMapping("/wxpayFace") +public class WxpayFaceTestController { + + @Resource + private CallWxpayFaceService callWxpayFaceService; + + @Resource + private WeChatPayFaceService weChatPayFaceService; + + /** + * 1 程序启动时初始化 :程序启动时初始化initWxpayface + * @return + * @throws JsonProcessingException + */ + @RequestMapping(value = "/initWxpayface", method = RequestMethod.POST) + public Result initWxpayface() throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException { + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("initWxpayface","1",System.currentTimeMillis()/1000); + WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq); + System.out.println("**************1、程序启动时初始化:"+wxFacePayResp.toString()); + return Result.ok(wxFacePayResp); + } + + + + /** + * 2 获取数据getWxpayfaceRawdata + * @return + * @throws JsonProcessingException + */ + @RequestMapping(value = "/getWxpayfaceRawdata", method = RequestMethod.POST) + public Result getWxpayfaceRawdata() throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException { + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("getWxpayfaceRawdata","1",System.currentTimeMillis()/1000); + WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq); + System.out.println("**************2、获取数据:"+wxFacePayResp.toString()); + + + + String rawdata = wxFacePayResp.getRawdata(); + return Result.ok(wxFacePayResp); + } + + + /** + * 3、获取调用凭证get_wxpayface_authinfo(rawdata)(获取调用凭证) + * @return + * @throws Exception + */ + @RequestMapping(value = "/getWxpayfaceAuthinfo", method = RequestMethod.POST) + public Result> getWxpayfaceAuthinfo() throws Exception { + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("getWxpayfaceRawdata","1",System.currentTimeMillis()/1000); + WxFacePayResp wxFacePayResp1 = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq); + System.out.println("**************2、获取数据返回值:"+wxFacePayResp1.toString()); + String rawdata = wxFacePayResp1.getRawdata(); + + // 构建请求参数的JSON字符串 + Map wxFacePayResp = weChatPayFaceService.getWxFaceAuthInfoReqMap(rawdata); + System.out.println("**************333:"+wxFacePayResp.toString()); + return Result.ok(wxFacePayResp); + } + + + /** + * 4、进行人脸识别getWxpayfaceCode(获取支付凭证) + * @return + * @throws Exception + */ + @RequestMapping(value = "/getWxpayfaceCode", method = RequestMethod.POST) + public Result getWxpayfaceCode() throws Exception { + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("getWxpayfaceCode","1",System.currentTimeMillis()/1000); + + WxFacePayAuthinfoResp authinfoResp = weChatPayFaceService.doWxPayMethod(wxFacePayReq); + System.out.println("**************4、进行人脸识别:"+authinfoResp.toString()); + + return Result.ok(authinfoResp); + } + + //5、进行发起订单支付 + + + + public static void main(String[] args) throws Exception { + //微信配置 + CallWxpayFaceService callWxpayFaceService = new CallWxpayFaceServiceImpl(); + WeChatPayFaceService weChatPayFaceService = new WeChatPayFaceServiceImpl(); + + WxFacePayReq wxFacePayReq = new WxFacePayReq("initWxpayface","1",System.currentTimeMillis()/1000); + WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq); + System.out.println("**************1、程序启动时初始化:"+wxFacePayResp.toString()); + } + + + //可以打印 JDK 中的 Provider 列表,以及所有签名算法 + public void outProvider() { + TreeSet algorithms = new TreeSet<>(); + + Provider[] providers = Security.getProviders(); + System.out.println("-----Provider 列表如下:-----"); + for (Provider provider : providers) { + System.out.println(provider.getName()); + } + + System.out.println("-----支持的签名算法如下:-----"); + + for (Provider provider : providers) { + for (Provider.Service service : provider.getServices()) + if (service.getType().equals("Signature")) { + algorithms.add(service.getAlgorithm()); + } + } + + for (String algorithm : algorithms) { + System.out.println(algorithm); + } + } + + + +} + diff --git a/src/main/java/com/dpkj/modules/scanface/wx/dll/WxpayFaceSDKDll.java b/src/main/java/com/dpkj/modules/scanface/wx/dll/WxpayFaceSDKDll.java new file mode 100644 index 0000000..a3709c6 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/dll/WxpayFaceSDKDll.java @@ -0,0 +1,116 @@ +package com.dpkj.modules.scanface.wx.dll; + +import com.dpkj.modules.scanface.ali.dll.AliScanFaceDll; +import com.sun.jna.Library; +import com.sun.jna.Native; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +/** + * @description: + * @author: Zhangxue + * @time: 2025/4/17 17:02 + */ +@Slf4j +public class WxpayFaceSDKDll { + private static final String DLL_NAME = "WxpayFaceSDK.dll"; // 确保文件名与实际资源一致 + //win32-x86-64/WxpayFaceSDK.dll + private static final String RESOURCE_PATH = "win32-x86-64/" + DLL_NAME; // 例如: resources/native/WxpayFaceSDK.dll + private static volatile boolean dllLoaded = false; + + static { + loadDllFromResources(); + } + + + private static void loadDllFromResources() { + if (dllLoaded) return; + + try { + // 从 resources 中读取 DLL 文件流 + InputStream inputStream = WxpayFaceSDKDll.class.getClassLoader().getResourceAsStream(RESOURCE_PATH); + if (inputStream == null) { + throw new RuntimeException("DLL 未找到: " + RESOURCE_PATH); + } + + // 创建临时目录并提取 DLL + Path tempDir = Files.createTempDirectory("native-libs"); + Path tempDll = tempDir.resolve(DLL_NAME); + Files.copy(inputStream, tempDll, StandardCopyOption.REPLACE_EXISTING); + inputStream.close(); + + // 显式加载 DLL + System.load(tempDll.toAbsolutePath().toString()); + dllLoaded = true; + + // 可选:JVM 退出时清理临时文件 + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + Files.deleteIfExists(tempDll); + Files.deleteIfExists(tempDir); + } catch (Exception e) { + e.printStackTrace(); + } + })); + } catch (Exception e) { + throw new RuntimeException("加载 DLL 失败", e); + } + } + + public static Dll instance() throws DllRegistrationException { + try { + // 加载已提取的 DLL + return Native.load(DLL_NAME, Dll.class); + } catch (UnsatisfiedLinkError e) { + log.error("[WxpayFaceSDK][instance] 加载失败", e); + throw new DllRegistrationException("Failed to load WxpayFaceSDK library: ", e); + } + } + + /** + * 定义自定义异常类,用于表示注册控件时发生的错误 + */ + public static class DllRegistrationException extends Exception { + public DllRegistrationException(String message) { + super(message); + } + + public DllRegistrationException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * 定义接口映射本地库中的函数。 + */ + public interface Dll extends Library { + /** + * 调用人脸服务 + * 注意:这里的方法签名需要与DLL中的C函数签名一致 + * req和resp都是JSON字符串,需要转换为Pointer类型 + * char * -->String; char ** -->String[] + * int -->int; int* --> IntByReference + * @param reqBuf 请求参数(JSON字符串) + * @param reqSize 请求参数长度 + * @param pRespBuf 用来接收响应结果(JSON字符串)的char**指针 + * @param pRespSize 用来接收响应结果长度的unsigned int*指针 + * @return 如果成功返回0,失败则返回非0 + */ + //int wxpayCallFaceService(String reqBuf, int reqSize, String[] pRespBuf, IntByReference pRespSize); + int wxpayCallFaceService(String reqBuf, int reqSize, long[] pRespBuf, int[] pRespSize); + + + /** + * char ** -->String[] + * 释放人脸服务的响应字符串,调用wxpayCallFaceService成功后务必调用此函数释放内存 + * @param pRespBuf 指向响应结果(JSON字符串)的指针 + */ + void wxpayReleaseResponse(String[] pRespBuf); + } + +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/dll/WxpayFaceSDKDll_old.java b/src/main/java/com/dpkj/modules/scanface/wx/dll/WxpayFaceSDKDll_old.java new file mode 100644 index 0000000..86a77f0 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/dll/WxpayFaceSDKDll_old.java @@ -0,0 +1,71 @@ +package com.dpkj.modules.scanface.wx.dll; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import lombok.extern.slf4j.Slf4j; + +/** + * @description: + * @author: Zhangxue + * @time: 2025/4/17 17:02 + */ +@Slf4j +public class WxpayFaceSDKDll_old { + + /** + * 获取 Dll 实例,同时注册 Dll 控件。 + * + * @return WxpayFaceSDKDll 实例 + * @throws WxpayFaceSDKDll_old.DllRegistrationException 如果注册控件失败,抛出此异常 + */ + public static WxpayFaceSDKDll_old.Dll instance() throws DllRegistrationException { + try { + return Native.load("WxpayFaceSDK", WxpayFaceSDKDll_old.Dll.class); + } catch (UnsatisfiedLinkError e) { + log.info("[WxpayFaceSDK][instance][微信扫脸动态库] SDK注册失败:{}", e.getMessage()); + throw new WxpayFaceSDKDll_old.DllRegistrationException("Failed to load WxpayFaceSDK library: ", e); + } + } + + /** + * 定义自定义异常类,用于表示注册控件时发生的错误 + */ + public static class DllRegistrationException extends Exception { + public DllRegistrationException(String message) { + super(message); + } + + public DllRegistrationException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * 定义接口映射本地库中的函数。 + */ + public interface Dll extends Library { + /** + * 调用人脸服务 + * 注意:这里的方法签名需要与DLL中的C函数签名一致 + * req和resp都是JSON字符串,需要转换为Pointer类型 + * char * -->String; char ** -->String[] + * int -->int; int* --> IntByReference + * @param reqBuf 请求参数(JSON字符串) + * @param reqSize 请求参数长度 + * @param pRespBuf 用来接收响应结果(JSON字符串)的char**指针 + * @param pRespSize 用来接收响应结果长度的unsigned int*指针 + * @return 如果成功返回0,失败则返回非0 + */ + //int wxpayCallFaceService(String reqBuf, int reqSize, String[] pRespBuf, IntByReference pRespSize); + int wxpayCallFaceService(String reqBuf, int reqSize, long[] pRespBuf, int[] pRespSize); + + + /** + * char ** -->String[] + * 释放人脸服务的响应字符串,调用wxpayCallFaceService成功后务必调用此函数释放内存 + * @param pRespBuf 指向响应结果(JSON字符串)的指针 + */ + void wxpayReleaseResponse(String[] pRespBuf); + } + +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/service/CallWxpayFaceService.java b/src/main/java/com/dpkj/modules/scanface/wx/service/CallWxpayFaceService.java new file mode 100644 index 0000000..fee65ef --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/service/CallWxpayFaceService.java @@ -0,0 +1,18 @@ +package com.dpkj.modules.scanface.wx.service; + +import com.dpkj.modules.scanface.wx.dll.WxpayFaceSDKDll; + +import java.io.UnsupportedEncodingException; +import java.util.List; + +public interface CallWxpayFaceService { + + /** + * 调用SDK微信刷脸服务 + * @param reqJson + * @param respJson + * @return + */ + String callWxpayFaceService(String reqJson, List respJson) throws UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException; + +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/service/WeChatPayFaceService.java b/src/main/java/com/dpkj/modules/scanface/wx/service/WeChatPayFaceService.java new file mode 100644 index 0000000..7063b1e --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/service/WeChatPayFaceService.java @@ -0,0 +1,53 @@ +package com.dpkj.modules.scanface.wx.service; + +import com.dpkj.modules.scanface.wx.dll.WxpayFaceSDKDll; +import com.dpkj.modules.scanface.wx.vo.WxFacePayAuthinfoResp; +import com.dpkj.modules.scanface.wx.vo.WxFacePayReq; +import com.dpkj.modules.scanface.wx.vo.WxFacePayResp; +import com.fasterxml.jackson.core.JsonProcessingException; + +import java.io.UnsupportedEncodingException; +import java.util.Map; + +/** + * @description: 调用微信刷脸方法 + * @author: Zhangxue + * @time: 2024/12/6 10:45 + */ +public interface WeChatPayFaceService { + + /** + * 调用方法 + * 1、程序启动时初始化initWxpayface;2、获取数据getWxpayfaceRawdata; + * @param wxFacePayReq + * @return + * @throws UnsupportedEncodingException + * @throws JsonProcessingException + */ + WxFacePayResp doWxPayIniMethod(WxFacePayReq wxFacePayReq) throws UnsupportedEncodingException, JsonProcessingException, WxpayFaceSDKDll.DllRegistrationException; + + + + + /** + * @description: 3、获取调用凭证get_wxpayface_authinfo(rawdata) + * @author: zhangxue + * @date: 2024/12/4 14:35 + * @Param rawData 初始化数据。由微信人脸SDK的接口返回。 + * @return: java.util.Map + */ + Map getWxFaceAuthInfoReqMap(String rawData) throws Exception; + + + /** + * 调用方法 + * 4、进行人脸识别getWxpayfaceCode(获取支付凭证) + * @param wxFacePayReq + * @return + * @throws UnsupportedEncodingException + * @throws JsonProcessingException + */ + WxFacePayAuthinfoResp doWxPayMethod(WxFacePayReq wxFacePayReq) throws UnsupportedEncodingException, JsonProcessingException, WxpayFaceSDKDll.DllRegistrationException; + + +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/service/impl/CallWxpayFaceServiceImpl.java b/src/main/java/com/dpkj/modules/scanface/wx/service/impl/CallWxpayFaceServiceImpl.java new file mode 100644 index 0000000..08ebb01 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/service/impl/CallWxpayFaceServiceImpl.java @@ -0,0 +1,78 @@ +package com.dpkj.modules.scanface.wx.service.impl; + +import com.dpkj.modules.scanface.wx.config.WxMpProperties; +import com.dpkj.modules.scanface.wx.dll.WxpayFaceSDKDll; +import com.dpkj.modules.scanface.wx.service.CallWxpayFaceService; +import com.sun.jna.Memory; +import com.sun.jna.Pointer; +import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.security.Security; +import java.util.List; + +/** + * @description: + * @author: Zhangxue + * @time: 2024/11/28 10:04 + */ +@Slf4j +@Service +public class CallWxpayFaceServiceImpl implements CallWxpayFaceService { + //将 BouncyCastleProvider 添加到 Provider 列表中 + static { + Security.addProvider(new BouncyCastleProvider()); + } + + //赋值 + private static WxMpProperties wxMpProperties; + public static void setWxMpConfiguration(WxMpProperties wxMpProperties) { + CallWxpayFaceServiceImpl.wxMpProperties = wxMpProperties; + } + + + /** + * 调用SDK微信刷脸服务 + * @param reqJson + * @param respJson + * @return + */ + @Override + public String callWxpayFaceService(String reqJson, List respJson) throws WxpayFaceSDKDll.DllRegistrationException { + //组装请求数据 + Pointer reqPointer = new Memory(reqJson.length() + 1); + reqPointer.setString(0, reqJson); + //请求数据长度 + int reqSize = reqPointer.getString(0).length(); + System.out.println("-----------调用微信刷脸请求数据----------"+reqPointer.getString(0)); + + //接收响应 + long[] pRespBuf = new long[1]; + int[] respSize = new int[1]; + String resStr = new String(); + + + // 调用本地方法 + WxpayFaceSDKDll.Dll dll = WxpayFaceSDKDll.instance(); + int result =dll.wxpayCallFaceService(reqPointer.getString(0), reqSize, pRespBuf, respSize); + if (result == 0) { + Pointer pointer = new Pointer(pRespBuf[0]); + byte[] byteArray = pointer.getByteArray(0, respSize[0]); + resStr = new String(byteArray, StandardCharsets.UTF_8); + + System.out.println("-----------调用服务成功结果: " + resStr); + //释放 + dll.wxpayReleaseResponse(new String[2]); + } else { + Pointer pointer = new Pointer(pRespBuf[0]); + byte[] byteArray = pointer.getByteArray(0, respSize[0]); + resStr = new String(byteArray, StandardCharsets.UTF_8); + System.err.println("-----------调用人脸服务失败: " +resStr); + } + return resStr; + } + + +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/service/impl/WeChatPayFaceServiceImpl.java b/src/main/java/com/dpkj/modules/scanface/wx/service/impl/WeChatPayFaceServiceImpl.java new file mode 100644 index 0000000..b9ad045 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/service/impl/WeChatPayFaceServiceImpl.java @@ -0,0 +1,385 @@ +package com.dpkj.modules.scanface.wx.service.impl; + +import com.dpkj.modules.scanface.ali.dll.AliScanFaceDll; +import com.dpkj.modules.scanface.wx.config.WechatUrlConfig; +import com.dpkj.modules.scanface.wx.config.WxMpProperties; +import com.dpkj.modules.scanface.wx.dll.WxpayFaceSDKDll; +import com.dpkj.modules.scanface.wx.service.CallWxpayFaceService; +import com.dpkj.modules.scanface.wx.service.WeChatPayFaceService; +import com.dpkj.modules.scanface.wx.util.WXPayUtil; +import com.dpkj.modules.scanface.wx.util.WxRandomUtils; +import com.dpkj.modules.scanface.wx.util.XmlUtils; +import com.dpkj.modules.scanface.wx.vo.WxFacePayAuthinfoResp; +import com.dpkj.modules.scanface.wx.vo.WxFacePayMicroPayResp; +import com.dpkj.modules.scanface.wx.vo.WxFacePayOrderResp; +import com.dpkj.modules.scanface.wx.vo.WxFacePayReq; +import com.dpkj.modules.scanface.wx.vo.WxFacePayResp; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sun.jna.Memory; +import com.sun.jna.Pointer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import com.github.binarywang.wxpay.constant.WxPayConstants; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * @description: 调用微信刷脸方法 + * @author: Zhangxue + * @time: 2024/12/6 10:45 + */ + +@Slf4j +@Service +public class WeChatPayFaceServiceImpl implements WeChatPayFaceService { + + @Autowired + private CallWxpayFaceService callWxpayFaceService; + + + //赋值 + private static WxMpProperties wxMpProperties; + + public static void setWxMpConfiguration(WxMpProperties wxMpProperties) { + WeChatPayFaceServiceImpl.wxMpProperties = wxMpProperties; + } + + + /** + * 调用方法 + * 1、程序启动时初始化initWxpayface;2、获取数据getWxpayfaceRawdata; + * + * @param wxFacePayReq + * @return + * @throws UnsupportedEncodingException + * @throws JsonProcessingException + */ + @Override + public WxFacePayResp doWxPayIniMethod(WxFacePayReq wxFacePayReq) throws JsonProcessingException, WxpayFaceSDKDll.DllRegistrationException { + // 构建请求参数的JSON字符串 + ObjectMapper mapper = new ObjectMapper(); + String reqJson = mapper.writeValueAsString(wxFacePayReq); + + // 创建一个Pointer来接收响应 + //List pResp= new ArrayList<>(); + //String result = callWxpayFaceService.callWxpayFaceService(req,pResp); + + //组装请求数据 + Pointer reqPointer = new Memory(reqJson.length() + 1); + reqPointer.setString(0, reqJson); + //请求数据长度 + int reqSize = reqPointer.getString(0).length(); + System.out.println("-----------调用微信刷脸请求数据----------" + reqPointer.getString(0)); + + //接收响应 + long[] pRespBuf = new long[1]; + int[] respSize = new int[1]; + String resStr = new String(); + + + // 调用本地方法 + WxpayFaceSDKDll.Dll dll = WxpayFaceSDKDll.instance(); + int result = dll.wxpayCallFaceService(reqPointer.getString(0), reqSize, pRespBuf, respSize); + log.info("[WeChatPayFaceServiceImpl][doWxPayIniMethod][95] DLL 返回码: {}", result); + if (result == 0) { + Pointer pointer = new Pointer(pRespBuf[0]); + byte[] byteArray = pointer.getByteArray(0, respSize[0]); + resStr = new String(byteArray, StandardCharsets.UTF_8); + + System.out.println("-----------调用服务成功结果: " + resStr); + //释放 + dll.wxpayReleaseResponse(new String[2]); + } else { + Pointer pointer = new Pointer(pRespBuf[0]); + byte[] byteArray = pointer.getByteArray(0, respSize[0]); + resStr = new String(byteArray, StandardCharsets.UTF_8); + System.err.println("-----------调用人脸服务失败: " + resStr); + } + + //响应结果 + WxFacePayResp wxFacePayResp = mapper.readValue(resStr, WxFacePayResp.class); + return wxFacePayResp; + } + + + /** + * @description: 3、获取调用凭证get_wxpayface_authinfo(rawdata) + * @author: zhangxue + * @date: 2024/12/4 14:35 + * @Param rawData 初始化数据。由微信人脸SDK的接口返回。 + * @return: java.util.Map + */ + @Override + public Map getWxFaceAuthInfoReqMap(String rawData) throws Exception { + SortedMap map = new TreeMap(); + //门店编号, 由商户定义, 各门店唯一。 + map.put("store_id", "1111111111111"); + //门店名称,由商户定义。(可用于展示);中文会导致调用失败 + String text = "刷脸设备一号"; + String storeName = Base64.getEncoder().encodeToString(text.getBytes()); + map.put("store_name", storeName); + //终端设备编号,由商户定义 + map.put("device_id", "test111"); + //初始化数据。由微信人脸SDK的接口返回。 + map.put("rawdata", rawData); + //商户号绑定的公众号/小程序 appid + map.put("appid", wxMpProperties.getMchConfig().getAppId()); + //商户号 + map.put("mch_id", wxMpProperties.getMchConfig().getMchId()); + //取当前时间,10位unix时间戳。 + long timeStampSec = System.currentTimeMillis() / 1000; + String timestamp = String.format("%010d", timeStampSec); + map.put("now", timestamp); + //版本号。固定为1 + map.put("version", "1"); + //随机字符串,不长于32位:工具类微信随机数 + map.put("nonce_str", WxRandomUtils.getNonceStr()); + //参数签名,使用MD5 + map.put("sign_type", "MD5"); + + //加密和生成微信v2指定的xml格式 + // WXPayUtil.createSign("UTF-8",map,wxMpProperties.getMchConfig().getKeyApi()); + String sign = WXPayUtil.generateSignedXml(map, wxMpProperties.getMchConfig().getKeyApi(), WxPayConstants.SignType.MD5); + log.info("--------构建微信获取刷脸授权XML参数:【{}】", sign); + + HttpHeaders headers = new HttpHeaders(); + HttpEntity stringHttpEntity = new HttpEntity(sign, headers); + RestTemplate restTemplate = new RestTemplate(); + //发起http调用 + ResponseEntity exchange = restTemplate.exchange(WechatUrlConfig.GET_WXPAYFACE_AUTHINFO, + HttpMethod.POST, + stringHttpEntity, + String.class); + log.info("--------发起http调用结果:【{}】", exchange.getBody()); + //转成map方便取值 + Map stringMap = XmlUtils.xmlParser(exchange.getBody(), "xml"); + + //转成返回对象 + ObjectMapper mapper = new ObjectMapper(); + WxFacePayAuthinfoResp wxFacePayAuthinfoResp = mapper.readValue(exchange.getBody(), WxFacePayAuthinfoResp.class); + log.info("--------发起http调用结果转换", wxFacePayAuthinfoResp.toString()); + + return stringMap; + } + + + /** + * 调用方法 + * 4、进行人脸识别getWxpayfaceCode(获取支付凭证) + * + * @param wxFacePayReq + * @return + * @throws UnsupportedEncodingException + * @throws JsonProcessingException + */ + @Override + public WxFacePayAuthinfoResp doWxPayMethod(WxFacePayReq wxFacePayReq) throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException { + // 构建请求参数的JSON字符串 + ObjectMapper mapper = new ObjectMapper(); + String req = mapper.writeValueAsString(wxFacePayReq); + + // 创建一个Pointer来接收响应 + List pResp = new ArrayList<>(); + String result = callWxpayFaceService.callWxpayFaceService(req, pResp); + + //响应结果 + WxFacePayAuthinfoResp authinfoResp = mapper.readValue(result, WxFacePayAuthinfoResp.class); + return authinfoResp; + } + + + //5、进行发起订单支付 + public Map createWxOrder() throws Exception { + SortedMap map = new TreeMap(); + //微信分配的公众账号ID(企业号corpid即为此appId) + map.put("appid", wxMpProperties.getMchConfig().getAppId()); + //商户号 + map.put("mch_id", wxMpProperties.getMchConfig().getMchId()); + //随机字符串,不长于32位:工具类微信随机数 + map.put("nonce_str", WxRandomUtils.getNonceStr()); + //签名, map.put("sign", ""); + //商品简单描述, + map.put("body", ""); + //商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一 + map.put("out_trade_no", ""); + //Int 订单总金额,单位为分,只能为整数 + map.put("total_fee", ""); + //支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP + map.put("spbill_create_ip", ""); + //扫码支付付款码,设备读取用户微信中的条码或者二维码信息 + map.put("auth_code", ""); + + //加密和生成微信v2指定的xml格式 + // WXPayUtil.createSign("UTF-8",map,wxMpProperties.getMchConfig().getKeyApi()); + String sign = WXPayUtil.generateSignedXml(map, wxMpProperties.getMchConfig().getKeyApi(), WxPayConstants.SignType.MD5); + log.info("--------构建微信获取刷脸授权XML参数:【{}】", sign); + + HttpHeaders headers = new HttpHeaders(); + HttpEntity stringHttpEntity = new HttpEntity(sign, headers); + RestTemplate restTemplate = new RestTemplate(); + //发起http调用 + ResponseEntity exchange = restTemplate.exchange(WechatUrlConfig.CREATEORDER, + HttpMethod.POST, + stringHttpEntity, + String.class); + log.info("--------5、进行发起订单支付:发起http调用结果:【{}】", exchange.getBody()); + //转成map方便取值 + Map stringMap = XmlUtils.xmlParser(exchange.getBody(), "xml"); + + //转成返回对象 + ObjectMapper mapper = new ObjectMapper(); + WxFacePayMicroPayResp microPayResp = mapper.readValue(exchange.getBody(), WxFacePayMicroPayResp.class); + log.info("--------5、进行发起订单支付,发起http调用结果转换", microPayResp.toString()); + + return stringMap; + } + + + //6、查询订单状态 + public Map orderquery() throws Exception { + SortedMap map = new TreeMap(); + //微信支付分配的公众账号ID(企业号corpid即为此appId) + map.put("appid", wxMpProperties.getMchConfig().getAppId()); + //微信支付分配的商户号 + map.put("mch_id", wxMpProperties.getMchConfig().getMchId()); + //微信的订单号,建议优先使用;必填:否 + map.put("transaction_id", ""); + //商户系统内部订单号;必填:否 + map.put("out_trade_no", ""); + //随机字符串,不长于32位:工具类微信随机数 + map.put("nonce_str", WxRandomUtils.getNonceStr()); + //参数签名,使用MD5 + map.put("sign_type", "MD5"); + //sign:通过签名算法计算得出的签名值 + String sign = WXPayUtil.generateSignedXml(map, wxMpProperties.getMchConfig().getKeyApi(), WxPayConstants.SignType.MD5); + log.info("--------构建微信获取刷脸授权XML参数:【{}】", sign); + + HttpHeaders headers = new HttpHeaders(); + HttpEntity stringHttpEntity = new HttpEntity("", headers); + RestTemplate restTemplate = new RestTemplate(); + //发起http调用 + ResponseEntity exchange = restTemplate.exchange(WechatUrlConfig.ORDERQUERY, + HttpMethod.POST, + stringHttpEntity, + String.class); + log.info("--------6、查询订单状态,发起http调用结果:【{}】", exchange.getBody()); + //转成map方便取值 + Map stringMap = XmlUtils.xmlParser(exchange.getBody(), "xml"); + + //转成返回对象 + ObjectMapper mapper = new ObjectMapper(); + WxFacePayOrderResp orderResp = mapper.readValue(exchange.getBody(), WxFacePayOrderResp.class); + log.info("--------6、查询订单状态,发起http调用结果转换", orderResp.toString()); + + return stringMap; + } + + + //7、撤销交易 + public Map reverse() throws Exception { + SortedMap map = new TreeMap(); + //微信分配的公众账号ID(企业号corpid即为此appId) + map.put("appid", wxMpProperties.getMchConfig().getAppId()); + //商户号 + map.put("mch_id", wxMpProperties.getMchConfig().getMchId()); + //微信的订单号,优先使用 + map.put("transaction_id", ""); + //商户系统内部的订单号,transaction_id、out_trade_no二选一,如果同时存在优先级:transaction_id> out_trade_no + map.put("out_trade_no", ""); + //随机字符串,不长于32位。 + map.put("nonce_str", WxRandomUtils.getNonceStr()); + + //加密和生成微信v2指定的xml格式 + String sign = WXPayUtil.generateSignedXml(map, wxMpProperties.getMchConfig().getKeyApi(), WxPayConstants.SignType.MD5); + log.info("--------构建微信XML参数:【{}】", sign); + + HttpHeaders headers = new HttpHeaders(); + HttpEntity stringHttpEntity = new HttpEntity("", headers); + RestTemplate restTemplate = new RestTemplate(); + //发起http调用 + ResponseEntity exchange = restTemplate.exchange(WechatUrlConfig.REVERSE, + HttpMethod.POST, + stringHttpEntity, + String.class); + log.info("--------7、撤销交易,发起http调用结果:【{}】", exchange.getBody()); + //转成map方便取值 + Map stringMap = XmlUtils.xmlParser(exchange.getBody(), "xml"); + + //转成返回对象 + ObjectMapper mapper = new ObjectMapper(); + WxFacePayOrderResp orderResp = mapper.readValue(exchange.getBody(), WxFacePayOrderResp.class); + log.info("--------7、撤销交易,发起http调用结果转换", orderResp.toString()); + + return stringMap; + } + + + //8、更新支付结果:通知人脸SDK更新支付结果 + public WxFacePayResp updateWxpayfacePayResult(String storeId, String authInfo, String payresult) throws Exception { + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("updateWxpayfacePayResult", "1", System.currentTimeMillis() / 1000); + //微信分配的公众账号ID(企业号corpid即为此appId) + wxFacePayReq.setAppid(wxMpProperties.getMchConfig().getAppId()); + //商户号 + wxFacePayReq.setMch_id(wxMpProperties.getMchConfig().getMchId()); + //门店编号 + wxFacePayReq.setStore_id(storeId); + //调用凭证 + wxFacePayReq.setAuthinfo(authInfo); + //支付结果。可取值:SUCCESS: 支付成功、ERROR: 支付失败 + wxFacePayReq.setPayresult(payresult); + + // 构建请求参数的JSON字符串 + ObjectMapper mapper = new ObjectMapper(); + String req = mapper.writeValueAsString(wxFacePayReq); + + // 创建一个Pointer来接收响应 + List pResp = new ArrayList<>(); + String result = callWxpayFaceService.callWxpayFaceService(req, pResp); + + //响应结果 + WxFacePayResp authinfoResp = mapper.readValue(result, WxFacePayResp.class); + return authinfoResp; + } + + + //9、停止刷脸支付 + public WxFacePayResp stopWxpayface(String storeId, String authInfo, String payresult) throws Exception { + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("stopWxpayface", "1", System.currentTimeMillis() / 1000); + //微信分配的公众账号ID(企业号corpid即为此appId) + wxFacePayReq.setAppid(wxMpProperties.getMchConfig().getAppId()); + //商户号 + wxFacePayReq.setMch_id(wxMpProperties.getMchConfig().getMchId()); + //调用凭证 + wxFacePayReq.setAuthinfo(authInfo); + + // 构建请求参数的JSON字符串 + ObjectMapper mapper = new ObjectMapper(); + String req = mapper.writeValueAsString(wxFacePayReq); + + // 创建一个Pointer来接收响应 + List pResp = new ArrayList<>(); + String result = callWxpayFaceService.callWxpayFaceService(req, pResp); + + //响应结果 + WxFacePayResp authinfoResp = mapper.readValue(result, WxFacePayResp.class); + return authinfoResp; + } + + +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/util/MD5Util.java b/src/main/java/com/dpkj/modules/scanface/wx/util/MD5Util.java new file mode 100644 index 0000000..7d44276 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/util/MD5Util.java @@ -0,0 +1,48 @@ +package com.dpkj.modules.scanface.wx.util; + +import java.security.MessageDigest; + +/** + * @description: MD5工具类 + * @author: Zhangxue + * @time: 2024/12/5 11:34 + */ +public class MD5Util { + + private static String byteArrayToHexString(byte b[]) { + StringBuffer resultSb = new StringBuffer(); + for (int i = 0; i < b.length; i++) + resultSb.append(byteToHexString(b[i])); + + return resultSb.toString(); + } + + private static String byteToHexString(byte b) { + int n = b; + if (n < 0) + n += 256; + int d1 = n / 16; + int d2 = n % 16; + return hexDigits[d1] + hexDigits[d2]; + } + + public static String MD5Encode(String origin, String charsetname) { + String resultString = null; + try { + resultString = new String(origin); + MessageDigest md = MessageDigest.getInstance("MD5"); + if (charsetname == null || "".equals(charsetname)) + resultString = byteArrayToHexString(md.digest(resultString + .getBytes())); + else + resultString = byteArrayToHexString(md.digest(resultString + .getBytes(charsetname))); + } catch (Exception exception) { + } + return resultString; + } + + private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; + +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/util/WXPayUtil.java b/src/main/java/com/dpkj/modules/scanface/wx/util/WXPayUtil.java new file mode 100644 index 0000000..4b4674f --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/util/WXPayUtil.java @@ -0,0 +1,115 @@ +package com.dpkj.modules.scanface.wx.util; + +import org.jdom2.Content; +import org.jdom2.Document; +import org.jdom2.Element; +import javax.xml.bind.DatatypeConverter; + + +import java.io.StringReader; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * @description: 生成签名 + * @author: Zhangxue + * @time: 2024/12/4 14:27 + */ +public class WXPayUtil { + + + /** + * 生成微信API请求的MD5签名,并返回XML格式的字符串 + * + * @param params 参数Map + * @param apiKey 微信API密钥 + * @return 微信v2指定的XML格式字符串 + */ + public static String generateSignedXml(Map params, String apiKey,String signType) { + // 将参数Map按照key的字典顺序排序 + Map sortedParams = new TreeMap<>(params); + + // 构建签名原文 + StringBuilder signSrc = new StringBuilder(); + for (Map.Entry entry : sortedParams.entrySet()) { + if (!"sign".equals(entry.getKey())) { + signSrc.append(entry.getKey()).append("=").append(entry.getValue()).append("&"); + } + } + signSrc.append("key=").append(apiKey); + + // 计算MD5签名 + String sign = getMD5(signSrc.toString(),signType).toUpperCase(); + + // 构建XML格式的字符串 + StringBuilder xmlBuilder = new StringBuilder(); + xmlBuilder.append(""); + for (Map.Entry entry : sortedParams.entrySet()) { + xmlBuilder.append("<").append(entry.getKey()).append(">").append(entry.getValue()).append(""); + } + xmlBuilder.append(""); + xmlBuilder.append(""); + + return xmlBuilder.toString(); + } + + /** + * 使用MD5算法计算字符串的签名 + * + * @param input 输入字符串 + * @return MD5签名字符串 + */ + private static String getMD5(String input,String signType) { + try { + MessageDigest md = MessageDigest.getInstance(signType); + byte[] digest = md.digest(input.getBytes()); + return DatatypeConverter.printHexBinary(digest).toLowerCase(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("MD5算法不支持", e); + } + } + + + public static Map xmlToMap(String xml) throws Exception { + Map map = new HashMap<>(); + Document document = new Document((List) new StringReader(xml)); + Element root = document.getRootElement(); + + for (Element child : root.getChildren()) { + map.put(child.getName(), child.getText()); + } + + return map; + } + + + //定义签名,微信根据参数字段的ASCII码值进行排序 加密签名,故使用SortMap进行参数排序 + public static String createSign(String characterEncoding, SortedMap parameters,String key){ + StringBuffer sb = new StringBuffer(); + Set es = parameters.entrySet(); + Iterator it = es.iterator(); + while(it.hasNext()) { + Map.Entry entry = (Map.Entry)it.next(); + String k = (String)entry.getKey(); + Object v = entry.getValue(); + if(null != v && !"".equals(v) + && !"sign".equals(k) && !"key".equals(k)) { + sb.append(k + "=" + v + "&"); + } + } + + sb.append("key=" + key);//最后加密时添加商户密钥,由于key值放在最后,所以不用添加到SortMap里面去,单独处理,编码方式采用UTF-8 + String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); + return sign; + } + + + +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/util/WxRandomUtils.java b/src/main/java/com/dpkj/modules/scanface/wx/util/WxRandomUtils.java new file mode 100644 index 0000000..aa69caa --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/util/WxRandomUtils.java @@ -0,0 +1,38 @@ +package com.dpkj.modules.scanface.wx.util; + +import java.security.SecureRandom; +import java.util.Random; + +public class WxRandomUtils { + + private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final Random RANDOM = new SecureRandom(); + + + /** + * 获取随机字符串 Nonce Str + * + * @return String 随机字符串 + */ + public static String getNonceStr() { + 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); + } + + /** + * 拼接参数 + * + * @return + */ + + private static String buildMessageTwo(String appId, long timestamp, String nonceStr, String packag) { + return appId + "\n" + + timestamp + "\n" + + nonceStr + "\n" + + packag + "\n"; + } +} + diff --git a/src/main/java/com/dpkj/modules/scanface/wx/util/XmlUtils.java b/src/main/java/com/dpkj/modules/scanface/wx/util/XmlUtils.java new file mode 100644 index 0000000..a01e581 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/util/XmlUtils.java @@ -0,0 +1,85 @@ +package com.dpkj.modules.scanface.wx.util; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.dom4j.Attribute; +import org.dom4j.Document; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +/** + * @description: xml解析为Map + * @author: Zhangxue + * @time: 2024/12/5 11:42 + */ +public class XmlUtils { + + /** + * xml解析器 + * + * @param xml xml字符串 + * @param filterRootEleName 匹配的根节点名 + * @return + * @返回map,格式:键:值 + * @返回属性值:->节点名=>属性名:属性值 + * @返回节点值:->节点名:值 + * @返回子节点属性值:->节点名->子节点名=>属性名:属性值 + * @返回子节点值:->节点名->子节点名:属性值 + */ + public static Map xmlParser(String xml, String filterRootEleName) { + Map retMap = new HashMap<>(); + //1.创建Reader对象 + try { + SAXReader reader = new SAXReader(); + InputStream targetStream = IOUtils.toInputStream(xml, StandardCharsets.UTF_8.name()); + Document document = reader.read(targetStream); + //3.获取根节点 + Element rootElement = document.getRootElement(); + StringBuilder builder = new StringBuilder(); + parser(rootElement, builder, filterRootEleName, retMap); + } catch (Exception ex) { + ex.printStackTrace(); + } + return retMap; + } + + /** + * xml递归解析器 + * + * @param ele 解析节点 + * @param eleKey 上级节点key + * @param retMap 返回map + */ + private static void parser(Element ele, StringBuilder eleKey, String firstEleName, Map retMap) { + StringBuilder builder = new StringBuilder(eleKey.toString()); + if (StringUtils.isEmpty(firstEleName) + || firstEleName.equals(ele.getName())) { + firstEleName = null; + builder.append("->" + ele.getName()); + if (org.apache.commons.lang3.StringUtils.isNotEmpty(org.apache.commons.lang3.StringUtils.stripToEmpty(ele.getData() + ""))) { + retMap.put(builder.toString(), org.apache.commons.lang3.StringUtils.stripToEmpty(ele.getData() + "")); + } + List attributes = ele.attributes(); + for (Attribute attribute : attributes) { + StringBuilder builder1 = new StringBuilder(builder.toString()); + builder1.append("=>" + attribute.getName()); + if (StringUtils.isNotEmpty(attribute.getValue())) { + retMap.put(builder1.toString(), attribute.getValue()); + } + } + } + Iterator iterator1 = ele.elementIterator(); + while (iterator1.hasNext()) { + Element eleChild = (Element) iterator1.next(); + parser(eleChild, builder, firstEleName, retMap); + } + } + + +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFaceAuthInfoReq.java b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFaceAuthInfoReq.java new file mode 100644 index 0000000..f0dd006 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFaceAuthInfoReq.java @@ -0,0 +1,20 @@ +package com.dpkj.modules.scanface.wx.vo; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.Map; + +/** + * @description: 硬件提供参数 + * @author: Zhangxue + * @time: 2024/11/28 10:01 + */ +@Data +@Accessors(chain = true) +public class WxFaceAuthInfoReq { + + private String deviceId; + + private Map rawData; +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayAuthinfoResp.java b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayAuthinfoResp.java new file mode 100644 index 0000000..d6153ed --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayAuthinfoResp.java @@ -0,0 +1,124 @@ +package com.dpkj.modules.scanface.wx.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * @description: 3、获取调用凭证返回值 + * @author: Zhangxue + * @time: 2024/12/6 9:23 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class WxFacePayAuthinfoResp { + + /** + * 错误码 + */ + private String return_code; + + /** + * 对错误码的描述 + */ + private String return_msg; + + /** + * 版本号 + */ + private String version; + + /** + * 初始化数据。用于接口调用获取authinfo + */ + private String rawdata; + + + /** + * 3、获取调用凭证 + * 必填:是 + * SDK调用凭证。用于调用SDK的人脸识别接口。 + */ + private String authinfo; + + /** + * 3、获取调用凭证 + * 必填:否 + * authinfo的有效时间, 单位秒。 + */ + private int expires_in; + + /** + * 3、获取调用凭证 + * 必填:是 + * 随机字符串 + */ + private String nonce_str; + + /** + * 3、获取调用凭证 + * 必填:是 + * 响应结果签名 + */ + private String sign; + + + /** + * 3、获取调用凭证 + * 必填:是 + * 公众号 + */ + private String appid; + + /** + * 3、获取调用凭证 + * 必填:否 + * 商户号 + */ + private String mch_id; + + /** + * 3、获取调用凭证 + * 必填:否 + * 子商户公众账号ID(服务商模式) + */ + private String sub_appid; + + /** + * 3、获取调用凭证 + * 必填:是 + * 子商户号(服务商模式) + */ + private String sub_mch_id; + + /** + * 4、进行人脸识别 + * 是否必填:S + * 人脸凭证, 用于刷脸支付。 + */ + private String face_code; + + /** + * 4、进行人脸识别 + * 是否必填:S + * openid + */ + private String openid; + + /** + * 4、进行人脸识别 + * 是否必填:否 + * 子商户号下的openid(服务商模式) + */ + private String sub_openid; + + /** + * 4、进行人脸识别 + * 是否必填:否 + * 用户身份信息查询凭证 + */ + private String face_sid; +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayMicroPayResp.java b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayMicroPayResp.java new file mode 100644 index 0000000..b879b48 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayMicroPayResp.java @@ -0,0 +1,95 @@ +package com.dpkj.modules.scanface.wx.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * @description: 5、进行发起订单支付 返回值 + * @author: Zhangxue + * @time: 2024/12/6 9:23 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class WxFacePayMicroPayResp extends WxFacePayOrderResp{ + +/************当return_code 和result_code都为SUCCESS的时,还会包括以下字段:*******************/ + + /** + * 用户在商户appid 下的唯一标识 + */ + private String openid; + + /** + * 用户是否关注公众账号,仅在公众账号类型支付有效,取值范围:Y或N;Y-关注;N-未关注 + */ + private String is_subscribe; + + /** + * MICROPAY 付款码支付 + */ + private String trade_type; + + /** + * 银行类型,采用字符串类型的银行标识 + */ + private String bank_type; + + /** + * 符合ISO 4217标准的三位字母代码,默认人民币:CNY + */ + private String fee_type; + + /** + * 订单总金额,单位为分,只能为整数 + */ + private Integer total_fee; + + /** + * 当订单使用了免充值型优惠券后返回该参数,应结订单金额=订单金额-免充值优惠券金额。 + */ + private Integer settlement_total_fee; + + /** + * “代金券”金额<=订单金额,订单金额-“代金券”金额=现金支付金额, + */ + private Integer coupon_fee; + + /** + * 符合ISO 4217标准的三位字母代码,默认人民币:CNY, + */ + private String cash_fee_type; + + /** + * 订单现金支付金额, + */ + private Integer cash_fee; + + /** + * 微信支付订单号 + */ + private String transaction_id; + + /** + * 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一。 + */ + private String out_trade_no; + + /** + * 商家数据包,原样返回 + */ + private String attach; + + /** + * 订单生成时间,格式为yyyyMMddHHmmss + */ + private String time_end; + + /** + * 新增返回,单品优惠功能字段 + */ + private String promotion_detail; +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayOrderResp.java b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayOrderResp.java new file mode 100644 index 0000000..bc7c730 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayOrderResp.java @@ -0,0 +1,136 @@ +package com.dpkj.modules.scanface.wx.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * @description: 5、进行发起订单支付 返回值;6、查询订单状态 + * @author: Zhangxue + * @time: 2024/12/10 15:30 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class WxFacePayOrderResp { + + /** + * 错误码 + * 文档:https://pay.weixin.qq.com/wiki/doc/wxfacepay/develop/windows/facepay.html#_5%E3%80%81%E8%BF%9B%E8%A1%8C%E5%8F%91%E8%B5%B7%E8%AE%A2%E5%8D%95%E6%94%AF%E4%BB%98 + */ + private String return_code; + + /** + * 对错误码的描述 + */ + private String return_msg; + + /** + * 调用接口提交的公众账号ID + */ + private String appid; + + /** + * 调用接口提交的商户号 + */ + private String mch_id; + + /** + * 调用接口提交的终端设备号, + */ + private String device_info; + + /** + * 微信返回的随机字符串, + */ + private String nonce_str; + + /** + * 微信返回的签名, + */ + private String sign; + + /** + * SUCCESS/FAIL + */ + private String result_code; + + /** + * 详细参见错误列表 + */ + private String err_code; + + /** + * 错误返回的信息描述 + */ + private String err_code_des; + + /** + * SUCCESS—支付成功, + * REFUND—转入退款当result_code为FAIL时返回错误代码, + * NOTPAY—未支付当result_code为FAIL时返回错误描述, + * CLOSED—已关闭,REVOKED—已撤销(付款码支付), + * USERPAYING--用户支付中(付款码支付), + * PAYERROR--支付失败(其他原因,如银行返回失败), + */ + private String trade_state; + + /** + * 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。 + */ + private String out_trade_no; + + /** + * 附加数据,原样返回 + */ + private String attach; + + /** + * 用户在商户appid下的唯一标识 + */ + private String openid; + + /** + * 用户是否关注公众账号,Y-关注,N-未关注 + */ + private String is_subscribe; + + /** + * 调用接口提交的交易类型,取值如下:JSAPI,NATIVE,APP,MICROPAY + */ + private String trade_type; + + /** + * 银行类型,采用字符串类型的银行标识 + */ + private String bank_type; + + /** + * 订单总金额,单位为分 + */ + private Integer total_fee; + + /** + * 现金支付金额订单现金支付金额 + */ + private Integer cash_fee; + + /** + * 微信支付订单号 + */ + private String transaction_id; + + /** + * 订单支付时间,格式为yyyyMMddHHmmss,如20091225091010 + */ + private String time_end; + + /** + * 对当前查询订单状态的描述和下一步操作的指引 + */ + private int trade_state_desc; + + +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayReq.java b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayReq.java new file mode 100644 index 0000000..0bf421f --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayReq.java @@ -0,0 +1,154 @@ +package com.dpkj.modules.scanface.wx.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * @description:WxFacePayReq + * @author: Zhangxue + * @time: 2024/11/29 17:18 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class WxFacePayReq { + + /** + * 命令字,即各接口名: + * initWxpayface + * getWxpayfaceRawdata + * getWxpayfaceCode + * releaseWxpayface + */ + private String cmd; + + /** + * 版本号, 固定填写1 + */ + private String version; + + /** + * 取当前时间的10位unix时间戮 + */ + private long now; + + /** + * 4、进行人脸识别 + * 必填:是 + * 商户号绑定的公众号/小程序 appid + */ + private String appid; + + /** + * 4、进行人脸识别 + * 必填:是 + * 商户号 + */ + private String mch_id; + + /** + * 4、进行人脸识别 + * 必填:否 + * 子商户绑定的公众号/小程序 appid(可不填) + */ + private String sub_appid; + + /** + * 4、进行人脸识别 + * 必填:否 + * 子商户号(非服务商模式不填) + */ + private String sub_mch_id; + + /** + * 4、进行人脸识别 + * 必填:是 + * 门店编号 + */ + private String store_id; + + /** + * 4、进行人脸识别 + * 必填:否 + * 通过getWxpayfaceUserInfo获取的openid,传入后可使用快捷支付模式。1.24版本以上支持该参数 + */ + private String openid; + + /** + * 4、进行人脸识别 + * 必填:否 + * 目标face_code类型,可选值:"1",刷卡付款码:18位数字,通过「付款码支付/被扫支付」接口完成支付, + * 如果不填写则默认为"0",人脸付款码:数字字母混合,通过「刷脸支付」接口完成支付。1.16版本以上支持该参数 + */ + private String face_code_type; + + /** + * 4、进行人脸识别 + * 必填:否 + * 订单金额(数字), 单位分,该字段在在face_code_type为"1"时可不填,为"0"时必填 + */ + private String total_fee; + + /** + * 4、进行人脸识别 + * 必填:否 + * 商户订单号,须与调用支付接口时字段一致,该字段在在face_code_type为"1"时可不填,为"0"时必填 + */ + private String out_trade_no; + + /** + * 4、进行人脸识别 + * 必填:是 + * FACEPAY: 人脸凭证(face_code),用于刷脸支付 FACE_AUTH: 实名认证(需联系微信支付开通权限) + */ + private String face_authtype; + + /** + * 4、进行人脸识别 + * 必填:是 + * 调用凭证 + */ + private String authinfo; + + /** + * 4、进行人脸识别 + * 必填:否 + * 指定刷脸界面显示的屏幕编号。 + * 编号1为主屏幕,其余屏幕按系统设置中的顺序从2开始编号,常用场景举例:双屏机器上传"2"即可显示在副屏上。如果不填写则默认显示在主屏幕。1.18版本以上支持该参数 + */ + private String screen_index; + + /** + * 4、进行人脸识别 + * 必填:否 + * 设置不接受外接键盘输入,可选值:"1",禁用。1.23版本以上支持该参数 + */ + private String disable_keyboard; + + /** + * 4、进行人脸识别 + * 必填:否 + * 设置刷脸窗口以无焦点方式启动,可选值:"1",无焦点启动窗口。1.26版本以上支持该参数 + */ + private String use_window_nofocus; + + + /** + * 8、更新支付结果 + * 支付结果。可取值:SUCCESS: 支付成功、ERROR: 支付失败 + */ + private String payresult; + + + + //构建 + public WxFacePayReq(String cmd, String version, long now) { + this.cmd= cmd; + this.version =version; + this.now = now; + } + +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayResp.java b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayResp.java new file mode 100644 index 0000000..5b53c03 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayResp.java @@ -0,0 +1,40 @@ +package com.dpkj.modules.scanface.wx.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * @description:WxFacePayResp 调用微信刷脸的返回值 + * @author: Zhangxue + * @time: 2024/11/29 17:18 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class WxFacePayResp { + /** + * 错误码 + */ + private String return_code; + + /** + * 对错误码的描述 + */ + private String return_msg; + + /** + * 版本号 + */ + private String version; + + /** + * 2、获取数据 + * 初始化数据。用于接口调用获取authinfo + */ + private String rawdata; + + +}