深入了解RBKmoney付款-付款平台的逻辑


哈Ha! 我将继续发布有关RBK.money付款平台内部的周期,该周期始于本文。 今天,我们将讨论逻辑处理方案,特定的微服务及其相互之间的关系,处理每个业务逻辑的服务如何在逻辑上分离,为什么处理核心不了解有关您的支付卡号的任何信息以及支付如何在平台内运行。 另外,我将详细介绍如何提供高可用性和扩展以处理高负载的主题。


概述逻辑和通用方法


通常,处理中负责支付的那部分的基本元素的方案如下所示。



从逻辑上讲,我们将责任范围划分为3个领域:


  • 外部区域,互联网上的实体,例如我们的付款表格的JS应用程序(您在此处输入卡的详细信息),我们的商人客户的后端以及我们的合作伙伴银行和其他付款方式的提供商的处理网关;
  • 内部有一个高度可访问的区域,微服务生活在该区域中,微服务直接提供支付网关的工作并管理借记资金,并在我们的系统和其他在线服务中加以考虑,其特点是“尽管DC内有任何故障,也应始终可用”;
    • 有一个单独的服务区域可直接处理持卡人的全部数据;这些服务具有铁道部提出的单独要求,并受PCI-DSS标准强制性认证的约束。 我们将在下面更详细地解释为什么如此分离;
  • 内区,在传统意义上,对所提供服务的可用性或响应时间的要求较低,这是一个后台办公室。 当然,尽管在这里我们也尝试确保“始终可用”的原则,但是我们为此付出了更少的努力。

在每个区域内,都有微服务执行其处理业务逻辑的部分。 它们在输入端接收RPC调用,在输出端生成使用嵌入式算法处理的数据,这些算法也作为链中其他微服务的调用执行。


为了确保可伸缩性,我们尝试将状态存储在尽可能少的位置。 图中的无状态服务没有与持久性存储的连接,持久性存储分别是有状态的。 通常,我们为持久状态存储使用几种有限的服务-对于处理的主要部分,这些是Riak KV集群,对于相关服务-PostgreSQL,对于异步队列处理,我们使用Kafka。


为了确保高可用性,我们在多个实例中部署服务,通常是3到5。


扩展无状态服务非常容易,我们只需增加在不同虚拟机上所需的实例数,即可在Consul中注册它们,可通过控制台DNS进行解析,并开始接收来自其他服务的呼叫,处理接收到的数据并进一步发送它们。


状态服务,或者说是我们的主要服务,在图中显示为Machinegun,实现了高度可访问的接口(分布式体系结构基于Erlang Distribution),并且通过Consul KV进行的同步用于确保排队和分布式锁定。 这是简短的详细说明,将在单独的帖子中。


Riak开箱即用,可提供高度可访问的持久性无主存储,我们不做任何准备,几乎是默认配置。 使用当前的负载配置文件,集群中有5个节点部署在单独的主机上。 重要说明-我们实际上不使用索引和大数据样本,而是使用特定的键。


在实施KV方案过于昂贵的地方,我们使用带复制甚至单模式解决方案的PostgeSQL数据库,因为一旦发生故障,我们总是可以通过Machinegun从在线部件上上传必要的事件。


图中微服务的颜色分离表示它们被编写的语言-浅绿色-这些是Java应用程序,浅蓝色-Erlang。


所有服务都在Docker容器中运行,这些容器是CI构建工件,位于本地Docker Registry中。 在SaltStack生产环境上部署服务,其配置位于私有的Github存储库中。


开发人员独立地请求对此存储库进行更改,并在其中描述服务的要求-指示所需的版本和参数,例如分配给容器的内存大小,传输到环境变量等。 此外,在授权员工手动确认更改请求(我们拥有DevOps,支持和信息安全)之后,CD会自动将具有新版本的容器实例汇总到产品环境的主机。



此外,每个服务都以Elasticsearch可以理解的格式写入日志。 日志文件由Filebeat拾取,然后将其写入Elasticsearch集群。 因此,尽管事实上开发人员无法访问产品环境,但他们始终有机会进行调试并查看其服务会发生什么。


与外界的互动



平台状态的任何变化都只能通过调用公共API的相应方法来实现。 我们不使用经典的Web应用程序和服务器端内容生成,实际上,您在UI中看到的只是对我们公共API的JS视图。 原则上,平台上的任何操作都可以通过控制台使用的一系列curl调用来执行,我们可以使用它们。 特别是要编写集成测试(我们已将它们作为库编写在JS中),在集成过程中,在CI中,每次组装时都要检查所有公共方法。


