🆕 #3816 【微信支付】增加 V3 医保相关接口的完整支持

This commit is contained in:
Sexy Six
2025-12-20 17:20:45 +08:00
committed by GitHub
parent 81816456c1
commit c8cdfb56e1
17 changed files with 2166 additions and 19 deletions

View File

@@ -0,0 +1,568 @@
package com.github.binarywang.wxpay.bean.mipay;
import com.github.binarywang.wxpay.bean.mipay.enums.CashAddTypeEnum;
import com.github.binarywang.wxpay.bean.mipay.enums.CashReduceTypeEnum;
import com.github.binarywang.wxpay.bean.mipay.enums.MixPayTypeEnum;
import com.github.binarywang.wxpay.bean.mipay.enums.OrderTypeEnum;
import com.github.binarywang.wxpay.bean.mipay.enums.UserCardTypeEnum;
import com.github.binarywang.wxpay.v3.SpecEncrypt;
import com.google.gson.annotations.SerializedName;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 医保自费混合收款下单请求
* <p>
* 从业机构调用该接口向微信医保后台下单
* 文档地址https://pay.weixin.qq.com/doc/v3/partner/4012503131
* @author xgl
* @date 2025/12/19 14:37
*/
@Data
@Builder(builderMethodName = "newBuilder")
@NoArgsConstructor
@AllArgsConstructor
public class MedInsOrdersRequest {
/**
* <pre>
* 字段名:混合支付类型
* 变量名mix_pay_type
* 必填:是
* 类型string
* 描述:
* 混合支付类型可选取值:
* - UNKNOWN_MIX_PAY_TYPE: 未知的混合支付类型,会被拦截
* - CASH_ONLY: 只向微信支付下单,没有向医保局下单
* - INSURANCE_ONLY: 只向医保局下单,没有向微信支付下单
* - CASH_AND_INSURANCE: 向医保局下单,也向微信支付下单
* </pre>
*/
@SerializedName("mix_pay_type")
public MixPayTypeEnum mixPayType;
/**
* <pre>
* 字段名:订单类型
* 变量名order_type
* 必填:是
* 类型string
* 描述:
* 订单类型可选取值:
* - UNKNOWN_ORDER_TYPE: 未知类型,会被拦截
* - REG_PAY: 挂号支付
* - DIAG_PAY: 诊间支付
* - COVID_EXAM_PAY: 新冠检测费用(核酸)
* - IN_HOSP_PAY: 住院费支付
* - PHARMACY_PAY: 药店支付
* - INSURANCE_PAY: 保险费支付
* - INT_REG_PAY: 互联网医院挂号支付
* - INT_RE_DIAG_PAY: 互联网医院复诊支付
* - INT_RX_PAY: 互联网医院处方支付
* - COVID_ANTIGEN_PAY: 新冠抗原检测
* - MED_PAY: 药费支付
* </pre>
*/
@SerializedName("order_type")
public OrderTypeEnum orderType;
/**
* <pre>
* 字段名:从业机构/服务商的公众号ID
* 变量名appid
* 必填:是
* 类型string(32)
* 描述:从业机构/服务商的公众号ID
* </pre>
*/
@SerializedName("appid")
public String appid;
/**
* <pre>
* 字段名医疗机构的公众号ID
* 变量名sub_appid
* 必填:是
* 类型string(32)
* 描述医疗机构的公众号ID
* </pre>
*/
@SerializedName("sub_appid")
public String subAppid;
/**
* <pre>
* 字段名:医疗机构的商户号
* 变量名sub_mchid
* 必填:是
* 类型string(32)
* 描述:医疗机构的商户号
* </pre>
*/
@SerializedName("sub_mchid")
public String subMchid;
/**
* <pre>
* 字段名用户在appid下的唯一标识
* 变量名openid
* 必填:否
* 类型string(128)
* 描述openid与sub_openid二选一传入openid时需要使用appid调起医保自费混合支付
* </pre>
*/
@SerializedName("openid")
public String openid;
/**
* <pre>
* 字段名用户在sub_appid下的唯一标识
* 变量名sub_openid
* 必填:否
* 类型string(128)
* 描述openid与sub_openid二选一传入sub_openid时需要使用sub_appid调起医保自费混合支付
* </pre>
*/
@SerializedName("sub_openid")
public String subOpenid;
/**
* <pre>
* 字段名:支付人身份信息
* 变量名payer
* 必填:是
* 类型object
* 描述:支付人身份信息
* </pre>
*/
@SerializedName("payer")
@SpecEncrypt
public PersonIdentification payer;
/**
* <pre>
* 字段名:是否代亲属支付
* 变量名pay_for_relatives
* 必填:否
* 类型boolean
* 描述:不传默认替本人支付
* - true: 代亲属支付
* - false: 本人支付
* </pre>
*/
@SerializedName("pay_for_relatives")
public Boolean payForRelatives;
/**
* <pre>
* 字段名:亲属身份信息
* 变量名relative
* 必填:否
* 类型object
* 描述pay_for_relatives为true时该字段必填
* </pre>
*/
@SerializedName("relative")
@SpecEncrypt
public PersonIdentification relative;
/**
* <pre>
* 字段名:从业机构订单号
* 变量名out_trade_no
* 必填:是
* 类型string(64)
* 描述:从业机构/服务商需要调两次接口:从业机构/服务商向微信支付下单获取微信支付凭证请求中会带上out_trade_no。下单成功后从业机构/服务商调用混合下单的接口即该接口请求中也会带上out_trade_no。
* </pre>
*/
@SerializedName("out_trade_no")
public String outTradeNo;
/**
* <pre>
* 字段名:医疗机构订单号
* 变量名serial_no
* 必填:是
* 类型string(40)
* 描述例如医院HIS系统订单号。传与费用明细上传中medOrgOrd字段一样的值局端会校验不一致将会返回错误
* </pre>
*/
@SerializedName("serial_no")
public String serialNo;
/**
* <pre>
* 字段名:支付订单号
* 变量名pay_order_id
* 必填:否
* 类型string
* 描述:支付订单号
* </pre>
*/
@SerializedName("pay_order_id")
public String payOrderId;
/**
* <pre>
* 字段名:支付授权号
* 变量名pay_auth_no
* 必填:否
* 类型string
* 描述:支付授权号
* </pre>
*/
@SerializedName("pay_auth_no")
public String payAuthNo;
/**
* <pre>
* 字段名:地理位置
* 变量名geo_location
* 必填:否
* 类型string
* 描述:地理位置
* </pre>
*/
@SerializedName("geo_location")
public String geoLocation;
/**
* <pre>
* 字段名城市ID
* 变量名city_id
* 必填:否
* 类型string
* 描述城市ID
* </pre>
*/
@SerializedName("city_id")
public String cityId;
/**
* <pre>
* 字段名:医疗机构名称
* 变量名med_inst_name
* 必填:否
* 类型string
* 描述:医疗机构名称
* </pre>
*/
@SerializedName("med_inst_name")
public String medInstName;
/**
* <pre>
* 字段名:医疗机构编号
* 变量名med_inst_no
* 必填:否
* 类型string
* 描述:医疗机构编号
* </pre>
*/
@SerializedName("med_inst_no")
public String medInstNo;
/**
* <pre>
* 字段名:医保订单创建时间
* 变量名med_ins_order_create_time
* 必填:否
* 类型string
* 描述:医保订单创建时间
* </pre>
*/
@SerializedName("med_ins_order_create_time")
public String medInsOrderCreateTime;
/**
* <pre>
* 字段名:总金额
* 变量名total_fee
* 必填:否
* 类型long
* 描述:总金额
* </pre>
*/
@SerializedName("total_fee")
public Long totalFee;
/**
* <pre>
* 字段名:医保统筹基金支付金额
* 变量名med_ins_gov_fee
* 必填:否
* 类型long
* 描述:医保统筹基金支付金额
* </pre>
*/
@SerializedName("med_ins_gov_fee")
public Long medInsGovFee;
/**
* <pre>
* 字段名:医保个人账户支付金额
* 变量名med_ins_self_fee
* 必填:否
* 类型long
* 描述:医保个人账户支付金额
* </pre>
*/
@SerializedName("med_ins_self_fee")
public Long medInsSelfFee;
/**
* <pre>
* 字段名:医保其他基金支付金额
* 变量名med_ins_other_fee
* 必填:否
* 类型long
* 描述:医保其他基金支付金额
* </pre>
*/
@SerializedName("med_ins_other_fee")
public Long medInsOtherFee;
/**
* <pre>
* 字段名:医保现金支付金额
* 变量名med_ins_cash_fee
* 必填:否
* 类型long
* 描述:医保现金支付金额
* </pre>
*/
@SerializedName("med_ins_cash_fee")
public Long medInsCashFee;
/**
* <pre>
* 字段名:微信支付现金支付金额
* 变量名wechat_pay_cash_fee
* 必填:否
* 类型long
* 描述:微信支付现金支付金额
* </pre>
*/
@SerializedName("wechat_pay_cash_fee")
public Long wechatPayCashFee;
/**
* <pre>
* 字段名:现金增加明细
* 变量名cash_add_detail
* 必填:否
* 类型list
* 描述:现金增加明细
* </pre>
*/
@SerializedName("cash_add_detail")
public List<CashAddEntity> cashAddDetail;
/**
* <pre>
* 字段名:现金减少明细
* 变量名cash_reduce_detail
* 必填:否
* 类型list
* 描述:现金减少明细
* </pre>
*/
@SerializedName("cash_reduce_detail")
public List<CashReduceEntity> cashReduceDetail;
/**
* <pre>
* 字段名回调URL
* 变量名callback_url
* 必填:否
* 类型string
* 描述回调URL
* </pre>
*/
@SerializedName("callback_url")
public String callbackUrl;
/**
* <pre>
* 字段名:预支付交易会话标识
* 变量名prepay_id
* 必填:否
* 类型string
* 描述:预支付交易会话标识
* </pre>
*/
@SerializedName("prepay_id")
public String prepayId;
/**
* <pre>
* 字段名:透传请求内容
* 变量名passthrough_request_content
* 必填:否
* 类型string
* 描述:透传请求内容
* </pre>
*/
@SerializedName("passthrough_request_content")
public String passthroughRequestContent;
/**
* <pre>
* 字段名:扩展字段
* 变量名extends
* 必填:否
* 类型string
* 描述:扩展字段
* </pre>
*/
@SerializedName("extends")
public String _extends;
/**
* <pre>
* 字段名:附加数据
* 变量名attach
* 必填:否
* 类型string
* 描述附加数据在查询API和支付通知中原样返回可作为自定义参数使用
* </pre>
*/
@SerializedName("attach")
public String attach;
/**
* <pre>
* 字段名:渠道编号
* 变量名channel_no
* 必填:否
* 类型string
* 描述:渠道编号
* </pre>
*/
@SerializedName("channel_no")
public String channelNo;
/**
* <pre>
* 字段名:医保测试环境标识
* 变量名med_ins_test_env
* 必填:否
* 类型boolean
* 描述:医保测试环境标识
* </pre>
*/
@SerializedName("med_ins_test_env")
public Boolean medInsTestEnv;
/**
* <pre>
* 支付人身份信息
* </pre>
*/
public static class PersonIdentification {
/**
* <pre>
* 字段名:姓名
* 变量名name
* 必填:是
* 类型string
* 描述:姓名,需加密
* </pre>
*/
@SerializedName("name")
@SpecEncrypt
public String name;
/**
* <pre>
* 字段名:身份证摘要
* 变量名id_digest
* 必填:是
* 类型string
* 描述:身份证摘要,需加密
* </pre>
*/
@SerializedName("id_digest")
@SpecEncrypt
public String idDigest;
/**
* <pre>
* 字段名:证件类型
* 变量名card_type
* 必填:是
* 类型string
* 描述:证件类型
* </pre>
*/
@SerializedName("card_type")
public UserCardTypeEnum cardType;
}
/**
* <pre>
* 现金增加明细实体
* </pre>
*/
public static class CashAddEntity {
/**
* <pre>
* 字段名:现金增加金额
* 变量名cash_add_fee
* 必填:是
* 类型long
* 描述:现金增加金额
* </pre>
*/
@SerializedName("cash_add_fee")
public Long cashAddFee;
/**
* <pre>
* 字段名:现金增加类型
* 变量名cash_add_type
* 必填:是
* 类型string
* 描述:现金增加类型
* </pre>
*/
@SerializedName("cash_add_type")
public CashAddTypeEnum cashAddType;
}
/**
* <pre>
* 现金减少明细实体
* </pre>
*/
public static class CashReduceEntity {
/**
* <pre>
* 字段名:现金减少金额
* 变量名cash_reduce_fee
* 必填:是
* 类型long
* 描述:现金减少金额
* </pre>
*/
@SerializedName("cash_reduce_fee")
public Long cashReduceFee;
/**
* <pre>
* 字段名:现金减少类型
* 变量名cash_reduce_type
* 必填:是
* 类型string
* 描述:现金减少类型
* </pre>
*/
@SerializedName("cash_reduce_type")
public CashReduceTypeEnum cashReduceType;
}
}

