支付网关设计-1
技術棧:java+groovy+velocity+支付系統相關概念
文章目錄
- 前言
- 一、相關概念
- 1. 客戶端、服務端區分
- 2. 基本關聯信息
- 二、流程實現分析
- 1. 一次請求我們都做了什么?
- 2. 實現方式
- 2.1 初級方式
- 2.2 中級方式
- 2.3 高級方式
- 三、詳細流程設計
- 3.1 核心表設計
- 四、代碼實現
- 4.1 接口定義
- 4.2 報文讀取
- 4.3 初始化解析腳本
- 4.4 腳本解析器
- 4.5 定義響應報文返回模板
- 五、總結
前言
背景:支付系統通過對接大量的銀行、第三方支付公司、銀聯/網聯等為業務方提供支付能力,如支付系統為了提供代扣能力,對接了微信、支付寶、中金等,為什么要對接那么多支付渠道呢,原因有很多,如當微信通道不可用時候走支付寶,當微信不支持從用戶某銀行卡扣錢時候可以走支付寶,同一筆交易走中金手續費更低,那么就走中金,最終走哪個通道是由支付路由系統進行決策。
這篇文章我們講解渠道對接的支付網關系統設計,支付網關系統作為與支付渠道交互的系統,也有公司叫前置系統,名字無所謂了,此系統主要負責接收內部系統請求,并將請求報文進行轉換,轉換為支付渠道要求格式的報文,發起調用請求支付渠道,將支付渠道響應的報文解析,業務處理,然后轉為為內部系統所需要的格式的報文返回,每個公司技術架構模式不同,自然職責也不盡相同,但是一定是會有報文解析與組裝。
有人可能會郁悶,支付網關有什么好設計的,Controller+Service+Dao三層架構模式開干,應屆生都可以完成,特別符合所謂的敏捷開發,無需設計,對接支付寶代扣寫一套流程,微信代扣寫一套,中金支付寫一套,多人并行開發互不影響,開發、測試、部署上線,一氣呵成,支付系統多則對接幾十家支付渠道,那么同一交易類型流程寫幾十遍,樂此不疲,代碼量體現了程序猿的價值,我相信這是絕大多數支付公司系統的開發現狀。
這篇文章呢,我們打破傳統的三層開發模式,加點設計會是什么樣的?此篇文章我們就為網關系統加點設計試一試,看一看是不是可以提高逼格,真正體現程序猿的價值。
我們首先明確下系統設計目標:
不管我們對接多少渠道,負責和支付渠道對接的網關系統不用進行代碼迭代,程序猿不用熬夜到凌晨部署上線,我們只需要寫個腳本、報文模板并在后臺管理系統進行通訊配置,通道對接即完成了,按照原方式對接一個通道要一個月的話,此模式三天也就足夠了,并且可以很大程度降低代碼迭代變更風險范圍,出問題也是僅僅此腳本出問題,并且修改后重新配置下即可,不用走什么上線流程。
如上圖所示,paycore、paygw是支付系統兩個子系統。paygw作為和支付渠道交互的系統,負責接收paycore系統請求、根據支付渠道報文格式要求組裝請求報文、根據通信協議發起調用、根據渠道響應報文格式解析返回報文,其次還有具體業務處理,比如根據支付渠道返回報文將交易表交易狀態置為成功/失敗等具體業務流程。
我們首先從簡單的開始吧,黃色框內邏輯(交易結果回調)為此篇文章內容,下面我們開始為此系統加點設計!
一、相關概念
1. 客戶端、服務端區分
我們首先拿這張來介紹一個很重要的概念,客戶端、服務端,從上圖中可以看出,在一個流程的不同階段同一個應用屬于不同角色,在下單過程中網關系統調用銀行系統,網關系統屬于客戶端,銀行屬于服務端;在交易結果通知過程中,銀行系統屬于客戶端,網關系統屬于服務端。
在以下系統設計過程中不管在什么過程中,我們始終站在支付網關的角度看待支付網關的上游系統(paycore)和下游系統(支付渠道)。將調用網關系統的應用看做客戶端,不管是內部系統(paycore)還是支付渠道(支付寶、微信等),此時網關屬于服務端。當網關系統調用其他系統時,不管是內部系統還是外部支付渠道,網關都屬于客戶端,被調用方屬于服務端,此概念要清楚,不然下面就暈了,不知道這個通訊應該配置客戶端還是服務端了。
2. 基本關聯信息
對于沒有做過支付系統的開發同學看到上面的圖可能有點懵逼,不過你也不要喪失信心,對于多數在做支付系統的人看到也懵逼,上面已經是簡化很多的設計了,先大概看下吧,下面設計流程也會涉及到上圖各概念。
交易類型下關聯主要內容:
- 交易機構:建設銀行、招商銀行等。
- 通訊:定義了和通道交互的協議、編碼、報文格式(通過報文模板定義格式)等相關信息,并關聯著通訊證書,如使用https協議,需要證書。同時還關聯著腳本,主要是輔助補充此次請求的報文,以及對請求報文的解析。
- 響應碼轉換:不同支付渠道對相同的處理結果響應碼也不盡相同,所以需要進行轉換為支付平臺統一響應碼。
二、流程實現分析
1. 一次請求我們都做了什么?
在設計之前我們先思考下這個問題:一次回調請求我們都做了什么?
一筆交易發送到支付渠道后,支付渠道返回已經受理,當支付渠道處理完成后,調用支付系統接口將交易結果“告訴”支付系統,這筆是成功了還是失敗了,然后支付系統再按照約定回復支付渠道說我收到你話了。
所以首先你需要編寫一個結果通知接口,接口參數定義是支付渠道定義的,并且接口所使用的協議(http/https等)、報文編碼(UTF-8/GBK等)、格式(JSON/XML等)都是由支付渠道定義,回調接口的接口文檔支付渠道已經定義好了,你只需要看著渠道提供的接口文檔編寫接口就行了。
當支付渠道回調支付系統提供的回調接口,接口根據報文格式以及報文編碼提取出參數,然后進行安全校驗如驗簽,校驗通過后進行業務處理,如根據訂單號更新交易表交易狀態。
支付系統完成相應業務流程處理后,再根據接口文檔響應報文格式組裝響應報文進行響應。
總結,經過上面的分析,處理流程大概分為如下三部分:
- 請求報文讀取并解析構建業務對象
- 業務處理
- 響應報文組裝
在以上處理流程的三個部分中,對于同一交易類型來說,第二部分處理邏輯是相同的,變的是前后兩部分。
2. 實現方式
下面簡要分析下實現上面三步處理流程常見方式。
2.1 初級方式
每個支付渠道每個交易類型結果回調都提供一個接口,這個是初級程序猿做法吧,在此不再說這個了。
2.2 中級方式
提供統一Controller接口,在Service層使用點設計模式,如策略模式、模板方法模式,這兩個設計模式,日常開發比較多,切記不要If…else…來一個加一個if,讓人崩潰的寫法,曾在一個公司項目中見過800多行的一個方法,只能說服了。這種方式雖然使用了設計模式,代碼看著也優雅點,每次新增只需要加個策略實現就行了,但是都要涉及到代碼變更,并且通道對接一般也不是一個開發人員在寫,代碼風格各異,并且在日常工作中,程序猿互相嫌棄。
第一步驟:定義處理通知接口
public interface NotifyService {/*** @Description 轉換進來消息*/void convertInMessage(PayContext context);/*** @Description 轉換出去消息*/void convertOutMessage(PayContext context); }第二步驟:定義處理通知接口抽象實現類
@Service("notifyServiceAbstract") public abstract class NotifyServiceAbstract implements NotifyService {@Autowiredprivate TemplateManager templateManager;/*** @Description 業務處理*/public void hand(PayContext context) {//1、消息讀取,讀取成指定業務對象;convertInMessage(context);//2、具體業務處理;process(context);//3、構建返回報文;convertOutMessage(context);}/*** @Description 具體業務處理*/protected void process(PayContext context) {templateManager.getTemplate(context.getClientTransCode()).execute(context);} }第三步驟:支付渠道代扣交易結果通知實現類
/*** @author kkk* @description 支付寶代扣交易結果通知實現*/ @Service("aliDeductNotifyService") public class AliDeductNotifyService extends NotifyServiceAbstract {@Overridepublic void convertInMessage(PayContext context) {//todo 數據解析}@Overridepublic void convertOutMessage(PayContext context) {//todo 返回報文組裝} }/*** @author kkk* @description 微信代扣交易結果通知實現*/ @Service("wxDeductNotifyService") public class WxDeductNotifyService extends NotifyServiceAbstract {@Overridepublic void convertInMessage(PayContext context) {//todo 數據解析}@Overridepublic void convertOutMessage(PayContext context) {//todo 返回報文組裝} }2.3 高級方式
高級點的方式也就是我們本篇要設計實現的方式,當然也有更高級的實現方式,我們實現方式也很簡單,可以理解為模板方法的升級吧,使用模板方法將不同邏輯處理部分定義為抽象方法,不同子類不同實現,如上節代碼實現案例,所以我們新增業務時候要不停的增加子實現類,那么我們本篇設計不增加實現類,我們將需要不同子類要實現的方法抽取出寫腳本,不同業務加載不同腳本來執行具體業務邏輯。
第一步驟:定義報文解析器–請求報文解析
/*** @author kkk* @description 報文解析引擎接口*/ public interface MessageParseEngine {/*** @Description 消息解析*/void messageParse(PayContext context); } /*** @author kkk* @description 報文解析引擎接口實現*/ @Component public class MessageParseEngineImpl implements MessageParseEngine {private static final Logger logger = LoggerFactory.getLogger(MessageParseEngineImpl.class);@Autowiredprivate GroovyScriptCache groovyScriptCache;@Overridepublic void messageParse(PayContext context) {MessageDescription messageDescription = context.getMessageDescription();//1.獲取待解析報文Object parseMessage = getParseMessage(messageDescription);//2.獲取報文解析器MessageParser messageParser = findParser(context);//3.進行報文解析Object obj = messageParser.parse(context, parseMessage);messageDescription.putDatas(BeanUtils.beanToMap(obj));}/*** @Description 獲取待解析報文*/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:logger.warn("processPhase = {} 沒有加 switch.", processPhase);throw new PayException(SystemErrorCode.SYSTEM_ERROR);}return message;}/*** @Description 獲取報文解析器*/private MessageParser findParser(PayContext context) {String parserName = getParserName(context);MessageParser messageParser = groovyScriptCache.getMessageParser(parserName);if (messageParser == null) {logger.error("未找到交易的報文解析器({})", parserName);throw new PayException(SystemErrorCode.SYSTEM_ERROR);}return messageParser;}/*** @Description 獲取報文解析器名稱*/private String getParserName(PayContext context) {MessageDescription messageDescription = context.getMessageDescription();return messageDescription.getCommunicationEntity().getMessageParserId();} }第二步驟:定義報文組裝引擎–響應報文組裝
/*** @author kkk* @description 報文組裝引擎接口*/ public interface MessageAssembleEngine {/*** @Description 報文組裝*/MessageEnvelope messageAssemble(MessageTemplate messageTemplate, PayContext payContext); } /*** @author kkk* @description 報文組裝引擎接口實現*/ @Component public class MessageAssembleEngineImpl implements MessageAssembleEngine {@Autowiredprivate VelocityContextHelper velocityContextHelper;@Overridepublic MessageEnvelope messageAssemble(MessageTemplate messageTemplate, PayContext payContext) {MessageEnvelope messageEnvelope = new MessageEnvelope();//初始化Velocity和TemplateVelocityContext 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<String, String> headers = MapUtils.covertText2MapByRule(headerPrototype);messageEnvelope.setExtraContent(headers);}return messageEnvelope;} }如上兩步通過報文解析引擎和報文組裝引擎來代替原來子實現類完成相同功能處理,只需要將引擎所需要的解析腳本和模板根據實際業務加載就行了,以此來做到系統不停機動態對接支付渠道。
三、詳細流程設計
通過上一節我們已經知道了我們的核心處理思路,接下來的工作就是將腳本和模板串聯到整個流程的問題了。下面我們開始設計這個串聯執行流程。
3.1 核心表設計
首先為了故事能夠繼續展開要簡要介紹下需要通過后臺管理系統要配置的一些東西了,需要簡單了解下如下幾張表關聯關系。
看著表還是挺多的,和我們這次分析相關的主要包含如下幾個部分,支付渠道基本信息表、支付渠道交易類型表、通訊配置表、腳本資源表、證書表等。
接下來簡要分析下涉及到的表及核心字段:
inst_base_info:
| id | 主鍵 | inst_type | 支付渠道類型 |
| inst_code | 支付渠道編碼 | inst_logo | 支付渠道LOGO |
| inst_name | 支付渠道名稱 | … | … |
inst_trans_type:
| id | 主鍵 | trans_code | 交易碼 |
| inst_id | 支付渠道id | trans_type_name | 交易類型名稱 |
| trans_id | 交易id | dc_flag | 借貸標識 |
| mop | 匯路 | syn_flag | 同/異步標識 |
| ep_flag | 對公/對私標識 | per_trans_limit | 單筆限額 |
| disable_date | 禁用日期 | properties | 擴展屬性 |
| … | … | … | … |
communication:
| id | 主鍵 | inst_trans_type_id | 支付渠道交易類型id |
| commu_code | 通訊編碼 | commu_name | 通訊名稱 |
| req_url | 通訊url | protocol_type | 通訊協議01-http 02-https 03-ftp 04-sftp 05-webservice 06-hessian 07-tcp 08-mq |
| asy_notify_protocol | 異步通知協議01-http 08-mq | http_method | http請求方式 00-all 01-get 02-post |
| connect_timeout | 連接超時時間(ms) | read_timeout | 讀超時時間(ms) |
| max_occurs | 最大并發數 | max_no_length | 流水號位數 |
| send_transform_type | 發送報文格式 byte text form json xml | 響應報文格式 receive_transform_type | byte text form json xml |
| send_transform_encode | 上送報文編碼 01-utf-8 02-gbk 03-gb2312 04-iso8859-1 | receive_transform_encode | 接收報文編碼 01-utf-8 02-gbk 03-gb2312 04-iso8859-1 |
| https_cert_code | https客戶端證書編碼 | https_trust_cert_code | https可信任的服務端證書編碼 |
| commu_order | 通訊順序 | cs_flag | 01-客戶端 02-服務端 |
| properties | 擴展屬性 | … | … |
resource:
| id | 主鍵 | resource_code | 資源編碼 |
| resource_name | 資源名稱 | commu_id | 通訊id |
| resource_type | 資源類型 01-主模板 02-子模板 03-擴展模板 04-頭模板 05-解析腳本 06-支付渠道請求流水號生成腳本 | content | 內容 |
| … | … | … | … |
certificate:
| id | 主鍵 | cert_code | 秘鑰編碼 |
| cert_name | 秘鑰名稱 | algorithm | 加密算法 |
| cert_store_type | 秘鑰存儲方式 01-txt 02-pfx 03-cer/crt 04-jks | cert_pwd | 證書庫密碼 |
| private_key_pwd | 私鑰密碼 | cert_alias | 證書別名 |
| cert_desc | 秘鑰描述 | valid_begin | 秘鑰有效期-開始日期 |
| valid_end | 秘鑰有效期-截止日期 | file_id | 證書文件id |
| … | … | … | … |
先了解以上表吧,多了也不知道我所云。
從以上表及關聯關系中可以看到,支付渠道–>交易類型—>通訊—> 資源腳本 這四者的關聯,我們只要能獲取到支付渠道+交易類型 就能獲取到通訊配置了,不過注意,在 paygw為客戶端時候,交易類型:通訊=1:n,在paygw為服務端時候交易類型:通訊=1:1,此篇我們分析的是交易結果回調,此時paygw為服務端所以是1:n,即我們確定好支付渠道+交易類型就能唯一確定通訊了。
這里我們解釋下,為什么paygw為客戶端時候交易類型:通訊是1:n,舉一個典型的例子吧,如下是對接平安銀行,進行對賬文件獲取需要處理過程。
第一次通信:每天9:00調用[KHKF05]接口查詢對賬文件生成狀態、文件名、密碼;
第二次通信:拿著[KHKF05]返回的文件名與密碼,調用[FILE03]請求下載對賬文件;
第三次通信:調用[FILE02]查詢對賬文件下載狀態,若下載完成,處理接下來流程。
從上可以看出,一種交易流程可能會涉及到多次和支付渠道的通信,并且每次使用的協議也可能不同,所以當paygw為客戶端時候,交易類型和通信配置是:1:n 關系。
回歸到正題,我們已經知道從支付渠道+交易類型就可以獲取到通信配置了,那么就簡單了,只要我們定義結果通知地址按照這個規則來就可以了:https://xxx.xxx.xxx/{instCode}/{transCode},如提供興業銀行代發交易結果通知地址如下:https://xxx.xxx.xxx/payCib/cibDeputeNotify,興業銀行調用支付系統提供的接口,接口收到請求后即也就可以根據路徑參數payCib/cibDeputeNotify獲取到通訊配置了,然后根據通訊關聯的解析腳本以及通訊配置的報文格式、編碼等信息進行報文解析了在解析腳本中將報文解析為業務對象進行下面統一的業務處理了。到這里其實我們整個系統設計就已完成了,剩下的也就是代碼實現過程了。
四、代碼實現
下面貼下核心處理代碼一些思路吧。
4.1 接口定義
@RequestMapping(value = "/{instCode}/{transCode}")public void processHandle(HttpServletRequest request, HttpServletResponse response, @PathVariable("instCode") String instCode, @PathVariable("transCode") String transCode) {logger.info(logger, "外部交易請求-客戶端渠道編碼({})-客戶端交易碼({})", instCode, transCode);//1、支付渠道編碼校驗verifyChannel(instCode);//2、構建contextPayContext context = new PayContext();context.addParam(PayContext.ParamType.HTTP_SERVER_REQUEST, request);context.addParam(PayContext.ParamType.HTTP_SERVER_RESPONSE, response);context.setClientInstCode(instCode);context.setClientTransCode(transCode);context.addParam(PayContext.ParamType.PROTOCOL, ProtocolTypeEnum.HTTP);//3、執行交易payBizService.bizProcess(context);//4、渲染視圖render(transCode, response, context.getMessageDescription().getClientResponseMessageEnvelope());}4.2 報文讀取
public void convertInMessage(PayContext context) {LoggerUtil.info(logger, "交易({})-讀取報文-開始", context.getClientTransCode());MessageDescription messageDescription = context.getMessageDescription();//1、獲取支付機構配置的接收消息的類型(map,text)CommunicationEntity communicationEntity = messageDescription.getCommunicationEntity();MessageFormatEnum messageFormatEnum = communicationEntity.getReceiveMessageFormat();//報文格式EncodeEnum encodeEnum = communicationEntity.getReceiveMessageEncode();//報文編碼//2、根據不同的消息類型進行不同的讀取Object message = messageRead(context.getHttpServletRequest(), messageFormatEnum, encodeEnum.getMessage());logger.info("交易({})-讀取報文-報文內容:{}", context.getClientTransCode(), message);MessageEnvelope clientRequestMessageEnvelope = new MessageEnvelope();clientRequestMessageEnvelope.setMessageFormat(messageFormatEnum);clientRequestMessageEnvelope.setEncode(encodeEnum);clientRequestMessageEnvelope.setContent(message);messageDescription.setClientRequestMessageEnvelope(clientRequestMessageEnvelope);LoggerUtil.info(logger, "交易({})-讀取報文-結束", context.getClientTransCode());}4.3 初始化解析腳本
private void initMessageParser() {logger.info("-----GroovyScriptCache開始加載報文解析腳本-----");long startTime = System.currentTimeMillis();Map<String, MessageParser> tempParserMap = new HashMap<>();GroovyClassLoader groovyClassLoader = new GroovyClassLoader();List<String> resourceTypeList = new ArrayList<>();resourceTypeList.add(ResourceTypeEnum.PARSE_SCRIPT.getCode());List<Resource> groovyScripts = repository.findByResourceTypeInAndStatus(resourceTypeList, StatusEnum.ENABLE.getCode());for (Resource resource : groovyScripts) {try {String content = resource.getContent();Class<MessageParser> groovyClass = groovyClassLoader.parseClass(content);AutowireCapableBeanFactory autowireCapableBeanFactory = ApplicationContextUtil.getApplicationContext().getAutowireCapableBeanFactory();MessageParser parser = groovyClass.newInstance();autowireCapableBeanFactory.autowireBean(parser);tempParserMap.put(resource.getResourceCode(), parser);logger.info("報文解析腳本加載:" + parser.getClass().getName());} catch (Exception ex) {logger.warn("報文解析腳本加載異常{}:", resource.getResourceCode());throw new PayGwException(SystemErrorCode.SYSTEM_ERROR,ex);}}parserMap = tempParserMap;logger.info("-----GroovyScriptCache結束加載報文解析腳本 size={},cost={}", parserMap.size(), System.currentTimeMillis() - startTime);}4.4 腳本解析器
/*** @author kkk* @description 消息解析器(供groovy腳本來實現)*/ public interface MessageParser {/*** @Description 解析*/Object parse(PayContext context, Object message);}以興業銀行代發交易結果通知為例,編寫解析腳本:
/*** @author kkk* @description 興業銀行代發交易結果通知解析腳本*/ class CIBDeputeNotifyAsyParser implements MessageParser {def logger = LoggerFactory.getLogger(CIBDeputeNotificationAsyParser.class)def resp_code_fail = ["E0100","E0101"]def resp_code_success = ["E0000"]/** 證書服務 */@AutowiredCertService certService@OverrideObject parse(PayContext context, Object message) {def messageParserResult = new MessageParserResult()messageParserResult.setOrgProcessStatus(ProcessStatusEnum.PROCESSING.getCode())//處理中try {Object result = JSON.parse(message)JSONObject jobj = (JSONObject) resultlogger.info("[興業銀行-單筆代付-異步通知] 同步返回: ({})", message)//驗證簽名def flag = verifySign(context, message)if (!flag) {throw new Exception("[興業銀行-單筆代付-異步通知] 返回參數,驗簽失敗!")}def respCode= jobj.get("respCode")def respMsg= jobj.get("respMsg")messageParserResult.setInstRespCode(respCode)messageParserResult.setInstRespMsg(respMsg)if(resp_code_success.contains(respCode)){messageParserResult.setTransStatus(TransStatusEnum.SUCCESS.getCode())messageParserResult.setProcessStatus(ProcessStatusEnum.FINISH.getCode())}else if(resp_code_fail.contains(respCode)){messageParserResult.setTransStatus(TransStatusEnum.FAIL.getCode())messageParserResult.setProcessStatus(ProcessStatusEnum.FINISH.getCode())}else {messageParserResult.setTransStatus(TransStatusEnum.PROCESS.getCode())messageParserResult.setProcessStatus(ProcessStatusEnum.PROCESSING.getCode())}return messageParserResult} catch (Exception e) {LoggerUtil.error(logger, "[興業銀行-單筆代付-異步通知] 報文解析異常異常", e)messageParserResult.setTransStatus(TransStatusEnum.PROCESS.getCode())messageParserResult.setProcessStatus(ProcessStatusEnum.PROCESSING.getCode())messageParserResult.setOrgProcessStatus(ProcessStatusEnum.PROCESSING.getCode())messageParserResult.setInstTransDate(DateUtil.getCurrentDate())return messageParserResult}}/*** 驗簽*/boolean verifySign(PayContext context, String resData) {def certCodePublic = context.getMessageDescription().getData("merExtends").get("certCodePublic")Map<String,String> resMap=MapUtils.covertToJSON(resData)String mac=resMap.get("mac")resMap.remove("mac")String oriSign=MapUtils.generateParamStr(resMap);boolean vflag= certService.checkSign(certCodePublic,mac,oriSign)logger.info("[興業銀行-單筆代付-異步通知],請求簽名值({}),驗簽結果({})",mac,vflag)return vflag} }4.5 定義響應報文返回模板
頭模板:cib_depute_notify_asy_header.vm
#set($map = {"Content-Type":"application/json;charset=UTF-8" }) $map主模板:cib_depute_notify_asy_main.vm
#set($umask = "1000") #set($version = "1.0.2") #set($mchtId=$data.merExtends.merId) #set($signType="RSA") #set($serialNo=$data.instReqNo) #set($businessMap = {"version":"$!version","mchtId":"$!mchtId","signType":"$!signType","serialNo":"$!serialNo","isSucc":"true" }) #set($certCodePrivate=$data.merExtends.certCodePrivate) #set($businessStr=$MapUtils.generateParamStr($businessMap)) #set($mac=$certService.sign($certCodePrivate,$businessStr)) #set($signMap = {"mac":"$!mac" }) $umask$JSON.toJSONString($MapUtils.putAll($businessMap,$signMap))完成整個系統代碼實現后,支付系統對接支付渠道只需要寫一個解析腳本和一個響應模板,編寫完成后扔給產品人員后臺配置下,就完成上線了啦。
五、總結
下篇寫交易處理流程吧,交易處理流程比較復雜點的是要配置兩個通訊,一個是內部通訊配置,一個是外部通訊配置。
也就是要配置paycore—(第一次通訊配置)–>paygw----(第二次通訊配置)–>支付渠道,
總之系統的設計目標是對接支付渠道不用進行代碼迭代變更,程序猿之間少一點互相嫌棄。
總結
- 上一篇: 数据分析技能点-MySQL表记录的检索
- 下一篇: 解决ios微信浏览器时间不兼容的问题