Compare commits
7 Commits
5b28c59217
...
0c09e9cd2c
Author | SHA1 | Date |
---|---|---|
|
0c09e9cd2c | |
|
11b684294d | |
|
18f74eb342 | |
|
265644b473 | |
|
81f0307bd9 | |
|
9189cbfcda | |
|
5b45330232 |
|
@ -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";
|
||||
}
|
|
@ -75,7 +75,7 @@ public class ReceiptPrintController {
|
|||
int dinyHeight = 0;
|
||||
if (templateName.equals("department")) {
|
||||
// 由于是使用的门诊小票-T2,那么默认的高度为1100,强行设置,通过动态修改渲染的图片的高度
|
||||
height = 800;//根据实际情况800较为合适
|
||||
height = 900;//根据实际情况调整
|
||||
|
||||
// 单行最大长度为10
|
||||
int singleLineMaxLength = 10;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 刷脸支付服务
|
||||
* <a href="https://ant-iot.alipay.com/open/iotbpaas/app/appManage/newAppPublish/serviceDetail">...</a>
|
||||
*/
|
||||
public static final String SMILEPAY = "BPaaSFaceSmilePayVerify";
|
||||
|
||||
|
||||
}
|
|
@ -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<JSONObject> 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<Object> iniAbcp() {
|
||||
return aliScanFaceService.iniAbcpAbsolute();
|
||||
}
|
||||
|
||||
/**
|
||||
* ABCP服务调用 刷脸去初始化服务,获取ftoken
|
||||
* 文档地址:https://opendocs.alipay.com/iot/05e9ye
|
||||
* 初始化成功后,商家App 可根据业务需求,调用接口 abcp_start_service 执行 ABCP 所提供的服务。
|
||||
* 上述服务调用过程可重复多次调用,通过传入不同的 service_code 来调用不同的 ABCP 服务。
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("abcpStartServiceIni")
|
||||
Result<Object> 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<Object> 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);
|
||||
}
|
||||
}
|
|
@ -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<Integer, CallbackStr> msMapAbcpString = new HashMap<Integer, CallbackStr>();
|
||||
private static Map<Integer, CallbackRsp> msMapAbcpReponse = new HashMap<Integer, CallbackRsp>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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<JSONObject> aliFacePay(AliOrderVo aliOrderVo);
|
||||
|
||||
/**
|
||||
* 初始化,调用阿里的ABCP_SDK部署出来的代码中的API文件
|
||||
* https://opendocs.alipay.com/iot/05e9ye#ABCP%E5%88%9D%E5%A7%8B%E5%8C%96
|
||||
*/
|
||||
Result<Object> 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<Object> startServiceIni(String json,String service_code);
|
||||
|
||||
|
||||
/**
|
||||
* ABCP服务停用
|
||||
* 终止正在进行中的某次(service_code + traceId)或某类(service_code)服务调用,然后通过回调返回服务终止结果
|
||||
* 如处于支付环节,此次服务调用将不接受停止。
|
||||
* @param json 组装参数
|
||||
* @param service_code 组件编码
|
||||
* @return
|
||||
*/
|
||||
Result<Object> stopService(String json,String service_code);
|
||||
}
|
|
@ -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<com.alibaba.fastjson.JSONObject>
|
||||
*/
|
||||
@Override
|
||||
public Result<JSONObject> 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<Object> startServiceIniResult = this.startServiceIni(json, service_code);
|
||||
if (startServiceIniResult.isSuccess()) {
|
||||
Map<String, String> res = (Map<String, String>) 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<java.lang.Object>
|
||||
*/
|
||||
@Override
|
||||
public Result<Object> 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<Object> startServiceIni(String json, String service_code) {
|
||||
try {
|
||||
String appId = aliFaceConfig.getAppId();
|
||||
|
||||
// 使用 CountDownLatch 实现线程同步
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
CountDownLatch latchFinish = new CountDownLatch(1);
|
||||
AtomicReference<Result<Object>> finishResultRef = new AtomicReference<>();
|
||||
|
||||
//获取返回数据
|
||||
AtomicReference<Integer> processCode = new AtomicReference<>();
|
||||
AtomicReference<String> processResult = new AtomicReference<>();
|
||||
AtomicReference<Integer> finishCode = new AtomicReference<>();
|
||||
AtomicReference<String> finishResult = new AtomicReference<>();
|
||||
|
||||
|
||||
//接收结果
|
||||
Map<String, String> 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<Object> stopService(String json, String service_code) {
|
||||
try {
|
||||
String appId = aliFaceConfig.getAppId();
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<Result<Object>> resultRef = new AtomicReference<>();
|
||||
AtomicReference<Integer> returnCode = new AtomicReference<>();
|
||||
AtomicReference<String> 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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/";
|
||||
|
||||
}
|
|
@ -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<MpConfig> 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;
|
||||
}
|
||||
}
|
|
@ -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<String> doFacePay(@RequestBody WxFaceOrderVo wxFaceOrderVo) throws Exception {
|
||||
Result<String> 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<String, Object> result = (Map<String, Object>) 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<String, Object> result = (Map<String, Object>) 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<String, Object> result = (Map<String, Object>) 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<WxFacePayAuthinfoResp> 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);
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -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<WxFacePayResp> 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<WxFacePayResp> 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<WxFacePayAuthinfoResp> 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<WxFacePayAuthinfoResp> 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<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String> respJson) throws UnsupportedEncodingException, WxpayFaceSDKDll.DllRegistrationException;
|
||||
|
||||
}
|
|
@ -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<java.lang.String,java.lang.String>
|
||||
*/
|
||||
WxFacePayAuthinfoResp getWxFaceAuthInfoReqMap(String rawData) throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* 调用方法
|
||||
* 4、进行人脸识别getWxpayfaceCode(获取支付凭证)
|
||||
* @param wxFacePayReq
|
||||
* @return
|
||||
* @throws UnsupportedEncodingException
|
||||
* @throws JsonProcessingException
|
||||
*/
|
||||
WxFacePayAuthinfoResp getWxpayfaceCode(WxFacePayReq wxFacePayReq) throws UnsupportedEncodingException, JsonProcessingException, WxpayFaceSDKDll.DllRegistrationException;
|
||||
|
||||
}
|
|
@ -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<String> 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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<String> 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<java.lang.String, java.lang.String>
|
||||
*/
|
||||
@Override
|
||||
public WxFacePayAuthinfoResp getWxFaceAuthInfoReqMap(String rawData) throws Exception {
|
||||
try {
|
||||
SortedMap<String, String> map = new TreeMap<String, String>();
|
||||
//门店编号, 由商户定义, 各门店唯一。
|
||||
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<String> stringHttpEntity = new HttpEntity<String>(sign, headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
//发起http调用
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(WechatUrlConfig.GET_WXPAYFACE_AUTHINFO,
|
||||
HttpMethod.POST,
|
||||
stringHttpEntity,
|
||||
String.class);
|
||||
log.info("--------3、获取调用凭证 发起http调用结果:{}", exchange.getBody());
|
||||
|
||||
XmlParserUtil.extractAuthInfo(exchange.getBody());
|
||||
//转成map方便取值
|
||||
Map<String, String> 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<String> 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<String, String> createWxOrder() throws Exception {
|
||||
SortedMap<String, String> map = new TreeMap<String, String>();
|
||||
//微信分配的公众账号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<String> stringHttpEntity = new HttpEntity<String>(sign, headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
//发起http调用
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(WechatUrlConfig.CREATEORDER,
|
||||
HttpMethod.POST,
|
||||
stringHttpEntity,
|
||||
String.class);
|
||||
log.info("--------5、进行发起订单支付:发起http调用结果:【{}】", exchange.getBody());
|
||||
//转成map方便取值
|
||||
Map<String, String> 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<String, String> orderquery() throws Exception {
|
||||
SortedMap<String, String> map = new TreeMap<String, String>();
|
||||
//微信支付分配的公众账号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<String> stringHttpEntity = new HttpEntity<String>("", headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
//发起http调用
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(WechatUrlConfig.ORDERQUERY,
|
||||
HttpMethod.POST,
|
||||
stringHttpEntity,
|
||||
String.class);
|
||||
log.info("--------6、查询订单状态,发起http调用结果:【{}】", exchange.getBody());
|
||||
//转成map方便取值
|
||||
Map<String, String> 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<String, String> reverse() throws Exception {
|
||||
SortedMap<String, String> map = new TreeMap<String, String>();
|
||||
//微信分配的公众账号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<String> stringHttpEntity = new HttpEntity<String>("", headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
//发起http调用
|
||||
ResponseEntity<String> exchange = restTemplate.exchange(WechatUrlConfig.REVERSE,
|
||||
HttpMethod.POST,
|
||||
stringHttpEntity,
|
||||
String.class);
|
||||
log.info("--------7、撤销交易,发起http调用结果:【{}】", exchange.getBody());
|
||||
//转成map方便取值
|
||||
Map<String, String> 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<String> 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<String> pResp = new ArrayList<>();
|
||||
String result = callWxpayFaceService.callWxpayFaceService(req, pResp);
|
||||
|
||||
//响应结果
|
||||
WxFacePayResp authinfoResp = mapper.readValue(result, WxFacePayResp.class);
|
||||
return authinfoResp;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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" };
|
||||
|
||||
}
|
|
@ -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<String, String> params, String apiKey,String signType) {
|
||||
// 将参数Map按照key的字典顺序排序
|
||||
Map<String, String> sortedParams = new TreeMap<>(params);
|
||||
|
||||
// 构建签名原文
|
||||
StringBuilder signSrc = new StringBuilder();
|
||||
for (Map.Entry<String, String> 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("<xml>");
|
||||
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
|
||||
xmlBuilder.append("<").append(entry.getKey()).append(">").append(entry.getValue()).append("</").append(entry.getKey()).append(">");
|
||||
}
|
||||
xmlBuilder.append("<sign><![CDATA[").append(sign).append("]]></sign>");
|
||||
xmlBuilder.append("</xml>");
|
||||
|
||||
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<String, String> xmlToMap(String xml) throws Exception {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
Document document = new Document((List<? extends Content>) 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<String,String> 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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表达式定位<authinfo>节点
|
||||
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("未找到<authinfo>节点");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String, String> xmlParser(String xml, String filterRootEleName) {
|
||||
Map<String, String> 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<String, String> 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<Attribute> 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> T mapToObject(Map<String, String> map, Class<T> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String,String> rawData;
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
||||
}
|
|
@ -26,6 +26,8 @@ logging:
|
|||
com.dpkj: debug
|
||||
|
||||
dpkj:
|
||||
#后端项目访问地址 #https://yinyitong.yzqingyan.cn/ http://172.16.11.13:15946/ ttps://yinyitong.yzqingyan.cn
|
||||
serverurl: http://localhost:5946/api/
|
||||
# 医保配置
|
||||
chs:
|
||||
# 医保机构编码
|
||||
|
@ -45,6 +47,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:
|
||||
|
|
|
@ -27,6 +27,8 @@ logging:
|
|||
|
||||
|
||||
dpkj:
|
||||
#后端项目访问地址 #https://yinyitong.yzqingyan.cn/ http://172.16.11.13:15946/ ttps://yinyitong.yzqingyan.cn
|
||||
serverurl: http://www.lczyyy.com/api/
|
||||
# 医保配置
|
||||
chs:
|
||||
# 医保机构编码
|
||||
|
@ -46,6 +48,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}
|
||||
|
||||
|
||||
# 身份证读取等待时间
|
||||
|
|
|
@ -18,35 +18,44 @@
|
|||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
</div>
|
||||
<div style="font-size: 29px;word-break: break-all;margin: 10px 20px;">
|
||||
<div style="display: flex;">
|
||||
<div style="margin-top: 10px;word-break: break-all;" th:if="${outpatientNumber}">门诊号:<span
|
||||
th:text="${outpatientNumber}"></span>
|
||||
</div>
|
||||
<div style="margin-top: 10px;word-break: break-all;">
|
||||
<span style="margin-right: 25px;">姓名:<span th:text="${name}"></span></span>
|
||||
<span style="margin-right: 25px;">性别:<span th:text="${gender}"></span></span>
|
||||
<span>年龄:<span th:text="${age}"></span></span>
|
||||
</div>
|
||||
<div style="word-break: break-all;margin-top: 10px;">
|
||||
<span style="margin-right: 25px;" th:if="${outpatientNumber}">
|
||||
门诊号:<span th:text="${outpatientNumber}"></span></span>
|
||||
<span th:if="${doctor}">
|
||||
就诊医生:<span th:text="${doctor}"></span>
|
||||
</span>
|
||||
<div style="margin-top: 10px;word-break: break-all;" th:if="${doctor}">
|
||||
就诊医生:<span th:text="${doctor}"></span>
|
||||
</div>
|
||||
<div style="margin-top: 10px;" th:if="${department}">就诊科室:<span th:text="${department}"></span>
|
||||
<div style="margin-top: 10px;word-break: break-all;" th:if="${department}">就诊科室:<span
|
||||
th:text="${department}"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
<div style="font-size: 29px; margin: 10px 20px">
|
||||
<div style="word-break: break-all;">
|
||||
<span style="margin-right: 25px;">费用总额:<span th:text="${totalFee}"></span></span>
|
||||
<span>个人支付:<span th:text="${personalPayment}"></span></span>
|
||||
<div style="text-align: center; font-size: 32px;font-weight: bold;"><span>费用信息</span></div>
|
||||
<div style="margin-top: 10px;">
|
||||
<div style="float: left;width: 190px;">金额合计:</div>
|
||||
<div style="margin-left: 190px;word-break: break-all;">
|
||||
<span>¥<span th:text="${totalFee}"></span>(小写金额)</span><br/>
|
||||
<span><span th:text="${actualReceiptAmountChinese}"></span>(大写金额)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 10px;word-break: break-all;"><span>实收金额:</span><span
|
||||
th:text="${actualReceiptAmount}"></span></div>
|
||||
<div style="margin-top: 10px;"><span>实收金额:</span><span th:text="${actualReceiptAmountChinese}"></span></div>
|
||||
<div th:if="${chs==1}">
|
||||
<div style="margin-top: 10px;word-break: break-all;">医保统筹支付:<span th:text="${ybtcjj}"></span></div>
|
||||
<div style="margin-top: 10px;word-break: break-all;">个人医保支付:<span th:text="${ybgrzh}"></span></div>
|
||||
<div style="margin-top: 10px;word-break: break-all;">医院账户支付:<span th:text="${yyzh}"></span></div>
|
||||
<div style="margin-top: 10px;word-break: break-all;">医院优惠金额:<span th:text="${yyyh}"></span></div>
|
||||
</div>
|
||||
<div style="margin-top: 10px;word-break: break-all;">个人自费:<span th:text="${personalPayment}"></span></div>
|
||||
</div>
|
||||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
<div style="margin: 10px 20px">
|
||||
<div style="text-align: center; font-size: 32px;font-weight: bold;"><span>费用明细</span></div>
|
||||
<table style="width: 100%; table-layout: fixed; border-collapse: collapse;">
|
||||
<tr style="font-size: 29px;">
|
||||
<tr style="font-size: 26px;">
|
||||
<th style="width: 300px;text-align: left;">项目名称</th>
|
||||
<th style="width: calc(25% - 5px);text-align: center;">数量</th>
|
||||
<th style="width: calc(25% - 5px);text-align: center;">单价</th>
|
||||
|
@ -63,7 +72,7 @@
|
|||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
<div style="margin-left: 20px;font-size: 29px;">终端编号:<span th:text="${terminalNumber}"></span></div>
|
||||
<div style="margin-left: 20px;font-size: 29px;">打印时间:<span th:text="${printTime}"></span></div>
|
||||
<div style="margin-left: 20px; margin-top: 10px;font-size: 27px;">
|
||||
<div style="margin-left: 20px;font-size: 27px;">
|
||||
<span>温馨提示:请取走缴费凭证,并妥善保管。<br/>如果对缴费存在疑问,请到人工窗口咨询!</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
<div style="margin-left: 20px;font-size: 29px;">终端编号:<span th:text="${terminalNumber}"></span></div>
|
||||
<div style="margin-left: 20px;font-size: 29px;">打印时间:<span th:text="${printTime}"></span></div>
|
||||
<div style="margin-left: 20px; margin-top: 10px;font-size: 27px;">
|
||||
<div style="margin-left: 20px;font-size: 27px;">
|
||||
<span>温馨提示:请取走缴费凭证,并妥善保管。<br/>如果对缴费存在疑问,请到人工窗口咨询!</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<div style="border-bottom: 1px dashed #000;margin: 3px 0;"></div>
|
||||
<div style="margin-left: 20px;font-size: 29px;">终端编号:<span th:text="${terminalNumber}"></span></div>
|
||||
<div style="margin-left: 20px;font-size: 29px;">打印时间:<span th:text="${printTime}"></span></div>
|
||||
<div style="margin-left: 20px; margin-top: 10px;font-size: 27px;">
|
||||
<div style="margin-left: 20px;font-size: 27px;">
|
||||
<span>温馨提示:请取走缴费凭证,并妥善保管。<br/>如果对缴费存在疑问,请到人工窗口咨询!</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<div style="border-bottom: 1px dashed #000;margin-bottom: 3px;"></div>
|
||||
<div style="margin-left: 20px;font-size: 29px;">终端编号:<span th:text="${terminalNumber}"></span></div>
|
||||
<div style="margin-left: 20px;font-size: 29px;">打印时间:<span th:text="${printTime}"></span></div>
|
||||
<div style="margin-left: 20px; margin-top: 10px;font-size: 27px;">
|
||||
<div style="margin-left: 20px;font-size: 27px;">
|
||||
<span>温馨提示:请取走挂号凭证,并妥善保管。<br/>如果对缴费存在疑问,请到人工窗口咨询!</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue