Java集成建行龙支付接口(详细)

这篇具有很好参考价值的文章主要介绍了Java集成建行龙支付接口(详细)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、准备工作

二、开始对接

三、总结

一、准备工作

1. 获取建行龙支付对接文档(注意:建行会给指定邮箱发送16个rar的压缩包)都下载完才能获取到完整文档,解压完可以看到名为“建行龙支付接入指南V1.32”的文件夹,里面的内容为6个文件夹1个pdf文档

2. 获取各种资料

        1).微信商户编号

        2).商户柜台编号

        3).建行商户编号

        4).终端号

        5).分行代码

        6).商户公钥

3. 开通权限

        注意:需要联系分管贵公司的建行工作人员,开通服务器实时反馈退款的权限。

二、开始对接

        这里我使用的是SpringBoot框架进行对接

1. 配置application.yml文件

jh:
  merchantId: 商户编号
  posId: 柜台编号
  branchId: 分行代码
  subAppId: 小程序APPID(这里参考贵公司的支付渠道,我方需使用微信小程序支付)
  tradeType: 支付类型(这里参考贵公司的支付渠道,我方需使用微信小程序支付)
  pub: 公钥串
  url: 接口地址
  operatorCode: 商户操作员编号
  pwd: 操作员密码
  wlptServerIp: 外联平台ip地址
  wlptPort: 外联平台端口号

2. 统一下单接口对接

        2.1 下单接口参数实体类

import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.math.BigDecimal;

/**
 * 建设银行下单接口参数
 * @author snkj
 * @create 2022-10-24 10:23
 */
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class JhPlaceOrderInfo {
    /**
     * 商户代码
     * 必填
     */
    @JSONField(name="MERCHANTID")
    private String MERCHANTID;
    /**
     * 商户柜台代码
     * 必填
     */
    @JSONField(name="POSID")
    private String POSID;
    /**
     * 分行代码
     * 必填
     */
    @JSONField(name="BRANCHID")
    private String BRANCHID;
    /**
     * 订单号(最长30位)
     * 必填
     */
    @JSONField(name="ORDERID")
    private String ORDERID;
    /**
     * 付款金额
     * 必填
     */
    @JSONField(name="PAYMENT")
    private String PAYMENT;
    /**
     * 币种
     * 必填
     * 缺省为 01-人民币
     */
    @JSONField(name="CURCODE")
    private String CURCODE;
    /**
     * 备注信息1
     * 一般作为商户自定义备注信
     * 息使用,可在对账单中显示
     */
    @JSONField(name="REMARK1")
    private String REMARK1;
    /**
     * 备注信息2
     * 一般作为商户自定义备注信
     * 息使用,可在对账单中显示
     */
    @JSONField(name="REMARK2")
    private String REMARK2;
    /**
     * 交易码
     * 由建行统一分配为 530590
     * 必填
     */
    @JSONField(name="TXCODE")
    private String TXCODE;
    /**
     * MAC 校验域
     * 采用标准 MD5 算法,由商户实现
     * 必填
     */
    @JSONField(name="MAC")
    private String MAC;
    /**
     * 接口类型
     * 分行业务人员在 P2 员工渠道后台设置防钓鱼的开关。
     * 1- 防钓鱼接口
     * 必填
     */
    @JSONField(name="TYPE")
    private String TYPE;
    /**
     * 公钥后 30 位
     * 商户从建行商户服务平台下载,截取后 30 位。
     * 仅作为源串参加 MD5 摘要,不作为参数传递
     * 必填
     */
    @JSONField(name="PUB")
    private String PUB;
    /**
     * 网关类型
     * 默认送 0
     * 必填
     */
    @JSONField(name="GATEWAY")
    private String GATEWAY;
    /**
     * 客户端 IP
     * 客户在商户系统中的 IP,即客户登陆(访问)商户系统时使用的 ip)
     */
    @JSONField(name="CLIENTIP")
    private String CLIENTIP;
    /**
     * 客户注册信息
     * 客户在商户系统中注册的信息,中文需使用 escape 编码
     */
    @JSONField(name="REGINFO")
    private String REGINFO;
    /**
     * 商品信息
     * 客户购买的商品中文需使用 escape 编码
     */
    @JSONField(name="PROINFO")
    private String PROINFO;
    /**
     * 商户 URL
     * 商户送空值即可;具体请看 REFERER 设置说明
     */
    @JSONField(name="EFERER")
    private String EFERER;
    /**
     * 订单超时时间
     * 格式:
     * YYYYMMDDHHMMSS如:
     * 20120214143005
     * 银行系统时间> TIMEOUT
     * 时拒绝交易,若送空值则不
     * 判断超时。
     * 当该字段有值时参与 MAC
     * 校验,否则不参与 MAC 校
     * 验。
     */
    @JSONField(name="TIMEOUT")
    private String TIMEOUT;
    /**
     * 交易类型
     * JSAPI-- 公 众 号 支 付 、
     * MINIPRO--小程序
     * 必填
     */
    @JSONField(name="TRADE_TYPE")
    private String TRADE_TYPE;
    /**
     * 小程序/公众号的 APPID
     * 当前调起支付的小程序/公众号 APPID
     * 必填
     */
    @JSONField(name="SUB_APPID")
    private String SUB_APPID;
    /**
     * 用户子标识
     * 用户在小程序/公众号 appid
     * 下的唯一标识,小程序通过
     * wx.login 获取,接口文档地
     * 址 :
     * https://developers.weixin.qq.com/miniprogram/dev/api/apilogin.html?t=20161122
     * 必填
     */
    @JSONField(name="SUB_OPENID")
    private String SUB_OPENID;
    /**
     * 渠道商号
     * 对于商户自定义的渠道商号当该字段有值时参与 MAC校验,否则不参与 MAC 校验。
     */
    @JSONField(name="WX_CHANNELID")
    private String WX_CHANNELID;
    /**
     * 返回信息位图
     * 共 20 位,商户通知是否返回某
     * 个字段的位图,0 或空-不返回,
     * 1-返回。
     * 第 1 位:是否返回 OPENID 和
     * SUB_OPENID
     * 第 2 位:保留位,默认送 0
     * 第 3 位:保留位,默认送 0
     * 第 4 位:是否返回支付详细信息
     * 字段
     * 示例:10000000000000000000
     */
    @JSONField(name="RETURN_FIELD")
    private String RETURN_FIELD;
    /**
     * 实名支付
     * 实名支付功能,包含类型、
     * 证件号、姓名三个子域(如果本字段
     * 出现,那么本字
     * 段包含的三个子域均需出现。详见下
     * 文说明5)USERPARAM字段说明)。
     * 当该字段有值时参与MAC校验,否则不
     * 参与MAC校验。
     * 暂未上线,请忽略
     */
    @JSONField(name="USERPARAM")
    private String USERPARAM;
}

         2.2 下单接口Service以及实现

/**
 * 建行支付service
 * @author snkj
 * @create 2022-11-02 18:10
 */
public interface IJhPayService {

    /**
     * 建行统一下单
     * @param jhPlaceOrderInfo
     */
    public Map<String,Object> unifiedPlaceOrder(JhPlaceOrderInfo jhPlaceOrderInfo);
}
/**
 * 建行支付service实现
 *
 * @author snkj
 * @create 2022-11-02 18:11
 */
@Service
public class JhPayServiceImpl implements IJhPayService {
    
    // 获取application.yml的配置信息
    @Value("${jh.merchantId}")
    private String merchantId;
    @Value("${jh.posId}")
    private String posId;
    @Value("${jh.branchId}")
    private String branchId;
    @Value("${jh.subAppId}")
    private String subAppId;
    @Value("${jh.tradeType}")
    private String tradeType;
    @Value("${jh.pub}")
    private String pub;
    @Value("${jh.url}")
    private String url;
    @Value("${jh.operatorCode}")
    private String operatorCode;
    @Value("${jh.pwd}")
    private String pwd;
    @Value("${jh.wlptServerIp}")
    private String wlptServerIp;
    @Value("${jh.wlptPort}")
    private String wlptPort;

    /**
     * 建行统一下单
     *
     * @param jhPlaceOrderInfo
     */
    @Override
    public Map<String, Object> unifiedPlaceOrder(JhPlaceOrderInfo jhPlaceOrderInfo) {
        Map<String, Object> map = new HashMap<>();
        // -----------封装请求参数-----------
        // 生成订单号
        OrderNoUtils idWorker = new OrderNoUtils(0, 0);
        long orderId = idWorker.nextId();
        jhPlaceOrderInfo.setORDERID(String.valueOf(orderId));
        jhPlaceOrderInfo.setPAYMENT("支付金额");
        jhPlaceOrderInfo.setSUB_OPENID("小程序/微信公众号,支付人的openId");
        // 注意,这里要对中文进行编码,工具类参考下方2.4部分
        jhPlaceOrderInfo.setPROINFO(EscapeUtils.escape("设备租用押金") +         jhPlaceOrderInfo.getPAYMENT());
        // 1. 截取公钥后30位
        String pubSub = pub.substring(pub.length() - 30);
        jhPlaceOrderInfo.setMERCHANTID(merchantId);
        jhPlaceOrderInfo.setPOSID(posId);
        jhPlaceOrderInfo.setBRANCHID(branchId);
        jhPlaceOrderInfo.setTRADE_TYPE(tradeType);
        jhPlaceOrderInfo.setTIMEOUT(DateUtils.addMinute(15));
        jhPlaceOrderInfo.setSUB_APPID(subAppId);
        jhPlaceOrderInfo.setCURCODE("01");
        jhPlaceOrderInfo.setTXCODE("530590");
        jhPlaceOrderInfo.setTYPE("1");
        jhPlaceOrderInfo.setGATEWAY("0");
        jhPlaceOrderInfo.setPUB(pubSub);
        // 2. 获取加密后的mac,这里需要注意,参与MAC的参数是固定排序格式,需参考文档手动排序并MD5加密
        String mac = getMac(jhPlaceOrderInfo);
        jhPlaceOrderInfo.setMAC(mac);
        // 3. 获取请求参数&连接
        String paramsStr = getParamsStr(JSON.parseObject(JSON.toJSONString(jhPlaceOrderInfo), Map.class));
        String result = HttpUtil.post(url, paramsStr);
        if (StringUtils.isEmpty(result)) {
            // 表示返回为空,自行处理
        }
        JSONObject jsonObject = JSON.parseObject(result);
        if (!jsonObject.getString("SUCCESS").equals("true")) {
            // SUCCESS返回状态码不为true时,表示通信失败,自行处理
        }
        // 获取接口返回的payUrl
        String payUrl = jsonObject.getString("PAYURL");
        if (StringUtils.isEmpty(payUrl)) {
            // 如果PAYURL为空,自行处理
        }
        // 返回结果参考{	"SUCCESS":"true",	"PAYURL":"https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_08_EPAY?BRANCHID=120000000&TXCODE=530590&SUB_APPID=wx8b28b84282cce9fc&CCB_IBSVersion=V6&CURCODE=01&GATEWAY=0&PROINFO=%25u79DF%25u7528%25u8BBE%25u5907%25u62BC%25u91D11.00&MERCHANTID=105000789993067&ORDERID=575469756870557696&RETURN_FIELD=10000000000000000000&POSID=071114078&PAYMENT=1.00&TRADE_TYPE=MINIPRO&MAC=5e4a3586bfbeb6f1e86b1ba9ea12d19e&SUB_OPENID=oz46x5GAP3LnbSJVozyozYN-63Tw&TYPE=1&TIMEOUT=20221107164541&QRCODE=1&CHANNEL=1"}
        // PAYURL不为空时,对于小程序支付而言,需手动发送一下get请求,获取小程序支付所需的参数,具体参数请参考相关文档
        // 小程序:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_sl_api.php?chapter=7_7&index=5
        // 公众号:https://pay.weixin.qq.com/wiki/doc/api/jsapi_sl.php?chapter=7_7&index=6
        String payUrlResult = HttpUtil.get(payUrl);
        if (StringUtils.isEmpty(payUrlResult)) {
            // 请求异常,自行处理
        }
        JSONObject payUrlJson = JSON.parseObject(payUrlResult);
        if (!payUrlJson.getString("ERRCODE").equals("000000")) {
            // ERRCODE为错误码,000000 表示交易成功,非 000000 表示交易失败,错误信息可以查看 ERRMSG 字段
        }
        map.put("appId", payUrlJson.getString("appId"));
        map.put("timeStamp", payUrlJson.getString("timeStamp"));
        map.put("nonceStr", payUrlJson.getString("nonceStr"));
        map.put("package", payUrlJson.getString("package"));
        map.put("signType", payUrlJson.getString("signType"));
        map.put("paySign", payUrlJson.getString("paySign"));
        return map;
    }