View File

@@ -0,0 +1,497 @@
package com.github.binarywang.wxpay.bean.mipay;
import com.github.binarywang.wxpay.bean.mipay.enums.MedInsPayStatusEnum;
import com.github.binarywang.wxpay.bean.mipay.enums.MixPayStatusEnum;
import com.github.binarywang.wxpay.bean.mipay.enums.MixPayTypeEnum;
import com.github.binarywang.wxpay.bean.mipay.enums.OrderTypeEnum;
import com.github.binarywang.wxpay.bean.mipay.enums.SelfPayStatusEnum;
import com.google.gson.annotations.SerializedName;
import java.util.List;
import lombok.Data;
/**
* 医保自费混合收款下单响应
* <p>
* 从业机构调用医保自费混合收款下单接口后返回的结果
* 文档地址https://pay.weixin.qq.com/doc/v3/partner/4012503131
* @author xgl
* @date 2025/12/19 14:37
*/
@Data
public class MedInsOrdersResult {
/**
* <pre>
* 字段名:混合交易订单号
* 变量名mix_trade_no
* 必填:是
* 类型string
* 描述:微信支付生成的混合交易订单号
* </pre>
*/
@SerializedName("mix_trade_no")
public String mixTradeNo;
/**
* <pre>
* 字段名:混合支付状态
* 变量名mix_pay_status
* 必填:是
* 类型string
* 描述:混合支付整体状态
* </pre>
*/
@SerializedName("mix_pay_status")
public MixPayStatusEnum mixPayStatus;
/**
* <pre>
* 字段名:自费支付状态
* 变量名self_pay_status
* 必填:是
* 类型string
* 描述:自费部分支付状态
* </pre>
*/
@SerializedName("self_pay_status")
public SelfPayStatusEnum selfPayStatus;
/**
* <pre>
* 字段名:医保支付状态
* 变量名med_ins_pay_status
* 必填:是
* 类型string
* 描述:医保部分支付状态
* </pre>
*/
@SerializedName("med_ins_pay_status")
public MedInsPayStatusEnum medInsPayStatusEnum;
/**
* <pre>
* 字段名:支付完成时间
* 变量名paid_time
* 必填:否
* 类型string
* 描述支付完成时间格式为yyyyMMddHHmmss
* </pre>
*/
@SerializedName("paid_time")
public String paidTime;
/**
* <pre>
* 字段名:透传响应内容
* 变量名passthrough_response_content
* 必填:否
* 类型string
* 描述:透传响应内容
* </pre>
*/
@SerializedName("passthrough_response_content")
public String passthroughResponseContent;
/**
* <pre>
* 字段名:混合支付类型
* 变量名mix_pay_type
* 必填:是
* 类型string
* 描述:
* 混合支付类型可选取值:
* - UNKNOWN_MIX_PAY_TYPE: 未知的混合支付类型,会被拦截
* - CASH_ONLY: 只向微信支付下单,没有向医保局下单
* - INSURANCE_ONLY: 只向医保局下单,没有向微信支付下单
* - CASH_AND_INSURANCE: 向医保局下单,也向微信支付下单
* </pre>
*/
@SerializedName("mix_pay_type")
public MixPayTypeEnum mixPayType;
/**
* <pre>
* 字段名:订单类型
* 变量名order_type
* 必填:是
* 类型string
* 描述:
* 订单类型可选取值:
* - UNKNOWN_ORDER_TYPE: 未知类型,会被拦截
* - REG_PAY: 挂号支付
* - DIAG_PAY: 诊间支付
* - COVID_EXAM_PAY: 新冠检测费用(核酸)
* - IN_HOSP_PAY: 住院费支付
* - PHARMACY_PAY: 药店支付
* - INSURANCE_PAY: 保险费支付
* - INT_REG_PAY: 互联网医院挂号支付
* - INT_RE_DIAG_PAY: 互联网医院复诊支付
* - INT_RX_PAY: 互联网医院处方支付
* - COVID_ANTIGEN_PAY: 新冠抗原检测
* - MED_PAY: 药费支付
* </pre>
*/
@SerializedName("order_type")
public OrderTypeEnum orderType;
/**
* <pre>
* 字段名:从业机构/服务商的公众号ID
* 变量名appid
* 必填:是
* 类型string(32)
* 描述:从业机构/服务商的公众号ID
* </pre>
*/
@SerializedName("appid")
public String appid;
/**
* <pre>
* 字段名医疗机构的公众号ID
* 变量名sub_appid
* 必填:是
* 类型string(32)
* 描述医疗机构的公众号ID
* </pre>
*/
@SerializedName("sub_appid")
public String subAppid;
/**
* <pre>
* 字段名:医疗机构的商户号
* 变量名sub_mchid
* 必填:是
* 类型string(32)
* 描述:医疗机构的商户号
* </pre>
*/
@SerializedName("sub_mchid")
public String subMchid;
/**
* <pre>
* 字段名用户在appid下的唯一标识
* 变量名openid
* 必填:否
* 类型string(128)
* 描述openid与sub_openid二选一传入openid时需要使用appid调起医保自费混合支付
* </pre>
*/
@SerializedName("openid")
public String openid;
/**
* <pre>
* 字段名用户在sub_appid下的唯一标识
* 变量名sub_openid
* 必填:否
* 类型string(128)
* 描述openid与sub_openid二选一传入sub_openid时需要使用sub_appid调起医保自费混合支付
* </pre>
*/
@SerializedName("sub_openid")
public String subOpenid;
/**
* <pre>
* 字段名:是否代亲属支付
* 变量名pay_for_relatives
* 必填:否
* 类型boolean
* 描述:不传默认替本人支付
* - true: 代亲属支付
* - false: 本人支付
* </pre>
*/
@SerializedName("pay_for_relatives")
public Boolean payForRelatives;
/**
* <pre>
* 字段名:从业机构订单号
* 变量名out_trade_no
* 必填:是
* 类型string(64)
* 描述:从业机构/服务商需要调两次接口:从业机构/服务商向微信支付下单获取微信支付凭证请求中会带上out_trade_no。下单成功后从业机构/服务商调用混合下单的接口即该接口请求中也会带上out_trade_no。
* </pre>
*/
@SerializedName("out_trade_no")
public String outTradeNo;
/**
* <pre>
* 字段名:医疗机构订单号
* 变量名serial_no
* 必填:是
* 类型string(40)
* 描述例如医院HIS系统订单号。传与费用明细上传中medOrgOrd字段一样的值局端会校验不一致将会返回错误
* </pre>
*/
@SerializedName("serial_no")
public String serialNo;
/**
* <pre>
* 字段名:支付订单号
* 变量名pay_order_id
* 必填:否
* 类型string
* 描述:支付订单号
* </pre>
*/
@SerializedName("pay_order_id")
public String payOrderId;
/**
* <pre>
* 字段名:支付授权号
* 变量名pay_auth_no
* 必填:否
* 类型string
* 描述:支付授权号
* </pre>
*/
@SerializedName("pay_auth_no")
public String payAuthNo;
/**
* <pre>
* 字段名:地理位置
* 变量名geo_location
* 必填:否
* 类型string
* 描述:地理位置
* </pre>
*/
@SerializedName("geo_location")
public String geoLocation;
/**
* <pre>
* 字段名城市ID
* 变量名city_id
* 必填:否
* 类型string
* 描述城市ID
* </pre>
*/
@SerializedName("city_id")
public String cityId;
/**
* <pre>
* 字段名:医疗机构名称
* 变量名med_inst_name
* 必填:否
* 类型string
* 描述:医疗机构名称
* </pre>
*/
@SerializedName("med_inst_name")
public String medInstName;
/**
* <pre>
* 字段名:医疗机构编号
* 变量名med_inst_no
* 必填:否
* 类型string
* 描述:医疗机构编号
* </pre>
*/
@SerializedName("med_inst_no")
public String medInstNo;
/**
* <pre>
* 字段名:医保订单创建时间
* 变量名med_ins_order_create_time
* 必填:否
* 类型string
* 描述:医保订单创建时间
* </pre>
*/
@SerializedName("med_ins_order_create_time")
public String medInsOrderCreateTime;
/**
* <pre>
* 字段名:总金额
* 变量名total_fee
* 必填:否
* 类型long
* 描述:总金额
* </pre>
*/
@SerializedName("total_fee")
public Long totalFee;
/**
* <pre>
* 字段名:医保统筹基金支付金额
* 变量名med_ins_gov_fee
* 必填:否
* 类型long
* 描述:医保统筹基金支付金额
* </pre>
*/
@SerializedName("med_ins_gov_fee")
public Long medInsGovFee;
/**
* <pre>
* 字段名:医保个人账户支付金额
* 变量名med_ins_self_fee
* 必填:否
* 类型long
* 描述:医保个人账户支付金额
* </pre>
*/
@SerializedName("med_ins_self_fee")
public Long medInsSelfFee;
/**
* <pre>
* 字段名:医保其他基金支付金额
* 变量名med_ins_other_fee
* 必填:否
* 类型long
* 描述:医保其他基金支付金额
* </pre>
*/
@SerializedName("med_ins_other_fee")
public Long medInsOtherFee;
/**
* <pre>
* 字段名:医保现金支付金额
* 变量名med_ins_cash_fee
* 必填:否
* 类型long
* 描述:医保现金支付金额
* </pre>
*/
@SerializedName("med_ins_cash_fee")
public Long medInsCashFee;
/**
* <pre>
* 字段名:微信支付现金支付金额
* 变量名wechat_pay_cash_fee
* 必填:否
* 类型long
* 描述:微信支付现金支付金额
* </pre>
*/
@SerializedName("wechat_pay_cash_fee")
public Long wechatPayCashFee;
/**
* <pre>
* 字段名:现金增加明细
* 变量名cash_add_detail
* 必填:否
* 类型list
* 描述:现金增加明细
* </pre>
*/
@SerializedName("cash_add_detail")
public List<MedInsOrdersRequest.CashAddEntity> cashAddDetail;
/**
* <pre>
* 字段名:现金减少明细
* 变量名cash_reduce_detail
* 必填:否
* 类型list
* 描述:现金减少明细
* </pre>
*/
@SerializedName("cash_reduce_detail")
public List<MedInsOrdersRequest.CashReduceEntity> cashReduceDetail;
/**
* <pre>
* 字段名回调URL
* 变量名callback_url
* 必填:否
* 类型string
* 描述回调URL
* </pre>
*/
@SerializedName("callback_url")
public String callbackUrl;
/**
* <pre>
* 字段名:预支付交易会话标识
* 变量名prepay_id
* 必填:否
* 类型string
* 描述:预支付交易会话标识
* </pre>
*/
@SerializedName("prepay_id")
public String prepayId;
/**
* <pre>
* 字段名:透传请求内容
* 变量名passthrough_request_content
* 必填:否
* 类型string
* 描述:透传请求内容
* </pre>
*/
@SerializedName("passthrough_request_content")
public String passthroughRequestContent;
/**
* <pre>
* 字段名:扩展字段
* 变量名extends
* 必填:否
* 类型string
* 描述:扩展字段
* </pre>
*/
@SerializedName("extends")
public String _extends;
/**
* <pre>
* 字段名:附加数据
* 变量名attach
* 必填:否
* 类型string
* 描述附加数据在查询API和支付通知中原样返回可作为自定义参数使用
* </pre>
*/
@SerializedName("attach")
public String attach;
/**
* <pre>
* 字段名:渠道编号
* 变量名channel_no
* 必填:否
* 类型string
* 描述:渠道编号
* </pre>
*/
@SerializedName("channel_no")
public String channelNo;
/**
* <pre>
* 字段名:医保测试环境标识
* 变量名med_ins_test_env
* 必填:否
* 类型boolean
* 描述:医保测试环境标识
* </pre>
*/
@SerializedName("med_ins_test_env")
public Boolean medInsTestEnv;
}