此外,这种方法还解决了与我们平台进行外部集成的所有问题,使您可以以输入付款数据的精美形式为最终用户获得单一协议,并且可以使用主机间专有的服务器间交互方式直接与第三方处理集成的主机到主机。


除了完整的集成测试外,我们还使用分段更新的方法,在分布式体系结构中这样做非常容易,例如,一次只从每个组中推出一项服务,然后暂停并分析日志和图表。


这样一来,我们几乎可以全天候进行部署,包括星期五晚上,无需担心就推出无法使用的东西,或者迅速回退,进行一次简单的带有更改的还原提交,直到没有人注意到为止。


平台注册和公共API



在调用public方法之前,我们需要对客户端进行授权和认证。 为了使客户端出现在平台上,您需要一项服务,该服务将处理与最终用户的所有交互,并提供用于注册,输入和重置密码,安全控制和其他绑定的界面。


在这里,我们不是发明自行车,而是简单地集成了Redhat- Keycloak的开源解决方案。 开始与我们的任何互动之前,您需要在平台上注册,实际上,这是通过Keycloak进行的。


在服务中成功认证后,客户端将收到JWT。 我们稍后将使用它进行授权-在Keycloak端,您可以指定任意字段来描述角色,这些角色将作为简单的json结构嵌入在JWT中并用服务的私钥进行签名。


JWT的功能之一是此结构由服务器的私钥签名;因此,要授权角色列表及其其他对象,我们不需要访问授权服务,该过程是完全分离的。 启动时,CAPI服务会读取Keycloak公共密钥,并使用它来授权对公共API方法的调用。


当我们提出密钥撤销方案时,这个故事是独立的,值得自己发表。


因此,我们已经收到了JWT,可以将其用于身份验证。 在图中,由CAPI和CAPI-DSS表示的一组微服务Common API起作用,它们实现以下功能:


  • 授权收到的消息。 每个公共API调用之前都有一个Authorizaion:Bearer {JWT} HTTP标头。 Common API组的服务使用它来使用授权服务的现有公钥来验证签名的数据;
  • 验证收到的数据。 因为该架构被描述为OpenAPI规范(也称为Swagger),所以数据验证非常容易,并且几乎没有机会在数据流中接收控制命令。 这对整个服务的安全性具有积极影响;
  • 将数据格式从公共REST JSON转换为内部二进制Thrift;
  • 使用诸如唯一的trace_id之类的数据来构建传输绑定,并将事件进一步传递到平台内部,该服务管理业务逻辑并知道例如什么是付款。

我们有很多这样的服务,它们非常简单,并且不会存储任何状态,因此对于线性性能扩展,我们仅以可用容量随意部署它们即可。


PCI-DSS和开放卡数据



从图中可以看到,我们有两个这样的服务组-主要的一个服务组Common API,负责处理所有没有打开的持卡人数据的数据流;第二个服务组PCI-DSS Common API直接与这些卡配合使用。 在内部,它们是完全相同的,但我们将它们物理分开,并将它们布置在不同的铁片上。


这样做是为了最大程度地减少用于存储和处理卡数据的位置数量,以减少泄漏该数据和PCI-DSS认证区域的风险。 相信我,这是一个相当耗时且成本很高的过程-作为支付公司,我们每年都需要接受付费认证以符合MPS标准,并且涉及的服务器和服务越少,完成此过程就越快,越容易。 好吧,在安全方面,这是最积极的体现。


计费和令牌化



因此,我们要开始付款并从付款人的卡中注销款项。


想象一下,对此的请求是以对我们的公共API的方法的调用链的形式出现的,它是由您作为付款人在您进入在线商店后发起的,收集了一篮子商品,单击“购买”,并将您的卡详细信息输入到我们的付款中表格,然后点击“付款”按钮。


我们提供了各种用于注销资金的业务流程,但最有趣的是使用应付帐款的流程。 在我们的平台上,您可以创建付款发票,也可以创建将成为付款容器的发票。


在一张发票中,您可以尝试一次付款,即创建付款,直到下一次付款成功为止。 例如,您可以尝试使用其他卡,钱包和任何其他付款方式支付发票。 如果其中一张卡上没有钱,您可以尝试另外一张,依此类推。


这对转换和用户体验有积极影响。


发票状态机



在平台内部,此链转化为沿着以下路径的交互:


  • 在将内容提供给您的浏览器之前,我们的客户-商家已与我们的平台集成在一起,在我们那里注册并获得了JWT的授权;
  • 商家从他的后端调用了createInvoice()方法,也就是说,他在我们的平台上创建了用于付款的发票。 实际上,商家后端向我们的端点发送了以下内容的HTTP POST请求:

curl -X POST \ https://api.rbk.money/v2/processing/invoices \ -H 'Authorization: Bearer {JWT}' \ -H 'Content-Type: application/json; charset=utf-8' \ -H 'X-Request-ID: 1554417367' \ -H 'cache-control: no-cache' \ -d '{ "shopID": "TEST", "dueDate": "2019-03-28T17:41:32.569Z", "amount": 6000, "currency": "RUB", "product": "Order num 12345", "description": "Delicious meals", "cart": [ { "price": 5000, "product": "Sandwich", "quantity": 1, "taxMode": { "rate": "10%", "type": "InvoiceLineTaxVAT" } }, { "price": 1000, "product": "Cola", "quantity": 1, "taxMode": { "rate": "18%", "type": "InvoiceLineTaxVAT" } } ], "metadata": { "order_id": "Internal order num 13123298761" } }' 

该请求在Common API组的erlang应用程序之一中得到平衡,该应用程序检查了其有效性,转到了Bender服务,该服务接收了幂等密钥,将其转移到tift并向Hellgate服务组发送了请求。 例如,Hellgate实例执行了业务检查,以确保该JWT的所有者原则上未受到阻止,可以创建发票并通常与平台进行交互并开始创建发票。


可以说,地狱之门是我们处理的核心,因为它与业务实体一起运作,知道如何发起付款,需要踢谁才能使这笔付款变成真实的钱款,如何计算该付款的路线,应该告诉谁注销反映在资产负债表中,计算佣金和其他约束。


通常,它也不存储任何状态,并且易于扩展。 但是我们不希望丢失发票,也不会因网络分裂或Hellgate因任何原因而失败而从卡中收取双倍费用。 有必要永久保存此数据。


这是第三个微服务,即Machinegun。 Hellgate向Machinegun发送了一个调用,以使用查询参数形式的有效载荷“创建自动机”。 机枪组织并发请求,并使用Hellgate从参数-InvoiceCreated创建第一个事件。 然后将其自身写入Riak和队列中。 之后,成功响应以与链中初始请求相反的顺序返回。


简而言之,Machinegun就是这样一种DBMS,它在平台的当前版本中-通过Riak,具有比任何其他DBMS计时器更高的计时器。 它提供了一个界面,使您可以控制独立的计算机,并保证了幂等性和记录顺序。 如果有几个HG突然向该事件请求,则MG将不允许事件自动从队列中写出。


自动机是平台内的唯一实体,由标识符,事件列表形式的数据集和计时器组成。 自动机的最终状态是根据其启动所有状态转换到相应状态的所有事件的处理来计算的。 我们使用这种方法与业务实体一起工作,将它们描述为有限状态机。 实际上,由我们的商人创建的所有发票以及其中的付款都是有限状态机,它们具有在状态之间转换的逻辑。


使用Machinegun中的计时器的界面,您可以从其他服务以及记录事件接收形式为“我希望在15年内继续处理此机器”的请求。 此类待处理任务在内置计时器上实现。 实际上,它们经常使用-定期向银行打电话,由于长时间不活动而自动付款等。


顺便说一句,根据我们的公共存储库中的Apache 2.0许可证,可以打开Machinegun源代码。 我们希望这项服务对社区有用。


有关Machinegun工作以及总体上我们如何准备分发系统的详细描述被拉到一个单独的大职位上,所以我在这里不再详细介绍。


外部客户授权的细微差别



成功保存后,Hellgate将数据返回到CAPI,它将二进制Trift结构转换为设计精美的JSON,准备发送给商家后端:


 { "invoice": { "amount": 6000, "cart": [ { "cost": 5000, "price": 5000, "product": "Sandwich", "quantity": 1, "taxMode": { "rate": "10%", "type": "InvoiceLineTaxVAT" } }, { "cost": 1000, "price": 1000, "product": "Cola", "quantity": 1, "taxMode": { "rate": "18%", "type": "InvoiceLineTaxVAT" } } ], "createdAt": "2019-04-04T23:00:31.565518Z", "currency": "RUB", "description": "Delicious meals", "dueDate": "2019-04-05T00:00:30.889000Z", "id": "18xtygvzFaa", "metadata": { "order_id": "Internal order num 13123298761" }, "product": "Order num 12345", "shopID": "TEST", "status": "unpaid" }, "invoiceAccessToken": { "payload": "{JWT}" } } 

