|
|
|
|
package com.ynxbd.wx.wxfactory;
|
|
|
|
|
|
|
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
import com.ynxbd.common.bean.enums.MerchantEnum;
|
|
|
|
|
import com.ynxbd.common.helper.common.*;
|
|
|
|
|
import com.ynxbd.common.result.ResultEnum;
|
|
|
|
|
import com.ynxbd.common.result.ServiceException;
|
|
|
|
|
import com.ynxbd.wx.config.WeChatConfig;
|
|
|
|
|
import com.ynxbd.wx.wxfactory.base.auth.models.RespAccessToken;
|
|
|
|
|
import com.ynxbd.wx.wxfactory.base.auth.models.RespJsapiTicket;
|
|
|
|
|
import com.ynxbd.wx.wxfactory.bean.*;
|
|
|
|
|
import com.ynxbd.wx.wxfactory.medical.WxMedConfig;
|
|
|
|
|
import com.ynxbd.wx.wxfactory.medical.enums.MdRefundTypeEnum;
|
|
|
|
|
import com.ynxbd.wx.wxfactory.utils.WxSignHelper;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.apache.commons.lang3.ObjectUtils;
|
|
|
|
|
import org.ehcache.Cache;
|
|
|
|
|
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
|
|
@Slf4j
|
|
|
|
|
public class WxMedHelper {
|
|
|
|
|
private static final RepeatKeyHelper KEYS = new RepeatKeyHelper(60);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [医保]获取用户信息
|
|
|
|
|
*
|
|
|
|
|
* @param openid openid
|
|
|
|
|
* @param qrCode 授权
|
|
|
|
|
*/
|
|
|
|
|
public static MedicalUserInfo getUserInfo(String openid, String qrCode, String cardNo, String realName) throws ServiceException {
|
|
|
|
|
log.info("[医保]获取用户信息 openid={}, qrCode={}", openid, qrCode);
|
|
|
|
|
MedicalUserInfo info = WxFactory.Medical.Common().getUserInfo(WxMedConfig.PARTNER_URL, openid, qrCode);
|
|
|
|
|
if (info == null || !info.isSuccess()) {
|
|
|
|
|
String message = info == null ? "授权失败" : info.getMessage();
|
|
|
|
|
log.info("[医保授权]失败 {}", message);
|
|
|
|
|
throw new ServiceException(message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cardNo != null) {
|
|
|
|
|
String meCardNo = info.getCardNo();
|
|
|
|
|
if (meCardNo != null && !cardNo.equalsIgnoreCase(meCardNo)) {
|
|
|
|
|
String tip = null;
|
|
|
|
|
if (realName != null) {
|
|
|
|
|
String userName = info.getUserName();
|
|
|
|
|
userName = ParamHelper.nameFilter(userName);
|
|
|
|
|
realName = ParamHelper.nameFilter(realName);
|
|
|
|
|
if (userName.length() < 3 && realName.length() < 3) {
|
|
|
|
|
userName += ParamHelper.hideIdCardNo(meCardNo);
|
|
|
|
|
realName += ParamHelper.hideIdCardNo(cardNo);
|
|
|
|
|
}
|
|
|
|
|
tip = String.format("医保卡绑定人是:【%s】,当前支付的订单属于患者:【%s】,该订单不属于医保卡本人,禁止支付", userName, realName);
|
|
|
|
|
}
|
|
|
|
|
log.info("[医保]不是本人禁止支付");
|
|
|
|
|
throw new ServiceException(ResultEnum.PAY_NO_SELF_NO_PAY, tip);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 医保支付回调
|
|
|
|
|
*
|
|
|
|
|
* @param request 请求
|
|
|
|
|
* @return 支付信息
|
|
|
|
|
*/
|
|
|
|
|
public static MedicalNotify paidMedNotify(HttpServletRequest request) throws ServiceException {
|
|
|
|
|
try {
|
|
|
|
|
// 转换数据对象
|
|
|
|
|
Map<String, Object> paramsMap = WxSignHelper.getReqXmlParamsMap(request);
|
|
|
|
|
if (paramsMap == null) {
|
|
|
|
|
throw new ServiceException("[医保]回调通知下单信息返回错误");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 签名验证
|
|
|
|
|
if (!WxSignHelper.validateSign(paramsMap, WxMedConfig.PAY_KEY)) { // 验证未通过,通知支付失败
|
|
|
|
|
throw new ServiceException("[医保]回调通知签名验证未通过!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String json = JsonHelper.toJsonString(paramsMap);
|
|
|
|
|
if (ObjectUtils.isEmpty(json)) {
|
|
|
|
|
throw new ServiceException("[医保]回调通知下单信息为空");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MedicalNotify notifyInfo = JsonHelper.parseObject(json, MedicalNotify.class);
|
|
|
|
|
if (notifyInfo == null) {
|
|
|
|
|
throw new ServiceException("[医保]回调通知下单信息转换失败");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String openid = notifyInfo.getOpenid();
|
|
|
|
|
String outTradeNo = notifyInfo.getHospOutTradeNo();
|
|
|
|
|
String medTransId = notifyInfo.getMedTransId();
|
|
|
|
|
BigDecimal totalFee = notifyInfo.getTotalFee();// 金额
|
|
|
|
|
BigDecimal cashFee = notifyInfo.getCashFee();
|
|
|
|
|
BigDecimal insuranceFee = notifyInfo.getInsuranceFee();
|
|
|
|
|
String timeEnd = notifyInfo.getTimeEnd();
|
|
|
|
|
|
|
|
|
|
if (openid == null || outTradeNo == null || medTransId == null || totalFee == null || cashFee == null || insuranceFee == null || timeEnd == null) {
|
|
|
|
|
throw new ServiceException(String.format("[医保]下单信息返回错误 outTradeNo={%s}", outTradeNo));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 已处理 去重
|
|
|
|
|
if (KEYS.isContainsKey(medTransId)) {
|
|
|
|
|
throw new ServiceException(ResultEnum.PAY_NOTIFY_REPEAT, String.format("[医保][重复请求]下单信息去重 outTradeNo={%s}", outTradeNo));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String info = WxSignHelper.getMapInfo(paramsMap, "response_content");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, String> dateMap = WxSignHelper.getDateMap(timeEnd);
|
|
|
|
|
notifyInfo.setPayDate(dateMap.get(WxSignHelper.DATE_KEY));
|
|
|
|
|
notifyInfo.setPayTime(dateMap.get(WxSignHelper.TIME_KEY));
|
|
|
|
|
//
|
|
|
|
|
notifyInfo.setInfo(info);
|
|
|
|
|
notifyInfo.setCashFee(cashFee.movePointLeft(2));
|
|
|
|
|
notifyInfo.setInsuranceFee(insuranceFee.movePointLeft(2));
|
|
|
|
|
notifyInfo.setTotalFee(totalFee.movePointLeft(2));
|
|
|
|
|
return notifyInfo;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
if (e instanceof ServiceException) {
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
ErrorHelper.println(e);
|
|
|
|
|
throw new ServiceException(String.format("[医保]支付通知异常:[%s]", e.getMessage()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [医保]创建订单
|
|
|
|
|
*
|
|
|
|
|
* @param outTradeNo 订单号
|
|
|
|
|
* @param serialNo 收费单据号
|
|
|
|
|
* @param openid openid
|
|
|
|
|
* @param cardNo 卡号
|
|
|
|
|
* @param realName 真实姓名
|
|
|
|
|
* @param payOrdId 医保订单号
|
|
|
|
|
* @param payAuthNo 用户授权码
|
|
|
|
|
* @param totalFee 支付总金额
|
|
|
|
|
* @param insuranceFee 个账 + 统筹支付金额
|
|
|
|
|
* @param cashFee 现金支付金额
|
|
|
|
|
* @param notifyUrl 回调地址
|
|
|
|
|
* @param callbackUrl 跳转地址
|
|
|
|
|
* @param body 说明
|
|
|
|
|
* @param attach 额外字段(通知类型)
|
|
|
|
|
*/
|
|
|
|
|
public static Map<String, Object> createOrder(OrderMIEnum OrderMIEnum, String openid, String outTradeNo, String serialNo, String cardNo, String realName, String ip, String payOrdId, String payAuthNo,
|
|
|
|
|
BigDecimal totalFee, BigDecimal insuranceFee, BigDecimal cashFee, String body, String callbackUrl, String notifyUrl, String attach, String familyType, String familyName, String familyCardNo) throws ServiceException {
|
|
|
|
|
Map<String, Object> map = new HashMap<>();
|
|
|
|
|
log.info("[微信医保]下单 outTradeNo={}, openid={}, cardNo={}, realName={}, payOrdId={}, payAuthNo={}, totalFee={}, insuranceFee={}, cashFee={}, familyType={}, familyName={}, familyCardNo={}"
|
|
|
|
|
, outTradeNo, openid, cardNo, realName, payOrdId, payAuthNo, totalFee, insuranceFee, cashFee, familyType, familyName, familyCardNo);
|
|
|
|
|
|
|
|
|
|
MedicalPayOrder order = WxFactory.Medical.Common().createOrder(OrderMIEnum, WxCacheHelper.getAccessToken(),
|
|
|
|
|
WxMedConfig.MD_APP_ID, WeChatConfig.MCH_ID, WxMedConfig.PAY_KEY,
|
|
|
|
|
WxMedConfig.CITY_CODE, WxMedConfig.HOSPITAL_NAME, WxMedConfig.ORG_NO, WxMedConfig.CHANNEL,
|
|
|
|
|
openid,
|
|
|
|
|
payAuthNo,
|
|
|
|
|
payOrdId,
|
|
|
|
|
outTradeNo,
|
|
|
|
|
serialNo,
|
|
|
|
|
totalFee,
|
|
|
|
|
insuranceFee,
|
|
|
|
|
cashFee,
|
|
|
|
|
cardNo,
|
|
|
|
|
realName,
|
|
|
|
|
ip,
|
|
|
|
|
notifyUrl,
|
|
|
|
|
callbackUrl,
|
|
|
|
|
body,
|
|
|
|
|
attach);
|
|
|
|
|
|
|
|
|
|
log.info("[医保]创建订单 {}", order);
|
|
|
|
|
if (order == null) {
|
|
|
|
|
throw new ServiceException("[医保]请求失败");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!order.isOk()) {
|
|
|
|
|
throw new ServiceException(order.getMessage());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String payUrl = order.getPayUrl();
|
|
|
|
|
if (payUrl == null) {
|
|
|
|
|
throw new ServiceException("[医保]下单链接为空");
|
|
|
|
|
}
|
|
|
|
|
log.info("[医保]下单url payUrl={}", payUrl);
|
|
|
|
|
map.put("payUrl", payUrl);
|
|
|
|
|
map.put("bankTransNo", order.getMedTransId());
|
|
|
|
|
return map;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [医保]订单查询
|
|
|
|
|
*
|
|
|
|
|
* @param accessToken accessToken
|
|
|
|
|
* @param outTradeNo 订单号
|
|
|
|
|
* @param medTransId 医保订单号
|
|
|
|
|
*/
|
|
|
|
|
public static WxMedOrder queryOrder(String accessToken, String outTradeNo, String medTransId) {
|
|
|
|
|
return WxFactory.Medical.Common().queryOrder(
|
|
|
|
|
accessToken,
|
|
|
|
|
WxMedConfig.MD_APP_ID,
|
|
|
|
|
WeChatConfig.MCH_ID,
|
|
|
|
|
WxMedConfig.PAY_KEY,
|
|
|
|
|
outTradeNo,
|
|
|
|
|
medTransId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [医保]获取授权链接
|
|
|
|
|
*
|
|
|
|
|
* @param redirectUrl 回调地址
|
|
|
|
|
* @return json
|
|
|
|
|
*/
|
|
|
|
|
public static JSONObject getMdAuthRedirectUrl(String redirectUrl) {
|
|
|
|
|
redirectUrl = (ObjectUtils.isEmpty(redirectUrl) ? "" : redirectUrl);
|
|
|
|
|
JSONObject jsonObj = new JSONObject();
|
|
|
|
|
jsonObj.put("url", WxFactory.Medical.Common().getAuthUrl(WeChatConfig.getWebUrl() + redirectUrl));
|
|
|
|
|
jsonObj.put("type", MerchantEnum.WX_MEDICAL.CODE);
|
|
|
|
|
return jsonObj;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [医保]获取授权链接
|
|
|
|
|
*/
|
|
|
|
|
public static JSONObject getMdAuthUrl(String callbackUrl, String callNo, boolean isHttps) {
|
|
|
|
|
callNo = ObjectUtils.isEmpty(callNo) ? "" : ("?callNo=" + callNo);
|
|
|
|
|
callbackUrl = ObjectUtils.isEmpty(callbackUrl) ? "" : callbackUrl;
|
|
|
|
|
|
|
|
|
|
JSONObject jsonObj = new JSONObject();
|
|
|
|
|
|
|
|
|
|
String authUrl = WxFactory.Medical.Common().getAuthUrl(WeChatConfig.getWebUrl(isHttps) + callbackUrl);
|
|
|
|
|
log.info("[医保] authUrl={}", authUrl);
|
|
|
|
|
if (ObjectUtils.isEmpty(authUrl)) {
|
|
|
|
|
jsonObj.put("url", "config_error");
|
|
|
|
|
} else {
|
|
|
|
|
jsonObj.put("url", authUrl + callNo);
|
|
|
|
|
}
|
|
|
|
|
jsonObj.put("type", MerchantEnum.WX_MEDICAL.CODE);
|
|
|
|
|
jsonObj.put("isProtocol", true); // 使用发起请求的页面的协议
|
|
|
|
|
return jsonObj;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [医保]退费
|
|
|
|
|
*/
|
|
|
|
|
public static WxMedOrder refund(String outTradeNo, String outRefundNo, String payOrdId, BigDecimal cashFee, String reason) {
|
|
|
|
|
return WxFactory.Medical.Common().refund(
|
|
|
|
|
WxCacheHelper.getAccessToken(),
|
|
|
|
|
WxMedConfig.MD_APP_ID,
|
|
|
|
|
WeChatConfig.MCH_ID,
|
|
|
|
|
WxMedConfig.PAY_KEY,
|
|
|
|
|
outTradeNo,
|
|
|
|
|
outRefundNo,
|
|
|
|
|
payOrdId,
|
|
|
|
|
cashFee,
|
|
|
|
|
null,
|
|
|
|
|
reason
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [医保]退现金部分
|
|
|
|
|
*/
|
|
|
|
|
public static WxMedOrder refundCash(String outTradeNo, String outRefundNo, String payOrdId, BigDecimal cashFee, String reason) {
|
|
|
|
|
return WxFactory.Medical.Common().refund(
|
|
|
|
|
WxCacheHelper.getAccessToken(),
|
|
|
|
|
WxMedConfig.MD_APP_ID,
|
|
|
|
|
WeChatConfig.MCH_ID,
|
|
|
|
|
WxMedConfig.PAY_KEY,
|
|
|
|
|
outTradeNo,
|
|
|
|
|
outRefundNo,
|
|
|
|
|
payOrdId,
|
|
|
|
|
cashFee,
|
|
|
|
|
MdRefundTypeEnum.CASH_ONLY,
|
|
|
|
|
reason
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [医保]退现金部分
|
|
|
|
|
*/
|
|
|
|
|
public static WxMedOrder refundCash(String outTradeNo, String tradeNo, BigDecimal cashFee, String reason) {
|
|
|
|
|
return refundCash(outTradeNo, "R" + tradeNo, tradeNo, cashFee, reason);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [医保] 下载对账单
|
|
|
|
|
*/
|
|
|
|
|
public static MedicalBill downBill(String billDate, String billType) {
|
|
|
|
|
|
|
|
|
|
return WxFactory.Medical.Common().downBill(
|
|
|
|
|
WxCacheHelper.getAccessToken(),
|
|
|
|
|
WxMedConfig.MD_APP_ID,
|
|
|
|
|
WeChatConfig.MCH_ID,
|
|
|
|
|
WxMedConfig.PAY_KEY,
|
|
|
|
|
billDate,
|
|
|
|
|
billType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [医保]获取授权链接
|
|
|
|
|
*/
|
|
|
|
|
public static MedicalRefundInfo queryRefund(String accessToken, String outTradeNo, String mdTransId) {
|
|
|
|
|
return WxFactory.Medical.Common().queryRefund(
|
|
|
|
|
accessToken,
|
|
|
|
|
WxMedConfig.MD_APP_ID,
|
|
|
|
|
WeChatConfig.MCH_ID,
|
|
|
|
|
WxMedConfig.PAY_KEY,
|
|
|
|
|
outTradeNo,
|
|
|
|
|
mdTransId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [微信]获取token
|
|
|
|
|
*/
|
|
|
|
|
public synchronized static AccessToken getAccessToken(Cache<String, AccessToken> cache) {
|
|
|
|
|
if (cache == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
AccessToken cacheToken = cache.get(WxCacheHelper.ACCESS_TOKEN_CACHE_NAME);
|
|
|
|
|
if (cacheToken != null) {
|
|
|
|
|
return cacheToken;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RespAccessToken response = WxFactory.Base.OAuth().getAccessToken(WxMedConfig.MD_APP_ID, WxMedConfig.MD_APP_SECRET);
|
|
|
|
|
if (!response.isSuccess()) {
|
|
|
|
|
log.error("[微信]access_token请求失败 code={}, message={}", response.getErrCode(), response.getErrMsg());
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
AccessToken token = new AccessToken();
|
|
|
|
|
token.setAccessToken(response.getAccessToken());
|
|
|
|
|
token.setCreateTime(DateHelper.getCurDateTime());
|
|
|
|
|
return token;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [微信]获取jsapi_ticket
|
|
|
|
|
*/
|
|
|
|
|
public synchronized static JsapiTicket getJsapiTicket(Cache<String, JsapiTicket> cache) {
|
|
|
|
|
if (cache == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
JsapiTicket cacheTicket = cache.get(WxCacheHelper.JSAPI_TICKET_CACHE_NAME);
|
|
|
|
|
if (cacheTicket != null) {
|
|
|
|
|
return cacheTicket;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RespJsapiTicket response = WxFactory.Base.OAuth().getJsapiTicket();
|
|
|
|
|
if (response.getErrCode() != 0) {
|
|
|
|
|
log.error("[微信]jsapi_ticket请求失败 code={}, message={}", response.getErrCode(), response.getErrMsg());
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
JsapiTicket token = new JsapiTicket();
|
|
|
|
|
token.setJsapiTicket(response.getTicket());
|
|
|
|
|
token.setCreateTime(DateHelper.getCurDateTime());
|
|
|
|
|
return token;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
String a = "{\\\"payAuthNo\\\":\\\"AUTH530100202509151551446281313\\\",\\\"setlLatlnt\\\":\\\"0,0\\\",\\\"payOrdId\\\":\\\"ORD530100202509151551510239700\\\"}";
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|