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 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 MedicalBill 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(MedicalBill.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 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; // } }