    /**
     * 生成mac并md5加密
     *
     * @param jhPlaceOrderInfo
     * @return
     */
    private String getMac(JhPlaceOrderInfo jhPlaceOrderInfo) {
        String postParams = "MERCHANTID=" + jhPlaceOrderInfo.getMERCHANTID() + "&POSID=" + jhPlaceOrderInfo.getPOSID() + "" +
                "&BRANCHID=" + jhPlaceOrderInfo.getBRANCHID() + "&ORDERID=" + jhPlaceOrderInfo.getORDERID() + "&PAYMENT=" + jhPlaceOrderInfo.getPAYMENT() + "" +
                "&CURCODE=01&TXCODE=530590&REMARK1=&REMARK2=&TYPE=1&PUB=" + jhPlaceOrderInfo.getPUB() + "&GATEWAY=0&CLIENTIP=&REGINFO=&PROINFO=" + jhPlaceOrderInfo.getPROINFO() + "&REFERER=" +
                "&TIMEOUT=" + jhPlaceOrderInfo.getTIMEOUT() + "&TRADE_TYPE=" + jhPlaceOrderInfo.getTRADE_TYPE() + "" +
                "&SUB_APPID=" + jhPlaceOrderInfo.getSUB_APPID() + "&SUB_OPENID=" + jhPlaceOrderInfo.getSUB_OPENID() + "";
        return MD5Utils.string2MD5(postParams);
    }

