支付系统设计三:渠道网关设计06

您所在的位置:网站首页 可用于即时支付的资金不足以满足支付需要 支付系统设计三:渠道网关设计06

支付系统设计三:渠道网关设计06

2023-05-21 09:03| 来源: 网络整理| 查看: 265

文章目录 前言一、业务服务工厂二、业务处理服务1. 业务处理服务2. 业务处理抽象服务3. 流量控制4. 报文提交4.1 获取交易的服务端通讯列表4.2 循环请求支付渠道4.2.1 报文组装4.2.2 报文发送4.2.2.1 协议处理器获取4.2.2.2 构建通讯客户端4.2.2.3 发送请求4.2.2.4 响应报文读取 5. 报文解析5.1 获取待解析报文5.2 获取报文解析器5.3 进行报文解析 6. 响应码解析 总结

前言

在这里插入图片描述 前面做了那么多工作《支付系统设计三:渠道网关设计05-交易持久化》、《支付系统设计三:渠道网关设计04-渠道数据补全》、《支付系统设计三:渠道网关设计03-参数验证》、《支付系统设计三:渠道网关设计02-客户端报文解析》,上送报文解析、参数校验、服务端渠道获取、参数补全、持久化工作等,下面开始请求支付渠道了,本篇将继续分解业务处理部分的逻辑实现。

businessServiceFactory.getBusinessService(context.getServerTransCode()).execute(context); 一、业务服务工厂

transCode+BusinessServiceImpl从业务服务工厂中获取到业务服务Service。

/** * @author Kkk * @Describe: 业务服务工厂类 */ @Component public class BusinessServiceFactory { private static final Logger logger = LoggerFactory.getLogger(BusinessServiceFactory.class); /** * 业务service bean 后缀 */ private final static String BUSINESSSERVICE_SUFF = "BusinessServiceImpl"; @Autowired private Map businessServiceMap; /** * 获取业务服务Service * @param transCode * @return */ public BusinessService getBusinessService(String transCode) { String key = new StringBuilder(transCode).append(BUSINESSSERVICE_SUFF).toString(); BusinessService businessService = businessServiceMap.get(key); return businessService; } }

在这里插入图片描述

