diff --git a/pom.xml b/pom.xml index 0aa86e1..a1eb3f7 100644 --- a/pom.xml +++ b/pom.xml @@ -135,6 +135,26 @@ validation-api 2.0.1.Final + + + + com.github.binarywang + weixin-java-pay + 4.7.0 + + + + + org.jdom + jdom2 + 2.0.6 + + + + jakarta.xml.bind + jakarta.xml.bind-api + + diff --git a/src/main/java/com/dpkj/common/constant/WxConstant.java b/src/main/java/com/dpkj/common/constant/WxConstant.java new file mode 100644 index 0000000..5799d96 --- /dev/null +++ b/src/main/java/com/dpkj/common/constant/WxConstant.java @@ -0,0 +1,42 @@ +package com.dpkj.common.constant; + +/** + * @description: + * @author: Zhangxue + * @time: 2025/5/28 11:04 + */ +public interface WxConstant { + + /** + * 门店编号, 由商户定义, 各门店唯一。 + * TODO 修改 + */ + String STORE_ID = "1111111111111"; + + //门店名称,由商户定义。(可用于展示);中文会导致调用失败 + String STORE_TEXT = "device8998No1"; + + //终端设备编号,由商户定义 + String DEVICE_ID = "device8998"; + + //版本号。固定为1 + String VERSION = "1"; + + //参数签名,使用MD5 + String SING_TYPE = "MD5"; + + /** + * 支付类型 + */ + String FACEPAY = "FACEPAY"; + + /** + * 结果状态 成功 + */ + String STATE_SUCCESS = "SUCCESS"; + + /** + * 结果状态 失败 + */ + String STATE_FAIL = "FAIL"; +} diff --git a/src/main/java/com/dpkj/modules/scanface/ali/config/AliFaceConfig.java b/src/main/java/com/dpkj/modules/scanface/ali/config/AliFaceConfig.java new file mode 100644 index 0000000..36ba73e --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/ali/config/AliFaceConfig.java @@ -0,0 +1,52 @@ +package com.dpkj.modules.scanface.ali.config; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @description: 支付宝刷脸配置信息 + * @author: Zhangxue + * @time: 2025/4/16 20:23 + */ +@Data +@Component +@ConfigurationProperties(prefix = "dpkj.ali.face") +public class AliFaceConfig { + + /** + * ABCP_SDK部署后DLL文件路径 + */ + private String dllPath; + + /** + * 应用管理-appid + */ + private String appId; + + /** + * IOT应用版本 + */ + private String appVersion; + + /** + * 签约商家的 PID,以 2088 开头,企业主体 + */ + private String merchantId; + + /** + * 商家机具终端编号,每台设备保持唯一 + */ + private String deviceNum; + + /** + * 服务商的 PID + */ + private String partnerId; + + /** + * 核心入参 serviceId + */ + private String serviceId; +} diff --git a/src/main/java/com/dpkj/modules/scanface/ali/constants/AliFaceConstants.java b/src/main/java/com/dpkj/modules/scanface/ali/constants/AliFaceConstants.java new file mode 100644 index 0000000..dda4f23 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/ali/constants/AliFaceConstants.java @@ -0,0 +1,25 @@ +package com.dpkj.modules.scanface.ali.constants; + +/** + * @description: 支付宝刷脸模块常量 + * @author: Zhangxue + * @time: 2025/4/24 9:25 + */ +public final class AliFaceConstants { + + /** + * 刷脸去初始化服务 + * https://ant-iot.alipay.com/open/iotbpaas/service/serviceManage/serviceDetail + */ + public static final String SMILEVERIFYNIN_V1 = "BPaaSSmileVerifyNonInitV1"; + + + + /** + * 刷脸支付服务 + * ... + */ + public static final String SMILEPAY = "BPaaSFaceSmilePayVerify"; + + +} diff --git a/src/main/java/com/dpkj/modules/scanface/ali/controller/AliScanFaceController.java b/src/main/java/com/dpkj/modules/scanface/ali/controller/AliScanFaceController.java new file mode 100644 index 0000000..895c899 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/ali/controller/AliScanFaceController.java @@ -0,0 +1,114 @@ +package com.dpkj.modules.scanface.ali.controller; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.http.ContentType; +import cn.hutool.http.Header; +import cn.hutool.http.HttpRequest; +import com.alibaba.fastjson.JSONObject; +import com.dpkj.common.vo.Result; +import com.dpkj.modules.scanface.ali.config.AliFaceConfig; +import com.dpkj.modules.scanface.ali.constants.AliFaceConstants; +import com.dpkj.modules.scanface.ali.dll.AbcpInvoke; +import com.dpkj.modules.scanface.ali.service.IAliScanFaceService; +import com.dpkj.modules.scanface.ali.vo.AliOrderVo; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.io.File; +import java.util.Map; + +/** + * @Auther: 萧道子 + * @Date: 2025/4/16 16:47 + * @Description: + */ +@Slf4j +@AllArgsConstructor +@RestController +@RequestMapping("/scanface/ali") +public class AliScanFaceController { + + private final IAliScanFaceService aliScanFaceService; + + @Resource + private AliFaceConfig aliFaceConfig; + + + /** + * 完整的统一的支付宝刷脸支付 + * 1、调用硬件刷脸获取到ftoken + * 2、调用后端的支付宝支付、存入hispay + * @param aliOrderVo + * @return + * @throws Exception + */ + @PostMapping("/aliFacePay") + public Result aliFacePay(@RequestBody AliOrderVo aliOrderVo) throws Exception { + return aliScanFaceService.aliFacePay(aliOrderVo); + } + + + /** + * ABCP初始化, + * 文档地址:https://opendocs.alipay.com/iot/05e9ye + * ABCP初始化:商家App 启动时通过调用接口 abcp_init 执行 ABCP SDK 的初始化,初始化动作只需启动时调用一次,初始化的时候需保证机具网络处于联网状态。 + * + * @return + */ + @GetMapping("iniAbcp") + Result iniAbcp() { + return aliScanFaceService.iniAbcpAbsolute(); + } + + /** + * ABCP服务调用 刷脸去初始化服务,获取ftoken + * 文档地址:https://opendocs.alipay.com/iot/05e9ye + * 初始化成功后,商家App 可根据业务需求,调用接口 abcp_start_service 执行 ABCP 所提供的服务。 + * 上述服务调用过程可重复多次调用,通过传入不同的 service_code 来调用不同的 ABCP 服务。 + * + * @return + */ + @GetMapping("abcpStartServiceIni") + Result abcpStartServiceIni() { + aliScanFaceService.iniAbcpAbsolute(); + + //参数 + JSONObject zolozConfig = new JSONObject().fluentPut("installAngle",90); + JSONObject params = new JSONObject() + .fluentPut("serviceId", aliFaceConfig.getServiceId()) + .fluentPut("zolozConfig",zolozConfig); + String json = params.toJSONString(); + + String service_code = AliFaceConstants.SMILEVERIFYNIN_V1; //调用的组件编码:初始化 + return aliScanFaceService.startServiceIni(json, service_code); + } + + + /** + * ABCP服务停用 + * 终止正在进行中的某次(service_code + traceId)或某类(service_code)服务调用 + * 如处于支付环节,此次服务调用将不接受停止。 + * @param traceId + * @return + */ + @PostMapping("stopService") + Result stopService(@RequestParam String traceId) { + aliScanFaceService.iniAbcpAbsolute(); + //参数 + JSONObject params = new JSONObject() + .fluentPut("traceId",traceId); + //.fluentPut("service_code",AliFaceConstants.SMILEVERIFYNIN_V1); // + String json = params.toJSONString(); + + String service_code = AliFaceConstants.SMILEVERIFYNIN_V1; //调用的组件编码 + return aliScanFaceService.stopService(json, service_code); + } +} diff --git a/src/main/java/com/dpkj/modules/scanface/ali/dll/AbcpInvoke.java b/src/main/java/com/dpkj/modules/scanface/ali/dll/AbcpInvoke.java new file mode 100644 index 0000000..bf8b6c3 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/ali/dll/AbcpInvoke.java @@ -0,0 +1,288 @@ +package com.dpkj.modules.scanface.ali.dll; + +import com.dpkj.common.vo.Result; +import com.sun.jna.Callback; +import com.sun.jna.Native; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@Slf4j +public class AbcpInvoke { + + public interface CallbackStr { + public void OnString(String result); + } + + public interface CallbackRsp { + public void OnProcess(int code, String subCode, String subMsg, String result); + + public void OnFinish(int code, String subCode, String subMsg, String result); + } + + + public static void SetAPIPathFile(String fileDylib) { + if (msAbcpNativeDll == null) { + File file = new File(fileDylib); + if (file.exists()) { + try { + msAbcpNativeDll = (AbcpNativeDll) Native.load(fileDylib, AbcpNativeDll.class); + } catch (Throwable e) { + msAbcpNativeDll = null; + log.error("## 本地库未加载,返回错误 ERROR :{},{} ", fileDylib, e.getMessage()); + } + } else { + log.error("## ERROR : dylib NOT exist ## %s %n", fileDylib); + } + } + } + + public static void AbcpInit(String appId, String appVersion, String json, CallbackRsp callback) { + if (msAbcpNativeDll == null) { + callback.OnFinish(3, "E69001", "[java] api load failed!", ""); + return; + } + + appId = FixNullString(appId); // involved null ptr! + appVersion = FixNullString(appVersion); // involved null ptr! + json = FixNullString(json); // involved null ptr! + msLock.lock(); + Integer seedId = msSeedId++; + msMapAbcpReponse.put(seedId, callback); + msLock.unlock(); + try { + msAbcpNativeDll.bp_api_set_isv_lan(3);// 0:cpp | 1:cshap | 2:jni | 3:jna + } catch (Throwable e) { + log.error("## ERROR ## %s %n", e.getMessage()); + } + try { + msAbcpNativeDll.abcp_init(seedId, appId, appVersion, json, msABCPProcess, msABCPFinish); + } catch (Throwable e) { + callback.OnFinish(3, "E69001", e.getMessage(), ""); + log.error("## ERROR ## %s %n", e.getMessage()); + } + } + + public static void AbcpGetMetaInfo(String appId, String json, CallbackRsp callback) { + if (msAbcpNativeDll == null) { + callback.OnFinish(3, "E69001", "[java] api load failed!", ""); + return; + } + appId = FixNullString(appId); // involved null ptr! + json = FixNullString(json); // involved null ptr! + msLock.lock(); + Integer seedId = msSeedId++; + msMapAbcpReponse.put(seedId, callback); + msLock.unlock(); + + try { + msAbcpNativeDll.abcp_get_meta_info(seedId, appId, json, msABCPProcess, msABCPFinish); + } catch (Throwable e) { + callback.OnFinish(3, "E69001", e.getMessage(), ""); + log.error("## ERROR ## %s %n", e.getMessage()); + } + } + + public static void AbcpStartService(String appId, String serviceCode, String json, CallbackRsp callback) { + if (msAbcpNativeDll == null) { + callback.OnFinish(3, "E69001", "[java] api load failed!", ""); + return; + } + appId = FixNullString(appId); // involved null ptr! + serviceCode = FixNullString(serviceCode); // involved null ptr! + json = FixNullString(json); // involved null ptr! + + Integer seedId; + msLock.lock(); + try { + seedId = msSeedId++; + msMapAbcpReponse.put(seedId, callback); + log.info("注册回调: seedId={}", seedId); + } finally { + msLock.unlock(); + } + + try { + log.info("调用 abcp_start_service: seedId={}, appId={}, serviceCode={}, jsonLength={}", seedId, appId, serviceCode, json.length()); + msAbcpNativeDll.abcp_start_service(seedId, appId, serviceCode, json, msABCPProcess, msABCPFinish); + } catch (Throwable e) { + log.error("##本地方法调用异常 ERROR ## %s %n", e.getMessage()); + callback.OnFinish(3, "E69001", e.getMessage(), ""); + } + } + + public static void AbcpTaskStopService(String appId, String serviceCode, String json, CallbackRsp callback) { + if (msAbcpNativeDll == null) { + callback.OnFinish(3, "E69001", "[java] api load failed!", ""); + return; + } + appId = FixNullString(appId); // involved null ptr! + serviceCode = FixNullString(serviceCode); // involved null ptr! + json = FixNullString(json); // involved null ptr! + msLock.lock(); + Integer seedId = msSeedId++; + msMapAbcpReponse.put(seedId, callback); + msLock.unlock(); + + try { + log.info("[调用abcp_stop_service]:seedId={}, appId={}, serviceCode={}, json={}", seedId, appId, serviceCode, json); + msAbcpNativeDll.abcp_stop_service(seedId, appId, serviceCode, json, msABCPProcess, msABCPFinish); + } catch (Throwable e) { + callback.OnFinish(3, "E69001", e.getMessage(), ""); + log.error("## ERROR ## %s %n", e.getMessage()); + } + } + + public static void AbcpZimIdInitTest(String metainfo, String smileType, String smileFlag, CallbackStr callback) { + if (msAbcpNativeDll == null) { + callback.OnString("{}"); + return; + } + metainfo = FixNullString(metainfo); // involved null ptr! + smileType = FixNullString(smileType); // involved null ptr! + smileFlag = FixNullString(smileFlag); // involved null ptr! + msLock.lock(); + Integer seedId = msSeedId++; + msMapAbcpString.put(seedId, callback); + msLock.unlock(); + + try { + msAbcpNativeDll.abcp_zimid_init_for_test(seedId, metainfo, smileType, smileFlag, msABCPString); + } catch (Throwable e) { + callback.OnString("{}"); + log.error("## ERROR ## %s %n", e.getMessage()); + } + } + + public static void AbcpTaskSubscribeEvent(String appId, String json, CallbackRsp callback) { + if (msAbcpNativeDll == null) { + callback.OnFinish(3, "E69001", "[java] api load failed!", ""); + return; + } + appId = FixNullString(appId); // involved null ptr! + json = FixNullString(json); // involved null ptr! + msLock.lock(); + Integer seedId = msSeedId++; + msMapAbcpReponse.put(seedId, callback); + msLock.unlock(); + + try { + msAbcpNativeDll.abcp_call(seedId, appId, "SubscribeEvent", json, msABCPProcess, msABCPFinish); + } catch (Throwable e) { + callback.OnFinish(3, "E69001", e.getMessage(), ""); + log.error("## ERROR ## %s %n", e.getMessage()); + } + } + + public static void AbcpTaskUnsubscribeEvent(String appId, String json, CallbackRsp callback) { + if (msAbcpNativeDll == null) { + callback.OnFinish(3, "E69001", "[java] api load failed!", ""); + return; + } + appId = FixNullString(appId); // involved null ptr! + json = FixNullString(json); // involved null ptr! + msLock.lock(); + Integer seedId = msSeedId++; + msMapAbcpReponse.put(seedId, callback); + msLock.unlock(); + + try { + msAbcpNativeDll.abcp_call(seedId, appId, "UnsubscribeEvent", json, msABCPProcess, msABCPFinish); + } catch (Throwable e) { + callback.OnFinish(3, "E69001", e.getMessage(), ""); + log.error("## ERROR ## %s %n", e.getMessage()); + } + } + + // ===================================== + private static AbcpNativeDll msAbcpNativeDll = null; + private static ABCPProcess msABCPProcess = new ABCPProcess(); + private static ABCPFinish msABCPFinish = new ABCPFinish(); + private static ABCPString msABCPString = new ABCPString(); + private static Lock msLock = new ReentrantLock(); + private static Integer msSeedId = Integer.valueOf(256); + private static Map msMapAbcpString = new HashMap(); + private static Map msMapAbcpReponse = new HashMap(); + + private static String FixNullString(String str) { + if (str.equals(null)) { + return ""; + } + return str; + } + + public static Integer getMsSeedId() { + return msSeedId; + } + + // ===================================== + public interface FuncProcess extends Callback { + public void onProcess(int seed_id, int code, String subCode, String subMsg, String result); + } + + public interface FuncFinish extends Callback { + public void onFinish(int seed_id, int code, String subCode, String subMsg, String result); + } + + public interface FuncString extends Callback { + public void onString(int seed_id, String result); + } + + public static class ABCPProcess implements FuncProcess { + public void onProcess(int seed_id, int _code, String subCode, String subMsg, String result) { + log.info("[ABCPProcess][onProcess][243][onProcess:][seed_id:{}],[_code:{}],[subMsg:{}],[result:{}]", seed_id, _code, subCode, subMsg, result); + Integer seedId = Integer.valueOf(seed_id); + CallbackRsp callback = null; + msLock.lock(); + if (msMapAbcpReponse.containsKey(seedId)) { + callback = msMapAbcpReponse.get(seedId); + } + msLock.unlock(); + + if (callback != null) { + Integer code = Integer.valueOf(_code); + callback.OnProcess(code, subCode, subMsg, result); + } + } + } + + public static class ABCPFinish implements FuncFinish { + public void onFinish(int seed_id, int _code, String subCode, String subMsg, String result) { + //log.info("[ABCPFinish][onFinish][261][ABCPFinish数据:][seed_id:{}],[_code:{}],[subMsg:{}],[result:{}]", seed_id, _code, subCode, subMsg, result); + Integer seedId = Integer.valueOf(seed_id); + CallbackRsp callback = null; + + msLock.lock(); + if (msMapAbcpReponse.containsKey(seedId)) { + callback = msMapAbcpReponse.remove(seedId); + } + msLock.unlock(); + + if (callback != null) { + Integer code = Integer.valueOf(_code); + callback.OnFinish(code, subCode, subMsg, result); + } + } + } + + public static class ABCPString implements FuncString { + public void onString(int seed_id, String result) { + Integer seedId = Integer.valueOf(seed_id); + CallbackStr callback = null; + + msLock.lock(); + if (msMapAbcpString.containsKey(seedId)) { + callback = msMapAbcpString.remove(seedId); + } + msLock.unlock(); + + if (callback != null) { + callback.OnString(result); + } + } + } +} diff --git a/src/main/java/com/dpkj/modules/scanface/ali/dll/AbcpNativeDll.java b/src/main/java/com/dpkj/modules/scanface/ali/dll/AbcpNativeDll.java new file mode 100644 index 0000000..37f3bf0 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/ali/dll/AbcpNativeDll.java @@ -0,0 +1,22 @@ +package com.dpkj.modules.scanface.ali.dll; + +import com.sun.jna.Callback; +import com.sun.jna.Library; + +public interface AbcpNativeDll extends Library { + void bp_api_set_isv_lan(Integer type_isv_language);// 0:cpp | 1:cshap | 2:jni | 3:jna + + void abcp_init(Integer arg, String app_id, String app_version, String json_param, Callback on_process, Callback on_finish); + + void abcp_start_service(Integer arg, String app_id, String service_code, String json_param, Callback on_process, Callback on_finish); + + void abcp_stop_service(Integer arg, String app_id, String service_code, String json_param, Callback on_process, Callback on_finish); + + void abcp_get_meta_info(Integer arg, String app_id, String json_param, Callback on_process, Callback on_finish); + + void abcp_stop_smile(Integer arg, String app_id, String json_param, Callback on_process, Callback on_finish); + + void abcp_zimid_init_for_test(Integer arg, String zimmetainfo, String smileType, String smileFlag, Callback callback); + + void abcp_call(Integer arg, String app_id, String call_method, String json_param, Callback on_process, Callback on_finish); +} diff --git a/src/main/java/com/dpkj/modules/scanface/ali/service/IAliScanFaceService.java b/src/main/java/com/dpkj/modules/scanface/ali/service/IAliScanFaceService.java new file mode 100644 index 0000000..9de13eb --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/ali/service/IAliScanFaceService.java @@ -0,0 +1,47 @@ +package com.dpkj.modules.scanface.ali.service; + +import com.alibaba.fastjson.JSONObject; +import com.dpkj.common.vo.Result; +import com.dpkj.modules.scanface.ali.vo.AliOrderVo; + +public interface IAliScanFaceService { + + /** + * 完整的统一的支付宝刷脸支付 + * 1、调用硬件刷脸获取到ftoken + * 2、调用后端的支付宝支付、存入hispay记录 + * @param aliOrderVo + * @return + * @throws Exception + */ + Result aliFacePay(AliOrderVo aliOrderVo); + + /** + * 初始化,调用阿里的ABCP_SDK部署出来的代码中的API文件 + * https://opendocs.alipay.com/iot/05e9ye#ABCP%E5%88%9D%E5%A7%8B%E5%8C%96 + */ + Result iniAbcpAbsolute(); + + + + /** + * ABCP服务调用 刷脸去初始化服务,获取ftoken + * 接受调用者传入的参数信息,执行指定服务(service_code),然后通过回调返回服务结果。 + * https://opendocs.alipay.com/iot/05e9ye#ABCP%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8 + * @param json 组装参数 + * @param service_code 所要调用的组件编码 + * @return + */ + Result startServiceIni(String json,String service_code); + + + /** + * ABCP服务停用 + * 终止正在进行中的某次(service_code + traceId)或某类(service_code)服务调用,然后通过回调返回服务终止结果 + * 如处于支付环节,此次服务调用将不接受停止。 + * @param json 组装参数 + * @param service_code 组件编码 + * @return + */ + Result stopService(String json,String service_code); +} diff --git a/src/main/java/com/dpkj/modules/scanface/ali/service/impl/AliScanFaceServiceImpl.java b/src/main/java/com/dpkj/modules/scanface/ali/service/impl/AliScanFaceServiceImpl.java new file mode 100644 index 0000000..cbeadbc --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/ali/service/impl/AliScanFaceServiceImpl.java @@ -0,0 +1,323 @@ +package com.dpkj.modules.scanface.ali.service.impl; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.http.ContentType; +import cn.hutool.http.Header; +import cn.hutool.http.HttpRequest; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.JSON; +import com.dpkj.common.vo.Result; +import com.dpkj.modules.scanface.ali.config.AliFaceConfig; +import com.dpkj.modules.scanface.ali.constants.AliFaceConstants; +import com.dpkj.modules.scanface.ali.dll.AbcpInvoke; +import com.dpkj.modules.scanface.ali.service.IAliScanFaceService; +import com.dpkj.modules.scanface.ali.vo.AliOrderVo; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import org.thymeleaf.util.StringUtils; + +import javax.annotation.Resource; +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + + +/** + * @Auther: 萧道子 + * @Date: 2025/4/16 16:47 + * @Description: + */ +@Slf4j +@Service +@Component +public class AliScanFaceServiceImpl implements IAliScanFaceService { + + @Value("${dpkj.serverurl}") + private String serverUrl; + + @Resource + private AliFaceConfig aliFaceConfig; + + + /** + * @description: * 完整的统一的支付宝刷脸支付 + * 1、调用硬件刷脸获取到ftoken + * 2、调用后端的支付宝支付、存入hispay + * @author: zhangxue + * @date: 2025/5/7 9:45 + * @Param [aliOrderVo] + * @return: com.dpkj.common.vo.Result + */ + @Override + public Result aliFacePay(AliOrderVo aliOrderVo) { + try { + /** + * 1、获取刷脸"刷脸去初始化服务"的ftoken返回值 + */ + this.iniAbcpAbsolute(); + //参数 + JSONObject zolozConfig = new JSONObject().fluentPut("installAngle", 90); + JSONObject params = new JSONObject() + .fluentPut("serviceId", aliFaceConfig.getServiceId()) + .fluentPut("zolozConfig", zolozConfig); + String json = params.toJSONString(); + String service_code = AliFaceConstants.SMILEVERIFYNIN_V1; //调用的组件编码:初始化 + Result startServiceIniResult = this.startServiceIni(json, service_code); + if (startServiceIniResult.isSuccess()) { + Map res = (Map) startServiceIniResult.getResult(); + String ftoken = res.get("ftoken"); + + /** + * 2、调用后端的支付宝统一收单交易支付接口、存入hisPay + */ + aliOrderVo.setAuthCode(ftoken);//Demo值:"fp128d26333fa66e66e7f34c493d30cdh76" + JSONObject serverParams = (JSONObject) JSON.toJSON(aliOrderVo); + + String url = serverUrl + "openapi/aliPayOrderApi/createOrder"; + String req = HttpRequest.post(url) + .header(Header.CONTENT_TYPE, ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8)) + .body(serverParams.toJSONString()) + .execute() + .body(); + JSONObject serverResult = JSONObject.parseObject(req); + + return Result.ok(serverResult); + } else { + //调用ABCP 刷脸初始化服务失败 + log.error("[AliScanFaceServiceImpl][aliFacePay][299]调用ABCP 刷脸初始化服务失败 :{}", startServiceIniResult.getMessage()); + return Result.error(startServiceIniResult.getMessage()); + } + } catch (Exception e) { + e.printStackTrace(); + log.error("[AliScanFaceServiceImpl][aliFacePay][302][整个支付宝刷脸模块出现失败:] :{}", e.getMessage()); + return Result.error("支付宝刷脸失败:" + e.getMessage()); + } + + } + + + /** + * @description: 使用绝对路径调用ABCP_SDK部署出来的AbcpInvoke类 + * @author: zhangxue + * @date: 2025/5/7 9:45 + * @Param [] + * @return: com.dpkj.common.vo.Result + */ + @Override + public Result iniAbcpAbsolute() { + try { + File dllFile = new File(aliFaceConfig.getDllPath()); + System.out.println("DLL 存在: " + dllFile.exists()); + if (dllFile.exists()) { + + //组装参数 + JSONObject params = new JSONObject() + .fluentPut("appId", aliFaceConfig.getAppId()) //应用ID + .fluentPut("merchantId", aliFaceConfig.getMerchantId()) //签约商家的 PID,以 2088 开头 + .fluentPut("deviceNum", aliFaceConfig.getDeviceNum())//商家机具终端编号,每台设备保持唯一 + .fluentPut("partnerId", aliFaceConfig.getPartnerId());//服务商的 PID + String json = params.toJSONString(); + + //指定支付宝LOT SDK的本地库路径 + AbcpInvoke.SetAPIPathFile(aliFaceConfig.getDllPath()); + + //创建回调实例 + AbcpInvoke.CallbackRsp callbackRsp = new AbcpInvoke.CallbackRsp() { + @Override + public void OnProcess(int code, String subCode, String subMsg, String result) { + log.info("[AliScanFaceServiceImpl][OnProcess][123][ABCP调用iniAbcpAbsolute][code:{}][subCode:{}][subMsg:{}][result:{}]", code, subCode, subMsg, result); + } + + @Override + public void OnFinish(int code, String subCode, String subMsg, String result) { + log.info("[AliScanFaceServiceImpl][OnFinish][128][ABCP调用iniAbcpAbsolute][code:{}][subCode:{}][subMsg:{}][result:{}]", code, subCode, subMsg, result); + } + }; + + //初始化 + AbcpInvoke.AbcpInit(aliFaceConfig.getAppId(), aliFaceConfig.getAppVersion(), json, callbackRsp); + + return Result.ok("支付宝ABCP初始化成功"); + } else { + log.info("[AliScanFaceServiceImpl][iniAbcpAbsolute][55][{} :文件不存在]", aliFaceConfig.getDllPath()); + return Result.error("支付宝ABCP初始化失败:" + aliFaceConfig.getDllPath() + "不存在"); + } + } catch (Exception e) { + e.printStackTrace(); + return Result.error("初始化失败"); + } + } + + /** + * ABCP服务调用 刷脸初始化服务,获取ftoken + * 接受调用者传入的参数信息,执行指定服务(service_code),然后通过回调返回服务结果。 + * https://opendocs.alipay.com/iot/05e9ye#ABCP%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8 + * + * @param json 组装参数 + * @param service_code 所要调用的组件编码 + * @return + */ + @Override + public Result startServiceIni(String json, String service_code) { + try { + String appId = aliFaceConfig.getAppId(); + + // 使用 CountDownLatch 实现线程同步 + CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latchFinish = new CountDownLatch(1); + AtomicReference> finishResultRef = new AtomicReference<>(); + + //获取返回数据 + AtomicReference processCode = new AtomicReference<>(); + AtomicReference processResult = new AtomicReference<>(); + AtomicReference finishCode = new AtomicReference<>(); + AtomicReference finishResult = new AtomicReference<>(); + + + //接收结果 + Map res = new HashMap<>(); + + AbcpInvoke.CallbackRsp callbackRsp = new AbcpInvoke.CallbackRsp() { + @Override + public void OnProcess(int code, String subCode, String subMsg, String result) { + log.info("[AliScanFaceServiceImpl][OnProcess][123][service_code:{}][code:{}][subCode:{}][subMsg:{}][result:{}]", service_code, code, subCode, subMsg, result); + try { + processCode.set(code); + processResult.set(result); + } finally { + latch.countDown(); // 确保无论如何都释放锁 + } + } + + @Override + public void OnFinish(int code, String subCode, String subMsg, String result) { + log.info("[AliScanFaceServiceImpl][OnFinish][128][service_code:{}][code:{}][subCode:{}][subMsg:{}][result:{}]", service_code, code, subCode, subMsg, result); + + /**Demo示例记录 + * [service_code:BPaaSSmileVerifyNonInitV1][code:1000][subCode:E00000][subMsg:SUCCESS][result:{"code":1000,"subCode":"OK_SUCCESS","subMessage":"SUCCESS","barCode":"281215320962898068","ftoken":"fp1efd3d4c0230a28f5261efe7c5050eh28","alipayUid":"2088812449506047","accountList":"[\"104***@qq.com\"]","authToken":"44686f7195c77ee2e09c09bcdc657dd5h28i","result":{"accountList":["104***@qq.com"],"alipayUid":"2088812449506047","allowRetry":false,"authToken":"44686f7195c77ee2e09c09bcdc657dd5h28i","barCode":"281215320962898068","certName":"您好,*雪","ftoken":"fp1efd3d4c0230a28f5261efe7c5050eh28","type":"selectUid"},"easterEgg":false,"zolozConfig":{"installAngle":90},"serviceId":"pay","traceId":"2444-44-1745802328","callStartTimeMs":1745802328114,"localTime":"2025-04-28-09-05-41-791"}] + */ + try { + finishCode.set(code); + finishResult.set(result); + } finally { + latchFinish.countDown(); // 确保无论如何都释放锁 + } + } + }; + + //调用 + log.info("[AliScanFaceServiceImpl][startService][141][ABCP调用刷脸初始化服务-调用AbcpStartService参数][appId:{}][service_code:{}][json:{}] ", appId, service_code, json.toString()); + AbcpInvoke.AbcpStartService(appId, service_code, json, callbackRsp); + + // 等待process回调完成(设置超时避免死锁) + boolean awaitSuccess = latch.await(10, TimeUnit.SECONDS); + if (!awaitSuccess) { + return Result.error("等待process回调超时"); + } else { + if (processCode.get() == 0) { + JSONObject jsonObject = JSONObject.parseObject(processResult.get()); + if (jsonObject.containsKey("traceId")) { + res.put("traceId", jsonObject.getString("traceId")); + } else { + return Result.error("ABCP调用刷脸初始化服务process失败,返回结果无traceId"); + } + } else { + return Result.error("ABCP调用刷脸初始化服务process失败"); + } + } + + // 等待finish回调完成(设置超时避免死锁) + boolean awaitFinishSuccess = latchFinish.await(120, TimeUnit.SECONDS); + if (!awaitFinishSuccess) { + return Result.error("等待finish回调超时"); + } else { + if (finishCode.get() == 1000) { + JSONObject jsonObject = JSONObject.parseObject(finishResult.get()); + if (jsonObject.containsKey("ftoken")) { + res.put("ftoken", jsonObject.getString("ftoken"));//ftoken参数的有效期为2分钟 + res.put("barCode", jsonObject.getString("barCode")); + finishResultRef.set(Result.ok("ABCP调用刷脸初始化finish服务成功", res)); + } else { + return Result.ok("ABCP调用刷脸初始化服务finish失败,返回结果无ftoken"); + } + } else { + return Result.error("ABCP调用刷脸初始化服务finish失败"); + } + } + + //结果返回 + return finishResultRef.get(); + } catch (Exception e) { + e.printStackTrace(); + return Result.error("ABCP调用刷脸初始化服务失败:" + e.getMessage()); + } + } + + + /** + * ABCP服务停用 + * 终止正在进行中的某次(service_code + traceId)或某类(service_code)服务调用,然后通过回调返回服务终止结果 + * 如处于支付环节,此次服务调用将不接受停止。 + * + * @param json 组装参数 + * @param service_code 组件编码 + * @return + */ + @Override + public Result stopService(String json, String service_code) { + try { + String appId = aliFaceConfig.getAppId(); + + CountDownLatch latch = new CountDownLatch(1); + AtomicReference> resultRef = new AtomicReference<>(); + AtomicReference returnCode = new AtomicReference<>(); + AtomicReference returnResult = new AtomicReference<>(); + + AbcpInvoke.CallbackRsp callbackRsp = new AbcpInvoke.CallbackRsp() { + @Override + public void OnProcess(int code, String subCode, String subMsg, String result) { + log.info("[AliScanFaceServiceImpl][261][ABCP服务停用-OnProcess][code:{}][subCode:{}][subMsg:{}][result:{}]", code, subCode, subMsg, result); + } + + @Override + public void OnFinish(int code, String subCode, String subMsg, String result) { + log.info("[AliScanFaceServiceImpl][OnFinish][266][ABCP服务停用-OnFinish] [code:{}][subCode:{}][subMsg:{}][result:{}]", code, subCode, subMsg, result); + try { + returnCode.set(code); + returnResult.set(result); + } finally { + latch.countDown(); // 确保无论如何都释放锁 + } + } + }; + + //调用 + log.info("[AliScanFaceServiceImpl][stopService][222][调用AbcpTaskStopService参数][appId:{}][service_code:{}][json:{}] ", appId, service_code, json.toString()); + AbcpInvoke.AbcpTaskStopService(appId, service_code, json, callbackRsp); + + // 等待回调完成(设置超时避免死锁) + boolean awaitSuccess = latch.await(60, TimeUnit.SECONDS); + if (!awaitSuccess) { + return Result.error("等待回调超时"); + } else { + if (returnCode.get() == 0) { + System.out.println("停止结果:" + returnResult.get()); + } else { + return Result.error("ABCP停止服务失败"); + } + } + + //结果返回 + return resultRef.get(); + } catch (Exception e) { + e.printStackTrace(); + return Result.error("ABCP服务停用失败:" + e.getMessage()); + } + } + +} diff --git a/src/main/java/com/dpkj/modules/scanface/ali/vo/AliOrderVo.java b/src/main/java/com/dpkj/modules/scanface/ali/vo/AliOrderVo.java new file mode 100644 index 0000000..7eeb922 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/ali/vo/AliOrderVo.java @@ -0,0 +1,81 @@ +package com.dpkj.modules.scanface.ali.vo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * @description: 支付宝支付参数,传入统一下单接口 + * @author: Zhangxue + * @time: 2025/4/29 14:24 + */ +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = false) +public class AliOrderVo implements Serializable { + + /** + * 付款模块 + */ + private String eventModule; + + /** + * 患者Id + */ + private String patientId; + + /** + * 商户机具终端编号 + */ + private String terminalId; + + /** + * 系统订单编号 + */ + private String outTradeNo; + + /** + * 用户支付金额 + */ + //@ApiModelProperty(value = "用户支付金额") + private String totalAmount; + + /** + * 订单标题 + */ + //@ApiModelProperty(value = "订单标题") + private String subject; + + /** + * 支付授权码 + * 刷脸标识串(fp开头的35位字符串) + * 从调用"BPaaSSmileVerifyNonInitV1"服务中获取 + */ + //@ApiModelProperty(value = "支付授权码") + private String authCode; + + /** + * 支付场景 + * bar_code:当面付条码支付场景;默认值 + * security_code:当面付刷脸支付场景,对应的auth_code为fp开头的刷脸标识串; + */ + //@ApiModelProperty(value = "支付授权码") + private String scene; + + /** + * 产品码:商家和支付宝签约的产品码 + * 当面付场景下,如果签约的是当面付快捷版,则传 OFFLINE_PAYMENT; + * 其它支付宝当面付产品传 FACE_TO_FACE_PAYMENT; + * 不传则默认使用FACE_TO_FACE_PAYMENT。 + */ + //@ApiModelProperty(value = "订单标题") + private String productCode; + + /** + * 卖家支付宝用户ID + */ + //@ApiModelProperty(value = "订单标题") + private String sellerId; +} 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..8e767fc --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/config/WxMpProperties.java @@ -0,0 +1,196 @@ +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 = "dpkj.wx") +public class WxMpProperties { + + /** + * 多个公众号配置信息 + */ + 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 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; + + } + + + + /** + * 访问地址 + */ + private MpUrl url; + @Data + public static class MpUrl { + /** + * H5地址 + */ + private String h5; + + /** + * 接口地址 + */ + private String server; + } +} diff --git a/src/main/java/com/dpkj/modules/scanface/wx/controller/WxFacePayController.java b/src/main/java/com/dpkj/modules/scanface/wx/controller/WxFacePayController.java new file mode 100644 index 0000000..4226bc1 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/controller/WxFacePayController.java @@ -0,0 +1,516 @@ +package com.dpkj.modules.scanface.wx.controller; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.http.ContentType; +import cn.hutool.http.Header; +import cn.hutool.http.HttpRequest; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.dpkj.common.constant.WxConstant; +import com.dpkj.common.vo.Result; +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.vo.WxFaceOrderVo; +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 com.fasterxml.jackson.databind.ObjectMapper; +import com.github.binarywang.wxpay.bean.request.WxPayOrderReverseRequest; +import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult; +import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryResult; +import com.github.binarywang.wxpay.bean.result.WxPayOrderReverseResult; +import jodd.util.StringUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.RequestBody; +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.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * @description: 微信刷脸支付 + * @author: Zhangxue + * @time: 2025/5/28 14:20 + */ +@Slf4j +@RestController +@RequestMapping("/wxpayFace") +public class WxFacePayController { + @Resource + private CallWxpayFaceService callWxpayFaceService; + + @Resource + private WeChatPayFaceService weChatPayFaceService; + + @Value("${dpkj.serverurl}") + private String serverUrl; + + //赋值 + @Autowired + private WxMpProperties wxMpProperties; + + + /** + * 调用整个刷脸的完整流程 + * + * @param wxFaceOrderVo + * @return + * @throws Exception + */ + @RequestMapping(value = "/doFacePay", method = RequestMethod.POST) + public Result doFacePay(@RequestBody WxFaceOrderVo wxFaceOrderVo) throws Exception { + Result result = new Result<>(); + try { + //1 初始化 + this.initWxpayface(); + + //2 获取数据getWxpayfaceRawdata + WxFacePayResp wxFacePayResp = this.getWxpayfaceRawdata(); + //String rawdata = wxFacePayResp.getRawdata(); + + //3、获取调用凭证get_wxpayface_authinfo(rawdata)(获取调用凭证) + WxFacePayAuthinfoResp wxFacePayResp0 = this.getWxpayfaceAuthinfo(wxFacePayResp.getRawdata()); + + //4、进行人脸识别getWxpayfaceCode(获取支付凭证) + WxFacePayAuthinfoResp authinfoResp = this.getWxpayfaceCode(wxFaceOrderVo, wxFacePayResp0); + + WxPayMicropayResult micropayResult = new WxPayMicropayResult(); + if (StringUtil.isNotBlank(authinfoResp.getFace_code())) { + //5、调用后台人脸支付API发起支付 + wxFaceOrderVo.setOutTradeNo(getOutTradeNo()); + micropayResult = this.toCreateWxOrder(authinfoResp, wxFaceOrderVo, wxFacePayResp0); + + //6、向后端查询订单状态 WxPayOrderQueryResult queryResult = queryOrderByNo(micropayResult); + boolean isSuccess = pollOrderStatus(micropayResult); + if (isSuccess) { + //8、更新支付结果updateWxpayfacePayResult + WxFacePayResp updFacePayResp = this.updateWxpayfacePayResult(wxFacePayResp0); + + if (ObjUtil.isNotEmpty(updFacePayResp) && WxConstant.STATE_SUCCESS.equals(updFacePayResp.getReturn_code())) { + //11、向后端发起更新订单,进行相应的操作、 + this.updatePayResult(micropayResult); + + result = Result.ok("微信刷脸成功!", micropayResult.getOutTradeNo()); + } + } else { //查询失败或者长时间未有结果 + //7、撤销交易reverse + WxPayOrderReverseRequest reverseRequest = new WxPayOrderReverseRequest(); + reverseRequest.setTransactionId(micropayResult.getTransactionId()); + reverseRequest.setOutTradeNo(micropayResult.getOutTradeNo()); + WxPayOrderReverseResult reverseResult = this.toReverseOrder(reverseRequest); + result = Result.error("微信刷脸失败后已经撤销订单!"); + } + } else { + result = Result.error("获取人脸识别支付凭证为空值"); + } + } catch (Exception e) { + e.printStackTrace(); + log.info("[WxFacePayController][doFacePay][84][微信刷脸完成流程出错] :{}", e.getMessage()); + result = Result.error("微信刷脸完成流程出错:" + e.getMessage()); + } + return result; + } + + + /** + * 1 程序启动时初始化 :程序启动时初始化initWxpayface + * + * @return + * @throws JsonProcessingException + */ + @RequestMapping(value = "/initWxpayface", method = RequestMethod.POST) + public WxFacePayResp initWxpayface() throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException { + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("initWxpayface", "1", System.currentTimeMillis() / 1000, 1); + WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq); + log.info("[WxFacePayController][initWxpayface][132] [1、程序启动时初始化:]:{}", wxFacePayResp.toString()); + return wxFacePayResp; + } + + /** + * 2 获取数据getWxpayfaceRawdata + * + * @return + * @throws JsonProcessingException + */ + public WxFacePayResp getWxpayfaceRawdata() throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException { + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("getWxpayfaceRawdata", "1", System.currentTimeMillis() / 1000); + WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq); + log.info("[WxFacePayController][getWxpayfaceRawdata][146][2、获取数据getWxpayfaceRawdata:] :{}", wxFacePayResp.toString()); + return wxFacePayResp; + } + + /** + * 3、获取调用凭证get_wxpayface_authinfo(rawdata)(获取调用凭证) + * + * @return + * @throws Exception + */ + public WxFacePayAuthinfoResp getWxpayfaceAuthinfo(String rawdata) throws Exception { + // 构建请求参数的JSON字符串 + WxFacePayAuthinfoResp wxFacePayResp = weChatPayFaceService.getWxFaceAuthInfoReqMap(rawdata); + log.info("[WxFacePayController][getWxpayfaceAuthinfo][159][3、获取调用凭证get_wxpayface_authinfo:] :{}", wxFacePayResp.toString()); + return wxFacePayResp; + } + + + /** + * 4、进行人脸识别getWxpayfaceCode(获取支付凭证) + * + * @param wxFaceOrderVo + * @return + * @throws Exception + */ + @RequestMapping(value = "/getWxpayfaceCode", method = RequestMethod.POST) + public WxFacePayAuthinfoResp getWxpayfaceCode(@RequestBody WxFaceOrderVo wxFaceOrderVo, WxFacePayAuthinfoResp wxFacePayResp0) throws Exception { + //4、进行人脸识别getWxpayfaceCode(获取支付凭证) + String outTradeNo = getOutTradeNo();//获取流水号 + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("getWxpayfaceCode", "1", System.currentTimeMillis() / 1000); + wxFacePayReq.setAuthinfo(wxFacePayResp0.getAuthinfo()) + .setOut_trade_no(outTradeNo)//订单流水号 + .setTotal_fee(wxFaceOrderVo.getTotalAmount())//金额:分 + /** + * 目标face_code类型,可选值:"1",刷卡付款码:18位数字,通过「付款码支付/被扫支付」接口完成支付, + * 如果不填写则默认为"0",人脸付款码:数字字母混合,通过「刷脸支付」接口完成支付。 + */ + .setFace_code_type("1"); + + WxFacePayAuthinfoResp authinfoResp = weChatPayFaceService.getWxpayfaceCode(wxFacePayReq); + log.info("[WxFacePayController][getWxpayfaceCode][189][4、进行人脸识别结果:] :{}", authinfoResp.toString()); + + return authinfoResp; + } + + /** + * 从后端获取流水号,保证唯一性 + * + * @return + */ + private String getOutTradeNo() { + String url0 = serverUrl + "openapi/wxFacePayOrderApi/getOutTradeNo"; + log.info("[WxFacePayController][getWxpayfaceCode][112][获取流水号地址] :{}", url0); + String req0 = HttpRequest.post(url0) + .header(Header.CONTENT_TYPE, ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8)) + .execute() + .body(); + JSONObject tradeNoResult = JSONObject.parseObject(req0); + String outTradeNo = tradeNoResult.getString("result"); + log.info("[WxFacePayController][getOutTradeNo][208][获取流水号] :{}", outTradeNo); + return outTradeNo; + } + + + /** + * 5、向后端进行发起订单支付 + * + * @return + */ + private WxPayMicropayResult toCreateWxOrder(WxFacePayAuthinfoResp authinfoResp, WxFaceOrderVo wxFaceOrderVo, WxFacePayAuthinfoResp wxFacePayResp0) { + authinfoResp + .setOut_trade_no(wxFaceOrderVo.getOutTradeNo())//订单流水号 + .setTotal_fee(wxFaceOrderVo.getTotalAmount())//金额:分 + .setPatientId(wxFaceOrderVo.getPatientId())//患者Id + .setEventModule(wxFaceOrderVo.getEventModule()) //付费模块 + .setTerminalId(wxFaceOrderVo.getTerminalId()) //商户机具终端编号 + .setAuthinfo(wxFacePayResp0.getAuthinfo()) + .setNonce_str(wxFacePayResp0.getNonce_str()) + .setSign(wxFacePayResp0.getSign()); + JSONObject serverParams = (JSONObject) JSON.toJSON(authinfoResp); + String url = serverUrl + "openapi/wxFacePayOrderApi/createFaceOrder"; + log.info("[WxFacePayController][getWxpayfaceCode][153][向后台发起微信刷脸订单创建路径] :{}", url); + log.info("[WxFacePayController][getWxpayfaceCode][153][调用后台人脸支付API发起支付参数] :{}", serverParams.toString()); + String req = HttpRequest.post(url) + .header(Header.CONTENT_TYPE, ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8)) + .body(serverParams.toString()) + .execute() + .body(); + log.info("[WxFacePayController][toCreateWxOrder][237][5、向后台发起进行发起订单支付 结果] :{}", req); + JSONObject serverResult = JSONObject.parseObject(req); + Map result = (Map) serverResult.get("result"); + log.info("[WxFacePayController][toCreateWxOrder][140][5、向后端进行发起订单支付请求结果] :{}", result.toString()); + + WxPayMicropayResult micropayResult = new WxPayMicropayResult(); + log.info("[WxFacePayController][toCreateWxOrder][243][5、调用后台人脸支付API发起支付结果:] :{}", result.toString()); + if (ObjUtil.isNotEmpty(result)) { + micropayResult.setOutTradeNo(result.get("outTradeNo").toString()); + micropayResult.setTransactionId(result.get("transactionId").toString()); + micropayResult.setTotalFee(Integer.valueOf(result.get("totalFee").toString())); + micropayResult.setReturnCode(result.get("returnCode").toString()); + } + + return micropayResult; + } + + + /** + * 轮询逻辑封装方法 + * 查询微信刷脸订单状态 + * 30秒 + * 调用步骤6 + * + * @param micropayResult + * @return + */ + public boolean pollOrderStatus(WxPayMicropayResult micropayResult) { + final long timeoutMillis = 30_000; // 30秒超时(毫秒) + final long pollInterval = 2_000; // 轮询间隔2秒 + + long startTime = System.currentTimeMillis(); + + while (true) { + // 1. 查询订单状态 + WxPayOrderQueryResult queryResult = this.queryOrderByNo(micropayResult); + log.info("[WxFacePayController][pollOrderStatus][272][1. 查询订单状态:] :{}", queryResult); + + // 2. 检查成功状态 + if (WxConstant.STATE_SUCCESS.equals(queryResult.getReturnCode())) { + log.info("[WxFacePayController][pollOrderStatus][275][订单支付成功!]"); + return true; + } + + // 3. 检查超时 + long elapsed = System.currentTimeMillis() - startTime; + if (elapsed >= timeoutMillis) { + log.info("[WxFacePayController][pollOrderStatus][284][支付超时,未获取成功状态]"); + return false; + } + + // 4. 等待下一次轮询 + try { + long nextPoll = Math.min(pollInterval, timeoutMillis - elapsed); + TimeUnit.MILLISECONDS.sleep(nextPoll); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.info("[WxFacePayController][pollOrderStatus][293] [轮询被中断]"); + return false; + } + } + } + + + /** + * 6、向后端查询订单状态 + * + * @param micropayResult + * @return + */ + public WxPayOrderQueryResult queryOrderByNo(WxPayMicropayResult micropayResult) { + WxPayOrderQueryResult queryResult = new WxPayOrderQueryResult(); + if (ObjUtil.isNotEmpty(micropayResult)) { + JSONObject serverParams = (JSONObject) JSON.toJSON(micropayResult); + String url = serverUrl + "openapi/wxFacePayOrderApi/queryOrderByNo"; + log.info("[WxFacePayController][getWxpayfaceCode][184][6、向后台发起查询订单状态创建路径] :{}", url); + log.info("[WxFacePayController][getWxpayfaceCode][186][6、向后端查询订单状态参数] :{}", serverParams.toString()); + String req = HttpRequest.post(url) + .header(Header.CONTENT_TYPE, ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8)) + .body(serverParams.toString()) + .execute() + .body(); + log.info("[WxFacePayController][queryOrderByNo][318][6、后台发起查询订单 结果:] :{}", req); + + JSONObject serverResult = JSONObject.parseObject(req); + Map result = (Map) serverResult.get("result"); + log.info("[WxFacePayController][toCreateWxOrder][140][6、后台发起查询订单 请求结果] :{}", result.toString()); + + if (ObjUtil.isNotEmpty(result)) { + queryResult.setReturnCode(result.get("returnCode").toString()); + queryResult.setOutTradeNo(result.get("outTradeNo").toString()); + queryResult.setTransactionId(result.get("transactionId").toString()); + queryResult.setTotalFee(Integer.valueOf(result.get("totalFee").toString())); + } + + log.info("[WxFacePayController][toCreateWxOrder][140][ 6、向后端查询订单状态结果] :{}", micropayResult.toString()); + } + return queryResult; + } + + + /** + * 7、撤销交易reverse + * 支付交易返回失败或支付系统超时,调用该接口撤销交易。 + * 如果此订单用户支付失败,微信支付系统会将此订单关闭; + * 如果用户支付成功,微信支付系统会将此订单资金退还给用户。 + * + * @param reverseRequest + * @return + */ + @RequestMapping(value = "/toReverseOrder", method = RequestMethod.POST) + public WxPayOrderReverseResult toReverseOrder(@RequestBody WxPayOrderReverseRequest reverseRequest) { + WxPayOrderReverseResult reverseResult = new WxPayOrderReverseResult(); + if (ObjUtil.isNotEmpty(reverseRequest)) { + JSONObject serverParams = (JSONObject) JSON.toJSON(reverseRequest); + String url = serverUrl + "openapi/wxFacePayOrderApi/reverseOrder"; + log.info("[WxFacePayController][toReverseOrder][303][向后台发起撤销交易reverse路径] :{}", url); + log.info("[WxFacePayController][toReverseOrder][304][调用后台发起撤销交易参数] :{}", serverParams.toString()); + String req = HttpRequest.post(url) + .header(Header.CONTENT_TYPE, ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8)) + .body(serverParams.toString()) + .execute() + .body(); + log.info("[WxFacePayController][toReverseOrder][359][向后台发起撤销交易 结果:] :{}", req); + JSONObject serverResult = JSONObject.parseObject(req); + reverseResult = (WxPayOrderReverseResult) serverResult.get("result"); + log.info("[WxFacePayController][toReverseOrder][314][7、向后台发起撤销交易请求结果] :{}", reverseResult.toString()); + } + return reverseResult; + } + + + /** + * 8、更新支付结果updateWxpayfacePayResult + * + * @return + * @throws JsonProcessingException + */ + @RequestMapping(value = "/updateWxpayfacePayResult", method = RequestMethod.POST) + public WxFacePayResp updateWxpayfacePayResult(WxFacePayAuthinfoResp wxFacePayResp0) throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException { + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("updateWxpayfacePayResult", "1", System.currentTimeMillis() / 1000); + wxFacePayReq.setAppid(wxMpProperties.getMchConfig().getAppId()) + .setMch_id(wxMpProperties.getMchConfig().getMchId()) + .setStore_id(WxConstant.STORE_ID) + .setPayresult("SUCCESS") + .setAuthinfo(wxFacePayResp0.getAuthinfo()); + + WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq); + log.info("[WxFacePayController][updateWxpayfacePayResult][385][8、更新支付结果updateWxpayfacePayResult] :{}", wxFacePayResp.toString()); + return wxFacePayResp; + } + + + /** + * 9、停止刷脸支付stopWxpayface + * 仅在人脸凭证/付款码face_code【第4步骤】未返回前可用,face_code返回后不可停止 + * + * @return + * @throws JsonProcessingException + */ + @RequestMapping(value = "/stopWxpayface", method = RequestMethod.POST) + public WxFacePayResp stopWxpayface(WxFacePayAuthinfoResp wxFacePayResp0) throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException { + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("stopWxpayface", "1", System.currentTimeMillis() / 1000); + wxFacePayReq.setAppid(wxMpProperties.getMchConfig().getAppId()) + .setMch_id(wxMpProperties.getMchConfig().getMchId()) + .setStore_id(WxConstant.STORE_ID) + .setAuthinfo(wxFacePayResp0.getAuthinfo()); + + WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq); + log.info("[WxFacePayController][stopWxpayface][407][9、停止刷脸支付stopWxpayface的结果:] :{}", wxFacePayResp.toString()); + return wxFacePayResp; + } + + + /** + * 10、释放资源releaseWxpayface + * 首次刷脸需要initWxpayface,刷脸结束后不需要调用releaseWxpayface,否则下次刷脸需要重新initWxpayface,影响启动耗时; + * + * @return + * @throws JsonProcessingException + */ + @RequestMapping(value = "/releaseWxpayface", method = RequestMethod.POST) + public WxFacePayResp releaseWxpayface(WxFacePayAuthinfoResp wxFacePayResp0) throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException { + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("releaseWxpayface", "1", System.currentTimeMillis() / 1000); + + WxFacePayResp wxFacePayResp = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq); + log.info("[WxFacePayController][releaseWxpayface][425][10、释放资源releaseWxpayface的结果:] :{}", wxFacePayResp.toString()); + return wxFacePayResp; + } + + /** + * 11、向后端发起更新状态为成功,进行相应的操作 + * + * @param micropayResult + * @return + */ + public WxPayOrderQueryResult updatePayResult(WxPayMicropayResult micropayResult) { + WxPayOrderQueryResult queryResult = new WxPayOrderQueryResult(); + if (ObjUtil.isNotEmpty(micropayResult)) { + JSONObject serverParams = (JSONObject) JSON.toJSON(micropayResult); + String url = serverUrl + "openapi/wxFacePayOrderApi/updatePayResult"; + log.info("[WxFacePayController][updatePayResult][184][11、向后端发起更新状态为成功创建路径] :{}", url); + log.info("[WxFacePayController][updatePayResult][186][11、向后端发起更新状态为成功参数] :{}", serverParams.toString()); + String req = HttpRequest.post(url) + .header(Header.CONTENT_TYPE, ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8)) + .body(serverParams.toString()) + .execute() + .body(); + log.info("[WxFacePayController][updatePayResult][447][11、向后端发起更新状态为成功 结果:] :{}", req.toString()); + JSONObject jsonObject = JSONObject.parseObject(req); + Map result = (Map) jsonObject.get("result"); + log.info("[WxFacePayController][updatePayResult][140][11、向后端发起更新状态为成功 请求结果] :{}", result.toString()); + } + return queryResult; + } + + + /** + * 4、进行人脸识别getWxpayfaceCode(获取支付凭证) + * @return + * @throws Exception + */ + /*@RequestMapping(value = "/getWxpayfaceCode", method = RequestMethod.POST) + public Result getWxpayfaceCode(@RequestBody WxFaceOrderVo wxFaceOrderVo) throws Exception { + //1 初始化 + this.initWxpayface(); + + //2 获取数据getWxpayfaceRawdata + WxFacePayResp wxFacePayResp = this.getWxpayfaceRawdata(); + //String rawdata = wxFacePayResp.getRawdata(); + + //3、获取调用凭证get_wxpayface_authinfo(rawdata)(获取调用凭证) + WxFacePayAuthinfoResp wxFacePayResp0 = this.getWxpayfaceAuthinfo(wxFacePayResp.getRawdata()); + + //4、进行人脸识别getWxpayfaceCode(获取支付凭证) + String outTradeNo = getOutTradeNo();//获取流水号 + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("getWxpayfaceCode", "1", System.currentTimeMillis() / 1000); + wxFacePayReq.setAuthinfo(wxFacePayResp0.getAuthinfo()) + .setOut_trade_no(outTradeNo)//订单流水号 + .setTotal_fee(wxFaceOrderVo.getTotalAmount())//金额:分 + */ + /** + * 目标face_code类型,可选值:"1",刷卡付款码:18位数字,通过「付款码支付/被扫支付」接口完成支付, + * 如果不填写则默认为"0",人脸付款码:数字字母混合,通过「刷脸支付」接口完成支付。 + */ + /* + .setFace_code_type("1"); + + WxFacePayAuthinfoResp authinfoResp = weChatPayFaceService.getWxpayfaceCode(wxFacePayReq); + System.out.println("**************4、进行人脸识别结果:" + authinfoResp.toString()); + if (StringUtil.isNotBlank(authinfoResp.getFace_code())) { + //5、调用后台人脸支付API发起支付 + WxPayMicropayResult micropayResult = this.toCreateWxOrder(authinfoResp, wxFaceOrderVo, wxFacePayResp0); + + //6、向后端查询订单状态 + //WxPayOrderQueryResult queryResult = queryOrderByNo(micropayResult); + boolean isSuccess = pollOrderStatus(micropayResult); + if (isSuccess) { + + } + } + + return Result.ok(authinfoResp); + } +*/ +} 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..c67c878 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/controller/WxpayFaceTestController.java @@ -0,0 +1,174 @@ +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 jodd.util.StringUtil; +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 org.thymeleaf.util.StringUtils; + +import javax.annotation.Resource; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.security.Provider; +import java.security.Security; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeSet; + +/** + * @description: + * @author: Zhangxue + * @time: 2024/11/28 16:43 + */ +@Slf4j +@RestController +@RequestMapping("/wxpayFaceTest") +public class WxpayFaceTestController { + + @Resource + private CallWxpayFaceService callWxpayFaceService; + + @Resource + private WeChatPayFaceService weChatPayFaceService; + + /** + * 1 程序启动时初始化 :程序启动时初始化initWxpayface + * + * @return + * @throws JsonProcessingException + */ + @RequestMapping(value = "/initWxpayface", method = RequestMethod.GET) + 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("**************3、获取数据getWxpayfaceRawdata:" + wxFacePayResp1.toString()); + WxFacePayAuthinfoResp wxFacePayResp = new WxFacePayAuthinfoResp(); + if ("SUCCESS".equals(wxFacePayResp1.getReturn_code())) { + String rawdata = wxFacePayResp1.getRawdata(); + // 构建请求参数的JSON字符串 + wxFacePayResp = weChatPayFaceService.getWxFaceAuthInfoReqMap(rawdata); + System.out.println("**************3、获取调用凭证get_wxpayface_authinfo:" + wxFacePayResp.toString()); + } + return Result.ok(wxFacePayResp); + } + + + /** + * 4、进行人脸识别getWxpayfaceCode(获取支付凭证) + * + * @return + * @throws Exception + */ + @RequestMapping(value = "/getWxpayfaceCode", method = RequestMethod.POST) + public Result getWxpayfaceCode() throws Exception { + WxFacePayAuthinfoResp wxFacePayResp0 = new WxFacePayAuthinfoResp(); + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq0 = new WxFacePayReq("getWxpayfaceRawdata", "1", System.currentTimeMillis() / 1000); + WxFacePayResp wxFacePayResp1 = weChatPayFaceService.doWxPayIniMethod(wxFacePayReq0); + System.out.println("**************3、获取数据getWxpayfaceRawdata:" + wxFacePayResp1.toString()); + if ("SUCCESS".equals(wxFacePayResp1.getReturn_code())) { + String rawdata = wxFacePayResp1.getRawdata(); + // 构建请求参数的JSON字符串 + wxFacePayResp0 = weChatPayFaceService.getWxFaceAuthInfoReqMap(rawdata); + System.out.println("**************3、获取调用凭证get_wxpayface_authinfo:" + wxFacePayResp0.toString()); + + + // 构建请求参数的JSON字符串 + WxFacePayReq wxFacePayReq = new WxFacePayReq("getWxpayfaceCode", "1", System.currentTimeMillis() / 1000); + wxFacePayReq.setAuthinfo(wxFacePayResp0.getAuthinfo()) + .setOut_trade_no("")//订单流水号 + .setTotal_fee(new String("1")); + + WxFacePayAuthinfoResp authinfoResp = weChatPayFaceService.getWxpayfaceCode(wxFacePayReq); + System.out.println("**************4、进行人脸识别结果:" + authinfoResp.toString()); + if (StringUtil.isNotBlank(authinfoResp.getFace_code())) { + //调用后台人脸支付API发起支付 + authinfoResp.setOut_trade_no(wxFacePayReq.getOut_trade_no())//订单流水号 + .setTotal_fee(wxFacePayReq.getTotal_fee()); + + + } + + return Result.ok(authinfoResp); + } else { + return Result.error("微信获取调用凭证失败"); + } + } + + + //5、进行发起订单支付 + + //可以打印 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..4ed974d --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/dll/WxpayFaceSDKDll.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: 32位的WxpayFaceSDK: /resources/win32-x86/WxpayFaceSDK.dll + * @author: Zhangxue + * @time: 2025/4/17 17:02 + */ +@Slf4j +public class WxpayFaceSDKDll { + + /** + * 获取 Dll 实例,同时注册 Dll 控件。 + * + * @return WxpayFaceSDKDll 实例 + * @throws DllRegistrationException 如果注册控件失败,抛出此异常 + */ + public static Dll instance() throws DllRegistrationException { + try { + return Native.load("WxpayFaceSDK", Dll.class); + } catch (UnsatisfiedLinkError e) { + log.info("[WxpayFaceSDK][instance][微信扫脸动态库] SDK注册失败:{}", e.getMessage()); + 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/service/CallWxpayFaceService.java b/src/main/java/com/dpkj/modules/scanface/wx/service/CallWxpayFaceService.java new file mode 100644 index 0000000..d83b271 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/service/CallWxpayFaceService.java @@ -0,0 +1,19 @@ +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..88e3cd3 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/service/WeChatPayFaceService.java @@ -0,0 +1,54 @@ +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; + * 8、更新支付结果updateWxpayfacePayResult + * 9、停止刷脸支付stopWxpayface + * @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 + */ + WxFacePayAuthinfoResp getWxFaceAuthInfoReqMap(String rawData) throws Exception; + + + /** + * 调用方法 + * 4、进行人脸识别getWxpayfaceCode(获取支付凭证) + * @param wxFacePayReq + * @return + * @throws UnsupportedEncodingException + * @throws JsonProcessingException + */ + WxFacePayAuthinfoResp getWxpayfaceCode(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..d5ce7d9 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/service/impl/CallWxpayFaceServiceImpl.java @@ -0,0 +1,79 @@ +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.beans.factory.annotation.Autowired; +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()); + } + + //赋值 + @Autowired + private 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("-----------调用微信刷脸DLL请求数据----------"+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); + //使用C:\Windows\System32目录下 int result = WxpayFaceSDK.INSTANCE.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("-----------调用微信刷脸DLL请求结果: " + resStr); + dll.wxpayReleaseResponse(new String[2]); + //释放 C:\Windows\System32目录下 WxpayFaceSDK.INSTANCE.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..757dccd --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/service/impl/WeChatPayFaceServiceImpl.java @@ -0,0 +1,406 @@ +package com.dpkj.modules.scanface.wx.service.impl; + +import com.dpkj.common.constant.WxConstant; +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.XmlParserUtil; +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; + + + //赋值 + @Autowired + private 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("-----------调用微信刷脸DLL请求数据----------" + 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); + + //使用C:\Windows\System32目录下 int result = WxpayFaceSDK.INSTANCE.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("-----------微信刷脸DLL调用服务成功结果: " + resStr); + //释放 + dll.wxpayReleaseResponse(new String[2]); + + //释放 C:\Windows\System32目录下 WxpayFaceSDK.INSTANCE.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 WxFacePayAuthinfoResp getWxFaceAuthInfoReqMap(String rawData) throws Exception { + try { + SortedMap map = new TreeMap(); + //门店编号, 由商户定义, 各门店唯一。 + map.put("store_id", WxConstant.STORE_ID); + //门店名称,由商户定义。(可用于展示);中文会导致调用失败 + String text = WxConstant.STORE_TEXT; + String storeName = Base64.getEncoder().encodeToString(text.getBytes()); + map.put("store_name", storeName); + //终端设备编号,由商户定义 + map.put("device_id", WxConstant.DEVICE_ID); + //初始化数据。由微信人脸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", WxConstant.VERSION); + //随机字符串,不长于32位:工具类微信随机数 + map.put("nonce_str", WxRandomUtils.getNonceStr()); + //参数签名,使用MD5 + map.put("sign_type", WxConstant.SING_TYPE); + + //加密和生成微信v2指定的xml格式 + // WXPayUtil.createSign("UTF-8",map,wxMpProperties.getMchConfig().getKeyApi()); + String sign = WXPayUtil.generateSignedXml(map, wxMpProperties.getMchConfig().getKeyApi(), WxPayConstants.SignType.MD5); + log.info("--------3、获取调用凭证 构建微信获取刷脸授权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("--------3、获取调用凭证 发起http调用结果:{}", exchange.getBody()); + + XmlParserUtil.extractAuthInfo(exchange.getBody()); + //转成map方便取值 + Map stringMap = XmlUtils.xmlParser(exchange.getBody(), "xml"); + log.info("------3、获取调用凭证 调用结果转换为map:{}", stringMap); + + //转成返回对象 + WxFacePayAuthinfoResp wxFacePayAuthinfoResp = XmlUtils.mapToObject( + stringMap, + WxFacePayAuthinfoResp.class + ); + log.info("--------发起http调用结果转换为WxFacePayAuthinfoResp", wxFacePayAuthinfoResp.toString()); + + return wxFacePayAuthinfoResp; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + + /** + * 调用方法 + * 4、进行人脸识别getWxpayfaceCode(获取支付凭证) + * + * @param wxFacePayReq + * @return + * @throws UnsupportedEncodingException + * @throws JsonProcessingException + */ + @Override + public WxFacePayAuthinfoResp getWxpayfaceCode(WxFacePayReq wxFacePayReq) throws JsonProcessingException, UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException { + //设置参数 + wxFacePayReq.setAppid(wxMpProperties.getMchConfig().getAppId()) + .setMch_id(wxMpProperties.getMchConfig().getMchId()) + .setStore_id(WxConstant.STORE_ID) + .setFace_authtype(WxConstant.FACEPAY) + .setAuthinfo(wxFacePayReq.getAuthinfo()); + + // 构建请求参数的JSON字符串 + ObjectMapper mapper = new ObjectMapper(); + String req = mapper.writeValueAsString(wxFacePayReq); + + // 创建一个Pointer来接收响应 + List pResp = new ArrayList<>(); + String result = callWxpayFaceService.callWxpayFaceService(req, pResp); + System.out.println("4、进行人脸识别getWxpayfaceCode结果:"+result); + log.info("[WeChatPayFaceServiceImpl][getWxpayfaceCode][220][4、进行人脸识别getWxpayfaceCode结果] :{}", result); + + //响应结果 + 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/XmlParserUtil.java b/src/main/java/com/dpkj/modules/scanface/wx/util/XmlParserUtil.java new file mode 100644 index 0000000..fe5d4e9 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/util/XmlParserUtil.java @@ -0,0 +1,45 @@ +package com.dpkj.modules.scanface.wx.util; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; +import java.io.ByteArrayInputStream; +/** + * @description: 解析微信返回的xml数据 + * @author: Zhangxue + * @time: 2025/5/28 10:10 + */ +public class XmlParserUtil { + + /** + * 解析微信返回的xml数据获取到authinfo + * @param xmlResponse + * @return + * @throws Exception + */ + public static String extractAuthInfo(String xmlResponse) throws Exception { + // 1. 创建DocumentBuilder解析XML + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + + // 2. 将字符串转为输入流 + ByteArrayInputStream input = new ByteArrayInputStream(xmlResponse.getBytes("UTF-8")); + Document doc = builder.parse(input); + + // 3. 创建XPath表达式定位节点 + XPath xpath = XPathFactory.newInstance().newXPath(); + String expression = "//authinfo"; // 使用XPath查找所有authinfo节点 + + // 4. 提取节点文本内容(自动处理CDATA) + Node authInfoNode = (Node) xpath.evaluate(expression, doc, XPathConstants.NODE); + if (authInfoNode != null) { + return authInfoNode.getTextContent(); + } else { + throw new RuntimeException("未找到节点"); + } + } + +} 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..3445552 --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/util/XmlUtils.java @@ -0,0 +1,114 @@ +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.lang.reflect.Field; +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 (StringUtils.isNotEmpty(StringUtils.stripToEmpty(ele.getData() + ""))) { + retMap.put(ele.getName(), 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); + } + } + + + /** + * 通用方法:将Map转换为Java对象 + */ + public static T mapToObject(Map map, Class clazz) throws Exception { + T obj = clazz.getDeclaredConstructor().newInstance(); + Field[] fields = clazz.getDeclaredFields(); + + for (Field field : fields) { + field.setAccessible(true); + String fieldName = field.getName(); + String value = map.get(fieldName); + + if (value != null) { + Class type = field.getType(); + // 类型转换逻辑 + if (type == int.class || type == Integer.class) { + field.set(obj, Integer.parseInt(value)); + } else if (type == boolean.class || type == Boolean.class) { + field.set(obj, Boolean.parseBoolean(value)); + } else { + field.set(obj, value); + } + } + } + return obj; + } + +} 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/WxFaceOrderVo.java b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFaceOrderVo.java new file mode 100644 index 0000000..204e17b --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFaceOrderVo.java @@ -0,0 +1,43 @@ +package com.dpkj.modules.scanface.wx.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * @description: 微信刷脸订单参数 + * @author: Zhangxue + * @time: 2025/5/28 15:35 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class WxFaceOrderVo { + /** + * 付款模块 + */ + private String eventModule; + + /** + * 患者Id + */ + private String patientId; + + /** + * 商户机具终端编号 + */ + private String terminalId; + + /** + * 系统订单编号 + */ + private String outTradeNo; + + /** + * 用户支付金额 分 + */ + private String totalAmount; + +} 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..12ae59c --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayAuthinfoResp.java @@ -0,0 +1,167 @@ +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的有效时间, 单位秒。 + * 在有效时间内, 对于同一台终端设备,相同的参数的前提下(如:相同的公众号、商户号、 门店编号等),可以用同一个authinfo,多次调用SDK的getWxpayfaceCode接口。 + */ + 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; + + + /** + * 传参到后台发起微信订单方法 + * 4、进行人脸识别 + * 必填:否 + * 订单金额(数字), 单位分,该字段在在face_code_type为"1"时可不填,为"0"时必填 + */ + private String total_fee; + + /** + * 传参到后台发起微信订单方法 + * 4、进行人脸识别 + * 必填:否 + * 商户订单号,须与调用支付接口时字段一致,该字段在在face_code_type为"1"时可不填,为"0"时必填 + */ + private String out_trade_no; + + /** + * 传参到后台发起微信订单方法 + * 商品描述 + */ + private String remark; + + /** + * 传参到后台发起微信订单方法 + * 患者Id + */ + private String patientId; + + + /** + * 传参到后台发起微信订单方法 + * 付款模块 + */ + private String eventModule; + + /** + * 传参到后台发起微信订单方法 + * 商户机具终端编号 + */ + private String terminalId; +} 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..d6e868c --- /dev/null +++ b/src/main/java/com/dpkj/modules/scanface/wx/vo/WxFacePayReq.java @@ -0,0 +1,169 @@ +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; + + + /** + * 对摄像头画面进行旋转,1为+90度,2为180度,3为-90度, + */ + private int camera_rotation; + + /** + * 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; + } + + //构建 + public WxFacePayReq(String cmd, String version, long now,int camera_rotation) { + this.cmd= cmd; + this.version =version; + this.now = now; + this.camera_rotation = camera_rotation; + } + + +} 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; + + +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index d800a93..bd6f20f 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -1,4 +1,6 @@ dpkj: + #后端项目访问地址 #https://yinyitong.yzqingyan.cn/ http://172.16.11.13:15946/ ttps://yinyitong.yzqingyan.cn + serverurl: http://localhost:5946/api/ # 医保配置 chs: # 医保机构编码 @@ -20,6 +22,66 @@ dpkj: port-name: # 波特率 串口连接下使用 baud-rate: + #支付宝刷脸 + ali: + face: + #dll文件路径 + dll-path: C:/opt/ant-abcp/bpaas_api.dll + #IOT 应用管理-appid + app-id: 2021005138656502 + #IOT应用版本 + app-version: 1.0.0.0 + #签约商家的 PID,以 2088 开头,企业主体 + merchant-id: 2088641941653700 + #商家机具终端编号,每台设备保持唯一 + device-num: P060003750 + #服务商的 PID + partner-id: 2088641941653700 + # 核心入参 serviceId + service-id: pay + #微信模块 + wx: + configs: #清研家 + - app-id: wxe8334dd2140bb0e1 + # 公众号的appsecret + secret: f83420d79cc6ecd1d7fe9684ac9cdfe4 + # 接口配置里的Token值 + token: dpkjylwjvote + # 接口配置里的EncodingAESKey值 + aes-key: go2uM3ASe2rEyeoNsZHoPCiKGgpku0Bi49P5IypdQWT + mch-config: #商户信息 清研家关联的商户:驿路万家-扬州清研软件科技 + app-id: wxe8334dd2140bb0e1 + secret: f83420d79cc6ecd1d7fe9684ac9cdfe4 + #调用接口所需service_id + service-id: service_id + #商户号 + mch-id: 1557642321 + #商户秘钥 + mch-key: yndpkj15288216506YndpkjKsjytZx12 + #报文解密 APIv3密钥 + v3-key: yndpkj15288216506YndpkjKsjytZx12 + #微信: 商户APIv2密钥 + key-api: yndpkj15288216506YndpkjKsjytZx12 + #p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath;开头) + key-path: classpath:test\apiclient_cert.p12 + private-key-path: classpath:test\apiclient_key.pem + private-cert-path: classpath:test\apiclient_cert.pem + #微信:商家api序列号 + mch-serial-no: + #JSAPI--公众号支付 NATIVE--原生扫码支付 APP--app支付 + trade-type: JSAPI + trade-type-native: NATIVE + #支付回调方法地址 + pay-notify-url: ${dpkj.url.server-url}/openapi/wxPayOrderApi/notify + #退款回调方法地址 + refund-notify-url: ${dpkj.url.server-url}/openapi/wxPayOrderApi/refundNotify + # 路径 + url: + # 后端接口地址 + # server: https://102760424tfyw.vicp.fun/api + server: ${dpkj.url.server-url} + # h5地址 + h5: ${dpkj.url.h5-url} # 身份证读取等待时间 IDCardReader: diff --git a/src/main/resources/application-pro.yml b/src/main/resources/application-pro.yml index bc814d7..f51dd11 100644 --- a/src/main/resources/application-pro.yml +++ b/src/main/resources/application-pro.yml @@ -1,4 +1,6 @@ dpkj: + #后端项目访问地址 #https://yinyitong.yzqingyan.cn/ http://172.16.11.13:15946/ ttps://yinyitong.yzqingyan.cn + serverurl: http://localhost:5946/api/ # 医保配置 chs: # 医保机构编码 @@ -20,6 +22,66 @@ dpkj: port-name: # 波特率 串口连接下使用 baud-rate: + #支付宝刷脸 + ali: + face: + #dll文件路径 + dll-path: C:/opt/ant-abcp/bpaas_api.dll + #IOT 应用管理-appid + app-id: 2021005138656502 + #IOT应用版本 + app-version: 1.0.0.0 + #签约商家的 PID,以 2088 开头,企业主体 + merchant-id: 2088641941653700 + #商家机具终端编号,每台设备保持唯一 + device-num: P060003750 + #服务商的 PID + partner-id: 2088641941653700 + # 核心入参 serviceId + service-id: pay + #微信模块 + wx: + configs: #清研家 + - app-id: wxe8334dd2140bb0e1 + # 公众号的appsecret + secret: f83420d79cc6ecd1d7fe9684ac9cdfe4 + # 接口配置里的Token值 + token: dpkjylwjvote + # 接口配置里的EncodingAESKey值 + aes-key: go2uM3ASe2rEyeoNsZHoPCiKGgpku0Bi49P5IypdQWT + mch-config: #商户信息 清研家关联的商户:驿路万家-扬州清研软件科技 + app-id: wxe8334dd2140bb0e1 + secret: f83420d79cc6ecd1d7fe9684ac9cdfe4 + #调用接口所需service_id + service-id: service_id + #商户号 + mch-id: 1557642321 + #商户秘钥 + mch-key: yndpkj15288216506YndpkjKsjytZx12 + #报文解密 APIv3密钥 + v3-key: yndpkj15288216506YndpkjKsjytZx12 + #微信: 商户APIv2密钥 + key-api: yndpkj15288216506YndpkjKsjytZx12 + #p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath;开头) + key-path: classpath:test\apiclient_cert.p12 + private-key-path: classpath:test\apiclient_key.pem + private-cert-path: classpath:test\apiclient_cert.pem + #微信:商家api序列号 + mch-serial-no: + #JSAPI--公众号支付 NATIVE--原生扫码支付 APP--app支付 + trade-type: JSAPI + trade-type-native: NATIVE + #支付回调方法地址 + pay-notify-url: ${dpkj.url.server-url}/openapi/wxPayOrderApi/notify + #退款回调方法地址 + refund-notify-url: ${dpkj.url.server-url}/openapi/wxPayOrderApi/refundNotify + # 路径 + url: + # 后端接口地址 + # server: https://102760424tfyw.vicp.fun/api + server: ${dpkj.server-url} + # h5地址 + h5: ${dpkj.h5-url} # 身份证读取等待时间 diff --git a/src/main/resources/win32-x86/WxpayFaceSDK.dll b/src/main/resources/win32-x86/WxpayFaceSDK.dll new file mode 100644 index 0000000..4163d97 Binary files /dev/null and b/src/main/resources/win32-x86/WxpayFaceSDK.dll differ