银商大华捷通平台对接代收款接口规范

时间:2021-01-30 20:08:58

银商大华捷通平台对接代收款接口规范

最近业务用到与大华捷通代收款(POS支付),要与其对接获取订单数据和支付通知两个接口。主要流程如下:

1. 中心系统产生订单,生成包含订单号的二维码,并显示在店铺的APP页面上;

2. 大华POS通过扫描店铺APP页面二维码获取到订单号,中心接收到请求要核对POS的机具序列号是否正确;

3. 大华POS根据此订单号向中心系统发送请求,获取到订单金额等数据;

4. 大华POS根据订单号、订单金额等数据跳转生成支付页面,由客户支付;

5. 支付成功,进行支付回调通知,中心系统收到通知相应改变订单状态。

银商大华捷通平台对接代收款接口规范

                                                          支付流程图

相关文档:

银商大华捷通平台与第三方物流ERP系统接口规范-完整版_V2.7.3.pdf

主要问题: MAC加密验证

主要代码:


import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.jsoup.Jsoup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
* Created by think on 2017/4/12.
*/
@Service
public class DahuaPaymentServiceImpl extends BaseJpaServiceImpl<PayPaymentEntity,Long,PayPaymentDaoImpl> implements DahuaPaymentService {
private Logger log = LoggerFactory.getLogger(DahuaPaymentServiceImpl.class);

//双方预定约好的MAC——32
@Value("${dahua.mac.appointed}")
private String MAC_APPOINTED;

@Autowired
private ShopOrderService shopOrderService;

@Autowired
private PayPaymentService paymentService;

@Autowired
private WkOrderService wkOrderService;



/**
* 通过订单SN号获取订单信息
* @param request
* @param response
* @throws IOException
*/
@RequestMapping(value = "/getOrderData", method = RequestMethod.POST)
public void getOrderData(HttpServletRequest request, HttpServletResponse response) throws IOException {
log.debug("大华获取订单数据开始");

String result = request.getParameter("context");
log.debug("大华获取订单数据请求报文: " + result);

String mac = Jsoup.parse(result).select("mac").html();
if(!checkSignMac(result)){
response.getWriter().write(errorXml(DahuaResponseCodeEnum.MAC_ERROR.getCode(), DahuaResponseCodeEnum.MAC_ERROR.getName()));
return;
}

String orderSn = Jsoup.parse(result).select("orderno").html();
String version = Jsoup.parse(result).select("version").html();
String employno = Jsoup.parse(result).select("employno").html();
String termid = Jsoup.parse(result).select("termid").html();
if(null == termid || termid.length() < 1){
response.getWriter().write(errorXml("05", "缺少设备ID信息(termid)"));
return;
}

QueryFilter filter = QueryFilterBuilder.of()
.joinFetch("t.mbTenant")
.eq("t.sn", orderSn)
.build();
ShopOrderEntity entity = shopOrderService.get(filter);
//判断是否存在这个订单
if(null == entity || null == entity.getId()){
response.getWriter().write(errorXml(DahuaResponseCodeEnum.NON_ORDER.getCode(), DahuaResponseCodeEnum.NON_ORDER.getName()));
return;
}
//判断工单的店铺信息是否正常
if(null == entity.getMbTenant() || null == entity.getMbTenant().getId()){
response.getWriter().write(errorXml(DahuaResponseCodeEnum.NOT_THIS_SHOP.getCode(), DahuaResponseCodeEnum.NOT_THIS_SHOP.getName()));
return;
}
//判断设备号是否相符
if(!termid.equals(entity.getMbTenant().getPosSn())){
response.getWriter().write(errorXml(DahuaResponseCodeEnum.NOT_THIS_SHOP.getCode(), DahuaResponseCodeEnum.NOT_THIS_SHOP.getName()));
return;
}


// 更改交易发起时间
Date sendPaymentDate = new Date(); //发起交易时间,与响应报文的响应时间相同
PayPaymentEntity paymentEntity = paymentService.getByOrderSn(orderSn);
if(null != paymentEntity){
paymentEntity.setSendPaymentDate(sendPaymentDate);
paymentService.updateSelect(paymentEntity);
}


//response.getWriter().write(createHtml(map, "UTF-8"));

Document document = DocumentHelper.createDocument();
Element rootElement = document.addElement("transaction");

Element headerElement = rootElement.addElement("transaction_header");
headerElement.addElement("version").setText(version);
headerElement.addElement("transtype").setText("P004");
headerElement.addElement("employno").setText(employno);
headerElement.addElement("termid").setText(termid);
headerElement.addElement("response_time").setText(formatTime(sendPaymentDate));
headerElement.addElement("response_code").setText("00");
headerElement.addElement("response_msg").setText("获取订单数据成功");
headerElement.addElement("mac").setText(mac);

Element bodyElement = rootElement.addElement("transaction_body");
bodyElement.addElement("netcode").setText("");
bodyElement.addElement("netname").setText("");
bodyElement.addElement("weight").setText("");
//金额
bodyElement.addElement("cod").setText(this.formatDecimal(entity.getOffsetAmount()).toString());
//运费
bodyElement.addElement("fee").setText("");
bodyElement.addElement("goodscount").setText("");
bodyElement.addElement("address").setText("");
bodyElement.addElement("people").setText("");
bodyElement.addElement("peopletel").setText("");
bodyElement.addElement("status").setText("02");
bodyElement.addElement("memo").setText("");
bodyElement.addElement("dssn").setText("");
bodyElement.addElement("dsname").setText("");
bodyElement.addElement("dsorderno").setText("");
bodyElement.addElement("dlvryno").setText("");
//bodyElement.addElement("buzitype").setText("1");

response.getWriter().write(signature(asXml(document)));

return;
}


/**
* 支付成功的回调通知
* @param request
* @param response
* @throws IOException
*/
@RequestMapping(value = "/notify", method = RequestMethod.POST)
public void notify(HttpServletRequest request, HttpServletResponse response) throws IOException {
log.debug("大华支付后台通知开始");

String result = request.getParameter("context");
log.debug("大华支付后台通知请求报文: " + result);

String mac = Jsoup.parse(result).select("mac").html();
if(!checkSignMac(result)){
response.getWriter().write(errorXml(DahuaResponseCodeEnum.MAC_ERROR.getCode(), DahuaResponseCodeEnum.MAC_ERROR.getName()));
return;
}

String orderSn = Jsoup.parse(result).select("orderno").html();
String payway = Jsoup.parse(result).select("payway").html();
String traceTime = Jsoup.parse(result).select("traceTime").html();
String cardid = Jsoup.parse(result).select("cardid").html();
String cod = Jsoup.parse(result).select("cod").html();
String postrace = Jsoup.parse(result).select("postrace").html(); //POS机的流水号
String memo = Jsoup.parse(result).select("memo").html(); //memo内为json格式数据
String version = Jsoup.parse(result).select("version").html();
String employno = Jsoup.parse(result).select("employno").html();
String termid = Jsoup.parse(result).select("termid").html();
if(null == termid || termid.length() < 1){
response.getWriter().write(errorXml("05", "缺少设备ID信息(termid)"));
return;
}

QueryFilter filter = QueryFilterBuilder.of()
.joinFetch("t.mbTenant")
.eq("t.sn", orderSn)
.build();
ShopOrderEntity entity = shopOrderService.get(filter);
//判断是否存在这个订单
if(null == entity || null == entity.getId()){
response.getWriter().write(errorXml(DahuaResponseCodeEnum.NON_ORDER.getCode(), DahuaResponseCodeEnum.NON_ORDER.getName()));
return;
}
//判断工单的店铺信息是否正常
if(null == entity.getMbTenant() || null == entity.getMbTenant().getId()){
response.getWriter().write(errorXml(DahuaResponseCodeEnum.NOT_THIS_SHOP.getCode(), DahuaResponseCodeEnum.NOT_THIS_SHOP.getName()));
return;
}
//判断设备号是否相符
if(!termid.equals(entity.getMbTenant().getPosSn())){
response.getWriter().write(errorXml(DahuaResponseCodeEnum.NOT_THIS_SHOP.getCode(), DahuaResponseCodeEnum.NOT_THIS_SHOP.getName()));
return;
}

//收到回调后,对涉及资金类的交易,请再发起查询接口查询,确定交易成功后更新数据库。
{ // 交易成功
PayPaymentEntity paymentEntity = paymentService.getByOrderSn(orderSn);
if(PaymentStatusEnum.SUCCESS.getCode().equals(paymentEntity.getStatusCode().getCode())){
response.getWriter().write(errorXml(DahuaResponseCodeEnum.PAID.getCode(), DahuaResponseCodeEnum.PAID.getName()));
return;
}
paymentEntity.setStatusCode(new ComDataDictionaryEntity(PaymentStatusEnum.SUCCESS.getCode()));
if(!StringUtils.isEmpty(cod)){
paymentEntity.setAmount(formatDecimal(new BigDecimal(cod)));
}else{
paymentEntity.setAmount(formatDecimal(new BigDecimal(0)));
}


paymentEntity.setPaymentSn(postrace); //设置为POS的流水号
paymentEntity.setPaymentDate(formatTime(traceTime));
paymentEntity.setMethod(payway);
paymentEntity.setPayCardNo(cardid);
paymentEntity.setMemo(memo);
String jsonResult = "";
paymentEntity.setPaymengResult(jsonResult);
paymentService.updateSelect(paymentEntity);

//改变工单状态
wkOrderService.changeWkOrderStatusToPay(orderSn);

}
//response.getWriter().write(createHtml(map, "UTF-8"));

Document document = DocumentHelper.createDocument();
Element rootElement = document.addElement("transaction");

Element headerElement = rootElement.addElement("transaction_header");
headerElement.addElement("version").setText(version);
headerElement.addElement("transtype").setText("P003");
headerElement.addElement("employno").setText(employno);
headerElement.addElement("termid").setText(termid);
headerElement.addElement("response_time").setText(formatTime(new Date()));
headerElement.addElement("response_code").setText("00");
headerElement.addElement("response_msg").setText("交易成功");
headerElement.addElement("mac").setText(mac);

Element bodyElement = rootElement.addElement("transaction_body");


response.getWriter().write(signature(asXml(document)));

return;
}


/**
* 时间格式化
* @param date
* @return
*/
private String formatTime(Date date){
return new SimpleDateFormat("yyyyMMddHHmmss").format(date);
}
private Date formatTime(String dateStr){
try {
return new SimpleDateFormat("yyyyMMddHHmmss").parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
return null;
}

}


/**
* 签名加密
* @param xmlStr
* @return
*/
private String signature(String xmlStr){
//第一步:约定32字节签名密文
String macAppointed = MAC_APPOINTED;

Document document = null;
try {
document = DocumentHelper.parseText(xmlStr);
Element rootElement = document.getRootElement();
Element headerElement = rootElement.element("transaction_header");
Element macElement = headerElement.element("mac");

//第二步:去除“mac“节点
headerElement.remove(macElement);

//第三步:约定密文附加在xml后面,组成待签名密文
String signXml = asXml(document);
signXml = signXml.replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", ""); //去掉表头
signXml = signXml.replace("<?xml version=\"1.0\" encoding=\"utf-8\"?>", ""); //去掉表头
signXml = signXml.replace("context=", ""); //去掉context=
signXml = signXml.replace("\n", ""); //去掉回车
signXml = signXml.replace(" ", ""); //去掉空格
signXml = signXml + macAppointed; //加约定密文

//第四叔:待签名密文进行MD5签名
String mac = this.MD5(signXml);
//第五步:MD5值放到mac节点中
headerElement.addElement("mac").setText(mac);

} catch (DocumentException e) {
e.printStackTrace();
}

return asXml(document);
}

/**
* 验证MAC
* @param xmlStr
* @return
*/
private boolean checkSignMac(String xmlStr){
//第一步:约定32字节签名密文
String macAppointed = MAC_APPOINTED;

String macReceive;
Document document = null;
try {
document = DocumentHelper.parseText(xmlStr);
Element rootElement = document.getRootElement();
Element headerElement = rootElement.element("transaction_header");
Element macElement = headerElement.element("mac");
macReceive = macElement.getText();
log.debug("大华请求报文MAC校验,接收到的MAC: " + macReceive);

//第二步:去除“mac“节点
headerElement.remove(macElement);

//第三步:约定密文附加在xml后面,组成待签名密文
String signXml = asXml(document);
signXml = signXml.replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", ""); //去掉表头
signXml = signXml.replace("<?xml version=\"1.0\" encoding=\"utf-8\"?>", ""); //去掉表头
signXml = signXml.replace("context=", ""); //去掉context=
signXml = signXml.replace("\n", ""); //去掉回车
signXml = signXml.replace(" ", ""); //去掉空格
signXml = signXml + macAppointed; //加约定密文
log.debug("大华请求报文MAC校验,待加密XML: " + signXml);

//第四步:待签名密文进行MD5签名
String mac = this.MD5(signXml);
log.debug("大华请求报文MAC校验,加密的MAC: " + mac);

if(null != macReceive && macReceive.length() > 0 && macReceive.equals(mac)){
log.debug("大华请求报文MAC校验成功");
return true;
}else{
log.error("大华请求报文MAC校验失败");
return false;
}

} catch (DocumentException e) {
e.printStackTrace();
log.error("大华请求报文MAC校验失败: " + e.getMessage());
return false;
}

}


private String MD5(String s) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(s.getBytes("utf-8"));
return toHex(bytes);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}