    /**
     * 生成提交参数
     *
     * @param params
     * @return
     */
    private String getParamsStr(Map params) {
        StringBuffer toBeMacStr = new StringBuffer();
        Set<Map.Entry<String, Object>> entries = params.entrySet();
        Iterator iterator = entries.iterator();
        while (iterator.hasNext()) {
            Object itset = iterator.next();
            Map.Entry entry = (Map.Entry) itset;
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            if (StringUtils.isNotEmpty(value)) {
                if (!key.equals("PUB")) {
                    toBeMacStr.append("&" + key + "=" + value);
                }
            }
        }
        return toBeMacStr.toString();
    }
}

        2.3 服务器通知回调实体类以及Controller

import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * @author snkj
 * @create 2022-11-08 19:05
 */
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class JhNotifyInfo {

    /**
     * 商户柜台代码
     */
    @JSONField(name="POSID")
    private String POSID;
    /**
     * 分行代码
     */
    @JSONField(name="BRANCHID")
    private String BRANCHID;
    /**
     * 订单号(最长30位)
     */
    @JSONField(name="ORDERID")
    private String ORDERID;
    /**
     * 付款金额
     */
    @JSONField(name="PAYMENT")
    private String PAYMENT;
    /**
     * 币种
     * 缺省为 01-人民币
     */
    @JSONField(name="CURCODE")
    private String CURCODE;
    /**
     * 备注信息1
     * 一般作为商户自定义备注信
     * 息使用,可在对账单中显示
     */
    @JSONField(name="REMARK1")
    private String REMARK1;
    /**
     * 备注信息2
     * 一般作为商户自定义备注信
     * 息使用,可在对账单中显示
     */
    @JSONField(name="REMARK2")
    private String REMARK2;
    /**
     * 账户类型
     * 服务器通知中有此字段返回且参与验签
     * AL:代表支付宝支付
     * WX:代表微信支付
     * 其他:代表建行支付或跨行付
     */
    @JSONField(name="ACC_TYPE")
    private String ACC_TYPE;
    /**
     * 成功-Y,失败-N
     */
    @JSONField(name="SUCCESS")
    private String SUCCESS;
    /**
     * 接口类型
     * 分行业务人员在 P2 员工渠道后台设置防钓鱼的开关。
     * 1- 防钓鱼接口
     */
    @JSONField(name="TYPE")
    private String TYPE;
    /**
     * Referer信息
     * 分行业务人员在P2员工渠道后台设置防钓鱼开关。
     * 1.开关关闭时,无此字段返回且不参与验签。
     * 2.开关打开时,有此字段返回且参与验签。
     */
    @JSONField(name="REFERER")
    private String REFERER;
    /**
     * 客户端IP
     * 客户在商户系统中的IP,即客户登陆(访问)商户系统时使用的IP)
     * 分行业务人员在P2员工渠道后台设置防钓鱼开关。
     * 1.开关关闭时,无此字段返回且不参与验签。
     * 2.开关打开时,有此字段返回且参与验签。
     */
    @JSONField(name="CLIENTIP")
    private String CLIENTIP;
    /**
     * 系统记账日期
     * 商户登陆商户后台设置返回记账日期的开关
     * 1.开关关闭时,无此字段返回且不参与验签。
     * 2.开关打开时,有此字段返回且参与验签。参数值格式为YYYYMMDD(如20100907)。
     */
    @JSONField(name="ACCDATE")
    private String ACCDATE;
    /**
     * 分期期数
     * 从商户传送的信息中获得;
     * 当分期期数为空或无此字段上送时,无此字段返回且不参与验签,否则有此字段返回且参与验签。
     */
    @JSONField(name="INSTALLNUM")
    private String INSTALLNUM;
    /**
     * 错误信息
     * 该值默认返回为空,商户无需处理,仅需参与验签即可。当有分期期数返回时,则有ERRMSG字段返回且参与验签,否则无此字段返回且不参与验签。
     */
    @JSONField(name="ERRMSG")
    private String ERRMSG;
    /**
     * 支付账户信息
     * 分行业务人员在P2员工渠道后台设置防钓鱼开关和返回账户信息的开关。
     * 1.开关关闭时,无此字段返回且不参与验签。
     * 2.开关打开但支付失败时,无此字段返回且不参与验签。
     * 3.开关打开且支付成功时,有此字段返回且参与验签。参数值格式如下:“姓名|账号加密后的密文”。
     * 解密方法请参考“商户通知验签包“文件夹下的《USERMSG》压缩包
     */
    @JSONField(name="USRMSG")
    private String USRMSG;
    /**
     * 客户加密信息
     * 分行业务人员在P2员工渠道后台设置防钓鱼开关和客户信息加密返回的开关。
     * 1.开关关闭时,无此字段返回且不参与验签
     * 2.开关打开时,有此字段返回且参数验签。参数值格式如下:“证件号密文|手机号密文”。该字段不可解密。
     */
    @JSONField(name="USRINFO")
    private String USRINFO;
    /**
     * 实付金额
     * 优惠之后的实际支付金额。
     * 目前只针对白名单商户返回,无此字段返回且不参与验签,有此字段返回且参与验签。
     */
    @JSONField(name="DISCOUNT")
    private String DISCOUNT;
    /**
     * 返回客户的积分使用情况,格式如下:
     * {“APnt_Hpn_Num”:”积分发生数量”,”APntCmpt_Amt”:”积分抵扣金额”}
     * 当综合积分字段为空或无此字段上送时,无此字段返回且不参与验签,否则有此字段返回且参与验签。
     */
    @JSONField(name="ZHJF")
    private String ZHJF;
    /**
     * 客户识别号
     * 提交建行的参数RETURN_FIELD打开对应开关才返回该字段。
     * 客户识别码, 微信、支付宝、龙支付时返回。
     * 有该字段返回时(无论返回值是空还是其他),需参与验签,否则无需参与验签。
     */
    @JSONField(name="OPENID")
    private String OPENID;
    /**
     * 用户子标识
     * 提交建行的参数RETURN_FIELD打开对应开关才返回该字段。
     * 微信支付专有字段。
     * 子商户appid下用户唯一标识,如需返回则请求时需要传sub_appid。
     * 有该字段返回时(无论返回值是空还是其他),需参与验签,否则无需参与验签。
     */
    @JSONField(name="SUB_OPENID")
    private String SUB_OPENID;
    /**
     * 支付详细信息
     * 支付详细信息。当RETURN_FIELD字段第四位上送1时返回。
     * 字段说明见下方[支付详细信息字段说明]
     * 格式如下:
     * {“TYPE“:"ALIPAY",“PAY_CHANNEL“:"BANKCARD",“DEBIT_CREDIT_TYPE“:"DEBIT_CARD",“THIRD_TRADE_NO“:"2018010521001004890523646975"}
     * 为防止特殊字符,建行会将该参数值用utf-8编码进行urlencode,因此商户需先decode之后才能拿到明文。
     * 编码之后为:
     * %7B%22TYPE%22%3A%22ALIPAY%22%2C%22PAY_CHANNEL%22%3A%22BANKCARD%22%2C%22DEBIT_CREDIT_TYPE%22%3A%22DEBIT_CARD%22%2C%22THIRD_TRADE_NO%22%3A%222018010521001004890523646975%22%7D
     * 有该字段返回时(无论返回值是空还是其他),需参与验签,否则无需参与验签,参与签名的是encode之后的参数值。
     */
    @JSONField(name="PAYMENT_DETAILS")
    private String PAYMENT_DETAILS;
    /**
     * 数字签名
     */
    @JSONField(name="SIGN")
    private String SIGN;
}
/**
 * @author snkj
 * @create 2022-11-02 18:14
 */
@Slf4j
@RestController
@RequestMapping("/pay")
public class ApiPayController {
    // 获取application.yml中的配置
    @Value("${jh.merchantId}")
    private String merchantId;
    @Value("${jh.posId}")
    private String posId;
    @Value("${jh.branchId}")
    private String branchId;
    @Value("${jh.subAppId}")
    private String subAppId;
    @Value("${jh.tradeType}")
    private String tradeType;
    @Value("${jh.pub}")
    private String pub;

    /**
     * 建行支付通知回调接口
     *
     * @return
     */
    @PostMapping("/jh/notify")
    public String pay(JhNotifyInfo jhNotifyInfo) {
        log.info("建行回调通知参数[{}]", JSON.toJSONString(jhNotifyInfo));
        RSASig rsaSig = new RSASig();
        rsaSig.setPublicKey(pub);
        String src = "POSID=" + jhNotifyInfo.getPOSID() + "&BRANCHID=" + jhNotifyInfo.getBRANCHID() + "&ORDERID=" + jhNotifyInfo.getORDERID() +
                "&PAYMENT=" + jhNotifyInfo.getPAYMENT() + "&CURCODE=" + jhNotifyInfo.getCURCODE() + "&REMARK1=" + jhNotifyInfo.getREMARK1() + "&REMARK2=" + jhNotifyInfo.getREMARK2() + "&ACC_TYPE=" + jhNotifyInfo.getACC_TYPE() +
                "&SUCCESS=" + jhNotifyInfo.getSUCCESS() + "&TYPE=" + jhNotifyInfo.getTYPE() + "&REFERER=" + jhNotifyInfo.getREFERER() + "&CLIENTIP=" + jhNotifyInfo.getCLIENTIP();
        // 校验签名
        boolean verifySigature = rsaSig.verifySigature(jhNotifyInfo.getSIGN(), src);
        if (verifySigature) {
            // 验签通过,业务逻辑自行处理
            // 这里需要调用建行龙支付提供的查询订单接口以保证订单确实支付成功
            log.info("验签通过");
            return "SUCCESS";
        } else {
            // 验签失败
            log.info("验签失败");
            return "FAIL";
        }
    }
}
        <dependency>
            <groupId>netpay.merchant.crypto</groupId>
            <artifactId>netpay</artifactId>
            <version>0.0.1</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/netpay.jar</systemPath>
        </dependency>

        2.4 部分工具类

/**
 * 时间工具类
 *
 * @author snkj
 */
public class DateUtils {

    public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";

    public static final String parseDateToStr(final String format, final Date date) {
        return new SimpleDateFormat(format).format(date);
    }

    /**
     * 当前时间加分钟
     * @param minute
     * @return
     */
    public static String addMinute(int minute){
        Calendar nowTime = Calendar.getInstance();
        nowTime.add(Calendar.MINUTE, minute);
        return parseDateToStr(YYYYMMDDHHMMSS,nowTime.getTime());
    }

}
/**
 * 对中文进行escape编码
 * @author snkj
 * @create 2022-11-02 19:07
 */
public class EscapeUtils {

    private final static String[] hex = { "00", "01", "02", "03", "04", "05",
            "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", "10",
            "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B",
            "1C", "1D", "1E", "1F", "20", "21", "22", "23", "24", "25", "26",
            "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", "30", "31",
            "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C",
            "3D", "3E", "3F", "40", "41", "42", "43", "44", "45", "46", "47",
            "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", "50", "51", "52",
            "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D",
            "5E", "5F", "60", "61", "62", "63", "64", "65", "66", "67", "68",
            "69", "6A", "6B", "6C", "6D", "6E", "6F", "70", "71", "72", "73",
            "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E",
            "7F", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89",
            "8A", "8B", "8C", "8D", "8E", "8F", "90", "91", "92", "93", "94",
            "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F",
            "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA",
            "AB", "AC", "AD", "AE", "AF", "B0", "B1", "B2", "B3", "B4", "B5",
            "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", "C0",
            "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB",
            "CC", "CD", "CE", "CF", "D0", "D1", "D2", "D3", "D4", "D5", "D6",
            "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", "E0", "E1",
            "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC",
            "ED", "EE", "EF", "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7",
            "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF" };

    private final static byte[] val = { 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x01,
            0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
            0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F };

    /** */
    /**
     * 编码
     * @param s
     * @return
     */
    public static String escape(String s) {
        StringBuffer sbuf = new StringBuffer();
        int len = s.length();
        for (int i = 0; i < len; i++) {
            int ch = s.charAt(i);
            if ('A' <= ch && ch <= 'Z') {
                sbuf.append((char) ch);
            } else if ('a' <= ch && ch <= 'z') {
                sbuf.append((char) ch);
            } else if ('0' <= ch && ch <= '9') {
                sbuf.append((char) ch);
            } else if (ch == '-' || ch == '_' || ch == '.' || ch == '!'
                    || ch == '~' || ch == '*' || ch == '\'' || ch == '('
                    || ch == ')') {
                sbuf.append((char) ch);
            } else if (ch <= 0x007F) {
                sbuf.append('%');
                sbuf.append(hex[ch]);
            } else {
                sbuf.append('%');
                sbuf.append('u');
                sbuf.append(hex[(ch >>> 8)]);
                sbuf.append(hex[(0x00FF & ch)]);
            }
        }
        return sbuf.toString();
    }