View File

@@ -0,0 +1,116 @@
package com.github.binarywang.wxpay.bean.mipay;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 医保退款通知请求
* <p>
* 从业机构调用该接口向微信医保后台通知医保订单的退款成功结果
* 文档地址https://pay.weixin.qq.com/doc/v3/partner/4012166534
* @author xgl
* @date 2025/12/20
*/
@Data
@Builder(builderMethodName = "newBuilder")
@NoArgsConstructor
@AllArgsConstructor
public class MedInsRefundNotifyRequest {
/**
* <pre>
* 字段名:医保自费混合订单号
* 必填:是
* 类型string(32)
* 描述:医保自费混合订单号
* </pre>
*/
private String mixTradeNo;
/**
* <pre>
* 字段名:医疗机构的商户号
* 变量名sub_mchid
* 必填:是
* 类型string(32)
* 描述:医疗机构的商户号
* </pre>
*/
@SerializedName("sub_mchid")
private String subMchid;
/**
* <pre>
* 字段名:医保退款的总金额
* 变量名med_refund_total_fee
* 必填:是
* 类型integer
* 描述:单位分,医保退款的总金额。
* </pre>
*/
@SerializedName("med_refund_total_fee")
private Integer medRefundTotalFee;
/**
* <pre>
* 字段名:医保统筹退款金额
* 变量名med_refund_gov_fee
* 必填:是
* 类型integer
* 描述:单位分,医保统筹退款金额。
* </pre>
*/
@SerializedName("med_refund_gov_fee")
private Integer medRefundGovFee;
/**
* <pre>
* 字段名:医保个账退款金额
* 变量名med_refund_self_fee
* 必填:是
* 类型integer
* 描述:单位分,医保个账退款金额。
* </pre>
*/
@SerializedName("med_refund_self_fee")
private Integer medRefundSelfFee;
/**
* <pre>
* 字段名:医保其他退款金额
* 变量名med_refund_other_fee
* 必填:是
* 类型integer
* 描述:单位分,医保其他退款金额。
* </pre>
*/
@SerializedName("med_refund_other_fee")
private Integer medRefundOtherFee;
/**
* <pre>
* 字段名:医保退款成功时间
* 变量名refund_time
* 必填:是
* 类型string(64)
* 描述遵循rfc3339标准格式格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE。
* </pre>
*/
@SerializedName("refund_time")
private String refundTime;
/**
* <pre>
* 字段名:从业机构\服务商退款单号
* 变量名out_refund_no
* 必填:是
* 类型string(64)
* 描述:有自费单时,从业机构\服务商应填与自费退款申请处一致的out_refund_no。否则从业机构透传医疗机构退款单号即可。
* </pre>
*/
@SerializedName("out_refund_no")
private String outRefundNo;
}

