You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
444 lines
20 KiB
444 lines
20 KiB
package com.ynxbd.wx.wxfactory.medical;
|
|
|
|
import com.alibaba.fastjson.JSON;
|
|
import com.alibaba.fastjson.JSONObject;
|
|
import com.ynxbd.common.helper.common.DateHelper;
|
|
import com.ynxbd.common.helper.common.ErrorHelper;
|
|
import com.ynxbd.common.helper.common.JsonHelper;
|
|
import com.ynxbd.common.helper.http.OkHttpHelper;
|
|
import com.ynxbd.common.result.JsonResult;
|
|
import com.ynxbd.wx.wxfactory.WxFactory;
|
|
import com.ynxbd.wx.wxfactory.bean.*;
|
|
import com.ynxbd.wx.wxfactory.utils.WxRequestHelper;
|
|
import com.ynxbd.wx.wxfactory.utils.WxSignHelper;
|
|
import lombok.NoArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.apache.commons.codec.digest.DigestUtils;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.UUID;
|
|
|
|
@Slf4j
|
|
@NoArgsConstructor
|
|
public class Client {
|
|
|
|
/**
|
|
* 免密授权地址
|
|
*
|
|
* @param url url
|
|
*/
|
|
public String getAuthUrl(String url) {
|
|
log.info("[医保]免密授权地址-{}",MdConfig.getConfigUrl(url));
|
|
return MdConfig.getConfigUrl(url);
|
|
}
|
|
|
|
/**
|
|
* 微信医保下单
|
|
*
|
|
* @param orderTypeEnum
|
|
* @param accessToken
|
|
* @param appId
|
|
* @param mchId
|
|
* @param mdPayKey
|
|
* @param cityCode
|
|
* @param hospitalName
|
|
* @param orgNo
|
|
* @param channel
|
|
* @param openid
|
|
* @param payAuthNo
|
|
* @param payOrdId
|
|
* @param outTradeNo
|
|
* @param serialNo
|
|
* @param totalFee
|
|
* @param insuranceFee
|
|
* @param cardNo
|
|
* @param realName
|
|
* @param notifyUrl
|
|
* @param callbackUrl
|
|
* @param body
|
|
* @return
|
|
*/
|
|
public MedicalPayOrder createOrder(OrderTypeEnum orderTypeEnum, String accessToken,
|
|
String appId, String mchId, String mdPayKey,
|
|
String cityCode, String hospitalName, String orgNo, String channel,
|
|
String openid, String payAuthNo, String payOrdId,
|
|
String outTradeNo, String serialNo,
|
|
BigDecimal totalFee, BigDecimal insuranceFee, BigDecimal cashFee,
|
|
String cardNo, String realName, String ip,
|
|
String notifyUrl, String callbackUrl, String body, String attach) {
|
|
|
|
int payType;
|
|
try {
|
|
if (cashFee == null) cashFee = new BigDecimal(0);
|
|
|
|
if (totalFee.compareTo(insuranceFee) == 0) {
|
|
payType = 2; // 医保
|
|
} else if (totalFee.compareTo(cashFee) == 0) {
|
|
payType = 3; // 现金(1)
|
|
} else {
|
|
payType = 3; // 医保+现金
|
|
}
|
|
} catch (Exception e) {
|
|
log.error("【医保】金额转换异常: {}", e.getMessage());
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
BigDecimal finalCashFee = cashFee;
|
|
JsonResult jsonResult = WxRequestHelper.postMdXml(("https://api.weixin.qq.com/payinsurance/unifiedorder?access_token=" + accessToken), params -> {
|
|
|
|
params.put("insurance_fee", insuranceFee.movePointRight(2)); // 医保支付金额(分)
|
|
params.put("total_fee", totalFee.movePointRight(2)); // 总金额(分)
|
|
params.put("cash_fee", finalCashFee.movePointRight(2)); // 现金需要支付的金额 { 单位为分>=0 线上预结算模式金额填0; 线下预结算模式填实际自费金额 }
|
|
params.put("pay_type", payType); // 支付方式-医保 (1:现金 2:医保 3:现金+医保)
|
|
|
|
params.put("order_type", orderTypeEnum.CODE);
|
|
params.put("appid", appId);
|
|
params.put("mch_id", mchId);
|
|
params.put("openid", openid);
|
|
|
|
params.put("hosp_out_trade_no", outTradeNo); // 第三方服务商订单号 outTradeNo
|
|
params.put("serial_no", serialNo); // 医院HIS系统订单号
|
|
|
|
params.put("body", body); // 商品描述
|
|
params.put("notify_url", notifyUrl); // 回调接口
|
|
params.put("return_url", callbackUrl); // 回调页面
|
|
params.put("attach", attach); // 附加数据
|
|
|
|
// 使用医保支付时必填
|
|
params.put("user_card_type", 1); // 证件类型
|
|
params.put("city_id", cityCode); // 城市编码
|
|
params.put("user_name", realName); // 真实姓名
|
|
params.put("user_card_no", DigestUtils.md5Hex(cardNo)); // 证件号码
|
|
|
|
//==========================================================
|
|
params.put("hospital_name", hospitalName); //
|
|
//=========================================================
|
|
|
|
params.put("allow_fee_change", 1); // 是否允许预结算费用发生变化 {0:不允许 1:允许 当医保局返回的预结算金额与医院上传的金额不一致时,此字段为0则直接报错,为1则以医保局金额为准}
|
|
params.put("spbill_create_ip", ip); // 客户端ip
|
|
|
|
// 使用医保支付时必填
|
|
params.put("is_dept", "4"); // 医保标识
|
|
params.put("org_no", orgNo); // 医疗机构编码(医保局分配给机构)
|
|
params.put("nonce_str", UUID.randomUUID().toString().replaceAll("-", ""));
|
|
params.put("gmt_out_create", DateHelper.dateToStr(new Date(), DateHelper.DateEnum.yyyyMMddHHmmss)); // 医院下单时间
|
|
|
|
|
|
Map<String, String> requestContent = new HashMap<>();
|
|
requestContent.put("payAuthNo", payAuthNo); // 授权码
|
|
requestContent.put("payOrdId", payOrdId); // 处方号
|
|
requestContent.put("setlLatlnt", "0,0"); // 经纬度"118.096435,24.485407"。未获取到用户定位时,传“0,0”。
|
|
|
|
params.put("request_content", JsonHelper.toJsonString(requestContent));
|
|
params.put("channel_no", channel); // 渠道号
|
|
//----------------------------------------------------------------
|
|
params.put("sign", WxSignHelper.generateSign(params, WxSignHelper.SIGN_TYPE_MD5, mdPayKey));
|
|
log.info("[医保][下单] req={}", JsonHelper.toJsonString(params));
|
|
});
|
|
|
|
log.info("[医保][下单] resp={}", JsonHelper.toJsonString(jsonResult));
|
|
if (!jsonResult.success()) {
|
|
return new MedicalPayOrder().createResult(jsonResult);
|
|
}
|
|
return jsonResult.dataMapToBean(MedicalPayOrder.class);
|
|
} catch (Exception e) {
|
|
ErrorHelper.println(e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
public MedicalOrder queryOrder(String accessToken, String appId, String mchId, String mdPayKey, String outTradeNo, String medTransId) {
|
|
JsonResult jsonResult = WxRequestHelper.postMdXml(("https://api.weixin.qq.com/payinsurance/queryorder?access_token=" + accessToken), params -> {
|
|
params.put("appid", appId);
|
|
params.put("mch_id", mchId);
|
|
// 二选一
|
|
params.put("hosp_out_trade_no", outTradeNo); // 第三方服务商订单号 outTradeNo
|
|
// params.put("med_trans_id", medTransId); // 微信生成的医疗订单号
|
|
params.put("nonce_str", UUID.randomUUID().toString().replaceAll("-", ""));
|
|
|
|
params.put("sign", WxSignHelper.generateSign(params, WxSignHelper.SIGN_TYPE_MD5, mdPayKey));
|
|
log.info("[医保][查询订单] req={}", JsonHelper.toJsonString(params));
|
|
});
|
|
if (!jsonResult.success()) {
|
|
return new MedicalOrder().createResult(jsonResult);
|
|
}
|
|
log.info("[医保][查询订单] req={}", JsonHelper.toJsonString(jsonResult));
|
|
return jsonResult.dataMapToBean(MedicalOrder.class);
|
|
}
|
|
|
|
|
|
/**
|
|
* [医保]退费(注意:需要先退医保部分,才能退现金部分)
|
|
*
|
|
* @param accessToken accessToken
|
|
* @param appId appId
|
|
* @param mchId mchId
|
|
* @param mdPayKey mdPayKey
|
|
* @param outTradeNo 订单号
|
|
* @param outRefundNo 退费订单号
|
|
* @param payOrdId 医保订单号
|
|
* @param cashFee 退费现金
|
|
* @param reason 退费原因
|
|
*/
|
|
public MedicalOrder refund(String accessToken, String appId, String mchId, String mdPayKey, String outTradeNo, String outRefundNo, String payOrdId, BigDecimal cashFee, String reason) {
|
|
if (reason == null) {
|
|
reason = "申请退款";
|
|
}
|
|
|
|
if (cashFee == null || cashFee.compareTo(BigDecimal.ZERO) == 0) {
|
|
return new MedicalRefund().createResult("退费金额错误或为0禁止退费");
|
|
}
|
|
String finalReason = reason;
|
|
JsonResult jsonResult = WxRequestHelper.postMdXml(("https://api.weixin.qq.com/payinsurance/refund?access_token=" + accessToken), params -> {
|
|
params.put("appid", appId);
|
|
params.put("mch_id", mchId);
|
|
// 二选一
|
|
params.put("hosp_out_trade_no", outTradeNo); // 第三方服务商订单号 outTradeNo
|
|
// params.put("med_trans_id", medTransId); // 微信生成的医疗订单号
|
|
|
|
params.put("hosp_out_refund_no", outRefundNo); // 退费订单号
|
|
params.put("nonce_str", UUID.randomUUID().toString().replaceAll("-", ""));
|
|
|
|
|
|
JSONObject requestContent = new JSONObject();
|
|
requestContent.put("ref_reason", finalReason);
|
|
requestContent.put("payOrdId", payOrdId);
|
|
params.put("request_content", JSON.toJSONString(requestContent));
|
|
|
|
// /**
|
|
// * 1、不填则全退,CASH_ONLY则只退现金部分;
|
|
// * 2、INS_ONLY:为全额退医保部分(统筹+个账),如果订单已经单独退了统筹部分则不再支持医保部分全额退款;
|
|
// * 3、INS_PLAN_PART:为只退医保统筹部分,当前统筹部分仅支持一次性退款;
|
|
// * 4、INS_INDIVIDUAL_PART:为只退医保个账部分,个账部分支持多次退款。
|
|
// * 现金跟医保可以分开退款,医保部分统筹和个账部分也可以分开退,需要填写不同的hosp_out_refund_no参数
|
|
// */
|
|
// params.put("part_refund_type", "INS_ONLY"); // 部分退款
|
|
// /**
|
|
// * 不填默认全退,填写金额时则指定金额不能为0;该参数只在part_refund_type为INS_INDIVIDUAL_PART生效
|
|
// */
|
|
// params.put("ins_refund_fee", new BigDecimal(refundFee).movePointRight(2)); // 个账部分退款
|
|
|
|
params.put("cash_refund_fee", cashFee.movePointRight(2).intValue()); // 现金退款
|
|
|
|
|
|
// 医保退款必须------------------------------------------------
|
|
params.put("cancel_bill_no", outRefundNo); // 撤销单据号
|
|
params.put("cancel_serial_no", outRefundNo); // 撤销流水号
|
|
|
|
params.put("sign", WxSignHelper.generateSign(params, WxSignHelper.SIGN_TYPE_MD5, mdPayKey));
|
|
log.info("[医保][退费] req={}", JsonHelper.toJsonString(params));
|
|
});
|
|
|
|
log.info("[医保][退费] resp={}", JsonHelper.toJsonString(jsonResult));
|
|
if (!jsonResult.success()) {
|
|
return new MedicalRefund().createResult(jsonResult);
|
|
}
|
|
return jsonResult.dataMapToBean(MedicalOrder.class);
|
|
}
|
|
|
|
|
|
/**
|
|
* [医保]查询退费
|
|
*
|
|
* @param accessToken accessToken
|
|
* @param appId appId
|
|
* @param mchId mchId
|
|
* @param mdPayKey mdPayKey
|
|
*/
|
|
public MedicalRefundInfo queryRefund(String accessToken, String appId, String mchId, String mdPayKey, String outTradeNo, String medTransId) {
|
|
|
|
JsonResult jsonResult = WxRequestHelper.postMdXml(("https://api.weixin.qq.com/payinsurance/queryrefund?access_token=" + accessToken), params -> {
|
|
params.put("appid", appId);
|
|
params.put("mch_id", mchId);
|
|
// 二选一
|
|
if (outTradeNo != null) {
|
|
params.put("hosp_out_trade_no", outTradeNo); // 第三方服务商订单号 outTradeNo
|
|
}
|
|
if (medTransId != null) {
|
|
params.put("med_trans_id", medTransId); // 微信生成的医疗订单号
|
|
}
|
|
params.put("nonce_str", UUID.randomUUID().toString().replaceAll("-", ""));
|
|
|
|
params.put("sign", WxSignHelper.generateSign(params, WxSignHelper.SIGN_TYPE_MD5, mdPayKey));
|
|
|
|
log.info("[医保][查询退费] req={}", JsonHelper.toJsonString(params));
|
|
});
|
|
if (!jsonResult.success()) {
|
|
return new MedicalRefundInfo().createResult(jsonResult);
|
|
}
|
|
return jsonResult.dataMapToBean(MedicalRefundInfo.class);
|
|
}
|
|
|
|
|
|
/**
|
|
* [医保]下载对账账单
|
|
*
|
|
* @param accessToken accessToken
|
|
* @param appId appId
|
|
* @param mchId mchId
|
|
* @param mdPayKey mdPayKey
|
|
* @param billDate 对账单日期(yyyyMMdd)
|
|
* @param billType 帐单类型 (ALL,返回当日所有订单信息;默认值 SUCCESS,返回当日成功支付的订单;REFUND,返回当日退款订单)
|
|
*/
|
|
public MedicalOrder downBill(String accessToken, String appId, String mchId, String mdPayKey, String billDate, String billType) {
|
|
|
|
JsonResult jsonResult = WxRequestHelper.postMdXml(("https://api.weixin.qq.com/payinsurance/billdownload?access_token=" + accessToken), params -> {
|
|
params.put("appid", appId);
|
|
params.put("mch_id", mchId);
|
|
//
|
|
params.put("bill_date", billDate);
|
|
params.put("bill_type", billType);
|
|
|
|
params.put("nonce_str", UUID.randomUUID().toString().replaceAll("-", ""));
|
|
params.put("sign", WxSignHelper.generateSign(params, WxSignHelper.SIGN_TYPE_MD5, mdPayKey));
|
|
log.info("[医保][账单下载] req={}", JsonHelper.toJsonString(params));
|
|
});
|
|
|
|
if (!jsonResult.success()) {
|
|
return new MedicalRefund().createResult(jsonResult);
|
|
}
|
|
log.info("[医保][账单下载] resp={}", JsonHelper.toJsonString(jsonResult));
|
|
return jsonResult.dataMapToBean(MedicalOrder.class);
|
|
}
|
|
|
|
|
|
/**
|
|
* 获取用户授权码等信息(注意:联系腾讯修改白名单后,调用接口会报授权码错误的提示,需切换请求路径)
|
|
*
|
|
* @param reqUrl 请求地址
|
|
* @param openid openid
|
|
* @param qrcode 用户授权码
|
|
* @return 用户信息
|
|
*/
|
|
public MedicalUserInfo getUserInfo(String reqUrl, String openid, String qrcode) {
|
|
log.info("[医保]用户授权地址-{}",reqUrl);
|
|
MedicalUserInfo info = new MedicalUserInfo();
|
|
String timestamp = Long.toString(System.currentTimeMillis());
|
|
|
|
String signature = MdConfig.createSignature(timestamp);
|
|
if (signature == null) {
|
|
info.setMessage("signature签名错误");
|
|
return info;
|
|
}
|
|
JSONObject respJson = OkHttpHelper.postForm(reqUrl, params -> {
|
|
params.put("qrcode", qrcode);
|
|
params.put("openid", openid);
|
|
log.info("[医保][授权] req={}", JsonHelper.toJsonString(params));
|
|
}, headers -> {
|
|
headers.set("Accept", "application/json");
|
|
headers.set("Content-Type", "application/json");
|
|
//
|
|
headers.set("god-portal-timestamp", timestamp);
|
|
headers.set("god-portal-signature", signature);
|
|
headers.set("god-portal-request-id", UUID.randomUUID().toString().replaceAll("-", ""));
|
|
});
|
|
if (respJson == null) {
|
|
info.setMessage("请求用户信息失败");
|
|
return info;
|
|
}
|
|
String code = respJson.getString("code");
|
|
String message = respJson.getString("message");
|
|
if (!"0".equals(code)) {
|
|
log.error("[医保授权]失败 resp={}", respJson);
|
|
|
|
info.setMessage(String.format("[%s] %s", code, message));
|
|
return info;
|
|
}
|
|
|
|
|
|
JSONObject longitudeLatitude = respJson.getJSONObject("user_longitude_latitude");
|
|
if (longitudeLatitude == null) {
|
|
info.setMessage(String.format("[%s] %s, tip=%s", code, message, "经纬度异常"));
|
|
return info;
|
|
}
|
|
String longitude = longitudeLatitude.getString("longitude");
|
|
String latitude = longitudeLatitude.getString("latitude");
|
|
|
|
String userName = respJson.getString("user_name");
|
|
String payAuthNo = respJson.getString("pay_auth_no");
|
|
String cardNo = respJson.getString("user_card_no");
|
|
if (cardNo != null && !"".equals(cardNo)) {
|
|
info.setCardNo(cardNo);
|
|
respJson.remove("user_card_no");
|
|
}
|
|
|
|
log.error("[医保授权]返回 resp={}", respJson);
|
|
|
|
info.setUserName(userName);
|
|
info.setPayAuthNo(payAuthNo);
|
|
info.setLongitude(longitude);
|
|
info.setLatitude(latitude);
|
|
info.setSuccess(true);
|
|
return info;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
WxFactory.Medical.Common().getUserInfo("http://www.baidu.com", "123", "222");
|
|
|
|
// WxFactory.Medical.Common().downBill("", "", "", "", "", "");
|
|
}
|
|
|
|
// public static void main(String[] args) {
|
|
// String accessToken = "57_WiWb2k55Xv1Lbj9y0rEW1wFxKzQPiaaFJ4oOdJwYvb2TlGOHgqi4pQt4dLFKv7EqF7939o6jk0d9QAyTKxrSF3rcCVRo73SnSpNGVjYbBlxe7vv7XlGWiax5ApS8Ut5gP-PMU_eIr9W4O5BxXLAgADAIWL";
|
|
// String payAuthNo = "AUTH530100202205071850280000014";
|
|
// createOrder(OrderTypeEnum.DIAG_PAY, accessToken, "wxd503671f502bd89d", "1288583001", "216677bcfa51be03905c867dd71f2952", "oeso-t62kkoRwLVVkSkwmmjPfUXk", payAuthNo,
|
|
// null, null, null, null, null, null, null);
|
|
// }
|
|
|
|
|
|
// public String getUserInfo(String reqUrl, String openid, String qrcode) {
|
|
// Map<String, Object> dataMap = new HashMap<>();
|
|
// dataMap.put("qrcode", qrcode);
|
|
// dataMap.put("openid", openid);
|
|
//
|
|
// HttpURLConnection conn = null;
|
|
// OutputStream os = null;
|
|
// try {
|
|
// conn = (HttpURLConnection) new URL(reqUrl).openConnection();
|
|
// conn.setDoOutput(true);
|
|
// conn.setDoInput(true);
|
|
// conn.setRequestMethod("POST");
|
|
// conn.setUseCaches(false);
|
|
// conn.setInstanceFollowRedirects(true);
|
|
// conn.setRequestProperty("Content-Type", "application/json");
|
|
// conn.setRequestProperty("Accept", "application/json");
|
|
// String timestamp = Long.toString(System.currentTimeMillis());
|
|
//
|
|
// String signature = Config.createSignature(timestamp);
|
|
//
|
|
// String requestId = UUID.randomUUID().toString().replaceAll("-", "");
|
|
// conn.setRequestProperty("god-portal-timestamp", timestamp);
|
|
// conn.setRequestProperty("god-portal-signature", signature);
|
|
// conn.setRequestProperty("god-portal-request-id", requestId);
|
|
// conn.connect();
|
|
//
|
|
// os = conn.getOutputStream();
|
|
// os.write(JsonHelper.toJsonString(dataMap).getBytes());
|
|
// os.flush();
|
|
// os.close();
|
|
// ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
|
// InputStream inputStream = conn.getInputStream();
|
|
// byte[] bytes = new byte[1024];
|
|
// int readBytes;
|
|
// while ((readBytes = inputStream.read(bytes)) != -1) {
|
|
// byteArrayOutputStream.write(bytes, 0, readBytes);
|
|
// }
|
|
// bytes = byteArrayOutputStream.toByteArray();
|
|
// inputStream.close();
|
|
// conn.disconnect();
|
|
// return new String(bytes, StandardCharsets.UTF_8);
|
|
// } catch (Exception e) {
|
|
// if (conn != null) {
|
|
// conn.disconnect();
|
|
// }
|
|
// e.printStackTrace();
|
|
// }
|
|
// return null;
|
|
// }
|
|
|
|
}
|
|
|