您似乎可以在浏览器中将内容发送给付款人并开始付款过程,但是在这里我们认为并非所有商家都准备好在客户端独立实现授权,因此我们自己实现了。 方法是CAPI生成另一个JWT,该JWT允许您启动卡令牌化过程并管理特定发票,并将其添加到返回的发票结构中。


在类似的JWT内部描述的角色示例:


  "resource_access": { "common-api": { "roles": [ "invoices.18xtygvzFaa.payments:read", "invoices.18xtygvzFaa.payments:write", "invoices.18xtygvzFaa:read", "payment_resources:write" ] } } 

该JWT的使用尝试次数有限,并且我们配置的有效期有限,因此您可以将其发布在付款人的浏览器中。 即使被截获,攻击者最多只能支付他人的发票或读取其数据。 此外,由于付款机不使用公开的卡数据进行操作,因此攻击者可以看到的最大值是4242 42** **** 4242类型的掩蔽卡号,付款金额以及(可选)一篮子商品。


创建的发票及其访问密钥使您可以启动支付业务流程。 我们将发票ID及其JWT提供给付款人浏览器,并将控制权转移到我们的JS应用程序。


我们的Checkout JS应用程序实现了一个与您作为付款人进行交互的界面-绘制付款数据输入表单,开始付款,接收其最终状态,显示有趣或可悲的要点。


令牌化和卡数据



但是Checkout不适用于卡数据。 如上所述,我们希望以持卡人数据的形式将敏感数据存储在尽可能少的位置。 为此,我们实现了令牌化。


这是Tokenizer JS库发挥作用的地方。 当您在输入字段中输入卡并单击“支付”时,它会拦截此数据,并通过调用createPaymentResource()方法将其异步发送给我们进行处理。


对于单个CAPI-DSS应用程序,此请求是平衡的,该应用程序也仅通过检查发票JWT,验证数据并将其发送到卡数据存储服务来授权该请求。 在图中,它表示为CDS-卡数据存储。


这项服务的主要目标:


  • 在输入中接收敏感数据,在我们的情况下-您卡的数据;
  • 用数据加密密钥对该数据加密;
  • 生成一些用作键的随机值;
  • 将加密数据保存在Riak群集中的此密钥上;
  • 将付款数据令牌形式的密钥返回给CAPI-DSS服务。

在此过程中,该服务解决了许多重要任务,例如生成用于加密密钥的密钥,安全地输入这些密钥,重新加密数据,控制付款后CVV的擦除等等,但这不在本文讨论范围之内。


并非不可能避免脚部射击。 旨在授权来自后端的请求的专用JWT在网络上发布到客户端浏览器的可能性不为零。 为了防止这种情况的发生,我们内置了保护功能-您只能使用发票授权密钥来调用createPaymentResource()方法。 尝试使用私有JWT平台时,将返回HTTP / 401错误。


完成令牌化请求后,令牌生成器将收到的令牌返回给Checkout并完成其工作。


支付机业务流程



Checkout启动支付过程,即调用createPayment()方法,传递先前收到的卡数据的令牌,并开始轮询事件的过程,实际上是每秒调用一次getInvoiceEvents() API方法。


这些通过CAPI发出的请求将落入Hellgate,后者开始不使用卡数据而实施支付业务流程:


  • 首先,Hellgate转到配置管理服务-Dominant,并接收域配置的当前修订版。 它包含付款的所有规则,将向其授权的银行,将记录哪些交易费用等。
    • 从会员管理服务(现在是HG的一部分)开始,它了解有关要付款的商户帐户内部编号的数据,应用费用金额,准备过帐计划并将其放入Shumway服务中。 该服务负责在付款时管理有关交易参与者的帐户上的货币移动的信息。 过帐计划中包含“冻结计划中指定的交易参与者帐户上资金的可能流动”的指示;
    • 通过参考其他服务(例如,在Binbase中)来丰富支付数据,以找出发行该卡的发卡银行所在的国家和类型,例如“黄金,信用卡”;
    • 呼叫检查员的服务,通常是Antifraud,以便获得付款评分并决定选择涵盖评分所产生的风险级别的终端。 例如,没有3D-Secure的终端可以用于低风险付款,并且已经达到致命风险等级的付款将在此终止其寿命。
    • 调用错误检测服务,即Faultdetector,并根据从错误检测服务接收到的数据来选择付款途径-银行协议适配器,该协议当前具有最少的错误和最高的成功付款概率;
    • 向选定的银行协议适配器发送请求,让它成为YellowBank适配器,在这种情况下,“从此令牌授权指定的金额”。

接收到的令牌的协议适配器转到CDS,接收解密的卡数据,将其传输到特定于银行的协议,并且通常接收授权-来自收单行的确认,指示金额已冻结在付款人的帐户上。