    /**
     * 解码 说明:本方法保证 不论参数s是否经过escape()编码,均能得到正确的“解码”结果
     *
     * @param s
     * @return
     */
    public static String unescape(String s) {
        StringBuffer sbuf = new StringBuffer();
        int i = 0;
        int len = s.length();
        while (i < len) {
            int ch = s.charAt(i);
            if ('A' <= ch && ch <= 'Z') {
                sbuf.append((char) ch);
            } else if ('a' <= ch && ch <= 'z') {
                sbuf.append((char) ch);
            } else if ('0' <= ch && ch <= '9') {
                sbuf.append((char) ch);
            } else if (ch == '-' || ch == '_' || ch == '.' || ch == '!'
                    || ch == '~' || ch == '*' || ch == '\'' || ch == '('
                    || ch == ')') {
                sbuf.append((char) ch);
            } else if (ch == '%') {
                int cint = 0;
                if ('u' != s.charAt(i + 1)) {
                    cint = (cint << 4) | val[s.charAt(i + 1)];
                    cint = (cint << 4) | val[s.charAt(i + 2)];
                    i += 2;
                } else {
                    cint = (cint << 4) | val[s.charAt(i + 2)];
                    cint = (cint << 4) | val[s.charAt(i + 3)];
                    cint = (cint << 4) | val[s.charAt(i + 4)];
                    cint = (cint << 4) | val[s.charAt(i + 5)];
                    i += 5;
                }
                sbuf.append((char) cint);
            } else {
                sbuf.append((char) ch);
            }
            i++;
        }
        return sbuf.toString();
    }
}
import java.util.HashSet;
import java.util.Set;

/**
 * 订单号工具类
 * @author snkj
 * @create 2022-09-25 16:14
 */
public class OrderNoUtils {

    // ==============================Fields===========================================
    /**
     * 开始时间截 (2018-07-03)
     */

    private final long twepoch = 1530607760000L;

    /**
     * 机器id所占的位数
     */
    private final long workerIdBits = 5L;

    /**
     * 数据标识id所占的位数
     */
    private final long datacenterIdBits = 5L;

    /**
     * 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /**
     * 支持的最大数据标识id,结果是31
     */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /**
     * 序列在id中占的位数
     */
    private final long sequenceBits = 12L;

    /**
     * 机器ID向左移12位
     */
    private final long workerIdShift = sequenceBits;

    /**
     * 数据标识id向左移17位(12+5)
     */
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    /**
     * 时间截向左移22位(5+5+12)
     */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /**
     * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
     */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /**
     * 工作机器ID(0~31)
     */
    private long workerId;

    /**
     * 数据中心ID(0~31)
     */
    private long datacenterId;

    /**
     * 毫秒内序列(0~4095)
     */
    private long sequence = 0L;

    /**
     * 上次生成ID的时间截
     */
    private long lastTimestamp = -1L;

    //==============================Constructors=====================================

    /**
     * 构造函数
     *
     * @param workerId     工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public OrderNoUtils(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    // ==============================Methods==========================================

    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        //如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        //时间戳改变,毫秒内序列重置
        else {
            sequence = 0L;
        }

        //上次生成ID的时间截
        lastTimestamp = timestamp;

        //移位并通过或运算拼到一起组成64位的ID
        return (((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence);
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     *
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    //==============================Test=============================================

    /**
     * 测试
     */
    public static void main(String[] args) {
        OrderNoUtils idWorker = new OrderNoUtils(0, 0);
        Set set = new HashSet();
        long id = idWorker.nextId();
        System.out.println(id);
        set.add(id);
    }

}

其他工具类采用Hutool,请自行百度并引入

三、总结

我方暂时只需要微信小程序之后,故以上案例均为小程序支付,后续会持续更新其他几个支付类型。

列举几个坑,各位看官注意一下

1. 生成MAC签名摘要时,需要商户的柜台公钥后30位

2. REMARK1和REMARK2可以传递两个备注,但长度不能超过30位,并且要求对中文使用js的escape函数进行编码(参考上方的后端escape编码工具类)

3. PROINFO也需要对中文使用js的escape函数进行编码(参考上方的后端escape编码工具类)

4. 在根据参数拼接MAC签名串时,要注意别把Null拼进去,就是说,要提前将Null => 空值

5. 回调验签坑1:文档中对于参数有返回值的意思是:包括空值,但不包括Null。再翻译一下:就算返回值是个空值,也算有返回值,但如果是Null就不算有返回值,就不参与验签;

6. 回调验签坑2:在验签时还需要商户柜台公钥,如果还像上面那样只截取后面的30位,就会顺利入坑。因为这次是全部

7. 回调验签坑3:需要引入建行提供验签的jar包;文章来源地址https://www.toymoban.com/news/detail-490017.html