View File

@@ -0,0 +1,29 @@
package com.github.binarywang.wxpay.bean.mipay.enums;
import com.google.gson.annotations.SerializedName;
/**
* 现金增加类型枚举
* <p>
* 描述医保自费混合支付中现金增加的类型
*
* @author xgl
* @date 2025/12/20
*/
public enum CashAddTypeEnum {
/**
* 默认增加类型
*/
@SerializedName("DEFAULT_ADD_TYPE")
DEFAULT_ADD_TYPE,
/**
* 运费
*/
@SerializedName("FREIGHT")
FREIGHT,
/**
* 其他医疗费用
*/
@SerializedName("OTHER_MEDICAL_EXPENSES")
OTHER_MEDICAL_EXPENSES
}

View File

@@ -0,0 +1,44 @@
package com.github.binarywang.wxpay.bean.mipay.enums;
import com.google.gson.annotations.SerializedName;
/**
* 现金减少类型枚举
* <p>
* 描述医保自费混合支付中现金减少的类型
*
* @author xgl
* @date 2025/12/20
*/
public enum CashReduceTypeEnum {
/**
* 默认减少类型
*/
@SerializedName("DEFAULT_REDUCE_TYPE")
DEFAULT_REDUCE_TYPE,
/**
* 医院减免
*/
@SerializedName("HOSPITAL_REDUCE")
HOSPITAL_REDUCE,
/**
* 药店折扣
*/
@SerializedName("PHARMACY_DISCOUNT")
PHARMACY_DISCOUNT,
/**
* 折扣优惠
*/
@SerializedName("DISCOUNT")
DISCOUNT,
/**
* 预付费抵扣
*/
@SerializedName("PRE_PAYMENT")
PRE_PAYMENT,
/**
* 押金扣除
*/
@SerializedName("DEPOSIT_DEDUCTION")
DEPOSIT_DEDUCTION
}

View File

@@ -0,0 +1,44 @@
package com.github.binarywang.wxpay.bean.mipay.enums;
import com.google.gson.annotations.SerializedName;
/**
* 医保支付状态枚举
* <p>
* 描述医保自费混合支付中医保部分的支付状态
*
* @author xgl
* @date 2025/12/20
*/
public enum MedInsPayStatusEnum {
/**
* 未知的医保支付状态
*/
@SerializedName("UNKNOWN_MED_INS_PAY_STATUS")
UNKNOWN_MED_INS_PAY_STATUS,
/**
* 医保支付已创建
*/
@SerializedName("MED_INS_PAY_CREATED")
MED_INS_PAY_CREATED,
/**
* 医保支付成功
*/
@SerializedName("MED_INS_PAY_SUCCESS")
MED_INS_PAY_SUCCESS,
/**
* 医保支付已退款
*/
@SerializedName("MED_INS_PAY_REFUND")
MED_INS_PAY_REFUND,
/**
* 医保支付失败
*/
@SerializedName("MED_INS_PAY_FAIL")
MED_INS_PAY_FAIL,
/**
* 无需医保支付
*/
@SerializedName("NO_MED_INS_PAY")
NO_MED_INS_PAY
}

View File

@@ -0,0 +1,39 @@
package com.github.binarywang.wxpay.bean.mipay.enums;
import com.google.gson.annotations.SerializedName;
/**
* 混合支付状态枚举
* <p>
* 描述医保自费混合支付的整体状态
*
* @author xgl
* @date 2025/12/20
*/
public enum MixPayStatusEnum {
/**
* 未知的混合支付状态
*/
@SerializedName("UNKNOWN_MIX_PAY_STATUS")
UNKNOWN_MIX_PAY_STATUS,
/**
* 混合支付已创建
*/
@SerializedName("MIX_PAY_CREATED")
MIX_PAY_CREATED,
/**
* 混合支付成功
*/
@SerializedName("MIX_PAY_SUCCESS")
MIX_PAY_SUCCESS,
/**
* 混合支付已退款
*/
@SerializedName("MIX_PAY_REFUND")
MIX_PAY_REFUND,
/**
* 混合支付失败
*/
@SerializedName("MIX_PAY_FAIL")
MIX_PAY_FAIL
}

View File

@@ -0,0 +1,41 @@
package com.github.binarywang.wxpay.bean.mipay.enums;
import com.google.gson.annotations.SerializedName;
/**
* 混合支付类型枚举
* <p>
* 描述医保自费混合支付的类型
* 文档地址https://pay.weixin.qq.com/doc/v3/partner/4012503131
*
* @author xgl
* @date 2025/12/20 09:21
*/
public enum MixPayTypeEnum {
/**
* 未知的混合支付类型,会被拦截。
*/
@SerializedName("UNKNOWN_MIX_PAY_TYPE")
UNKNOWN_MIX_PAY_TYPE,
/**
* 只向微信支付下单,没有向医保局下单。包括没有向医保局上传费用明细、预结算。
*/
@SerializedName("CASH_ONLY")
CASH_ONLY,
/**
* 只向医保局下单,没有向微信支付下单。如果医保局分账结果中有自费部份,但由于有减免抵扣,没有向微信支付下单,也是纯医保。
*/
@SerializedName("INSURANCE_ONLY")
INSURANCE_ONLY,
/**
* 向医保局下单,也向微信支付下单。如果医保预结算全部需自费,也属于混合类型。
*/
@SerializedName("CASH_AND_INSURANCE")
CASH_AND_INSURANCE
}

View File

@@ -0,0 +1,87 @@
package com.github.binarywang.wxpay.bean.mipay.enums;
import com.google.gson.annotations.SerializedName;
/**
* 订单类型枚举
* <p>
* 描述医保自费混合支付的订单类型
* 文档地址https://pay.weixin.qq.com/doc/v3/partner/4012503131
*
* @author xgl
* @date 2025/12/20
*/
public enum OrderTypeEnum {
/**
* 未知类型,会被拦截
*/
@SerializedName("UNKNOWN_ORDER_TYPE")
UNKNOWN_ORDER_TYPE,
/**
* 挂号支付
*/
@SerializedName("REG_PAY")
REG_PAY,
/**
* 诊间支付
*/
@SerializedName("DIAG_PAY")
DIAG_PAY,
/**
* 新冠检测费用(核酸)
*/
@SerializedName("COVID_EXAM_PAY")
COVID_EXAM_PAY,
/**
* 住院费支付
*/
@SerializedName("IN_HOSP_PAY")
IN_HOSP_PAY,
/**
* 药店支付
*/
@SerializedName("PHARMACY_PAY")
PHARMACY_PAY,
/**
* 保险费支付
*/
@SerializedName("INSURANCE_PAY")
INSURANCE_PAY,
/**
* 互联网医院挂号支付
*/
@SerializedName("INT_REG_PAY")
INT_REG_PAY,
/**
* 互联网医院复诊支付
*/
@SerializedName("INT_RE_DIAG_PAY")
INT_RE_DIAG_PAY,
/**
* 互联网医院处方支付
*/
@SerializedName("INT_RX_PAY")
INT_RX_PAY,
/**
* 新冠抗原检测
*/
@SerializedName("COVID_ANTIGEN_PAY")
COVID_ANTIGEN_PAY,
/**
* 药费支付
*/
@SerializedName("MED_PAY")
MED_PAY
}

View File

@@ -0,0 +1,44 @@
package com.github.binarywang.wxpay.bean.mipay.enums;
import com.google.gson.annotations.SerializedName;
/**
* 自费支付状态枚举
* <p>
* 描述医保自费混合支付中自费部分的支付状态
*
* @author xgl
* @date 2025/12/20
*/
public enum SelfPayStatusEnum {
/**
* 未知的自费支付状态
*/
@SerializedName("UNKNOWN_SELF_PAY_STATUS")
UNKNOWN_SELF_PAY_STATUS,
/**
* 自费支付已创建
*/
@SerializedName("SELF_PAY_CREATED")
SELF_PAY_CREATED,
/**
* 自费支付成功
*/
@SerializedName("SELF_PAY_SUCCESS")
SELF_PAY_SUCCESS,
/**
* 自费支付已退款
*/
@SerializedName("SELF_PAY_REFUND")
SELF_PAY_REFUND,
/**
* 自费支付失败
*/
@SerializedName("SELF_PAY_FAIL")
SELF_PAY_FAIL,
/**
* 无需自费支付
*/
@SerializedName("NO_SELF_PAY")
NO_SELF_PAY
}

View File

@@ -0,0 +1,54 @@
package com.github.binarywang.wxpay.bean.mipay.enums;
import com.google.gson.annotations.SerializedName;
/**
* 用户证件类型枚举
* <p>
* 描述医保自费混合支付中用户的证件类型
*
* @author xgl
* @date 2025/12/20
*/
public enum UserCardTypeEnum {
/**
* 未知的用户证件类型
*/
@SerializedName("UNKNOWN_USER_CARD_TYPE")
UNKNOWN_USER_CARD_TYPE,
/**
* 居民身份证
*/
@SerializedName("ID_CARD")
ID_CARD,
/**
* 户口本
*/
@SerializedName("HOUSEHOLD_REGISTRATION")
HOUSEHOLD_REGISTRATION,
/**
* 外国护照
*/
@SerializedName("FOREIGNER_PASSPORT")
FOREIGNER_PASSPORT,
/**
* 台湾居民来往大陆通行证
*/
@SerializedName("MAINLAND_TRAVEL_PERMIT_FOR_TW")
MAINLAND_TRAVEL_PERMIT_FOR_TW,
/**
* 澳门居民来往大陆通行证
*/
@SerializedName("MAINLAND_TRAVEL_PERMIT_FOR_MO")
MAINLAND_TRAVEL_PERMIT_FOR_MO,
/**
* 香港居民来往大陆通行证
*/
@SerializedName("MAINLAND_TRAVEL_PERMIT_FOR_HK")
MAINLAND_TRAVEL_PERMIT_FOR_HK,
/**
* 外国人永久居留身份证
*/
@SerializedName("FOREIGN_PERMANENT_RESIDENT")
FOREIGN_PERMANENT_RESIDENT
}