private static String toHex(byte[] bytes) {

final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
StringBuilder ret = new StringBuilder(bytes.length * 2);
for (int i=0; i<bytes.length; i++) {
ret.append(HEX_DIGITS[(bytes[i] >> 4) & 0x0f]);
ret.append(HEX_DIGITS[bytes[i] & 0x0f]);
}
return ret.toString();
}

/**
* 返回错误信息
* @param errorCode
* @param errorStr
* @return
*/
private String errorXml(String errorCode, String errorStr){
log.error("ERROR: " + "errorCode " + errorCode + " errorStr " + errorStr);

Document document = DocumentHelper.createDocument();
Element rootElement = document.addElement("transaction");
Element headerElement = rootElement.addElement("transaction_header");
headerElement.addElement("response_code").setText(errorCode);
headerElement.addElement("response_msg").setText(errorStr);
Element bodyElement = rootElement.addElement("transaction_body");

return asXml(document);
}

//转换为标准格式(避免自闭合的问题)
private String asXml(Document document){
OutputFormat format = new OutputFormat();
format.setEncoding("UTF-8");
//format.setExpandEmptyElements(true);
StringWriter out = new StringWriter();
XMLWriter writer = new XMLWriter(out, format);
try {
writer.write(document);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
return out.toString();
}

//保留小数据点后两位
private BigDecimal formatDecimal(BigDecimal amount){
if(null != amount){
amount = amount.setScale(2, BigDecimal.ROUND_HALF_UP);
}
return amount;
}



}

ENUM也贴上

public enum DahuaResponseCodeEnum {

SUCCESS("成功", "00"),
NON_ORDER("订单不存在", "H3"),
MAC_ERROR("数据校验错误", "A0"),
NOT_THIS_SHOP("不属于本店订单", "H1"),
PAID("已支付", "35"),
CANCELED("已取消", "36");

private String name;

private String code;

DahuaResponseCodeEnum(String name, String code) {
this.name = name;
this.code = code;
}

public String getCode() {
return this.code;
}

public String getName() {
return this.name;
}

public static String getName(String code) {
for (DahuaResponseCodeEnum c : DahuaResponseCodeEnum.values()) {
if (code.equals(c.getCode())) {
return c.name;
}
}
return null;
}
}