到了这里,关于Java集成建行龙支付接口(详细)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java实现支付宝沙箱环境支付,SDK接口远程调试

    转发自cpolar内网穿透的文章:Java支付宝沙箱环境支付,SDK接口远程调试【内网穿透】 Maven Spring boot Jdk 1.8 获取支付宝支付Java SDK,maven项目可以选择maven版本,普通java项目可以在GitHub下载,这里以maven为例 SDK下载地址:https://doc.open.alipay.com/docs/doc.htm?treeId=193articleId=103419docType=1 选择

    2024年02月06日
    浏览(41)
  • Java支付SDK接口远程调试 - 支付宝沙箱环境【公网地址调试】

    转发自CSDN远程穿透的文章:Java支付宝沙箱环境支付,SDK接口远程调试【内网穿透】 Maven Spring boot Jdk 1.8 获取支付宝支付Java SDK,maven项目可以选择maven版本,普通java项目可以在GitHub下载,这里以maven为例 SDK下载地址:https://doc.open.alipay.com/docs/doc.htm?treeId=193articleId=103419docType=1 选择

    2023年04月18日
    浏览(35)
  • Java支付宝沙箱环境支付,SDK接口远程调试【内网穿透】

    转发自CSDN远程穿透的文章:Java支付宝沙箱环境支付,SDK接口远程调试【内网穿透】 Maven Spring boot Jdk 1.8 获取支付宝支付Java SDK,maven项目可以选择maven版本,普通java项目可以在GitHub下载,这里以maven为例 SDK下载地址:https://doc.open.alipay.com/docs/doc.htm?treeId=193articleId=103419docType=1  选择

    2024年02月02日
    浏览(40)
  • 【超详细,全流程】java对接支付宝支付

    本文对接使用的是支付宝的电脑网站支付,其它对接方式可做参考 接入分两种 第一种是常规接入,主要是针对 企业用户 ,需要有 营业执照;经过备案能正常访问的网站 第二种是 沙箱接入 ,主要针对 个人用户 ,只需要有能正常使用的支付宝就可以完成接入,真实企业用户

    2024年02月02日
    浏览(33)
  • Postman接口测试实战-接口断言/newman执行集成(详细)

    接口测试用例 接口的功能测试维度:单接口(正向和反向)和业务接口(一般做正向测试) 接口测试的测试用例编写: 不单单针对参数值正确与否进行,还可以针对参数本身进行测试 正向参数 必选参数:所有的必选(必填参数)都包含 组合参数:所有的必选+任意一个或多

    2024年02月16日
    浏览(30)
  • Java 实现微信支付详细教程

    摘要 :最近的一个项目中涉及到了支付业务,其中用到了微信支付和支付宝支付,在做的过程中也遇到些问题,所以现在总结梳理一下,分享给有需要的人,也为自己以后回顾留个思路。 首先,微信支付,只支持企业用户,个人用户是不能接入微信支付的,所以要想接入微

    2024年02月15日
    浏览(21)
  • 【微信小程序】Java实现微信支付(小程序支付JSAPI-V3)java-sdk工具包(包含支付出现的多次回调的问题解析,接口幂等性)

          对于一个没有写过支付的小白,打开微信支付官方文档时彻底懵逼 ,因为 微信支付文档太过详细, 导致我无从下手,所以写此文章,帮助第一次写支付的小伙伴梳理一下。 一、流程分为三个接口:(这是前言,先看一遍,保持印象,方便理解代码) 1、第一个接口:

    2024年01月16日
    浏览(60)
  • 支付宝沙箱环境配置结合内网穿透实现远程调试Java SDK接口

    1.测试环境 Maven Spring boot Jdk 1.8 2.本地配置 获取支付宝支付Java SDK,maven项目可以选择maven版本,普通java项目可以在GitHub下载,这里以maven为例 SDK下载地址:https://doc.open.alipay.com/docs/doc.htm?treeId=193articleId=103419docType=1 选择最新版的SDK版本 复制maven坐标,导入spring boot项目pom 文件里面

    2024年02月04日
    浏览(42)
  • 微信支付-超详细java开发-小程序对接

    本文适用于有一定基础的开发者,简单易通。后台用的的是java,我用的是springBoot,其它框架基本同理,前端就是一个简单的demo。微信官方提供了V2和V3两种方式,本文基于V2版支付开发(后续更新V3)。V2和V3版本区别 1.思路介绍 本次以微信小程序开发为例,如果自己想要玩一

    2024年02月09日
    浏览(38)
  • Java对接微信支付(史上最详细)

    本文将介绍如何使用Java对接微信支付,包括获取支付参数、支付回调处理等步骤。本文适用于已经熟悉微信支付基本原理的读者。 JDK 1.8 Maven Spring Boot 2.x 微信支付开发文档 为了进行支付,我们需要先获取微信支付的参数信息,包括appid、商户id、支付密钥等。 配置文件 我们

    2024年02月15日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包