Literally today, the world saw the new Yandex Yandex.Cash API , developed by programmers for programmers. The protocol suite has become uniform, logical, and easy to learn. But the article is not about that - I want to tell you how and why, at one point, it was decided to rewrite the API from scratch.
Under the cut, you'll find some perfectionist suffering, developer pain, and our approach to applying the best design patterns in a specific financial product.
Historically, Yandex.Money protocols appeared in the API as needed and were developed by different departments - the Yandex.Cashy services were no exception.
Yandex.Cassa - a universal solution for online payments - was born in 2013 within the walls of Yandex.Money. With its help, companies can accept payment in all popular ways: from wallets in Yandex.Money, from bank cards, from mobile phone accounts or in cash through special terminals.
Then Cashier mainly consisted of a protocol for receiving payments through the payment form. This protocol allowed, through the payment form on the counterparty’s website, to redirect the payer to the Yandex.Money site and make a payment.
When counterparties needed to make returns, receive lists of returns and payments, another new protocol appeared for all this. He worked on the basis of PKCS # 7 crypto packets and XML, and the threshold of technical entry for him was quite high. In general, only those who really needed it could cope with the implementation.
In addition, in 2014, a payment protocol and bank card acquiring protocol appeared for large counterparties who have undergone a PCI DSS audit and can store bank card data on their side.
All these protocols performed their narrowly focused tasks, but, unfortunately, were different in terms of integration. If the merchant wanted to accept payment in all possible ways, he needed to implement as many as four protocols that were very different from each other.
That is why we have set ourselves the task of significantly reducing the technical threshold for entering Yandex.Kassa and reducing the time of integration with online stores. In addition, the new API should not only take into account the best world practices in the industry, but also be as convenient as possible for the developers of our contractors.
This is how the new Yandex.Cash API appeared, which was built on three main principles: a single data model for the entire interaction, uniqueness of signs and payment status, asynchrony when the store interacts with Yandex.Cash. Now the new API covers all protocols of the previous version, except for payments - they will appear a little later.
When designing the API, we used an object-oriented approach to the core values ​​of the REST-like protocols. At the same time, they tried to use entities familiar to counterparties and developers that already exist in their systems and processes: payments, refunds, saved payment methods (they are also bindings) and others. This concept is called problem-oriented design .
The structure of the payment object is unchanged from the request to the request and allows you to receive the necessary information at any time without having to store it all on its servers. Most importantly, it allows the same data to be interpreted at the same time. Whether you create a payment object (Payment) through POST, get its status via GET, confirm or cancel it - in all situations you work with the same Payment object.
{ "id": "2171eeff-000f-50be-b000-02e31251204a", "status": "pending", "paid": false, "amount": { "value": "2.00", "currency": "RUB" }, "confirmation": { "type": "redirect", "confirmation_url": "https://money.yandex.ru/payments/kassa/confirmation?orderId=2171eeff-000f-50be-b000-02e31251204a" }, "created_at": "2017-10-12T21:14:39.577Z", "payment_method": { "type": "bank_card", "id": "2171eeff-000f-50be-b000-02e31251204a", "saved": false, "card": { "last4": "7918", "expiry_year": "2017", "expiry_month": "07", "card_type": "MasterCard" } } }
An example of a payment object (Payment).
For reasons of uniformity, all objects of payment, return, and payment methods live according to the same laws and have similar features and structure.
Design patterns and a single design system make the product predictable and lower the entry threshold. That feeling, when you have already mastered Payments , and now you are trying to realize Returns - and everything works without re-studying the documentation.
Payment systems have one feature that seriously complicates the implementation of protocols according to the REST-like principle: all entities within the API have their own life cycle. It differs from method to method and affects the values ​​and structure of fields.
We sought to make the number of payment and return statuses as minimal as possible so that Cashier’s clients could implement the necessary business logic only when they really needed it. For a general understanding, let us examine in more detail the life cycle of payment. It consists of three states, as shown in the figure:
Payment conditions for a new life cycle: waiting_for_capture is not always present - there is a mode of payment with automatic capture .
At the pending and waiting_for_capture stages, the payment can be canceled by the merchant or by us and then it will go to the canceled status.
{ "id": "2171f067-000f-50be-b000-01cc0f0843c3", "status": "waiting_for_capture", "paid": true, "amount": { "value": "1000.00", "currency": "RUB" }, "created_at": "2017-10-12T21:21:08.594Z", "payment_method": { "type": "bank_card", "id": "2171eeff-000f-50be-b000-02e31251204a", "saved": false, "card": { "last4": "7918", "expiry_year": "2017", "expiry_month": "07", "card_type": "MasterCard" } }, "receipt_registration": "succeeded" }
Server response when waiting_for_capture is passed.
The transition to the last state Succeeded means that the payment has passed and is ready for transfer to the account of the store. You can safely ship the goods.
We also give counterparties a set of features for which you can get additional information: for example, about the need to confirm payment by the user, about making money or registering a check at the cloud office to meet the requirements of 54- .
The Yandex.Cashi client can always get complete information about the payment by its identifier and decide on further actions: show a success page, tell the buyer about fiscalization or issue the goods. At the same time, the creation and execution of a payment is always linear and has a fixed set of steps:
Steps 2-4 are optional, but the sequence of these actions is always the same.
To get acquainted with real examples, look at the instructions in the instructions section for the payment " Quick start ".
I'll start from afar. The Yandex.Cashi API links the store, customer, partners and issuers of bank cards (the bank that issued your card), that is, many participants are involved in each payment. Sometimes the response of all participants in the process takes quite a long time.
If the interaction was synchronous, companies would have to keep the connection open all this time, which requires more resources. And that's why we made the API asynchronous.
In order to maintain asynchrony and protect against back payments, we suggest using the idempotency key. If, for some reason, the Cashier fails to make a payment in the allotted time, the API client will receive in response HTTP-code 202 (Request Accepted) with a request to repeat the request with the same key a little later. Even with multiple requests with the same key and the same request body, the operation will always be performed only once.
curl https://payment.yandex.net/api/v3/payments \ -X POST \ -u < >:< > \ -H 'Idempotence-Key: < >' \ -H 'Content-Type: application/json' \ -d '{ "amount": { "value": "2.00", "currency": "RUB" }, "payment_method_data": { "type": "bank_card" }, "confirmation": { "type": "redirect", "return_url": "https://www.merchant-website.com/return_url" } }'
An example of a query with a key idempotency.
It is enough to choose a random number generator with the minimum possible collision (we recommend using v4 UUID ). We remember each of the identifiers for 24 hours, after which we can use them again. At the same time, you do not need to keep the idempotency key forever - it is enough to obtain a permanent identifier of the object of the Payment or Return generated by the Cashier. Subsequently, all work with the object is carried out through this identifier.
Some payment processes in Yandex.Cash are asynchronous in nature. For example, when confirming payment via SMS in Sberbank Online, the counterparty must give us the phone number of his buyer, and then wait for the payment confirmation by the user, which can take from several minutes to an hour.
Therefore, the Cashier transmits information to the counterparty about the fact of sending an SMS and waits for confirmation by the user. At this time, the Cashier's client can periodically repeat requests for payment status or subscribe to notifications from the server, which are sent to the URL specified in the Cashier’s personal account when the payment status changes. In both cases, the counterparty receives the full payment object.
All these solutions allow companies not to tune their business processes under Yandex.Cash, but flexibly embed our API into any payment scenarios.
When creating previous protocols, we were faced with the problem of discrepancies in documentation and implementation, because the source documents existed separately. The new API also needed to be done with this, so we decided to use Swagger to assemble the documents. Now there are many all-in-one solutions on the market that allow you to import Swagger-specifications, but were not found ideal in terms of customization and information design.
Therefore, the documents were converted using the Markdown format, and Slate was chosen as the display tool. For the prototype of each new function, the specification is now used in the Swagger format and scenarios that describe the sequence of actions and possible outcomes.
The classic scenario of adding new functions to the API is as follows: the product manager creates a pull request to the repository with documentation, accompanying it with a link to the modified user script. Pull Request is reviewed by both analysts and the API development team.
PaymentMethodDataBankCard: description: allOf: - $ref: '#/definitions/PaymentMethodData' - type: object properties: type: description: . type: string enum: - bank_card card: description: (, ). type: object properties: number: description: type: string pattern: "[0-9]{13,19}" example: '4444444444444448' expiry_year: description: , , YY type: string pattern: "[0-9]{4}" example: '2017' expiry_month: description: , , MM type: string pattern: "[0-9]{2}" example: '10' csc: description: CVC2 CVV2, 3 4 , type: string pattern: "[0-9]{3,4}" example: '012' cardholder: description: type: string pattern: "[a-zA-Z]{0,26}" example: 'John Smith' required: - number - expiry_year - expiry_month - csc required: - type
After all have agreed on the final version of the specification and the Pull Request has received all the necessary approvals, implementation begins. At the same time, another one is given away from the specification branch, in which copywriters correct texts and merge them into a separate Pull Request.
As soon as a new feature is implemented by the developer, it is tested according to this specification and then sent to production.
Before giving a new function to users, we will definitely arrange “ dogfooding ”, that is, we try our product ourselves. For this purpose, a specially developed demo store is used, where you can buy a virtual Capybara and implement other scenarios for Cashier clients. The auxiliary goal of its use is to lower the entry threshold for novice employees and show payment scenarios to anyone within the company.
As soon as we subtract the documentation, test the new functions and verify their simplicity and clarity, the Merge Pull Request of the Swagger specification is executed in the master branch so that it automatically gathers into the public documentation and hits the site.
After the release of the new function in the world, an endlessly fascinating moment of support and monitoring comes. Like other Yandex.Money divisions, we use Grafana. Observations are carried out using a variety of metrics, so I’ll give here only the main ones: requests to the API, sent notifications, success in passing payments and their confirmation (capture), the frequency of occurrence of certain errors.
Along with the launch of the new API, an updated portal of documentation and the SDK for PHP appeared. The gradual transition of our CMS modules to the new protocol has begun - now registration to the new protocol is available for OpenCart 1.5 and WooComerce. By November 2017, they will be joined by Y.CMS for Opencart 2, Prestashop, Webasyst Shop-Script, Drupal (module for Commerce and Ubercart), JoomShopping and SimplaCMS.
We also run the YandexCheckout.js library, which allows you to create forms for accepting card payments of any shapes and sizes. The first such library 6 years ago was proposed by Stripe. Since then, other payment aggregators have done something similar to solve the problem of paying with cards directly on the website of the online store. We took into account the best practices of our Western colleagues and released YandexCheckout.js. Now, companies do not have to undergo a full and expensive PCI DSS audit in order to accept payment on their side — all you have to do is fill out the SAQ D questionnaire and go through an ASV scan (with which we can also help). Now this option is available individually, but opening it for everyone is just in the plans for the future.
And for dessert: with the new API, we are launching a full sandbox. Right in your personal account, you can now run in the main payment scenarios using test cards and wallets.
Many are worried about what will happen to the old protocol and how soon we will invite everyone to switch to a new one. I can calm you down - we do not intend to limit the work with the old protocol. That is, for everyone who has a streamlined process and whom he fully arranges, nothing will change. But if you still want to try new features - our managers can create for you a new store with an updated API.
If you yourself are developing a new platform or have been waiting for the new API for a long time, the newly updated Yandex.Cashy API will be used for registration. At the beginning of 2018, a public launch of the possibility of transferring old stores to a new protocol is also planned.
Finally, the two most important links:
If you have any questions or concerns regarding the new API, I invite you to comment on the article.
Source: https://habr.com/ru/post/340210/
All Articles