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 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<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;
 | |
| //    }
 | |
| 
 | |
| }
 | |
| 
 |