二、业务处理服务 1. 业务处理服务 /** * @author Kkk * @Describe: 单笔代扣业务服务 */ @Service public class DeductBusinessServiceImpl extends AbstractBusinessService { @Override public void execute(PayGwContext context) { super.submitWithfluxControl(context); } } 2. 业务处理抽象服务 /** * @author Kkk * @Describe: 业务处理抽象服务 */ @Service public abstract class AbstractBusinessService implements BusinessService { protected Logger logger = LoggerFactory.getLogger(getClass()); /** * 报文传输组件 */ @Autowired private MessageTransport messageTransport; /** * 流量控制服务 */ @Autowired private RateLimitService rateLimitService; /** * 发送支付渠道(进行限流控制) * @param context */ protected void submitWithfluxControl(PayGwContext context) { MessageDescription messageDescription = context.getMessageDescription(); Map data = messageDescription.getDatas(); String instTransCode = StringUtils.valueOf(data.get(PayGwConstant.INST_TRANS_CODE)); List transCodeList = getRateLimitTransCodeList(); if (transCodeList.contains(instTransCode)) { String instCode = StringUtils.valueOf(data.get(PayGwConstant.INST_CODE)); String instTransId = StringUtils.valueOf(data.get(PayGwConstant.INST_TRANS_ID)); boolean hasRateLimit = rateLimitService.hasRateLimit(instCode, instTransId); if (hasRateLimit) { acceptAndQueue(context); return; } } submit(context); } /** * 发送支付渠道(服务端渠道通讯) * @param context */ protected void submit(PayGwContext context) { String transCode = context.getServerTransCode(); LoggerUtil.info(logger, "交易({})-请求支付渠道-开始", transCode); messageTransport.submit(context); LoggerUtil.info(logger, "交易({})-请求支付渠道-结束", transCode); } /** * 发送支付渠道(指定客户端通讯或者服务端通讯) * @param context * @param csFlagEnum */ protected void submit(PayGwContext context, CsFlagEnum csFlagEnum) { String transCode = context.getServerTransCode(); LoggerUtil.info(logger, "交易({})-请求支付渠道-开始", transCode); messageTransport.submit(context, csFlagEnum); LoggerUtil.info(logger, "交易({})-请求支付渠道-结束", transCode); } /** * 获取进行流量控制的transCode * @return */ protected List getRateLimitTransCodeList() { List transCodeList = Lists.newArrayList(); transCodeList.add(TransactionEnum.DEDUCT.getCode()); transCodeList.add(TransactionEnum.DEDUCT_ASY.getCode()); transCodeList.add(TransactionEnum.DEPUTE.getCode()); transCodeList.add(TransactionEnum.DEPUTE_ASY.getCode()); // ... .... return transCodeList; } /** * 交易登记受理幷用于限流排队 * @param context */ protected void acceptAndQueue(PayGwContext context) { LoggerUtil.info(logger, "交易({})-登记受理并排队-开始", context.getClientTransCode()); Map data = context.getMessageDescription().getDatas(); data.put(PayGwConstant.ORG_PROCESS_STATUS, ProcessStatusEnum.PROCESSING.getCode());//处理状态:处理中 data.put(PayGwConstant.PAYGW_PROCESS_STATUS, ProcessStatusEnum.QUEUEING.getCode());//处理状态:已受理并排队 data.put(PayGwConstant.PAYGW_TRANS_STATUS, TransStatusEnum.PROCESS.getCode());//交易状态:处理中 data.put(PayGwConstant.PAYGW_RESP_CODE, SystemErrorCode.PROCESS.getCode());//支付网关响应码 data.put(PayGwConstant.PAYGW_RESP_MSG, SystemErrorCode.PROCESS.getMessage());//支付网关响应信息:交易已受理 LoggerUtil.info(logger, "交易({})-登记受理并排队-结束", context.getClientTransCode()); } } 3. 流量控制

在请求支付渠道之前,先判断是否配置流量控制,如果配置了流量控制,则将processStatus置为QUEUEING排队中,后面处理会通过Redis实现流量控制。 在这里插入图片描述 此处就不再展开,后期有时间单独介绍渠道侧流量控制的实现。

4. 报文提交

和支付渠道通讯过程报文组装客户端获取、通讯证书等加载比较复杂,这里简要介绍下实现脉络,后期专门再介绍这块具体实现。

4.1 获取交易的服务端通讯列表 /** * 获取服务端通讯列表 * @param context * @param csFlagEnum * @return */ public List getServerCommunicationEntityList(PayGwContext context, CsFlagEnum csFlagEnum) { MessageDescription messageDescription = context.getMessageDescription(); //区分客户端和服务端调用,获取交易的服务端通讯 List serverCommunicationEntityList = null; if (csFlagEnum == CsFlagEnum.SERVER) {//服务端机构 messageDescription.setInstCode(context.getServerInstCode()); messageDescription.setTransCode(context.getServerTransCode()); messageDescription.setTransId(context.getServerTransId()); PayInstitution payInstitution = messageDescription.getServerPayInstitution(); serverCommunicationEntityList = conmmunicationCacheManager.getByType(payInstitution.getInstTransTypeEntityByTransId(context.getServerTransId()).getId(), csFlagEnum.getCode()); } else {//客户端机构 messageDescription.setInstCode(context.getClientInstCode()); messageDescription.setTransCode(context.getClientTransCode()); messageDescription.setTransId(context.getClientTransId()); PayInstitution payInstitution = messageDescription.getClientPayInstitution(); serverCommunicationEntityList = conmmunicationCacheManager.getByType(payInstitution.getInstTransTypeEntityByTransCode(context.getClientTransCode()).get(0).getId(), csFlagEnum.getCode()); } AssertUtils.isNotEmpty(serverCommunicationEntityList, SystemErrorCode.UNFOUND_CONFIG, new Object[]{"通讯"}); return serverCommunicationEntityList; }

获取交易的服务端通讯列表之前MessageDescription 属性: 在这里插入图片描述 获取交易的服务端通讯列表之后MessageDescription 属性: 在这里插入图片描述 即将服务端支付渠道信息作为处理的目标:当前处理的通讯设置为服务端支付渠道通讯信息。

4.2 循环请求支付渠道 /** * 循环请求支付渠道 * @param context * @param serverCommunicationEntityList */ public void submit(PayGwContext context, List serverCommunicationEntityList) { //1、循环请求支付渠道 MessageDescription messageDescription = context.getMessageDescription(); for (Iterator iterator = serverCommunicationEntityList.iterator(); iterator.hasNext(); ) { CommunicationEntity communicationEntity = iterator.next(); messageDescription.setCommunicationEntity(communicationEntity); try { submitCommunication(context); if (iterator.hasNext()) { if (isShortCut(messageDescription)) { LoggerUtil.error(logger, "前置通讯交易状态不是成功-结束此交易的所有通讯"); break; } } } catch (PayGwException e) { if (StringUtils.equals(COMMUNICATION_EXCEPTION.getCode(), e.getErrorCode())) { if (iterator.hasNext()) { LoggerUtil.error(logger, "前置通讯异常", e); throw new PayGwException(SystemErrorCode.FAIL, e); } } throw e; } catch (Exception e) { throw e; } } } 4.2.1 报文组装

使用报文组装引擎进行请求报文的组装工作,并将组装好的报文设置到MessageDescription serverRequestMessageEnvelope(发送到服务端的请求报文)中。

@Override public MessageEnvelope messageAssemble(MessageTemplate messageTemplate, PayGwContext payGwContext) { MessageEnvelope messageEnvelope = new MessageEnvelope(); //初始化Velocity和Template 将对应交易所需要的 velocity 模板(头,尾,补充模板等)与上下文中的参数 放到 velocitycontext 中 VelocityContext context = velocityContextHelper.fillContext(messageTemplate, payGwContext); //报文体组装 if (StringUtils.isNotBlank(messageTemplate.getMainTemplate())) { String messageBody = VelocityUtil.evaluate(context, messageTemplate.getMainTemplate()); messageEnvelope.setContent(messageBody); } //报文头组装 String headerTemplate = messageTemplate.getHeaderTemplate(); if (StringUtils.isNotBlank(headerTemplate)) { String headerPrototype = VelocityUtil.evaluate(context, headerTemplate); Map headers = MapUtils.covertText2MapByRule(headerPrototype); messageEnvelope.setExtraContent(headers); } return messageEnvelope; }

组装的请求渠道报文前4位为报文掩码,在发送前判断报文掩码是否需要发送支付渠道,有些交易类型不需要发送支付渠道,只需要给前端业务系统组装成一个JSON或者HTML表单之类的响应。

0000:其中第一位标识是否需要发送支付渠道(0-不需要发送 1-需要发送) 第一位:0-不需要发送;1-需要发送 第二位:0-报文内容需要trim();1-报文内容不需要trim() 第三位:预留 第四位:预留

4.2.2 报文发送 4.2.2.1 协议处理器获取

根据配置的和支付渠道交互的通讯协议类型从Client和Server的管理工厂中获取协议处理器进行处理。

ProtocolTypeEnum protocolTypeEnum = ProtocolTypeEnum.getByCode(communicationEntity.getProtocolType()); ClientManager clientManager = ServiceManagerFactory.getClientManager(protocolTypeEnum);

在这里插入图片描述 在这里插入图片描述 此时paygw属于客户端,所以获取Client端,如使用http协议将获取到对应的HttpClientManager处理类。

4.2.2.2 构建通讯客户端 @Override public void messageSend(PayGwContext context) { MessageDescription messageDescription = context.getMessageDescription(); CommunicationEntity communicationEntity = messageDescription.getCommunicationEntity(); //1、通过支付机构号、交易类型为key进行客户端获取; PayInstitution payInstitution = messageDescription.getServerPayInstitution(); if (StringUtils.isBlank(payInstitution.getId()) || StringUtils.isBlank(messageDescription.getTransCode())) { throw new PayGwException(SystemErrorCode.OPTION_VALIDATE_ERROR, new Object[]{"通讯key->支付机构ID或交易码"}); } //key默认->支付机构+交易码+商户号 StringBuilder key = new StringBuilder(StringUtils.valueOf(payInstitution.getId())); key.append(StringUtils.valueOf(messageDescription.getTransCode())); Map datas = messageDescription.getDatas(); //兼容多个商户号-多个通讯证书,key值后+instAcctNo if (StringUtils.isNotBlank(datas.get(INST_ACCT_NO))) { key.append(StringUtils.valueOf(datas.get(INST_ACCT_NO))); } //2、获取HttpClient,如果获取不到进行初始化,初始化后加入缓存; HttpClient httpClient = null; httpClient = getHttpClient(key.toString(), communicationEntity, ProtocolTypeEnum.getByCode(communicationEntity.getProtocolType()), datas); context.addParam(ParamType.HTTP_CLIENT, httpClient); //3、生成调用参数; //根据GET/POST创建不同的请求(HttpGet/HttpPost) HttpUriRequest request = createHttpUriRequest(communicationEntity); context.addParam(ParamType.HTTP_METHOD, request); //4、获取协议处理客户端服务进行服务处理; httpClientService.hand(context); } 4.2.2.3 发送请求 @Override protected void messageSend(PayGwContext context) { HttpClient httpClient = context.getHttpClient(); HttpUriRequest httpMethod = context.getHttpMethod(); HttpResponse httpResponse = null; try { httpResponse = httpClient.execute(httpMethod); } catch (IOException e) { throw new PayGwException(SystemErrorCode.COMMUNICATION_EXCEPTION, e); } context.addParam(ParamType.HTTP_CLIENT_RESPONSE, httpResponse); } 4.2.2.4 响应报文读取 @Override public void convertInMessage(PayGwContext context) { MessageDescription messageDescription = context.getMessageDescription(); messageDescription.setProcessPhase(ProcessPhaseEnum.SERVER_RESPONSE_RECEIVE); HttpResponse httpResponse = context.getHttpResponse(); MessageEnvelope messageEnvelope = new MessageEnvelope(); Object content = null; try { CommunicationEntity communicationEntity = messageDescription.getCommunicationEntity(); MessageFormatEnum messageFormat = communicationEntity.getReceiveMessageFormat(); messageEnvelope.setMessageFormat(messageFormat); if (messageFormat == MessageFormatEnum.BYTE) { content = EntityUtils.toByteArray(httpResponse.getEntity()); } else if (messageFormat == MessageFormatEnum.FILE) { content = readResponse(httpResponse, communicationEntity); } else { String charset = EncodeEnum.getByCode(communicationEntity.getReceiveTransformEncode()).getMessage(); content = EntityUtils.toString(httpResponse.getEntity(), charset); } messageEnvelope.setContent(content); } catch (IOException e) { throw new PayGwException(SystemErrorCode.COMMUNICATION_EXCEPTION, e); } messageDescription.setServerResponseMessageEnvelope(messageEnvelope); }

根据配置的通讯报文类型、编码进行响应报文的读取,并构建MessageEnvelope 赋值填充到MessageDescription的serverResponseMessageEnvelope,此时的MessageDescription对象: 在这里插入图片描述

5. 报文解析

将不同支付渠道的同一交易类型的响应报文解析为系统所需要的同一格式对象。

/** * 消息解析 * @param context */ @Override public void messageParse(PayGwContext context) { MessageDescription messageDescription = context.getMessageDescription(); //1.获取待解析报文 Object parseMessage = getParseMessage(messageDescription); LoggerUtil.info(logger, "交易-开始报文解析"); //2.获取报文解析器(加载groovy脚本,有可能是默认实现的java类,脚本统一实现MessageParser接口) MessageParser messageParser = findParser(context); //3.进行报文解析 Object obj = messageParser.parse(context, parseMessage); messageDescription.putDatas(BeanUtils.beanToMap(obj)); } 5.1 获取待解析报文 public Object getParseMessage(MessageDescription messageDescription) { Object message = null; ProcessPhaseEnum processPhase = messageDescription.getProcessPhase(); switch (processPhase) { case CLIENT_REQUEST_RECEIVE://客户端请求(支付核心请求或支付渠道异步通知) message = messageDescription.getClientRequestMessageEnvelope().getContent(); break; case SERVER_RESPONSE_RECEIVE://服务端响应 message = messageDescription.getServerResponseMessageEnvelope().getContent(); break; default: LoggerUtil.warn(logger, "processPhase = {} 没有加 switch.", processPhase); throw new PayGwException(SystemErrorCode.SYSTEM_ERROR); } return message; } 5.2 获取报文解析器 /** * 获取报文解析器 * @param context * @return */ private MessageParser findParser(PayGwContext context) { String parserName = getParserName(context); MessageParser messageParser = groovyScriptCache.getMessageParser(parserName); if (messageParser == null) { LoggerUtil.error(logger, "未找到交易的报文解析器({})", parserName); throw new PayGwException(SystemErrorCode.SYSTEM_ERROR); } return messageParser; } 5.3 进行报文解析 Object obj = messageParser.parse(context, parseMessage); messageDescription.putDatas(BeanUtils.beanToMap(obj));

如下在解析报文中将单笔交易解析为如下对象,以统一后面的业务处理。

/** * @author Kkk * @Describe: 单笔报文解析结果 */ public class MessageParserResult { /** * 支付网关原处理状态 */ private String orgProcessStatus; /** * 支付网关处理状态 */ private String processStatus; /** * 支付网关交易状态 */ private String transStatus; /** * 支付网关响应码 */ private String respCode; /** * 支付网关响应信息 */ private String respMsg; /** * 支付渠道请求流水号 */ private String instReqNo; /** * 支付渠道返回流水号 */ private String instRespNo; /** * 支付渠道交易状态 */ private String instTransStatus; /** * 支付渠道交易金额 */ private BigDecimal instTransAmount; /** * 支付渠道响应码 */ private String instRespCode; /** * 支付渠道响应码 */ private String instRespMsg; /** * 支付渠道交易日期 */ private Date instTransDate; /** * 支付渠道交易完成时间 */ private Date instTransTime; /** * 扩展1 */ private String extend1; /** * 扩展2 */ private String extend2; //... ... } 6. 响应码解析

使用响应信息解析器将不同支付渠道响应的响应码转换为平台统一的响应码。

respInfoResolver.resolve(messageDescription.getInstCode(), messageDescription.getTransId(), messageDescription.getDatas());

在这里插入图片描述 到此就完成了和服务端支付渠道的第一次通讯过程,由于有的交易类型涉及到多次通讯,所以在for循环通讯中进行上一次通讯判断,前置通讯交易状态不是成功-结束此交易的所有通讯。

总结

本篇主要介绍了paygw和支付渠道的通讯,在通讯之前先判断是否进行流量控制,如果配置了限流则不再和支付渠道进行通讯,等待后续处理入队走异步处理。如果未配置流量控制,则根据通讯配置构建通讯客户端,进行通讯,并将渠道响应报文进行解析,转化为系统统一格式对象,然后对渠道响应码进行转换。后续处理就是交易表数据更新,构建客户端渠道响应报文,具体实现见后文。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3