Appearance
河北分行医保自费混合收款成功回调通知
1 功能说明
注意
!!!!只有支付成功的情况才会有回调通知!!!!
同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
回调通知需要与订单查询接口配合使用,不能只依靠回调接口作为订单状态确认手段。如果在订单发起30秒后没有收到回调,应调用查询订单接口确认订单状态。
商户系统对于确认订单通知的内容一定要做签名验证,并校验通知的信息是否与商户侧的信息一致,防止数据泄露导致出现“假通知”,造成损失。
当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
工行通过该接口给商户发送支付结果通知。在接收到工行的支付结果通知后,一定要按文档6,7,8返回应答,否则工行会认为该通知失败,在一定时间区间内多次发起通知
2 通用请求(异步回调)参数
| 参数名 | 类型 | 是否必输 | 最大长度 | 描述 | 示例值 |
|---|---|---|---|---|---|
| from | str | true | - | 工行调用固定为icbc-api | icbc-api |
| api | str | true | 200 | 接口路径,调用哪一个接口的回调 | /hbfh/mimp/order/V1 |
| app_id | str | true | 20 | 合作方在工行开具的应用编号 | |
| charset | str | true | 10 | 调用过程使用的编码格式 | utf-8 |
| format | str | true | 10 | 报文类型 | json |
| encrypt_type | str | false | 10 | 加密方式,此接口默认不加密 | - |
| timestamp | str | true | - | 回调发生时间,格式为“yyyy-MM-dd HH:mm:ss” | 2017-01-12 17:33:56 |
| sign_type | str | true | - | 签名类型,目前网关回调请求的签名只支持RSA方式,需要合作方使用网关公钥验签 | RSA |
| sign | str | true | - | 工行签名 | FSDFDRYXDS342FDSR |
| biz_content | str | true | - | 业务参数集合,详见业务参数说明 | - |
3 请求(异步回调)参数
| 参数名 | 类型 | 是否必输 | 最大长度 | 描述 | 实例值 |
|---|---|---|---|---|---|
| msg_id | str | true | 用于确定一笔签名的唯一性,不能重复。 | ||
| mix_trade_no | str | true | 32 | 医保自费混合订单号。唯一定义此资源的标识 | |
| mix_pay_status | str | true | 医保自费混合订单支付状态。按照字典发送。 | MIX_PAY_SUCCESS | |
| self_pay_status | str | false | 混合订单中自费部分的支付状态,按照字典发送。 | SELF_PAY_SUCCESS | |
| med_ins_pay_status | str | false | 混合订单中医保部分的支付状态,按照字典发送。 | MED_INS_PAY_SUCCESS | |
| paid_time | str | false | 64 | 订单支付时间,遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE,yyyy-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2020-03-26T10:43:39+08:00表示北京时间2020年03月26日10点43分38秒 | |
| passthrough_response_content | str | false | 2048 | 支付完成后医保局返回内容(透传给医疗机构) | |
| order_type | str | false | 按字典值发送 | 订单类型,按照字典发送。 | |
| mix_pay_type | str | true | 按字典值发送 | 混合支付类型。按照字典发送。 | |
| sub_appid | str | true | 32 | 医疗机构的公众号ID | |
| sub_mchid | str | true | 32 | 医疗机构的商户号 | |
| sub_openid | str | true | 128 | 用户在医疗机构AppID下的唯一标识 | |
| pay_for_relatives | boolean | false | 是否代亲属支付,不传默认替本人支付: 代亲属支付:true 本人支付:false | ||
| out_trade_no | str | true | 64 | 从业机构订单号 | |
| serial_no | str | false | 40 | 医疗机构订单号 | |
| pay_order_id | str | false | 64 | 医保局返回的支付单ID | |
| pay_auth_no | str | false | 40 | 医保局返回的支付授权码 | |
| geo_location | str | false | 40 | 用户定位信息,经纬度。格式:经度,纬度 | |
| city_id | str | true | 8 | 城市ID | |
| med_inst_name | str | true | 128 | 医疗机构名称 | |
| med_inst_no | str | true | 32 | 医疗机构编码 | |
| med_ins_order_create_time | str | false | 64 | 医保下单时间,遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE | |
| total_fee | int | true | 使用该接口下单的总金额,单位分。在接口说明示例中,该值为2020元。 | 202000 | |
| med_ins_gov_fee | int | false | 医保局结算后,医保统筹支付金额,单位分,结算后金额为0也需要填写。在接口说明示例中,该值为1000元。 | 100000 | |
| med_ins_self_fee | int | false | 医保局结算后,医保个账支付金额,单位分,结算后金额为0也需要填写。在接口说明示例中,该值为450元。 | 45000 | |
| med_ins_other_fee | int | false | 医保局结算后,医保统筹、个账之外的其他津贴支付金额,单位分,结算后金额为0也需要填写。在接口说明示例中,该值为50元。 | 5000 | |
| med_ins_cash_fee | int | false | 医保局结算后,需要自费的金额,单位分,结算后金额为0也需要填写。在接口说明示例中,该值为500元。 | 50000 | |
| wechat_pay_cash_fee | int | false | 实际需要用户微信支付的金额,单位分,在接口说明示例中,该值为420元。 | 42000 | |
| cash_add_detail | object | false | 现金补充列表。 | ||
| cash_reduce_detail | object | false | 现金减免列表 | ||
| callback_url | str | true | 256 | 回调通知URL | |
| prepay_id | str | false | 64 | 自费预下单ID,微信支付预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时 | |
| passthrough_request_content | str | false | 2048 | 医疗机构透传给医保的数据 | |
| extends | str | false | 512 | 医疗机构与微信医保约定的扩展字段 | |
| attach | str | false | 128 | 查询与通知中返回,医疗机构可用于自定义业务逻辑 | |
| channel_no | str | false | 32 | 渠道号。由腾讯工程师分配的渠道号,给医疗机构使用,标记医保支付渠道信息。 |
现金补充列表 cash_add_detail
| 参数 | 类型 | 是否必输 | 最大长度 | 描述 | 示例值 |
|---|---|---|---|---|---|
| cash_add_fee | uint64 | true | 现金补充金额,单位分 | 20 | |
| cash_add_type | str | true | 64 | 现金补充类型(字典较多,详情请见5.3) | FREIGHT |
现金减免列表 cash_reduce_detail
| 参数 | 类型 | 是否必输 | 最大长度 | 描述 | 示例值 |
|---|---|---|---|---|---|
| cash_reduce_fee | uint64 | true | 现金减免金额 | 20 | |
| cash_reduce_type | str | true | 64 | 现金减免类型(字典较多,详情请见5.4) | HOSPITAL_REDUCE |
4 请求(异步回调)示例 涉及验签,解密,请参考样例代码!
json
POST HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
http://xxxx?api=/api/cardbusiness/aggregatepay/b2c/online/consumepurchase/V1&app_id=2014072300007140&charset=utf-8&format=json&from=icbc-api&sign_type=RSA×tamp=2016-09-18 17:33:56&sign=xxxxx&biz_content=
{
"mix_trade_no": "1217752501201407033233368318",
"mix_pay_order_status": "MIX_PAY_SUCCESS",
"self_pay_order_status": "SELF_PAY_SUCCESS",
"med_ins_pay_order_status": "MED_INS_PAY_SUCCESS",
"paid_time": "2018-06-08T10:34:56+08:00",
"passthrough_response_content": {
"payAuthNo": "AUTH530100202204022006310000034",
"payOrdId": "ORD530100202204022006350000021",
"setlLatlnt": "102.682296,25.054260"
},
"order_type": "REG_PAY",
"appid": "wxdace645e0bc2cXXX",
"sub_appid": "wxdace645e0bc2cXXX",
"sub_mchid": "1900008XXX",
"sub_openid": "o4GgauInH_RCEdvrrNGrntXDuXXX",
"pay_for_relatives": false,
"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": 20000,
"med_ins_gov_fee": 5000,
"med_ins_self_fee": 5000,
"med_ins_other_fee": 0,
"cash_add_detail": {
"cash_add_fee": 1000,
"cash_add_type": "DEFAULT_ADD_TYPE"
},
"cash_reduce_detail": {
"cash_reduce_fee": 1000,
"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"
}5 字典值清单
5.1订单类型
| 标题 | 代码 | 描述 |
|---|---|---|
| 默认值 | 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 | 药费支付 |
5.2证件类型
| 标题 | 代码 | 描述 |
|---|---|---|
| 未知证件类型 | UNKNOWN_USER_CARD_TYPE | 未知证件类型 |
| 身份证 | ID_CARD | 身份证 |
| 户口本 | HOUSEHOLD_REGISTRATION | 户口本 |
| 外国人护照 | FOREIGNER_PASSPORT | 外国人护照 |
| 台湾居民来往大陆通行证 | MAINLAND_TRAVEL_PERMIT_FOR_TW | 台湾居民来往大陆通行证 |
| 澳门居民往来内地通行证 | MAINLAND_TRAVEL_PERMIT_FOR_MO | 澳门居民往来内地通行证 |
| 香港居民往来内地通行证 | MAINLAND_TRAVEL_PERMIT_FOR_HK | 香港居民往来内地通行证 |
| 外国人永久居留证 | FOREIGN_PERMANENT_RESIDENT | 外国人永久居留证 |
5.3现金补充类型
| 标题 | 代码 | 描述 |
|---|---|---|
| 默认值 | DEFAULT_ADD_TYPE | 默认显示“机构加收费用” |
| 运费 | FREIGHT | 运费 |
| 其他医疗费用 | OTHER_MEDICAL_EXPENSES | 其他医疗费用 |
5.4现金减免类型
| 标题 | 代码 | 描述 |
|---|---|---|
| 默认值 | DEFAULT_REDUCE_TYPE | 默认为“机构优惠金额” |
| 医院减免 | HOSPITAL_REDUCE | 医院减免 |
| 药店优惠 | PHARMACY_DISCOUNT | 药店优惠 |
| 优惠金 | DISCOUNT | 优惠金 |
| 预缴金 | PRE_PAYMENT | 预缴金 |
| 押金抵扣 | DEPOSIT_DEDUCTION | 押金抵扣 |
5.5混合支付类型
| 标题 | 代码 | 描述 |
|---|---|---|
| 未知的混合支付类型 | UNKNOWN_MIX_PAY_TYPE | 未知的混合支付类型,会被拦截 |
| 纯现金支付 | CASH_ONLY | 纯现金支付。只向微信支付下单,未向医保局下单。 |
| 纯医保支付 | INSURANCE_ONLY | 纯医保支付。只向医保局下单,未向微信支付下单。如果医保局分账结果中有自费的部分,但由于有减免折扣,没有向微信支付下单,也是纯医保。 |
| 现金和医保混合支付 | CASH_AND_INSURANCE | 现金和医保混合支付。向医保局下单,也向微信支付下单。如果医保预结算全部需自费,也属于混合类型。 |
5.6医保自费混合订单支付状态
| 标题 | 代码 | 描述 |
|---|---|---|
| 未知类型 | UNKNOWN_MIX_PAY_STATUS | 未知类型,需报错 |
| 等待支付 | MIX_PAY_CREATED | 等待支付 |
| 支付成功 | MIX_PAY_SUCCESS | 支付成功 |
| 自费和医保均已退款 | MIX_PAY_REFUND | 自费和医保均已退款 |
| 支付失败 | MIX_PAY_FAIL | 支付失败 |
5.7 混合订单中自费部分的支付状态
| 标题 | 代码 | 描述 |
|---|---|---|
| 未知类型 | UNKNOWN_SELF_PAY_STATUS | 未知类型,需报错 |
| 等待支付 | SELF_PAY_CREATED | 等待支付 |
| 支付成功 | SELF_PAY_SUCCESS | 支付成功 |
| 已退款 | SELF_PAY_REFUND | 已退款 |
| 支付失败 | SELF_PAY_FAIL | 支付失败 |
| 订单不含自费部分 | NO_SELF_PAY | 订单不含自费部分 |
5.8 混合订单中医保部分的支付状态
| 标题 | 代码 | 描述 |
|---|---|---|
| 未知类型 | UNKNOWN_MED_INS_PAY_STATUS | 未知类型,需报错 |
| 等待支付 | MED_INS_PAY_CREATED | 等待支付 |
| 支付成功 | MED_INS_PAY_SUCCESS | 支付成功 |
| 已退款 | MED_INS_PAY_REFUND | 已退款 |
| 支付失败 | MED_INS_PAY_FAIL | 支付失败 |
| 订单不含医保部分 | NO_MED_INS_PAY | 订单不含医保部分 |
6 通用响应(异步回调)参数
| 参数名 | 类型 | 是否必输 | 最大长度 | 描述 | 示例 |
|---|---|---|---|---|---|
| response_biz_content | str | true | - | 接口返回参数 | |
| sign_type | str | true | - | 签名类型,需与商户在工行登记app的签名类型保持一致 | RSA2 |
| ca | str | false | - | 签发的证书(base64enc),若签名类型为CA则该字段必输 | |
| sign | str | true | - | 工行签名 | FSDFDRYXDS342FDSR |
7 响应(异步回调)参数
| 参数名 | 类型 | 是否必输 | 最大长度 | 描述 | 示例值 |
|---|---|---|---|---|---|
| return_code | str | true | 10 | 返回码,交易成功返回0,其他表示业务报错 | 0 |
| return_msg | str | true | 200 | 返回码说明 | - |
| msg_id | str | true | 40 | 用于确定一笔签名的唯一性,不能重复 | 201808311413244567657 |
通知签名处理
上行网关会对通知报文做如下签名处理逻辑如下:
签名数据为:通知地址路径path+”?”+请求参数asc排序(参数中间通过”&”连接,参数名值对之间通过”=”连接),若商户通知地址为https://www.scgsj.com/notify.do,签名方式为RSA,参数带签名内容为:
**/notify.do?**api=/api/scfh/mixhp/mixorder/artest/V1&app_id=2014072300007140&biz_content={"msg_id":"201808311413244567657","return_code":0,"return_msg":"success",orderInfo":{"interfaceName": "ICBC_PEEBANK_CERTVERIFY","language":"ZH_CN","orderDate":"20130606","orderTime":"180000"}}&charset=utf-8&format=json&from=icbc-api&sign_type=RSA2×tamp=2016/9/18 17:33:56
8 响应(异步回调)示例 涉及加签,加密,请参考样例代码!
通知应答可参考sdk中的icbc-api-sdk-cop\doc\attachment\NotifyDemo,应答注意要点:
1、签名字符串为**"response_biz_content":{ "return_code":0,"return_msg":"success","msg_id":"201808311413244567657"},"sign_type":"RSA2"**不含空格换行符;
2、sign_type为在工行登记app的签名类型保持一致,一般为RSA2;
3、return_code为数字,成功时为0;
4、返回字符串顺序不能变,为response_biz_content、sign_type、sign,中间不含空格换行符;
5、正常只会通知一次,如果应答失败,则会在一定时间,目前是10mins内,尝试发通知,每次发三次。
- NotifyDemo
java
package com.icbc.apip.demo;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSON;
import com.icbc.api.utils.IcbcEncrypt;
import com.icbc.api.utils.IcbcSignature;
import com.icbc.api.utils.WebUtils;
import cn.com.infosec.icbc.ReturnValue;
public class NotifyUrlServlet extends HttpServlet{
private static final long serialVersionUID = 5702944193354207267L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
PrintWriter out = null;
try {
SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMddHHmmss");
String reqTime=sdf.format(new Date());
String results="Only support POST method. systime is :"+reqTime;
resp.setContentType("application/json; charset=utf-8");
out = resp.getWriter();
out.write(results);
} catch (Exception e) {
e.printStackTrace();
out.write(e.getMessage());
} finally {
out.flush();
out.close();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
PrintWriter out = null;
try {
//网关公钥
String APIGW_PUBLIC_KEY="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwFgHD4kzEVPdOj03ctKM7KV+16bWZ5BMNgvEeuEQwfQYkRVwI9HFOGkwNTMn5hiJXHnlXYCX+zp5r6R52MY0O7BsTCLT7aHaxsANsvI9ABGx3OaTVlPB59M6GPbJh0uXvio0m1r/lTW3Z60RU6Q3oid/rNhP3CiNgg0W6O3AGqwIDAQAB";
//商户证书,RSA方式验签不涉及
//cert_sn=517722432651109523795125,有效期=20150302-20350302
// String cert="MIIDDTCCAfWgAwIBAgIKbaHKEE0tAAAstTANBgkqhkiG9w0BAQUFADA3MRowGAYDVQQDExFjb3JiYW5rMTAzIHNkYyBDTjEZMBcGA1UEChMQY29yYmFuazQzLmNvbS5jbjAeFw0xNTAzMDIwMzA4MDFaFw0zNTAzMDIwMzA4MDFaMEMxFzAVBgNVBAMTDmxpdXdoMzEueS4wMjAwMQ0wCwYDVQQLEwQwMjAwMRkwFwYDVQQKExBjb3JiYW5rNDMuY29tLmNuMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDzvGvNMJKzqZHrEKKcboCLemlLGnVQ1Za28qL6IQXgT0W4zKG7tes7YMDd3JL6EngE/RXhTviymkzyZwp7RTbe4Hxhsjl3l6+HsCflRJv8dPH1PqQjtzq0gD2primFI/iGm7oSruTcE8/G1ONkVlCMbNysc1IhnCOVU66JR/XsyQIDAQABo4GSMIGPMB8GA1UdIwQYMBaAFKnyXV7yfyOkd7D4zZtPLyquqLWdME0GA1UdHwRGMEQwQqBAoD6kPDA6MQ4wDAYDVQQDEwVjcmwxMjENMAsGA1UECxMEY2NybDEZMBcGA1UEChMQY29yYmFuazQzLmNvbS5jbjAdBgNVHQ4EFgQUcL8GdzLHRtN6yiJUlDC/u63nTyAwDQYJKoZIhvcNAQEFBQADggEBABaLovTpH0ID9HH7nIrv4zMWGc5PWvDBdc8k/lmHo6HfdAVwI/050Lll0IyzZ19FZhfA5Frpnwmk49QLRMjIoVJvbO1Zs1Ey8fkxd2LBniBkKUbdmU6YbmkEnn2aIOi6fjyDPbLT7fVmOnZmNqNeA8/woo5DmW7/hcD0ImxApq9iMUGmWU+q3G7Wv9N77QhRGFefGvxQ9zRVb86xVv9yyCSA/ICx5Y9M68wiC/fIG3uC9t4kfE/+tikHCW2crY2BWQ/Q0vfo3gGbZx3zAxkd/HUnfKJTgkhNTX3tv8UxHg6DHX16SPkoXb9zjQsL+aJaLqbFruusDRvmbgJex+muE+0=";
//商户私钥
String CertPriKey="AmGFOO6yoIabb6bYYp4luRMfRfzcYDlUWuys5VMcZDO3yi2W4f8oTyIz+mRGVMIYVWOiD7VaWZ9avkpAvdsosBj3D+M4Me9ZQCx1Y3TefnX3zu1ZLL3xk6Z+dH4Vt1yMLwaWfcF20jM+HkFF4DpCi1scv/6/XmTBae/unRscM26+zWqBVygOHUkXP2jnfOJAM1o3yp2mI5A+YxRD2JwR16lqgoW55xZ/1ufj/qd1MEBfA+aCeXMoqbdWUDsJGC8xyCMAi52TnrnROpcd4cHZFFbzwvaP3wyOmrWVkepOYj4RGGNeReCS4E7A2DTBEMTFyhgQdXzGCZm+tYXhsA4gqucI0yi3oQfAKlwGX4L77DB4Mula0wovAedvPpWseQouZfXSwt0N5yHG/3r/Du80s5gvCwgwncYvcsJm87Ot1ZH3PrHmSp50ndi703BkyuHIN7HjP27MO57crwEqxHvZ3getRt5+nkOGxxweHcZciMmNrLrB7huYBPcc7TarLHzUWQkC5fBqSrwDXjJCWZ7ZHd+5O0x/6uYnFCpKGXyOV4it+nzS6lo0RcYlM/eDxdQfkUaAKGtojNF5dZAdB1lCO/hGDD4KG0V5hZlI+ncZO8H2L1rJ4Rumz/qp9no8NgC+3OVVxnPd8jIsQFU+t68EhJ/oXkjmcSw+BhKtJLPxybZFkpx6rw903Og9qjcFu1tc7m+WKu0F8RC4a2cWI9Th6vR6Gk1gF34XNlkZVLolBbj7RkJ3/7fhBZlB5wnQIa0fZHw4qOoT86JItcxPrzbptYiqhCU18BA6Bna5EojumAy9zZo=";
/* 获取证书序列号cert_sn的方法。 RSA方式验签不涉及
byte[] merCert = ReturnValue.base64dec(cert.getBytes());
ByteArrayInputStream bin = new ByteArrayInputStream(merCert);
CertificateFactory cf = CertificateFactory.getInstance("X.509", "INFOSEC");
Certificate certificate = cf.generateCertificate(bin);
BigInteger sn = ((X509Certificate)certificate).getSerialNumber();
bin.close();
String serialNumber = sn.toString();
System.out.print(serialNumber);
*/
Map<String, String> params=new HashMap<String, String>();
String from=req.getParameter("from");
String api=req.getParameter("api");
String app_id=req.getParameter("app_id");
String charset=req.getParameter("charset");
String format=req.getParameter("format");
String encrypt_type=req.getParameter("encrypt_type");
String timestamp=req.getParameter("timestamp");
String biz_content=req.getParameter("biz_content");
String sign_type=req.getParameter("sign_type");
String sign=req.getParameter("sign");
params.put("from", from);
params.put("api", api);
params.put("app_id", app_id);
params.put("charset", charset);
params.put("format", format);
params.put("encrypt_type", encrypt_type);
params.put("timestamp", timestamp);
params.put("biz_content", biz_content);
params.put("sign_type", sign_type);//目前上行网关签名暂时仅支持RSA
/**********验证工行上行网关RSA签名**********/
//notify_url=http://122.20.29.133:9081/notifyUrlServlet
String path="/notifyUrlServlet";
String signStr=WebUtils.buildOrderedSignStr(path, params);
String results = null;
String responseBizContent= null;
//工行sdk中提供的验签方法,实际默认使用RSA方式
boolean flag=IcbcSignature.verify(signStr, sign_type, APIGW_PUBLIC_KEY, charset, sign);
if (!flag) {
responseBizContent= "{\"return_code\":-12345,\"return_msg\":\"icbc sign not pass.\"}";
}else
{
/**********biz_content解密**********/
if ("AES".equals(encrypt_type))
{
String theKey="12345678901234567890123456789012";
biz_content=IcbcEncrypt.decryptContent(biz_content, encrypt_type, theKey, charset);
}
/**********合作方/分行 业务逻辑处理**********/
@SuppressWarnings("unchecked")
Map<String, Object> respMap = (Map<String, Object>) JSON.parse(biz_content);
String msg_id=respMap.get("msg_id").toString();
//业务请求字段获取
String busiParamRq=(String)respMap.get("busi_param_rq");
//业务处理逻辑......
System.out.print(busiParamRq);
//业务返回参数设置
int return_code=0;
String return_msg="success.";
responseBizContent="{\"return_code\":"+return_code+",\"return_msg\":\""+return_msg+"\",\"msg_id\":\""+msg_id+"\","
+"\"busi_param_rp\":\"thisisresponseparameter\"}";
/**********response_biz_content加密**********/
if ("AES".equals(encrypt_type))
{
String theKey="12345678901234567890123456789012";
responseBizContent=IcbcEncrypt.encryptContent(responseBizContent, encrypt_type, theKey, charset);
responseBizContent="\""+responseBizContent+"\"";
}
}
/**********商户对消息返回响应进行签名,签名方式需与在API平台登记APP的sign_type保持一致**********/
//1、商户以CA证书签名为例,实际默认使用RSA2方式:
// signStr="\"response_biz_content\":"+responseBizContent+","+"\"sign_type\":"+"\"CA\","+"\"ca\":\""+cert+"\"";
// sign=IcbcSignature.sign(signStr, "CA", CertPriKey,
// charset,"12345678");
// results="{"+signStr+",\"sign\":\""+sign+"\"}";
// 2、商户以RSA2签名为例,如下:其中,priKey为商户私钥;
String priKey="MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALAWAcPiTMRU906PTdy0ozspX7XptZnkEw2C8R64RDB9BiRFXAj0cU4aTA1MyfmGIlceeVdgJf7OnmvpHnYxjQ7sGxMItPtodrGwA2y8j0AEbHc5pNWU8Hn0zoY9smHS5e+KjSbWv+VNbdnrRFTpDeiJ3+s2E/cKI2CDRbo7cAarAgMBAAECgYABiA933q4APyTvf/uTYdbRmuiEMoYr0nn/8hWayMt/CHdXNWs5gLbDkSL8MqDHFM2TqGYxxlpOPwnNsndbW874QIEKmtH/SSHuVUJSPyDW4B6MazA+/e6Hy0TZg2VAYwkB1IwGJox+OyfWzmbqpQGgs3FvuH9q25cDxkWntWbDcQJBAP2RDXlqx7UKsLfM17uu+ol9UvpdGoNEed+5cpScjFcsB0XzdVdCpp7JLlxR+UZNwr9Wf1V6FbD2kDflqZRBuV8CQQCxxpq7CJUaLHfm2kjmVtaQwDDw1ZKRb/Dm+5MZ67bQbvbXFHCRKkGI4qqNRlKwGhqIAUN8Ynp+9WhrEe0lnxo1AkEA0flSDR9tbPADUtDgPN0zPrN3CTgcAmOsAKXSylmwpWciRrzKiI366DZ0m6KOJ7ew8z0viJrmZ3pmBsO537llRQJAZLrRxZRRV6lGrwmUMN+XaCFeGbgJ+lphN5/oc9F5npShTLEKL1awF23HkZD9HUdNLS76HCp4miNXbQOVSbHi2QJAUw7KSaWENXbCl5c7M43ESo9paHHXHT+/5bmzebq2eoBofn+IFsyJB8Lz5L7WciDK7WvrGC2JEbqwpFhWwCOl/w==";
signStr="\"response_biz_content\":"+responseBizContent+","+"\"sign_type\":"+"\"RSA2\"";
sign=IcbcSignature.sign(signStr, "RSA2", priKey,
charset,"12345678");
results="{"+signStr+",\"sign\":\""+sign+"\"}";
resp.setContentType("application/json; charset=utf-8");
out = resp.getWriter();
out.write(results);
}catch (Throwable e) {
e.printStackTrace();
out.write(e.getMessage());
} finally {
out.flush();
out.close();
}
}
}