View File

@@ -0,0 +1,265 @@
package com.github.binarywang.wxpay.bean.notify;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* <pre>
* 医保混合收款成功通知结果
* 文档地址https://pay.weixin.qq.com/doc/v3/partner/4012165722
* </pre>
*
* @author xgl
* @date 2025/12/20
*/
@Data
@NoArgsConstructor
public class MiPayNotifyV3Result implements Serializable, WxPayBaseNotifyV3Result<MiPayNotifyV3Result.DecryptNotifyResult> {
/**
* 源数据
*/
private OriginNotifyResponse rawData;
/**
* 解密后的数据
*/
private DecryptNotifyResult result;
@Data
@NoArgsConstructor
public static class DecryptNotifyResult implements Serializable {
private static final long serialVersionUID = 1L;
/**
* <pre>
* 字段名应用ID
* 变量名appid
* 是否必填:是
* 类型string(32)
* 描述:
* 从业机构/服务商的公众号ID
* </pre>
*/
@SerializedName(value = "appid")
private String appid;
/**
* <pre>
* 字段名医疗机构的公众号ID
* 变量名sub_appid
* 是否必填:是
* 类型string(32)
* 描述:
* 医疗机构的公众号ID
* </pre>
*/
@SerializedName(value = "sub_appid")
private String subAppid;
/**
* <pre>
* 字段名:医疗机构的商户号
* 变量名sub_mchid
* 是否必填:是
* 类型string(32)
* 描述:
* 医疗机构的商户号
* </pre>
*/
@SerializedName(value = "sub_mchid")
private String subMchid;
/**
* <pre>
* 字段名:从业机构订单号
* 变量名out_trade_no
* 是否必填:是
* 类型string(64)
* 描述:
* 从业机构/服务商订单号
* </pre>
*/
@SerializedName(value = "out_trade_no")
private String outTradeNo;
/**
* <pre>
* 字段名:医保自费混合订单号
* 变量名mix_trade_no
* 是否必填:是
* 类型string(32)
* 描述:
* 微信支付系统生成的医保自费混合订单号
* </pre>
*/
@SerializedName(value = "mix_trade_no")
private String mixTradeNo;
/**
* <pre>
* 字段名:微信支付订单号
* 变量名transaction_id
* 是否必填:是
* 类型string(32)
* 描述:
* 微信支付系统生成的订单号
* </pre>
*/
@SerializedName(value = "transaction_id")
private String transactionId;
/**
* <pre>
* 字段名:医保订单创建时间
* 变量名med_ins_order_create_time
* 是否必填:是
* 类型string(64)
* 描述:
* 医保订单创建时间遵循rfc3339标准格式
* </pre>
*/
@SerializedName(value = "med_ins_order_create_time")
private String medInsOrderCreateTime;
/**
* <pre>
* 字段名:医保订单完成时间
* 变量名med_ins_order_finish_time
* 是否必填:是
* 类型string(64)
* 描述:
* 医保订单完成时间遵循rfc3339标准格式
* </pre>
*/
@SerializedName(value = "med_ins_order_finish_time")
private String medInsOrderFinishTime;
/**
* <pre>
* 字段名:总金额
* 变量名total_fee
* 是否必填:否
* 类型long
* 描述:
* 总金额,单位为分
* </pre>
*/
@SerializedName(value = "total_fee")
private Long totalFee;
/**
* <pre>
* 字段名:医保统筹基金支付金额
* 变量名med_ins_gov_fee
* 是否必填:否
* 类型long
* 描述:
* 医保统筹基金支付金额,单位为分
* </pre>
*/
@SerializedName(value = "med_ins_gov_fee")
private Long medInsGovFee;
/**
* <pre>
* 字段名:医保个人账户支付金额
* 变量名med_ins_self_fee
* 是否必填:否
* 类型long
* 描述:
* 医保个人账户支付金额,单位为分
* </pre>
*/
@SerializedName(value = "med_ins_self_fee")
private Long medInsSelfFee;
/**
* <pre>
* 字段名:医保其他基金支付金额
* 变量名med_ins_other_fee
* 是否必填:否
* 类型long
* 描述:
* 医保其他基金支付金额,单位为分
* </pre>
*/
@SerializedName(value = "med_ins_other_fee")
private Long medInsOtherFee;
/**
* <pre>
* 字段名:医保现金支付金额
* 变量名med_ins_cash_fee
* 是否必填:否
* 类型long
* 描述:
* 医保现金支付金额,单位为分
* </pre>
*/
@SerializedName(value = "med_ins_cash_fee")
private Long medInsCashFee;
/**
* <pre>
* 字段名:微信支付现金支付金额
* 变量名wechat_pay_cash_fee
* 是否必填:否
* 类型long
* 描述:
* 微信支付现金支付金额,单位为分
* </pre>
*/
@SerializedName(value = "wechat_pay_cash_fee")
private Long wechatPayCashFee;
/**
* <pre>
* 字段名:附加数据
* 变量名attach
* 是否必填:否
* 类型string(128)
* 描述:
* 附加数据在查询API和支付通知中原样返回可作为自定义参数使用
* </pre>
*/
@SerializedName(value = "attach")
private String attach;
/**
* <pre>
* 字段名:支付状态
* 变量名trade_state
* 是否必填:是
* 类型string(32)
* 描述:
* 交易状态,枚举值:
* SUCCESS支付成功
* REFUND转入退款
* NOTPAY未支付
* CLOSED已关闭
* REVOKED已撤销
* USERPAYING用户支付中
* PAYERROR支付失败
* </pre>
*/
@SerializedName(value = "trade_state")
private String tradeState;
/**
* <pre>
* 字段名:支付状态描述
* 变量名trade_state_desc
* 是否必填:是
* 类型string(256)
* 描述:
* 交易状态描述
* </pre>
*/
@SerializedName(value = "trade_state_desc")
private String tradeStateDesc;
}
}

View File