此时,您会收到一条SMS,其中包含有关从您的银行卡中扣除资金的信息,尽管实际上,资金实际上只是冻结在您的帐户中。


适配器将通知HG成功授权,您的CVV代码已从CDS服务中删除,并且这是交互阶段的结束。 管理层重返HG。



HG取决于付款业务流程的商家指定的createPayment()调用,HG希望从外部API调用授权捕获方法,即确认从您的卡中提款,或者如果商家选择了方案,则立即自行完成单阶段付款。


通常,大多数商人使用分阶段付款,但是有些业务类别在授权时尚不知道借记的总金额。 这在旅游业中经常发生,您只预订一笔金额的旅行,并且在确认预订后,就指定了金额,并且可能与一开始授权的金额有所不同。


尽管确认金额可以完全等于或小于授权金额,但这里存在一些陷阱。 假设您从卡中以与卡所链接的银行帐户所用货币不同的货币来购买产品或服务。


授权时,金额会根据授权当天的汇率在您的帐户中冻结。 由于付款可能处于“授权”状态(尽管铁道部有最长期限的建议,现在为3天),因此将以授权当日的价格进行授权的收取。


因此,您要承担货币风险,这可能对您有利也不利于您,特别是在货币市场高度波动的情况下。


为了获得授权,将与协议适配器进行与接收适配器相同的通信过程,如果成功,HG将在Shumway内应用帐户过帐计划,并将付款转移到“已付款”状态。 此时,作为付款系统,我们对交易的参与者有财务义务。


还值得注意的是,Hellgate在Machinegun中记录了发票机状态的任何变化(包括付款过程),以确保数据持久性并用新事件丰富发票。


支付机和用户界面的状态同步



在平台内部进行后台付款过程时,Checkout通过请求事件来倾倒处理过程。 接收到某些事件后,他以人们可以理解的形式绘制付款的当前状态-绘制预加载器,显示“您的付款已成功处理”或“付款无法执行”屏幕,或将浏览器重定向到发卡行的页面以输入3D安全密码;


如果失败,则Checkout将为您提供其他付款方式或重试,从而开始新的付款作为发票的一部分。


这种具有事件轮询的方案甚至可以在关闭浏览器选项卡后恢复状态-如果重复启动,Checkout将接收当前事件列表并绘制用户交互的当前场景,例如,建议输入3D安全代码或表明付款已成功完成。


复制脱机区域中的事件



与机器控制接口一起,Machinegun实现了一项服务,该服务负责将事件流溢出到负责该平台的其他在线任务的服务。


作为决赛中的队列经纪人,我们选择了Kafka,尽管我们以前是使用Machinegun本身实现此功能的。 在一般情况下,此服务是保留有保证的事件顺序流,或根据其他消费者的请求发布特定的事件列表。


我们最初还实施了事件重复数据删除方案,以确保不会重复复制同一事件,但是,由类似事件产生的Riak负载迫使我们放弃它-毕竟,索引搜索不是最好的方法具有KV存储功能。 现在,每个服务使用者都独立负责事件重复数据删除。


通常,Machinegun对事件的复制以确认Kafka中的数据存储为结尾,并且使用者已经连接到Kafka主题并下载了感兴趣的事件列表。


典型的离线区域应用模板


例如,Dudoser服务负责向您发送有关成功付款的电子邮件通知。 在启动时,它将抽出成功付款事件的列表,从那里获取有关地址和金额的信息,将其保存到本地PostgreSQL实例中,并将其用于业务逻辑的进一步处理。


所有其他类似服务均根据相同的逻辑操作,例如Magista服务(该服务负责在商家的个人帐户中搜索发票和付款)或Hooker服务,该服务将异步回调发送至后端给商家的商家,商家出于某种原因而无法通过联系来组织轮询事件直接到处理API。


这种方法使我们可以释放处理负担,分配最大的资源,并提供支付处理的高速度和可用性,并提供高转换率。 诸如“企业客户希望查看过去一年的付款统计信息”之类的繁重查询是由服务处理的,这些服务不会影响在线处理部分的当前负载,因此不会像付款人和商人那样影响您以及我们的客户。


也许我们会在此止步,以免将职位变成太长的派系。 在以后的文章中,我肯定会告诉您有关确保使用Machinegun,Bender,CAPI和Hellgate作为示例的已加载分布式系统中更改,保证和订单的原子性的细微差别。


好吧,关于下一次盐堆¯\_(ツ)_/¯

Source: https://habr.com/ru/post/zh-CN447440/


All Articles