@@ -0,0 +1,94 @@
package com.github.binarywang.wxpay.service;
import com.github.binarywang.wxpay.bean.mipay.MedInsOrdersRequest;
import com.github.binarywang.wxpay.bean.mipay.MedInsOrdersResult;
import com.github.binarywang.wxpay.bean.mipay.MedInsRefundNotifyRequest;
import com.github.binarywang.wxpay.bean.notify.MiPayNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.exception.WxPayException;
/**
* <a href="https://pay.weixin.qq.com/doc/v3/partner/4012503131">医保相关接口</a>
* 医保相关接口
* @author xgl
* @date 2025/12/20
*/
public interface MiPayService {
/**
* <pre>
* 医保自费混合收款下单
*
* 从业机构调用该接口向微信医保后台下单
*
* 文档地址:<a href="https://pay.weixin.qq.com/doc/v3/partner/4012503131">医保自费混合收款下单</a>
* </pre>
*
* @param request 下单参数
* @return ReservationTransferNotifyResult 下单结果
* @throws WxPayException the wx pay exception
*/
MedInsOrdersResult medInsOrders(MedInsOrdersRequest request) throws WxPayException;
/**
* <pre>
* 使用医保自费混合订单号查看下单结果
*
* 从业机构使用混合下单订单号,通过该接口主动查询订单状态,完成下一步的业务逻辑。
*
* 文档地址:<a href="https://pay.weixin.qq.com/doc/v3/partner/4012503155">使用医保自费混合订单号查看下单结果</a>
* </pre>
*
* @param mixTradeNo 医保自费混合订单号
* @param subMchid 医疗机构的商户号
* @return MedInsOrdersResult 下单结果
* @throws WxPayException the wx pay exception
*/
MedInsOrdersResult getMedInsOrderByMixTradeNo(String mixTradeNo, String subMchid) throws WxPayException;
/**
* <pre>
* 使用从业机构订单号查看下单结果
*
* 从业机构使用从业机构订单号、医疗机构商户号,通过该接口主动查询订单状态,完成下一步的业务逻辑。
*
* 文档地址:<a href="https://pay.weixin.qq.com/doc/v3/partner/4012503286">使用从业机构订单号查看下单结果</a>
* </pre>
*
* @param outTradeNo 从业机构订单号
* @param subMchid 医疗机构的商户号
* @return MedInsOrdersResult 下单结果
* @throws WxPayException the wx pay exception
*/
MedInsOrdersResult getMedInsOrderByOutTradeNo(String outTradeNo, String subMchid) throws WxPayException;
/**
* <pre>
* 解析医保混合收款成功通知
*
* 微信支付会通过POST请求向商户设置的回调URL推送医保混合收款成功通知商户需要接收处理该消息并返回应答。
*
* 文档地址:<a href="https://pay.weixin.qq.com/doc/v3/partner/4012165722">医保混合收款成功通知</a>
* </pre>
*
* @param notifyData 通知数据字符串
* @return MiPayNotifyV3Result 医保混合收款成功通知结果
* @throws WxPayException the wx pay exception
*/
MiPayNotifyV3Result parseMiPayNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException;
/**
* <pre>
* 医保退款通知
*
* 从业机构调用该接口向微信医保后台通知医保订单的退款成功结果
*
* 文档地址:<a href="https://pay.weixin.qq.com/doc/v3/partner/4012166534">医保退款通知</a>
* </pre>
*
* @param request 医保退款通知请求参数
* @throws WxPayException the wx pay exception
*/
void medInsRefundNotify(MedInsRefundNotifyRequest request) throws WxPayException;
}

View File

@@ -1706,4 +1706,12 @@ public interface WxPayService {
* @return the partner pay score sign plan service
*/
PartnerPayScoreSignPlanService getPartnerPayScoreSignPlanService();
/**
* 获取医保支付服务类
*
* @return the merchant transfer service
*/
MiPayService getMiPayService();
}

View File

@@ -1,17 +1,21 @@
package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.utils.qrcode.QrcodeUtils;
import com.github.binarywang.wxpay.bean.WxPayApiData;
import static com.github.binarywang.wxpay.constant.WxPayConstants.QUERY_COMMENT_DATE_FORMAT;
import static com.github.binarywang.wxpay.constant.WxPayConstants.TarType;
import com.github.binarywang.wxpay.bean.coupon.*;
import com.github.binarywang.wxpay.bean.notify.*;
import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.service.*;
import java.util.*;
import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.utils.qrcode.QrcodeUtils;
import com.github.binarywang.wxpay.bean.WxPayApiData;
import com.github.binarywang.wxpay.bean.order.WxPayAppOrderResult;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult;
import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult;
import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum;
import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.config.WxPayConfigHolder;
@@ -19,7 +23,6 @@ import com.github.binarywang.wxpay.constant.WxPayConstants.SignType;
import com.github.binarywang.wxpay.constant.WxPayConstants.TradeType;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.exception.WxSignTestException;
import com.github.binarywang.wxpay.service.*;
import com.github.binarywang.wxpay.util.SignUtils;
import com.github.binarywang.wxpay.util.XmlConfig;
import com.github.binarywang.wxpay.util.ZipUtils;
@@ -29,14 +32,6 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxRuntimeException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.http.entity.ContentType;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -45,12 +40,15 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.ZipException;
import static com.github.binarywang.wxpay.constant.WxPayConstants.QUERY_COMMENT_DATE_FORMAT;
import static com.github.binarywang.wxpay.constant.WxPayConstants.TarType;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxRuntimeException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.http.entity.ContentType;
/**
* <pre>
@@ -139,6 +137,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
@Getter
private final BusinessOperationTransferService businessOperationTransferService = new BusinessOperationTransferServiceImpl(this);
@Getter
private final MiPayService miPayService = new MiPayServiceImpl(this);
protected Map<String, WxPayConfig> configMap = new ConcurrentHashMap<>();
@Override

View File

@@ -0,0 +1,68 @@
package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.wxpay.bean.mipay.MedInsOrdersRequest;
import com.github.binarywang.wxpay.bean.mipay.MedInsOrdersResult;
import com.github.binarywang.wxpay.bean.mipay.MedInsRefundNotifyRequest;
import com.github.binarywang.wxpay.bean.notify.MiPayNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.MiPayService;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.v3.util.RsaCryptoUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.security.cert.X509Certificate;
import lombok.RequiredArgsConstructor;
/**
* <a href="https://pay.weixin.qq.com/doc/v3/partner/4012503131">医保相关接口</a>
* 医保相关接口
* @author xgl
* @date 2025/12/20
*/
@RequiredArgsConstructor
public class MiPayServiceImpl implements MiPayService {
private final WxPayService payService;
private static final Gson GSON = new GsonBuilder().create();
@Override
public MedInsOrdersResult medInsOrders(MedInsOrdersRequest request) throws WxPayException {
String url = String.format("%s/v3/med-ins/orders", this.payService.getPayBaseUrl());
X509Certificate validCertificate = this.payService.getConfig().getVerifier().getValidCertificate();
RsaCryptoUtil.encryptFields(request, validCertificate);
String result = this.payService.postV3WithWechatpaySerial(url, GSON.toJson(request));
return GSON.fromJson(result, MedInsOrdersResult.class);
}
@Override
public MedInsOrdersResult getMedInsOrderByMixTradeNo(String mixTradeNo, String subMchid) throws WxPayException {
String url = String.format("%s/v3/med-ins/orders/mix-trade-no/%s?sub_mchid=%s", this.payService.getPayBaseUrl(), mixTradeNo, subMchid);
String result = this.payService.getV3(url);
return GSON.fromJson(result, MedInsOrdersResult.class);
}
@Override
public MedInsOrdersResult getMedInsOrderByOutTradeNo(String outTradeNo, String subMchid) throws WxPayException {
String url = String.format("%s/v3/med-ins/orders/out-trade-no/%s?sub_mchid=%s", this.payService.getPayBaseUrl(), outTradeNo, subMchid);
String result = this.payService.getV3(url);
return GSON.fromJson(result, MedInsOrdersResult.class);
}
@Override
public MiPayNotifyV3Result parseMiPayNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException {
return this.payService.baseParseOrderNotifyV3Result(notifyData, header, MiPayNotifyV3Result.class, MiPayNotifyV3Result.DecryptNotifyResult.class);
}
@Override
public void medInsRefundNotify(MedInsRefundNotifyRequest request) throws WxPayException {
String url = String.format("%s/v3/med-ins/refunds/notify?mix_trade_no=%s", this.payService.getPayBaseUrl(), request.getMixTradeNo());
this.payService.postV3(url, GSON.toJson(request));
}
}

View File

@@ -0,0 +1,148 @@
package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.wxpay.bean.mipay.MedInsOrdersRequest;
import com.github.binarywang.wxpay.bean.mipay.MedInsOrdersResult;
import com.github.binarywang.wxpay.bean.mipay.MedInsRefundNotifyRequest;
import com.github.binarywang.wxpay.bean.notify.MiPayNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.MiPayService;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.testbase.ApiTestModule;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
/**
* 医保接口测试
* @author xgl
* @date 2025/12/20 10:04
*/
@Slf4j
@Test
@Guice(modules = ApiTestModule.class)
public class MiPayServiceImplTest {
@Inject
private WxPayService wxPayService;
private static final Gson GSON = new GsonBuilder().create();
/**
* 医保自费混合收款下单测试
* @throws WxPayException
*/
@Test
public void medInsOrders() throws WxPayException {
String requestParamStr = "{\"mix_pay_type\":\"CASH_AND_INSURANCE\",\"order_type\":\"REG_PAY\",\"appid\":\"wxdace645e0bc2cXXX\",\"sub_appid\":\"wxdace645e0bc2cXXX\",\"sub_mchid\":\"1900008XXX\",\"openid\":\"o4GgauInH_RCEdvrrNGrntXDuXXX\",\"sub_openid\":\"o4GgauInH_RCEdvrrNGrntXDuXXX\",\"payer\":{\"name\":\"张三\",\"id_digest\":\"09eb26e839ff3a2e3980352ae45ef09e\",\"card_type\":\"ID_CARD\"},\"pay_for_relatives\":false,\"relative\":{\"name\":\"张三\",\"id_digest\":\"09eb26e839ff3a2e3980352ae45ef09e\",\"card_type\":\"ID_CARD\"},\"out_trade_no\":\"202204022005169952975171534816\",\"serial_no\":\"1217752501201\",\"pay_order_id\":\"ORD530100202204022006350000021\",\"pay_auth_no\":\"AUTH530100202204022006310000034\",\"geo_location\":\"102.682296,25.054260\",\"city_id\":\"530100\",\"med_inst_name\":\"北大医院\",\"med_inst_no\":\"1217752501201407033233368318\",\"med_ins_order_create_time\":\"2015-05-20T13:29:35+08:00\",\"total_fee\":202000,\"med_ins_gov_fee\":100000,\"med_ins_self_fee\":45000,\"med_ins_other_fee\":5000,\"med_ins_cash_fee\":50000,\"wechat_pay_cash_fee\":42000,\"cash_add_detail\":[{\"cash_add_fee\":2000,\"cash_add_type\":\"FREIGHT\"}],\"cash_reduce_detail\":[{\"cash_reduce_fee\":10000,\"cash_reduce_type\":\"DEFAULT_REDUCE_TYPE\"}],\"callback_url\":\"https://www.weixin.qq.com/wxpay/pay.php\",\"prepay_id\":\"wx201410272009395522657a690389285100\",\"passthrough_request_content\":\"{\\\"payAuthNo\\\":\\\"AUTH****\\\",\\\"payOrdId\\\":\\\"ORD****\\\",\\\"setlLatlnt\\\":\\\"118.096435,24.485407\\\"}\",\"extends\":\"{}\",\"attach\":\"{}\",\"channel_no\":\"AAGN9uhZc5EGyRdairKW7Qnu\",\"med_ins_test_env\":false}";
MedInsOrdersRequest request = GSON.fromJson(requestParamStr, MedInsOrdersRequest.class);
MiPayService miPayService = wxPayService.getMiPayService();
MedInsOrdersResult result = miPayService.medInsOrders(request);
log.info(result.toString());
}
/**
* 使用医保自费混合订单号查看下单结果测试
* @throws WxPayException
*/
@Test
public void getMedInsOrderByMixTradeNo() throws WxPayException {
// 测试用的医保自费混合订单号和医疗机构商户号
String mixTradeNo = "202204022005169952975171534816";
String subMchid = "1900000109";
MiPayService miPayService = wxPayService.getMiPayService();
MedInsOrdersResult result = miPayService.getMedInsOrderByMixTradeNo(mixTradeNo, subMchid);
log.info(result.toString());
}
/**
* 使用从业机构订单号查看下单结果测试
* @throws WxPayException
*/
@Test
public void getMedInsOrderByOutTradeNo() throws WxPayException {
// 测试用的从业机构订单号和医疗机构商户号
String outTradeNo = "202204022005169952975171534816";
String subMchid = "1900000109";
MiPayService miPayService = wxPayService.getMiPayService();
MedInsOrdersResult result = miPayService.getMedInsOrderByOutTradeNo(outTradeNo, subMchid);
log.info(result.toString());
}
/**
* 解析医保混合收款成功通知测试
* @throws WxPayException
*/
@Test
public void parseMiPayNotifyV3Result() throws WxPayException {
// 模拟的医保混合收款成功通知数据
String notifyData = "{\"id\":\"EV-202401011234567890\",\"create_time\":\"2024-01-01T12:34:56+08:00\",\"event_type\":\"MEDICAL_INSURANCE.SUCCESS\",\"summary\":\"医保混合收款成功\",\"resource_type\":\"encrypt-resource\",\"resource\":{\"algorithm\":\"AEAD_AES_256_GCM\",\"ciphertext\":\"encrypted_data\",\"associated_data\":\"\",\"nonce\":\"random_string\"}}";
// 模拟的签名信息
String signature = "test_signature";
String timestamp = "1234567890";
String nonce = "test_nonce";
String serial = "test_serial";
MiPayService miPayService = wxPayService.getMiPayService();
SignatureHeader header = SignatureHeader.builder()
.signature(signature)
.timeStamp(timestamp)
.nonce(nonce)
.serial(serial)
.build();
try {
// 调用解析方法,预期会失败,因为是模拟数据
MiPayNotifyV3Result result = miPayService.parseMiPayNotifyV3Result(notifyData, header);
log.info("解析结果:{}", result);
} catch (WxPayException e) {
// 预期会抛出异常,因为是模拟数据,签名验证和解密都会失败
log.info("预期的异常:{}", e.getMessage());
}
}
/**
* 医保退款通知测试
* @throws WxPayException
*/
@Test
public void medInsRefundNotify() throws WxPayException {
// 测试用的医保自费混合订单号
String mixTradeNo = "202204022005169952975171534816";
// 模拟的医保退款通知请求数据
String requestParamStr = "{\"sub_mchid\":\"1900008XXX\",\"med_refund_total_fee\":45000,\"med_refund_gov_fee\":45000,\"med_refund_self_fee\":45000,\"med_refund_other_fee\":45000,\"refund_time\":\"2015-05-20T13:29:35+08:00\",\"out_refund_no\":\"R202204022005169952975171534816\"}";
// 解析请求参数
MedInsRefundNotifyRequest request = GSON.fromJson(requestParamStr, MedInsRefundNotifyRequest.class);
request.setMixTradeNo(mixTradeNo);
MiPayService miPayService = wxPayService.getMiPayService();
try {
// 调用医保退款通知方法,预期会失败,因为是模拟数据
miPayService.medInsRefundNotify(request);
log.info("医保退款通知调用成功");
} catch (WxPayException e) {
// 预期会抛出异常,因为是模拟数据
log.info("预期的异常:{}", e.getMessage());
}
}
}