{"id":476540,"date":"2026-04-19T11:45:51","date_gmt":"2026-04-19T11:45:51","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=476540"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=476540","title":{"rendered":"DDD \u0432 Go \u0431\u0435\u0437 \u043a\u0440\u0430\u0441\u0438\u0432\u044b\u0445 \u0441\u0445\u0435\u043c: \u043a\u0430\u043a \u043e\u0434\u0438\u043d \u043f\u043b\u0430\u0442\u0435\u0436 \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u0442\u0440\u0438 \u043a\u0443\u0440\u0441\u0430 \u0432\u0430\u043b\u044e\u0442"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0438\u0441\u0430\u0442\u044c \u043f\u0440\u043e DDD \u043b\u0435\u0433\u043a\u043e, \u043f\u043e\u043a\u0430 \u0432 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445 <code>User<\/code>, <code>Order<\/code> \u0438 \u043f\u0430\u0440\u0430 \u043a\u0440\u0430\u0441\u0438\u0432\u044b\u0445 \u0441\u0442\u0440\u0435\u043b\u043e\u0447\u0435\u043a. \u0412 \u043f\u0440\u043e\u0434\u0435 \u043e\u043d\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043c\u0435\u043d\u0435\u0435 \u0430\u043a\u043a\u0443\u0440\u0430\u0442\u043d\u043e: \u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u043e\u0434\u043d\u0430 \u0441\u0443\u043c\u043c\u0430, \u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u0440\u0443\u0433\u0430\u044f, \u0441\u0430\u043f\u043f\u043e\u0440\u0442 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0430\u0434\u043c\u0438\u043d\u043a\u0443 \u0438 \u0432\u0438\u0434\u0438\u0442 \u0442\u0440\u0435\u0442\u044c\u044e.<\/p>\n<p>\u0420\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043f\u0440\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0439 \u043a\u0443\u0441\u043e\u043a, \u0433\u0434\u0435 \u043c\u044b \u043d\u0430 Go \u0432 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043c\u043e\u043c\u0435\u043d\u0442 \u0443\u043f\u0435\u0440\u043b\u0438\u0441\u044c \u0432 \u043a\u0443\u0440\u0441\u044b \u0432\u0430\u043b\u044e\u0442. \u041d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0447\u0443\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u044b, \u043d\u043e \u0441\u0443\u0442\u044c \u0442\u0430 \u0436\u0435. \u042d\u0442\u043e \u043d\u0435 \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u043f\u0440\u043e \u201c\u043a\u0430\u043a \u043c\u044b \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u043b\u0438 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u0443\u044e \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443\u201d. \u0421\u043a\u043e\u0440\u0435\u0435 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442: \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e \u043d\u0430 \u0432\u0438\u0434, \u043f\u043e\u0442\u043e\u043c \u043e\u043d\u043e \u043d\u0430\u0447\u0430\u043b\u043e \u043f\u0440\u043e\u0442\u0435\u043a\u0430\u0442\u044c \u0432 \u0441\u0430\u043c\u044b\u0445 \u043d\u0435\u043f\u0440\u0438\u044f\u0442\u043d\u044b\u0445 \u043c\u0435\u0441\u0442\u0430\u0445.<\/p>\n<h3>\u041a\u0430\u043a \u0431\u044b\u043b\u043e \u0432 \u043f\u0435\u0440\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438<\/h3>\n<p>\u0421\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0431\u044b\u043b\u043e \u043d\u0435\u043c\u043d\u043e\u0433\u043e:<\/p>\n<pre><code>checkout-apipayment-servicefx-rate-servicebilling-serviceledger-service<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0442\u043e\u043a \u043f\u0440\u043e\u0441\u0442\u043e\u0439:<\/p>\n<ol>\n<li>\n<p><code>checkout-api<\/code> \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u0441\u0443\u043c\u043c\u0443.<\/p>\n<\/li>\n<li>\n<p><code>payment-service<\/code> \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043f\u043b\u0430\u0442\u0435\u0436.<\/p>\n<\/li>\n<li>\n<p><code>billing-service<\/code> \u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0434\u0435\u043d\u044c\u0433\u0438.<\/p>\n<\/li>\n<li>\n<p><code>ledger-service<\/code> \u043f\u0438\u0448\u0435\u0442 \u043f\u0440\u043e\u0432\u043e\u0434\u043a\u0443.<\/p>\n<\/li>\n<\/ol>\n<p>\u041d\u0430 \u0441\u0442\u0430\u0440\u0442\u0435 \u0442\u0440\u0430\u0444\u0438\u043a \u0431\u044b\u043b \u0441\u043c\u0435\u0448\u043d\u043e\u0439: 20-30 \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439 \u0432 \u043c\u0438\u043d\u0443\u0442\u0443, p95 \u043f\u043e <code>payment-service<\/code> \u043e\u043a\u043e\u043b\u043e 180 \u043c\u0441. \u041a\u0443\u0440\u0441\u044b \u0432\u0430\u043b\u044e\u0442 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u043b\u0438\u0441\u044c \u0440\u0430\u0437 \u0432 5 \u043c\u0438\u043d\u0443\u0442. \u0412\u0440\u043e\u0434\u0435 \u0431\u044b \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u043c\u0435\u0441\u0442\u043e, \u0433\u0434\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0431\u043e\u043b\u044c\u043d\u043e.<\/p>\n<p>\u041f\u0435\u0440\u0432\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u043a\u043e\u0434\u0430 \u0431\u044b\u043b\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a\u0430\u044f:<\/p>\n<pre><code class=\"go\">type CreatePaymentRequest struct {UserID      string  `json:\"user_id\"`MerchantID  string  `json:\"merchant_id\"`Amount      float64 `json:\"amount\"`Currency    string  `json:\"currency\"`Target      string  `json:\"target_currency\"`}func (s *Service) CreatePayment(ctx context.Context, req CreatePaymentRequest) error {rate, err := s.fx.GetRate(ctx, req.Currency, req.Target)if err != nil {return err}amountToCharge := math.Round(req.Amount*rate*100) \/ 100if err := s.billing.Charge(ctx, req.UserID, amountToCharge, req.Target); err != nil {return err}return s.ledger.Append(ctx, LedgerEntry{UserID:   req.UserID,Amount:   amountToCharge,Currency: req.Target,})}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0430 \u0440\u0435\u0432\u044c\u044e \u044d\u0442\u043e \u043d\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b\u043e \u0443\u0436\u0430\u0441\u043d\u043e. \u0414\u0430, <code>float64<\/code> \u0434\u043b\u044f \u0434\u0435\u043d\u0435\u0433 \u043f\u0430\u0445\u043d\u0435\u0442 \u043f\u043b\u043e\u0445\u043e, \u043d\u043e \u201c\u043f\u043e\u043a\u0430 \u0431\u044b\u0441\u0442\u0440\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c, \u043f\u043e\u0442\u043e\u043c \u043f\u043e\u043f\u0440\u0430\u0432\u0438\u043c\u201d. \u0417\u043d\u0430\u043a\u043e\u043c\u0430\u044f \u0444\u0440\u0430\u0437\u0430, \u0434\u0430.<\/p>\n<p>\u041f\u043e\u0442\u043e\u043c \u043f\u043e\u0448\u043b\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0432\u0435\u0440\u043a\u0438.<\/p>\n<h3>\u0411\u0430\u0433 \u043f\u0435\u0440\u0432\u044b\u0439: \u0443 \u043f\u043b\u0430\u0442\u0435\u0436\u0430 \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0442\u0440\u0438 \u043a\u0443\u0440\u0441\u0430<\/h3>\n<p>\u0412 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043c\u043e\u043c\u0435\u043d\u0442 \u0444\u0438\u043d\u0430\u043d\u0441\u044b \u043f\u0440\u0438\u0441\u043b\u0430\u043b\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u043d\u0430 47 \u0441\u0442\u0440\u043e\u043a. \u0422\u0430\u043c \u0431\u044b\u043b\u0438 \u0440\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u0430\u043c: 2-5 \u0442\u0435\u043d\u0433\u0435 \u043d\u0430 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u0437\u0430\u043a\u0430\u0437\u0430\u0445 \u0438 \u0434\u043e 180 \u0442\u0435\u043d\u0433\u0435 \u043d\u0430 \u043a\u0440\u0443\u043f\u043d\u044b\u0445.<\/p>\n<p>\u0414\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b\u043e \u0442\u0443\u043f\u043e:<\/p>\n<pre><code>\u0412 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435: 12 430.00 KZT\u0412 SMS \u043e\u0442 \u0431\u0430\u043d\u043a\u0430: 12 435.27 KZT\u0412 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u0441\u0430\u043f\u043f\u043e\u0440\u0442\u0430: 12 428.91 KZT<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u043e\u0434\u0443\u043c\u0430\u043b\u0438 \u043d\u0430 \u043e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435. \u042d\u0442\u043e \u0431\u044b\u043b\u0430 \u0445\u043e\u0440\u043e\u0448\u0430\u044f \u0433\u0438\u043f\u043e\u0442\u0435\u0437\u0430, \u043d\u043e \u043d\u0435 \u0432\u0441\u044f \u043f\u0440\u0430\u0432\u0434\u0430.<\/p>\n<p>\u041e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0432\u0435\u0441\u0435\u043b\u0435\u0435:<\/p>\n<pre><code>checkout-api     -&gt; Redis, TTL 5 \u043c\u0438\u043d\u0443\u0442payment-service  -&gt; fx-rate-service \u043f\u043e HTTPledger-service   -&gt; \u0442\u0430\u0431\u043b\u0438\u0446\u0430 fx_rates_snapshot \u0432 Postgres<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u043e\u0434\u043d\u043e\u0439 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0443\u0447\u0430\u0441\u0442\u0432\u043e\u0432\u0430\u043b\u0438 \u0442\u0440\u0438 \u0440\u0430\u0437\u043d\u044b\u0445 \u043a\u0443\u0440\u0441\u0430.<\/p>\n<p>\u0412 \u043b\u043e\u0433\u0430\u0445 \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u044b\u043b \u201c\u043f\u0440\u0430\u0432\u201d:<\/p>\n<pre><code class=\"json\">{  \"service\": \"checkout-api\",  \"payment_id\": \"pay_7f31\",  \"pair\": \"USD\/KZT\",  \"rate\": \"448.12\",  \"source\": \"redis\",  \"rate_age_sec\": 241}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<pre><code class=\"json\">{  \"service\": \"payment-service\",  \"payment_id\": \"pay_7f31\",  \"pair\": \"USD\/KZT\",  \"rate\": \"448.31\",  \"source\": \"fx-rate-service\",  \"latency_ms\": 96}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<pre><code class=\"json\">{  \"service\": \"ledger-service\",  \"payment_id\": \"pay_7f31\",  \"pair\": \"USD\/KZT\",  \"rate\": \"448.08\",  \"source\": \"postgres_snapshot\",  \"snapshot\": \"2026-04-14T09:00:00Z\"}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u043d\u0438 \u043e\u0434\u0438\u043d \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0435 \u0432\u0440\u0430\u043b. \u041d\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0432 \u0446\u0435\u043b\u043e\u043c \u0432\u0440\u0430\u043b\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0443.<\/p>\n<p>\u0412\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u0441\u0442\u044b\u043b\u044c \u0431\u044b\u043b \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u0438\u0437\u0435\u043c\u043b\u0435\u043d\u043d\u044b\u0439: \u043d\u0430 \u0434\u0432\u0430 \u0434\u043d\u044f \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u043b\u0438 \u043a\u0435\u0448 \u043a\u0443\u0440\u0441\u0430 \u0432 <code>checkout-api<\/code> \u0434\u043b\u044f \u0432\u0430\u043b\u044e\u0442\u043d\u044b\u0445 \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439 \u0432\u044b\u0448\u0435 50 000 KZT. \u0414\u0430, \u0441\u0442\u0430\u043b\u043e \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u0435\u0435: p95 checkout \u0432\u044b\u0440\u043e\u0441 \u0441 120 \u043c\u0441 \u0434\u043e 430-500 \u043c\u0441. \u0417\u0430\u0442\u043e \u0441\u0430\u043f\u043f\u043e\u0440\u0442 \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043b \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0442\u0438\u043a\u0435\u0442\u044b \u043f\u0430\u0447\u043a\u0430\u043c\u0438.<\/p>\n<p>\u041d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0444\u0438\u043a\u0441 \u0431\u044b\u043b \u0434\u0440\u0443\u0433\u043e\u0439: \u043a\u0443\u0440\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0447\u0430\u0441\u0442\u044c\u044e \u043f\u043b\u0430\u0442\u0435\u0436\u0430, \u0430 \u043d\u0435 \u0447\u0435\u043c-\u0442\u043e, \u0447\u0442\u043e \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043e\u0441\u0442\u0430\u0435\u0442 \u043a\u0430\u043a \u0445\u043e\u0447\u0435\u0442.<\/p>\n<h3>Quote \u043a\u0430\u043a \u0447\u0430\u0441\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u0430, \u0430 \u043d\u0435 DTO \u0434\u043b\u044f \u0444\u0440\u043e\u043d\u0442\u0430<\/h3>\n<p>\u041c\u044b \u0432\u0432\u0435\u043b\u0438 <code>Quote<\/code>. \u041d\u0435 \u043a\u0430\u043a \u201c\u043e\u0442\u0432\u0435\u0442 \u0440\u0443\u0447\u043a\u0438 \u0434\u043b\u044f UI\u201d, \u0430 \u043a\u0430\u043a \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0431\u0438\u0437\u043d\u0435\u0441-\u0440\u0435\u0448\u0435\u043d\u0438\u0435: \u0432\u043e\u0442 \u044d\u0442\u0430 \u0441\u0443\u043c\u043c\u0430, \u0432\u043e\u0442 \u044d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441, \u0432\u043e\u0442 \u0441\u0440\u043e\u043a \u0436\u0438\u0437\u043d\u0438.<\/p>\n<pre><code class=\"go\">type Currency stringtype Money struct {amount   decimal.Decimalcurrency Currency}type RateSnapshot struct {Pair      stringValue     decimal.DecimalSource    stringVersion   int64CreatedAt time.TimeExpiresAt time.Time}type Quote struct {ID        stringUserID    stringFrom      MoneyTo        MoneyRate      RateSnapshotCreatedAt time.Time}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043b\u0430\u0442\u0435\u0436 \u0442\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u043b\u0441\u044f \u043d\u0435 \u0438\u0437 <code>amount + currency<\/code>, \u0430 \u0438\u0437 <code>quote_id<\/code>.<\/p>\n<pre><code class=\"go\">func NewPayment(quote Quote, idemKey string) (*Payment, error) {if idemKey == \"\" {return nil, ErrEmptyIdempotencyKey}if time.Now().After(quote.Rate.ExpiresAt) {return nil, ErrQuoteExpired}return &amp;Payment{ID:             newPaymentID(),UserID:         quote.UserID,QuoteID:        quote.ID,Debit:          quote.From,Credit:         quote.To,Rate:           quote.Rate,Status:         PaymentCreated,IdempotencyKey: idemKey,}, nil}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421 \u044d\u0442\u043e\u0433\u043e \u043c\u043e\u043c\u0435\u043d\u0442\u0430 <code>billing-service<\/code> \u0438 <code>ledger-service<\/code> \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0438\u043c\u0435\u043b\u0438 \u043f\u0440\u0430\u0432\u0430 \u201c\u0443\u0442\u043e\u0447\u043d\u0438\u0442\u044c \u043a\u0443\u0440\u0441\u201d. \u041e\u043d\u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u043b\u0438 \u0443\u0436\u0435 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u043d\u0443\u044e \u0441\u0443\u043c\u043c\u0443 \u0438 <code>rate_version<\/code>.<\/p>\n<p>\u0422\u0430\u0431\u043b\u0438\u0446\u0430 <code>payments<\/code> \u0441\u0442\u0430\u043b\u0430 \u043c\u0435\u043d\u0435\u0435 \u043a\u0440\u0430\u0441\u0438\u0432\u043e\u0439:<\/p>\n<pre><code class=\"sql\">payment_idquote_iddebit_amountdebit_currencycredit_amountcredit_currencyrate_pairrate_valuerate_versionrate_expires_atstatusidempotency_key<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0442\u0430\u043a \u0441\u0435\u0431\u0435. \u0421 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u0440\u0430\u0437\u0431\u043e\u0440\u0430 \u0438\u043d\u0446\u0438\u0434\u0435\u043d\u0442\u0430 \u0447\u0435\u0440\u0435\u0437 \u043c\u0435\u0441\u044f\u0446 &#8212; \u043e\u0442\u043b\u0438\u0447\u043d\u043e. \u041c\u043e\u0436\u043d\u043e \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043e\u0434\u043d\u0443 \u0441\u0442\u0440\u043e\u043a\u0443 \u0438 \u043f\u043e\u043d\u044f\u0442\u044c, \u043f\u043e\u0447\u0435\u043c\u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u0441\u043f\u0438\u0441\u0430\u043b\u0438 \u0438\u043c\u0435\u043d\u043d\u043e \u0441\u0442\u043e\u043b\u044c\u043a\u043e.<\/p>\n<h3>\u0411\u0430\u0433 \u0432\u0442\u043e\u0440\u043e\u0439: float64 \u043f\u0440\u043e\u0448\u0435\u043b \u0440\u0435\u0432\u044c\u044e, \u043d\u043e \u043d\u0435 \u043f\u0440\u043e\u0448\u0435\u043b \u0434\u0435\u043d\u044c\u0433\u0438<\/h3>\n<p>\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0431\u044b\u043b\u0430 \u0443\u0436\u0435 \u0431\u0430\u043d\u0430\u043b\u044c\u043d\u0430\u044f. \u0413\u0434\u0435-\u0442\u043e \u0443 \u043d\u0430\u0441 \u0431\u044b\u043b <code>float64<\/code>, \u0433\u0434\u0435-\u0442\u043e <code>numeric(18, 6)<\/code>, \u0433\u0434\u0435-\u0442\u043e <code>decimal.Decimal<\/code>.<\/p>\n<p>\u0421\u0438\u043c\u043f\u0442\u043e\u043c:<\/p>\n<pre><code>expected ledger amount: 12435.27actual ledger amount:   12435.26diff: 0.01<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0434\u0438\u043d \u0442\u0435\u043d\u0433\u0435 \u0438\u043b\u0438 \u043e\u0434\u043d\u0430 \u043a\u043e\u043f\u0435\u0439\u043a\u0430 &#8212; \u0437\u0432\u0443\u0447\u0438\u0442 \u0441\u043c\u0435\u0448\u043d\u043e, \u043f\u043e\u043a\u0430 \u0442\u0430\u043a\u0438\u0445 \u0441\u0442\u0440\u043e\u043a \u043d\u0435 8 000 \u0437\u0430 \u043d\u043e\u0447\u044c.<\/p>\n<p>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0431\u044b\u043b\u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043e\u043a\u0440\u0443\u0433\u043b\u044f\u043b\u0438 \u0432 \u0442\u0440\u0435\u0445 \u043c\u0435\u0441\u0442\u0430\u0445:<\/p>\n<pre><code class=\"go\">\/\/ payment-serviceamount := math.Round(raw*100) \/ 100<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<pre><code class=\"go\">\/\/ billing-serviceamount := decimal.NewFromFloat(raw).Round(2)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<pre><code class=\"sql\">-- nightly reportround(amount * rate, 2)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u0432\u043e\u0442 \u044d\u0442\u043e <code>decimal.NewFromFloat(raw)<\/code> \u043f\u043e\u0442\u043e\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0432\u0441\u043f\u043e\u043c\u0438\u043d\u0430\u043b\u0438 \u043d\u0435\u0445\u043e\u0440\u043e\u0448\u0438\u043c\u0438 \u0441\u043b\u043e\u0432\u0430\u043c\u0438.<\/p>\n<p>\u0412\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435: \u0441\u0434\u0435\u043b\u0430\u043b\u0438 nightly job, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u043b \u0440\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u0434\u043e 1 KZT \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0442\u0430\u0431\u043b\u0438\u0446\u0443 <code>rounding_adjustments<\/code>. \u041d\u0435\u043a\u0440\u0430\u0441\u0438\u0432\u043e. \u0417\u0430\u0442\u043e \u043e\u0442\u0447\u0435\u0442\u044b \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043b\u0438 \u043f\u0430\u0434\u0430\u0442\u044c \u043a\u0430\u0436\u0434\u043e\u0435 \u0443\u0442\u0440\u043e, \u043f\u043e\u043a\u0430 \u043c\u044b \u0432\u044b\u0447\u0438\u0449\u0430\u043b\u0438 \u043a\u043e\u0434.<\/p>\n<p>\u041d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435:<\/p>\n<ul>\n<li>\n<p>\u0437\u0430\u043f\u0440\u0435\u0442\u0438\u043b\u0438 <code>float64<\/code> \u0432 \u0434\u043e\u043c\u0435\u043d\u043d\u044b\u0445 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430\u0445;<\/p>\n<\/li>\n<li>\n<p>\u0441\u0443\u043c\u043c\u044b \u043d\u0430\u0447\u0430\u043b\u0438 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u043e\u0439;<\/p>\n<\/li>\n<li>\n<p>\u043e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043b\u0438 \u043a \u0432\u0430\u043b\u044e\u0442\u0435;<\/p>\n<\/li>\n<li>\n<p>\u0432\u0441\u0435 \u0440\u0430\u0441\u0447\u0435\u0442\u044b \u0432\u044b\u043d\u0435\u0441\u043b\u0438 \u0432 <code>Money<\/code>.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"go\">func NewMoney(raw string, currency Currency) (Money, error) {amount, err := decimal.NewFromString(raw)if err != nil {return Money{}, err}if amount.IsNegative() {return Money{}, ErrNegativeAmount}return Money{amount: amount, currency: currency}, nil}func (m Money) Convert(rate decimal.Decimal, target Currency) Money {return Money{amount:   m.amount.Mul(rate),currency: target,}}func (m Money) RoundByCurrency() Money {scale := map[Currency]int32{\"USD\": 2,\"EUR\": 2,\"KZT\": 2,\"JPY\": 0,}[m.currency]return Money{amount:   m.amount.Round(scale),currency: m.currency,}}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0435 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u043e: <code>decimal<\/code> \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u0435\u0435. \u041d\u0430 batch-\u043f\u0435\u0440\u0435\u0441\u0447\u0435\u0442\u0435 200k \u0441\u0442\u0440\u043e\u043a \u0432\u0440\u0435\u043c\u044f \u0432\u044b\u0440\u043e\u0441\u043b\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0441 1.8 \u0441\u0435\u043a\u0443\u043d\u0434\u044b \u0434\u043e 6.4. \u041d\u043e \u044d\u0442\u043e \u0431\u044b\u043b backoffice job, \u0430 \u043d\u0435 \u0433\u043e\u0440\u044f\u0447\u0430\u044f \u0440\u0443\u0447\u043a\u0430. \u0420\u0435\u0448\u0438\u043b\u0438, \u0447\u0442\u043e \u043b\u0443\u0447\u0448\u0435 \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e \u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e, \u0447\u0435\u043c \u0431\u044b\u0441\u0442\u0440\u043e \u0438 \u043f\u043e\u0442\u043e\u043c \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u0442\u044c \u0444\u0438\u043d\u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0443 \u0440\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f.<\/p>\n<h3>\u0411\u0430\u0433 \u0442\u0440\u0435\u0442\u0438\u0439: \u0442\u0430\u0439\u043c\u0430\u0443\u0442 \u043d\u0435 \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u043f\u043b\u0430\u0442\u0435\u0436 \u043d\u0435 \u043f\u0440\u043e\u0448\u0435\u043b<\/h3>\n<p>\u0415\u0449\u0435 \u043e\u0434\u0438\u043d \u043d\u0435\u043f\u0440\u0438\u044f\u0442\u043d\u044b\u0439 \u044d\u043f\u0438\u0437\u043e\u0434 \u0431\u044b\u043b \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u043e\u043c.<\/p>\n<p><code>payment-service<\/code> \u0445\u043e\u0434\u0438\u043b \u0432 <code>acquirer-api<\/code> \u0441 \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u043e\u043c 3 \u0441\u0435\u043a\u0443\u043d\u0434\u044b. \u041e\u0431\u044b\u0447\u043d\u043e \u043e\u0442\u0432\u0435\u0442 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u043b \u0437\u0430 400-700 \u043c\u0441. \u041d\u043e \u043f\u0430\u0440\u0443 \u0440\u0430\u0437 \u0432 \u0434\u0435\u043d\u044c p99 \u0443\u043b\u0435\u0442\u0430\u043b \u0434\u043e 5-8 \u0441\u0435\u043a\u0443\u043d\u0434.<\/p>\n<p>\u0427\u0442\u043e \u0432\u0438\u0434\u0435\u043b\u0438 \u043c\u044b:<\/p>\n<pre><code>POST \/charge -&gt; context deadline exceededretry after 500msPOST \/charge -&gt; 200 OK<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0427\u0442\u043e \u0432\u0438\u0434\u0435\u043b \u043a\u043b\u0438\u0435\u043d\u0442 \u0432 \u0431\u0430\u043d\u043a\u0435:<\/p>\n<pre><code>\u0421\u043f\u0438\u0441\u0430\u043d\u0438\u0435 12 435.27 KZT\u0421\u043f\u0438\u0441\u0430\u043d\u0438\u0435 12 435.27 KZT<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0435 \u0443\u043c\u0435\u0440. \u041e\u043d \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0442\u0432\u0435\u0442\u0438\u043b \u043f\u043e\u0437\u0436\u0435. \u0410 \u043c\u044b \u0443\u0436\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b\u0438 \u0432\u0442\u043e\u0440\u043e\u0439.<\/p>\n<p>\u0412 \u043b\u043e\u0433\u0430\u0445 \u044d\u0442\u043e \u0431\u044b\u043b\u043e \u0442\u0430\u043a:<\/p>\n<pre><code>payment_id=pay_9921 attempt=1 timeout_ms=3000 err=deadline_exceededpayment_id=pay_9921 attempt=2 status=authorized acquirer_id=acq_771payment_id=pay_9921 callback attempt=1 status=authorized acquirer_id=acq_770<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u043a\u043e\u0441\u0442\u044b\u043b\u044c: \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u043b\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 retry \u0434\u043b\u044f <code>POST \/charge<\/code>, \u0435\u0441\u043b\u0438 \u043d\u0435\u0442 \u044f\u0432\u043d\u043e\u0433\u043e \u043e\u0442\u0432\u0435\u0442\u0430 \u043e\u0442 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430. \u042d\u0442\u043e \u0441\u0440\u0430\u0437\u0443 \u0441\u043d\u0438\u0437\u0438\u043b\u043e \u0440\u0438\u0441\u043a \u0434\u0432\u043e\u0439\u043d\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044f, \u043d\u043e \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u043b\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 <code>unknown<\/code>.<\/p>\n<p>\u041f\u043e\u0442\u043e\u043c \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u044d\u0442\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c\u0438 API:<\/p>\n<ul>\n<li>\n<p><code>idempotency_key<\/code> \u0441\u0442\u0430\u043b \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c;<\/p>\n<\/li>\n<li>\n<p><code>(merchant_id, idempotency_key)<\/code> \u043f\u043e\u043b\u0443\u0447\u0438\u043b unique index;<\/p>\n<\/li>\n<li>\n<p>callback \u0441\u0442\u0430\u043b \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u0434\u043e\u043c\u0435\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 <code>Authorize<\/code>;<\/p>\n<\/li>\n<li>\n<p>\u0437\u0430\u0432\u0438\u0441\u0448\u0438\u0435 \u043f\u043b\u0430\u0442\u0435\u0436\u0438 \u0443\u0448\u043b\u0438 \u0432 reconciliation job.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"sql\">create unique index payments_idem_uqon payments (merchant_id, idempotency_key);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<pre><code class=\"go\">func (p *Payment) Authorize(acquirerID string) error {switch p.Status {case PaymentAuthorized:return nilcase PaymentCreated, PaymentUnknown:p.Status = PaymentAuthorizedp.AcquirerID = acquirerIDreturn nildefault:return ErrInvalidPaymentState}}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u0434\u0430, <code>PaymentUnknown<\/code> &#8212; \u043d\u0435 \u0441\u0430\u043c\u044b\u0439 \u043f\u0440\u0438\u044f\u0442\u043d\u044b\u0439 \u0441\u0442\u0430\u0442\u0443\u0441. \u041d\u043e \u043e\u043d \u0447\u0435\u0441\u0442\u043d\u044b\u0439. \u0412\u043d\u0435\u0448\u043d\u0438\u0439 \u043c\u0438\u0440 \u0438\u043d\u043e\u0433\u0434\u0430 \u043d\u0435 \u0434\u0430\u0435\u0442 \u0442\u0435\u0431\u0435 \u0431\u0438\u043d\u0430\u0440\u043d\u043e\u0433\u043e \u043e\u0442\u0432\u0435\u0442\u0430 \u201c\u0443\u0441\u043f\u0435\u0448\u043d\u043e\/\u043d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e\u201d.<\/p>\n<p><code>reconciliation-worker<\/code> \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043b\u0441\u044f \u043a\u0430\u0436\u0434\u0443\u044e \u043c\u0438\u043d\u0443\u0442\u0443 \u0438 \u0434\u043e\u0431\u0438\u0440\u0430\u043b \u043f\u043b\u0430\u0442\u0435\u0436\u0438 \u0441\u0442\u0430\u0440\u0448\u0435 90 \u0441\u0435\u043a\u0443\u043d\u0434:<\/p>\n<pre><code class=\"sql\">select payment_id, acquirer_request_idfrom paymentswhere status = 'unknown'  and created_at &lt; now() - interval '90 seconds'limit 500;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e \u043a\u043e\u0441\u0442\u044b\u043b\u044c? \u0427\u0430\u0441\u0442\u0438\u0447\u043d\u043e. \u041d\u043e \u0431\u0435\u0437 \u043d\u0435\u0433\u043e \u043b\u044e\u0431\u0430\u044f \u043a\u0440\u0430\u0441\u0438\u0432\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043b\u043e\u043c\u0430\u043b\u0430\u0441\u044c \u043e\u0431 API, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0438\u043d\u043e\u0433\u0434\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u043b\u043e <code>502<\/code>, \u0445\u043e\u0442\u044f \u043f\u043b\u0430\u0442\u0435\u0436 \u0443\u0436\u0435 \u0431\u044b\u043b \u0441\u043e\u0437\u0434\u0430\u043d.<\/p>\n<h3>\u0411\u0430\u0433 \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u044b\u0439: read model \u043e\u0442\u0441\u0442\u0430\u0435\u0442, \u0441\u0430\u043f\u043f\u043e\u0440\u0442 \u043f\u0430\u043d\u0438\u043a\u0443\u0435\u0442<\/h3>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0444\u0438\u043a\u0441\u0430 <code>Quote<\/code> \u043c\u044b \u0434\u0443\u043c\u0430\u043b\u0438, \u0447\u0442\u043e \u0440\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u0438\u0441\u044c. \u041d\u0443 \u0434\u0430, \u043a\u043e\u043d\u0435\u0447\u043d\u043e.<\/p>\n<p>\u0427\u0435\u0440\u0435\u0437 \u043d\u0435\u0434\u0435\u043b\u044e \u0441\u0430\u043f\u043f\u043e\u0440\u0442 \u043f\u0438\u0448\u0435\u0442: \u201c\u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0441\u043f\u0438\u0441\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e, \u043d\u043e \u0432 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u0441\u0442\u0430\u0440\u0430\u044f \u0441\u0443\u043c\u043c\u0430\u201d. \u041a\u043b\u0438\u0435\u043d\u0442 \u0443\u0436\u0435 \u043d\u0435\u0440\u0432\u043d\u0438\u0447\u0430\u0435\u0442, \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u0442\u043e\u0436\u0435.<\/p>\n<p>\u041e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c, \u0447\u0442\u043e <code>ledger-read-model<\/code> \u043e\u0442\u0441\u0442\u0430\u0432\u0430\u043b \u043e\u0442 Kafka \u043d\u0430 3-4 \u043c\u0438\u043d\u0443\u0442\u044b \u043f\u043e\u0441\u043b\u0435 \u0434\u0435\u043f\u043b\u043e\u044f. \u0421\u0430\u043c \u043f\u043b\u0430\u0442\u0435\u0436 \u0431\u044b\u043b \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439, \u0437\u0430\u043f\u0438\u0441\u044c \u0432 <code>payments<\/code> \u0442\u043e\u0436\u0435. \u041d\u043e \u0430\u0434\u043c\u0438\u043d\u043a\u0430 \u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0430 \u043d\u0435 \u0442\u0443\u0434\u0430.<\/p>\n<p>\u041c\u0435\u0442\u0440\u0438\u043a\u0430 lag \u0442\u043e\u0433\u0434\u0430 \u0431\u044b\u043b\u0430, \u043d\u043e \u0430\u043b\u0435\u0440\u0442 \u0441\u0442\u043e\u044f\u043b \u043d\u0430 10 \u043c\u0438\u043d\u0443\u0442. \u041f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u201c\u043c\u0435\u043d\u044c\u0448\u0435 \u043d\u0435 \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e\u201d. \u041e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c, \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e, \u0435\u0441\u043b\u0438 \u044d\u0442\u043e\u0439 \u0430\u0434\u043c\u0438\u043d\u043a\u043e\u0439 \u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0430\u043f\u043f\u043e\u0440\u0442 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u043e\u0433\u043e \u0438\u043d\u0446\u0438\u0434\u0435\u043d\u0442\u0430.<\/p>\n<p>\u0427\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u0431\u044b\u0441\u0442\u0440\u043e:<\/p>\n<ul>\n<li>\n<p>\u0432 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u0440\u044f\u0434\u043e\u043c \u0441 \u0441\u0443\u043c\u043c\u043e\u0439 \u043f\u043e\u043a\u0430\u0437\u0430\u043b\u0438 <code>quote_id<\/code> \u0438 <code>rate_version<\/code>;<\/p>\n<\/li>\n<li>\n<p>\u0434\u043b\u044f \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439 \u043c\u043b\u0430\u0434\u0448\u0435 15 \u043c\u0438\u043d\u0443\u0442 \u043d\u0430\u0447\u0430\u043b\u0438 \u0447\u0438\u0442\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0438\u0437 <code>payments<\/code>;<\/p>\n<\/li>\n<li>\n<p>consumer lag \u043f\u043e <code>payment-events<\/code> \u043e\u043f\u0443\u0441\u0442\u0438\u043b\u0438 \u0432 \u0430\u043b\u0435\u0440\u0442\u0430\u0445 \u0434\u043e 60 \u0441\u0435\u043a\u0443\u043d\u0434.<\/p>\n<\/li>\n<\/ul>\n<p>\u0427\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u043f\u043e\u0442\u043e\u043c:<\/p>\n<pre><code>fresh payment view:  source = payments tablehistorical\/report view:  source = ledger read model<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0435 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u043e, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0432 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u043f\u043e\u044f\u0432\u0438\u043b\u043e\u0441\u044c \u0434\u0432\u0435 \u0432\u0435\u0442\u043a\u0438 \u0447\u0442\u0435\u043d\u0438\u044f. \u041d\u043e \u044d\u0442\u043e \u043b\u0443\u0447\u0448\u0435, \u0447\u0435\u043c \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0441\u0430\u043f\u043f\u043e\u0440\u0442\u0443 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438 \u0433\u043e\u0432\u043e\u0440\u0438\u0442\u044c \u201c\u043d\u0443 \u043e\u043d\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u0434\u043e\u0435\u0434\u0435\u0442\u201d.<\/p>\n<h3>\u0413\u0434\u0435 \u0437\u0434\u0435\u0441\u044c DDD, \u0435\u0441\u043b\u0438 \u0431\u0435\u0437 \u0440\u0435\u043b\u0438\u0433\u0438\u0438<\/h3>\n<p>DDD \u043f\u043e\u043c\u043e\u0433 \u043d\u0435 \u043f\u0430\u043f\u043a\u043e\u0439 <code>domain<\/code>. \u041f\u0430\u043f\u043a\u0443 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0437\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u0443\u0433\u043e\u0434\u043d\u043e \u0438 \u0432\u0441\u0435 \u0440\u0430\u0432\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043a\u0430\u0448\u0443.<\/p>\n<p>\u041f\u043e\u043b\u044c\u0437\u0430 \u0431\u044b\u043b\u0430 \u0432 \u0434\u0440\u0443\u0433\u043e\u043c: \u043c\u044b \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043b\u0438 \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u043a\u0443\u0440\u0441 \u0432\u0430\u043b\u044e\u0442 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0434\u0435\u0442\u0430\u043b\u044c\u044e.<\/p>\n<p>\u0420\u0430\u043d\u044c\u0448\u0435 \u043f\u0440\u0430\u0432\u0434\u0430 \u0431\u044b\u043b\u0430 \u0440\u0430\u0437\u043c\u0430\u0437\u0430\u043d\u0430:<\/p>\n<pre><code>\u043a\u0443\u0440\u0441 \u0432 Redis\u043a\u0443\u0440\u0441 \u0432 fx-rate-service\u043a\u0443\u0440\u0441 \u0432 Postgres\u0441\u0443\u043c\u043c\u0430 \u0432 billing\u0441\u0443\u043c\u043c\u0430 \u0432 ledger<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u043f\u0440\u0430\u0432\u0434\u0430 \u0441\u0442\u0430\u043b\u0430 \u0431\u043b\u0438\u0436\u0435 \u043a \u043f\u043b\u0430\u0442\u0435\u0436\u0443:<\/p>\n<pre><code class=\"go\">type Payment struct {ID             stringMerchantID     stringUserID         stringQuoteID        stringDebit          MoneyCredit         MoneyRate           RateSnapshotStatus         PaymentStatusIdempotencyKey stringAcquirerID     string}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0410\u0433\u0440\u0435\u0433\u0430\u0442 \u0441\u0442\u0430\u043b \u043e\u0442\u0432\u0435\u0447\u0430\u0442\u044c \u0437\u0430 \u043f\u0440\u043e\u0441\u0442\u044b\u0435, \u043d\u043e \u0432\u0430\u0436\u043d\u044b\u0435 \u0432\u0435\u0449\u0438:<\/p>\n<ul>\n<li>\n<p>\u043f\u043b\u0430\u0442\u0435\u0436 \u043d\u0435\u043b\u044c\u0437\u044f \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u043e \u043f\u0440\u043e\u0442\u0443\u0445\u0448\u0435\u043c\u0443 <code>Quote<\/code>;<\/p>\n<\/li>\n<li>\n<p>\u0441\u0443\u043c\u043c\u0443 \u043d\u0435\u043b\u044c\u0437\u044f \u043f\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u0430\u0442\u044c \u0434\u0440\u0443\u0433\u0438\u043c \u043a\u0443\u0440\u0441\u043e\u043c \u043f\u043e\u0441\u043b\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f;<\/p>\n<\/li>\n<li>\n<p><code>float64<\/code> \u043d\u0435 \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u0442 \u0432 \u0434\u043e\u043c\u0435\u043d;<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0435 \u0434\u0435\u043b\u0430\u0435\u0442 \u0432\u0442\u043e\u0440\u043e\u0435 \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435;<\/p>\n<\/li>\n<li>\n<p><code>unknown<\/code> &#8212; \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435, \u0430 \u043d\u0435 \u201c\u043d\u0443 \u043f\u043e\u0442\u043e\u043c \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c\u0441\u044f\u201d.<\/p>\n<\/li>\n<\/ul>\n<p>Application layer \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442 \u0448\u0430\u0433\u0438:<\/p>\n<pre><code class=\"go\">func (uc *UseCase) CreatePayment(ctx context.Context, cmd CreatePaymentCommand) (*Payment, error) {quote, err := uc.quotes.Get(ctx, cmd.QuoteID)if err != nil {return nil, err}payment, err := NewPayment(*quote, cmd.IdempotencyKey)if err != nil {return nil, err}if err := uc.payments.Save(ctx, payment); err != nil {return nil, err}uc.outbox.Add(ctx, PaymentCreated{PaymentID: payment.ID,QuoteID:   payment.QuoteID,})return payment, nil}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0438\u043a\u0430\u043a\u043e\u0439 \u043c\u0430\u0433\u0438\u0438. \u041f\u0440\u043e\u0441\u0442\u043e \u043c\u0435\u043d\u044c\u0448\u0435 \u043c\u0435\u0441\u0442, \u0433\u0434\u0435 \u043c\u043e\u0436\u043d\u043e \u201c\u0447\u0443\u0442\u044c-\u0447\u0443\u0442\u044c \u043f\u043e-\u0441\u0432\u043e\u0435\u043c\u0443\u201d \u043f\u043e\u0441\u0447\u0438\u0442\u0430\u0442\u044c \u0434\u0435\u043d\u044c\u0433\u0438.<\/p>\n<h3>\u041a\u0430\u043a \u0434\u0435\u0431\u0430\u0436\u0438\u043b\u0438<\/h3>\n<p>\u0421\u0430\u043c\u043e\u0435 \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e\u0435. \u041c\u044b \u043f\u0440\u043e\u0442\u0430\u0449\u0438\u043b\u0438 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0443\u044e \u043a\u043e\u0440\u0440\u0435\u043b\u044f\u0446\u0438\u044e:<\/p>\n<pre><code>payment_idquote_idrate_versionidempotency_keyacquirer_request_idtrace_id<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u043e \u044d\u0442\u043e\u0433\u043e \u043b\u043e\u0433 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b \u0442\u0430\u043a:<\/p>\n<pre><code>charge failed: timeout<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u043f\u0430\u0441\u0438\u0431\u043e, \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u043c\u043e\u0433\u043b\u043e.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435:<\/p>\n<pre><code class=\"json\">{  \"service\": \"payment-service\",  \"payment_id\": \"pay_9921\",  \"quote_id\": \"qt_18aa\",  \"rate_version\": 184233,  \"idempotency_key\": \"ord_71_attempt_1\",  \"acquirer_request_id\": \"req_770\",  \"status\": \"unknown\",  \"duration_ms\": 3000,  \"error\": \"context deadline exceeded\"}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421 \u0442\u0430\u043a\u0438\u043c\u0438 \u043b\u043e\u0433\u0430\u043c\u0438 \u0443\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u0441\u043f\u043e\u0440\u0438\u0442\u044c \u043d\u0435 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u201c\u043c\u043d\u0435 \u043a\u0430\u0436\u0435\u0442\u0441\u044f\u201d, \u0430 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u0430.<\/p>\n<h3>\u0427\u0442\u043e \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043a\u0440\u0438\u0432\u044b\u043c<\/h3>\n<p>\u041d\u0435 \u0445\u043e\u0447\u0443 \u0434\u0435\u043b\u0430\u0442\u044c \u0432\u0438\u0434, \u0447\u0442\u043e \u043f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0432\u0441\u0435 \u0441\u0442\u0430\u043b\u043e \u043a\u0440\u0430\u0441\u0438\u0432\u043e.<\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u0438\u0441\u044c \u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0438\u0441\u0441\u044b:<\/p>\n<ul>\n<li>\n<p><code>payments<\/code> \u0445\u0440\u0430\u043d\u0438\u0442 <code>RateSnapshot<\/code>, \u0445\u043e\u0442\u044f \u044d\u0442\u043e \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435;<\/p>\n<\/li>\n<li>\n<p><code>PaymentUnknown<\/code> \u0443\u0441\u043b\u043e\u0436\u043d\u0438\u043b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p><code>reconciliation-worker<\/code> \u0438\u043d\u043e\u0433\u0434\u0430 \u0434\u043e\u0433\u043e\u043d\u044f\u0435\u0442 \u043f\u043b\u0430\u0442\u0435\u0436\u0438 \u0447\u0435\u0440\u0435\u0437 2-3 \u043c\u0438\u043d\u0443\u0442\u044b;<\/p>\n<\/li>\n<li>\n<p><code>decimal<\/code> \u0437\u0430\u043c\u0435\u0434\u043b\u0438\u043b batch-\u0440\u0430\u0441\u0447\u0435\u0442\u044b;<\/p>\n<\/li>\n<li>\n<p>\u0441\u0442\u0430\u0440\u044b\u0435 \u043f\u043b\u0430\u0442\u0435\u0436\u0438 \u0434\u043e \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u0438\u043c\u0435\u044e\u0442 <code>rate_version<\/code>;<\/p>\n<\/li>\n<li>\n<p>\u0432 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u0442\u0435\u043f\u0435\u0440\u044c \u0434\u0432\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u0447\u0442\u0435\u043d\u0438\u044f: \u0441\u0432\u0435\u0436\u0430\u044f \u0438 \u043e\u0442\u0447\u0435\u0442\u043d\u0430\u044f.<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u043e \u0437\u0430\u0442\u043e \u043c\u044b \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043b\u0438 \u043b\u043e\u0432\u0438\u0442\u044c \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044e, \u0433\u0434\u0435 \u043e\u0434\u0438\u043d \u0441\u0435\u0440\u0432\u0438\u0441 \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u201c448.12\u201d, \u0432\u0442\u043e\u0440\u043e\u0439 \u201c448.31\u201d, \u0442\u0440\u0435\u0442\u0438\u0439 \u201c448.08\u201d, \u0438 \u0432\u0441\u0435 \u0442\u0440\u043e\u0435 \u0444\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u0430\u0432\u044b.<\/p>\n<h3>\u0412\u044b\u0432\u043e\u0434<\/h3>\n<p>DDD \u0442\u0443\u0442 \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u043b\u0441\u044f \u043d\u0435 \u043a\u0430\u043a \u043d\u0430\u0431\u043e\u0440 \u0442\u0435\u0440\u043c\u0438\u043d\u043e\u0432, \u0430 \u043a\u0430\u043a \u0441\u043f\u043e\u0441\u043e\u0431 \u0440\u0435\u0448\u0438\u0442\u044c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0443\u044e \u0431\u043e\u043b\u044c: \u0433\u0434\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u0440\u0430\u0432\u0434\u0430 \u043e \u043f\u043b\u0430\u0442\u0435\u0436\u0435.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 CRUD \u043d\u0430 \u0442\u0440\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b, \u043d\u0435 \u043d\u0430\u0434\u043e \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0445\u0440\u0430\u043c \u0441 \u0430\u0433\u0440\u0435\u0433\u0430\u0442\u0430\u043c\u0438 \u0438 \u0434\u043e\u043c\u0435\u043d\u043d\u044b\u043c\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u043c\u0438. \u041d\u043e \u0435\u0441\u043b\u0438 \u0434\u0435\u043d\u044c\u0433\u0438 \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0442 \u0447\u0435\u0440\u0435\u0437 \u043a\u0435\u0448, \u0432\u043d\u0435\u0448\u043d\u0438\u0439 API, ledger, \u043e\u0442\u0447\u0435\u0442\u044b \u0438 \u0441\u0430\u043f\u043f\u043e\u0440\u0442\u0441\u043a\u0443\u044e \u0430\u0434\u043c\u0438\u043d\u043a\u0443, \u043b\u0443\u0447\u0448\u0435 \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0431\u0438\u0437\u043d\u0435\u0441-\u0444\u0430\u043a\u0442\u044b \u044f\u0432\u043d\u043e.<\/p>\n<p>\u0412 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0442\u0430\u043a\u0438\u043c\u0438 \u0444\u0430\u043a\u0442\u0430\u043c\u0438 \u0441\u0442\u0430\u043b\u0438 <code>Quote<\/code>, <code>RateSnapshot<\/code>, <code>Money<\/code> \u0438 <code>Payment<\/code>.<\/p>\n<p>\u041d\u0435 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430. \u041f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u0441\u043b\u0435 \u043d\u0435\u0435 \u0441\u0442\u0430\u043b\u043e \u043c\u0435\u043d\u044c\u0448\u0435 \u043d\u043e\u0447\u043d\u044b\u0445 \u0441\u0432\u0435\u0440\u043e\u043a, \u043c\u0435\u043d\u044c\u0448\u0435 \u0440\u0443\u0447\u043d\u044b\u0445 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u043e\u043a \u0438 \u043c\u0435\u043d\u044c\u0448\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u043e\u0432 \u0432\u0438\u0434\u0430: \u201c\u0430 \u043f\u043e\u0447\u0435\u043c\u0443 \u043a\u043b\u0438\u0435\u043d\u0442 \u0443\u0432\u0438\u0434\u0435\u043b \u043e\u0434\u043d\u0443 \u0441\u0443\u043c\u043c\u0443, \u0430 \u0441\u043f\u0438\u0441\u0430\u043b\u0438 \u0434\u0440\u0443\u0433\u0443\u044e?\u201d<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1025226\/\">https:\/\/habr.com\/ru\/articles\/1025226\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u041f\u0438\u0441\u0430\u0442\u044c \u043f\u0440\u043e DDD \u043b\u0435\u0433\u043a\u043e, \u043f\u043e\u043a\u0430 \u0432 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445 User, Order \u0438 \u043f\u0430\u0440\u0430 \u043a\u0440\u0430\u0441\u0438\u0432\u044b\u0445 \u0441\u0442\u0440\u0435\u043b\u043e\u0447\u0435\u043a. \u0412 \u043f\u0440\u043e\u0434\u0435 \u043e\u043d\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043c\u0435\u043d\u0435\u0435 \u0430\u043a\u043a\u0443\u0440\u0430\u0442\u043d\u043e: \u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u043e\u0434\u043d\u0430 \u0441\u0443\u043c\u043c\u0430, \u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u0440\u0443\u0433\u0430\u044f, \u0441\u0430\u043f\u043f\u043e\u0440\u0442 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0430\u0434\u043c\u0438\u043d\u043a\u0443 \u0438 \u0432\u0438\u0434\u0438\u0442 \u0442\u0440\u0435\u0442\u044c\u044e.\u0420\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043f\u0440\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0439 \u043a\u0443\u0441\u043e\u043a, \u0433\u0434\u0435 \u043c\u044b \u043d\u0430 Go \u0432 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043c\u043e\u043c\u0435\u043d\u0442 \u0443\u043f\u0435\u0440\u043b\u0438\u0441\u044c \u0432 \u043a\u0443\u0440\u0441\u044b \u0432\u0430\u043b\u044e\u0442. \u041d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0447\u0443\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u044b, \u043d\u043e \u0441\u0443\u0442\u044c \u0442\u0430 \u0436\u0435. \u042d\u0442\u043e \u043d\u0435 \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u043f\u0440\u043e \u201c\u043a\u0430\u043a \u043c\u044b \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u043b\u0438 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u0443\u044e \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443\u201d. \u0421\u043a\u043e\u0440\u0435\u0435 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442: \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e \u043d\u0430 \u0432\u0438\u0434, \u043f\u043e\u0442\u043e\u043c \u043e\u043d\u043e \u043d\u0430\u0447\u0430\u043b\u043e \u043f\u0440\u043e\u0442\u0435\u043a\u0430\u0442\u044c \u0432 \u0441\u0430\u043c\u044b\u0445 \u043d\u0435\u043f\u0440\u0438\u044f\u0442\u043d\u044b\u0445 \u043c\u0435\u0441\u0442\u0430\u0445.\u041a\u0430\u043a \u0431\u044b\u043b\u043e \u0432 \u043f\u0435\u0440\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438\u0421\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0431\u044b\u043b\u043e \u043d\u0435\u043c\u043d\u043e\u0433\u043e:checkout-apipayment-servicefx-rate-servicebilling-serviceledger-service\u041f\u043e\u0442\u043e\u043a \u043f\u0440\u043e\u0441\u0442\u043e\u0439:checkout-api \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u0441\u0443\u043c\u043c\u0443.payment-service \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043f\u043b\u0430\u0442\u0435\u0436.billing-service \u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 \u0434\u0435\u043d\u044c\u0433\u0438.ledger-service \u043f\u0438\u0448\u0435\u0442 \u043f\u0440\u043e\u0432\u043e\u0434\u043a\u0443.\u041d\u0430 \u0441\u0442\u0430\u0440\u0442\u0435 \u0442\u0440\u0430\u0444\u0438\u043a \u0431\u044b\u043b \u0441\u043c\u0435\u0448\u043d\u043e\u0439: 20-30 \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439 \u0432 \u043c\u0438\u043d\u0443\u0442\u0443, p95 \u043f\u043e payment-service \u043e\u043a\u043e\u043b\u043e 180 \u043c\u0441. \u041a\u0443\u0440\u0441\u044b \u0432\u0430\u043b\u044e\u0442 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u043b\u0438\u0441\u044c \u0440\u0430\u0437 \u0432 5 \u043c\u0438\u043d\u0443\u0442. \u0412\u0440\u043e\u0434\u0435 \u0431\u044b \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u043c\u0435\u0441\u0442\u043e, \u0433\u0434\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0431\u043e\u043b\u044c\u043d\u043e.\u041f\u0435\u0440\u0432\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u043a\u043e\u0434\u0430 \u0431\u044b\u043b\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a\u0430\u044f:type CreatePaymentRequest struct {UserID      string  `json:&#187;user_id&#187;`MerchantID  string  `json:&#187;merchant_id&#187;`Amount      float64 `json:&#187;amount&#187;`Currency    string  `json:&#187;currency&#187;`Target      string  `json:&#187;target_currency&#187;`}func (s *Service) CreatePayment(ctx context.Context, req CreatePaymentRequest) error {rate, err := s.fx.GetRate(ctx, req.Currency, req.Target)if err != nil {return err}amountToCharge := math.Round(req.Amount*rate*100) \/ 100if err := s.billing.Charge(ctx, req.UserID, amountToCharge, req.Target); err != nil {return err}return s.ledger.Append(ctx, LedgerEntry{UserID:   req.UserID,Amount:   amountToCharge,Currency: req.Target,})}\u041d\u0430 \u0440\u0435\u0432\u044c\u044e \u044d\u0442\u043e \u043d\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b\u043e \u0443\u0436\u0430\u0441\u043d\u043e. \u0414\u0430, float64 \u0434\u043b\u044f \u0434\u0435\u043d\u0435\u0433 \u043f\u0430\u0445\u043d\u0435\u0442 \u043f\u043b\u043e\u0445\u043e, \u043d\u043e \u201c\u043f\u043e\u043a\u0430 \u0431\u044b\u0441\u0442\u0440\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c, \u043f\u043e\u0442\u043e\u043c \u043f\u043e\u043f\u0440\u0430\u0432\u0438\u043c\u201d. \u0417\u043d\u0430\u043a\u043e\u043c\u0430\u044f \u0444\u0440\u0430\u0437\u0430, \u0434\u0430.\u041f\u043e\u0442\u043e\u043c \u043f\u043e\u0448\u043b\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0432\u0435\u0440\u043a\u0438.\u0411\u0430\u0433 \u043f\u0435\u0440\u0432\u044b\u0439: \u0443 \u043f\u043b\u0430\u0442\u0435\u0436\u0430 \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0442\u0440\u0438 \u043a\u0443\u0440\u0441\u0430\u0412 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043c\u043e\u043c\u0435\u043d\u0442 \u0444\u0438\u043d\u0430\u043d\u0441\u044b \u043f\u0440\u0438\u0441\u043b\u0430\u043b\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u043d\u0430 47 \u0441\u0442\u0440\u043e\u043a. \u0422\u0430\u043c \u0431\u044b\u043b\u0438 \u0440\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u0430\u043c: 2-5 \u0442\u0435\u043d\u0433\u0435 \u043d\u0430 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u0437\u0430\u043a\u0430\u0437\u0430\u0445 \u0438 \u0434\u043e 180 \u0442\u0435\u043d\u0433\u0435 \u043d\u0430 \u043a\u0440\u0443\u043f\u043d\u044b\u0445.\u0414\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b\u043e \u0442\u0443\u043f\u043e:\u0412 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435: 12 430.00 KZT\u0412 SMS \u043e\u0442 \u0431\u0430\u043d\u043a\u0430: 12 435.27 KZT\u0412 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u0441\u0430\u043f\u043f\u043e\u0440\u0442\u0430: 12 428.91 KZT\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u043e\u0434\u0443\u043c\u0430\u043b\u0438 \u043d\u0430 \u043e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435. \u042d\u0442\u043e \u0431\u044b\u043b\u0430 \u0445\u043e\u0440\u043e\u0448\u0430\u044f \u0433\u0438\u043f\u043e\u0442\u0435\u0437\u0430, \u043d\u043e \u043d\u0435 \u0432\u0441\u044f \u043f\u0440\u0430\u0432\u0434\u0430.\u041e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0432\u0435\u0441\u0435\u043b\u0435\u0435:checkout-api     -&gt; Redis, TTL 5 \u043c\u0438\u043d\u0443\u0442payment-service  -&gt; fx-rate-service \u043f\u043e HTTPledger-service   -&gt; \u0442\u0430\u0431\u043b\u0438\u0446\u0430 fx_rates_snapshot \u0432 Postgres\u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u043e\u0434\u043d\u043e\u0439 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0443\u0447\u0430\u0441\u0442\u0432\u043e\u0432\u0430\u043b\u0438 \u0442\u0440\u0438 \u0440\u0430\u0437\u043d\u044b\u0445 \u043a\u0443\u0440\u0441\u0430.\u0412 \u043b\u043e\u0433\u0430\u0445 \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u044b\u043b \u201c\u043f\u0440\u0430\u0432\u201d:{  &#171;service&#187;: &#171;checkout-api&#187;,  &#171;payment_id&#187;: &#171;pay_7f31&#187;,  &#171;pair&#187;: &#171;USD\/KZT&#187;,  &#171;rate&#187;: &#171;448.12&#187;,  &#171;source&#187;: &#171;redis&#187;,  &#171;rate_age_sec&#187;: 241}{  &#171;service&#187;: &#171;payment-service&#187;,  &#171;payment_id&#187;: &#171;pay_7f31&#187;,  &#171;pair&#187;: &#171;USD\/KZT&#187;,  &#171;rate&#187;: &#171;448.31&#187;,  &#171;source&#187;: &#171;fx-rate-service&#187;,  &#171;latency_ms&#187;: 96}{  &#171;service&#187;: &#171;ledger-service&#187;,  &#171;payment_id&#187;: &#171;pay_7f31&#187;,  &#171;pair&#187;: &#171;USD\/KZT&#187;,  &#171;rate&#187;: &#171;448.08&#187;,  &#171;source&#187;: &#171;postgres_snapshot&#187;,  &#171;snapshot&#187;: &#171;2026-04-14T09:00:00Z&#187;}\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u043d\u0438 \u043e\u0434\u0438\u043d \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0435 \u0432\u0440\u0430\u043b. \u041d\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0432 \u0446\u0435\u043b\u043e\u043c \u0432\u0440\u0430\u043b\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0443.\u0412\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u0441\u0442\u044b\u043b\u044c \u0431\u044b\u043b \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u0438\u0437\u0435\u043c\u043b\u0435\u043d\u043d\u044b\u0439: \u043d\u0430 \u0434\u0432\u0430 \u0434\u043d\u044f \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u043b\u0438 \u043a\u0435\u0448 \u043a\u0443\u0440\u0441\u0430 \u0432 checkout-api \u0434\u043b\u044f \u0432\u0430\u043b\u044e\u0442\u043d\u044b\u0445 \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439 \u0432\u044b\u0448\u0435 50 000 KZT. \u0414\u0430, \u0441\u0442\u0430\u043b\u043e \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u0435\u0435: p95 checkout \u0432\u044b\u0440\u043e\u0441 \u0441 120 \u043c\u0441 \u0434\u043e 430-500 \u043c\u0441. \u0417\u0430\u0442\u043e \u0441\u0430\u043f\u043f\u043e\u0440\u0442 \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043b \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0442\u0438\u043a\u0435\u0442\u044b \u043f\u0430\u0447\u043a\u0430\u043c\u0438.\u041d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0444\u0438\u043a\u0441 \u0431\u044b\u043b \u0434\u0440\u0443\u0433\u043e\u0439: \u043a\u0443\u0440\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0447\u0430\u0441\u0442\u044c\u044e \u043f\u043b\u0430\u0442\u0435\u0436\u0430, \u0430 \u043d\u0435 \u0447\u0435\u043c-\u0442\u043e, \u0447\u0442\u043e \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043e\u0441\u0442\u0430\u0435\u0442 \u043a\u0430\u043a \u0445\u043e\u0447\u0435\u0442.Quote \u043a\u0430\u043a \u0447\u0430\u0441\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u0430, \u0430 \u043d\u0435 DTO \u0434\u043b\u044f \u0444\u0440\u043e\u043d\u0442\u0430\u041c\u044b \u0432\u0432\u0435\u043b\u0438 Quote. \u041d\u0435 \u043a\u0430\u043a \u201c\u043e\u0442\u0432\u0435\u0442 \u0440\u0443\u0447\u043a\u0438 \u0434\u043b\u044f UI\u201d, \u0430 \u043a\u0430\u043a \u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0431\u0438\u0437\u043d\u0435\u0441-\u0440\u0435\u0448\u0435\u043d\u0438\u0435: \u0432\u043e\u0442 \u044d\u0442\u0430 \u0441\u0443\u043c\u043c\u0430, \u0432\u043e\u0442 \u044d\u0442\u043e\u0442 \u043a\u0443\u0440\u0441, \u0432\u043e\u0442 \u0441\u0440\u043e\u043a \u0436\u0438\u0437\u043d\u0438.type Currency stringtype Money struct {amount   decimal.Decimalcurrency Currency}type RateSnapshot struct {Pair      stringValue     decimal.DecimalSource    stringVersion   int64CreatedAt time.TimeExpiresAt time.Time}type Quote struct {ID        stringUserID    stringFrom      MoneyTo        MoneyRate      RateSnapshotCreatedAt time.Time}\u041f\u043b\u0430\u0442\u0435\u0436 \u0442\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u043b\u0441\u044f \u043d\u0435 \u0438\u0437 amount + currency, \u0430 \u0438\u0437 quote_id.func NewPayment(quote Quote, idemKey string) (*Payment, error) {if idemKey == &#171;&#187; {return nil, ErrEmptyIdempotencyKey}if time.Now().After(quote.Rate.ExpiresAt) {return nil, ErrQuoteExpired}return &amp;Payment{ID:             newPaymentID(),UserID:         quote.UserID,QuoteID:        quote.ID,Debit:          quote.From,Credit:         quote.To,Rate:           quote.Rate,Status:         PaymentCreated,IdempotencyKey: idemKey,}, nil}\u0421 \u044d\u0442\u043e\u0433\u043e \u043c\u043e\u043c\u0435\u043d\u0442\u0430 billing-service \u0438 ledger-service \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0438\u043c\u0435\u043b\u0438 \u043f\u0440\u0430\u0432\u0430 \u201c\u0443\u0442\u043e\u0447\u043d\u0438\u0442\u044c \u043a\u0443\u0440\u0441\u201d. \u041e\u043d\u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u043b\u0438 \u0443\u0436\u0435 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u043d\u0443\u044e \u0441\u0443\u043c\u043c\u0443 \u0438 rate_version.\u0422\u0430\u0431\u043b\u0438\u0446\u0430 payments \u0441\u0442\u0430\u043b\u0430 \u043c\u0435\u043d\u0435\u0435 \u043a\u0440\u0430\u0441\u0438\u0432\u043e\u0439:payment_idquote_iddebit_amountdebit_currencycredit_amountcredit_currencyrate_pairrate_valuerate_versionrate_expires_atstatusidempotency_key\u0421 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u043d\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0442\u0430\u043a \u0441\u0435\u0431\u0435. \u0421 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u0440\u0430\u0437\u0431\u043e\u0440\u0430 \u0438\u043d\u0446\u0438\u0434\u0435\u043d\u0442\u0430 \u0447\u0435\u0440\u0435\u0437 \u043c\u0435\u0441\u044f\u0446 &#8212; \u043e\u0442\u043b\u0438\u0447\u043d\u043e. \u041c\u043e\u0436\u043d\u043e \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043e\u0434\u043d\u0443 \u0441\u0442\u0440\u043e\u043a\u0443 \u0438 \u043f\u043e\u043d\u044f\u0442\u044c, \u043f\u043e\u0447\u0435\u043c\u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0443 \u0441\u043f\u0438\u0441\u0430\u043b\u0438 \u0438\u043c\u0435\u043d\u043d\u043e \u0441\u0442\u043e\u043b\u044c\u043a\u043e.\u0411\u0430\u0433 \u0432\u0442\u043e\u0440\u043e\u0439: float64 \u043f\u0440\u043e\u0448\u0435\u043b \u0440\u0435\u0432\u044c\u044e, \u043d\u043e \u043d\u0435 \u043f\u0440\u043e\u0448\u0435\u043b \u0434\u0435\u043d\u044c\u0433\u0438\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0431\u044b\u043b\u0430 \u0443\u0436\u0435 \u0431\u0430\u043d\u0430\u043b\u044c\u043d\u0430\u044f. \u0413\u0434\u0435-\u0442\u043e \u0443 \u043d\u0430\u0441 \u0431\u044b\u043b float64, \u0433\u0434\u0435-\u0442\u043e numeric(18, 6), \u0433\u0434\u0435-\u0442\u043e decimal.Decimal.\u0421\u0438\u043c\u043f\u0442\u043e\u043c:expected ledger amount: 12435.27actual ledger amount:   12435.26diff: 0.01\u041e\u0434\u0438\u043d \u0442\u0435\u043d\u0433\u0435 \u0438\u043b\u0438 \u043e\u0434\u043d\u0430 \u043a\u043e\u043f\u0435\u0439\u043a\u0430 &#8212; \u0437\u0432\u0443\u0447\u0438\u0442 \u0441\u043c\u0435\u0448\u043d\u043e, \u043f\u043e\u043a\u0430 \u0442\u0430\u043a\u0438\u0445 \u0441\u0442\u0440\u043e\u043a \u043d\u0435 8 000 \u0437\u0430 \u043d\u043e\u0447\u044c.\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0431\u044b\u043b\u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043e\u043a\u0440\u0443\u0433\u043b\u044f\u043b\u0438 \u0432 \u0442\u0440\u0435\u0445 \u043c\u0435\u0441\u0442\u0430\u0445:\/\/ payment-serviceamount := math.Round(raw*100) \/ 100\/\/ billing-serviceamount := decimal.NewFromFloat(raw).Round(2)&#8212; nightly reportround(amount * rate, 2)\u0418 \u0432\u043e\u0442 \u044d\u0442\u043e decimal.NewFromFloat(raw) \u043f\u043e\u0442\u043e\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0432\u0441\u043f\u043e\u043c\u0438\u043d\u0430\u043b\u0438 \u043d\u0435\u0445\u043e\u0440\u043e\u0448\u0438\u043c\u0438 \u0441\u043b\u043e\u0432\u0430\u043c\u0438.\u0412\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435: \u0441\u0434\u0435\u043b\u0430\u043b\u0438 nightly job, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u043b \u0440\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u0434\u043e 1 KZT \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0442\u0430\u0431\u043b\u0438\u0446\u0443 rounding_adjustments. \u041d\u0435\u043a\u0440\u0430\u0441\u0438\u0432\u043e. \u0417\u0430\u0442\u043e \u043e\u0442\u0447\u0435\u0442\u044b \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043b\u0438 \u043f\u0430\u0434\u0430\u0442\u044c \u043a\u0430\u0436\u0434\u043e\u0435 \u0443\u0442\u0440\u043e, \u043f\u043e\u043a\u0430 \u043c\u044b \u0432\u044b\u0447\u0438\u0449\u0430\u043b\u0438 \u043a\u043e\u0434.\u041d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435:\u0437\u0430\u043f\u0440\u0435\u0442\u0438\u043b\u0438 float64 \u0432 \u0434\u043e\u043c\u0435\u043d\u043d\u044b\u0445 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430\u0445;\u0441\u0443\u043c\u043c\u044b \u043d\u0430\u0447\u0430\u043b\u0438 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u043e\u0439;\u043e\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043b\u0438 \u043a \u0432\u0430\u043b\u044e\u0442\u0435;\u0432\u0441\u0435 \u0440\u0430\u0441\u0447\u0435\u0442\u044b \u0432\u044b\u043d\u0435\u0441\u043b\u0438 \u0432 Money.func NewMoney(raw string, currency Currency) (Money, error) {amount, err := decimal.NewFromString(raw)if err != nil {return Money{}, err}if amount.IsNegative() {return Money{}, ErrNegativeAmount}return Money{amount: amount, currency: currency}, nil}func (m Money) Convert(rate decimal.Decimal, target Currency) Money {return Money{amount:   m.amount.Mul(rate),currency: target,}}func (m Money) RoundByCurrency() Money {scale := map[Currency]int32{&#171;USD&#187;: 2,&#187;EUR&#187;: 2,&#187;KZT&#187;: 2,&#187;JPY&#187;: 0,}[m.currency]return Money{amount:   m.amount.Round(scale),currency: m.currency,}}\u041d\u0435 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u043e: decimal \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u0435\u0435. \u041d\u0430 batch-\u043f\u0435\u0440\u0435\u0441\u0447\u0435\u0442\u0435 200k \u0441\u0442\u0440\u043e\u043a \u0432\u0440\u0435\u043c\u044f \u0432\u044b\u0440\u043e\u0441\u043b\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0441 1.8 \u0441\u0435\u043a\u0443\u043d\u0434\u044b \u0434\u043e 6.4. \u041d\u043e \u044d\u0442\u043e \u0431\u044b\u043b backoffice job, \u0430 \u043d\u0435 \u0433\u043e\u0440\u044f\u0447\u0430\u044f \u0440\u0443\u0447\u043a\u0430. \u0420\u0435\u0448\u0438\u043b\u0438, \u0447\u0442\u043e \u043b\u0443\u0447\u0448\u0435 \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e \u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e, \u0447\u0435\u043c \u0431\u044b\u0441\u0442\u0440\u043e \u0438 \u043f\u043e\u0442\u043e\u043c \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u0442\u044c \u0444\u0438\u043d\u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0443 \u0440\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f.\u0411\u0430\u0433 \u0442\u0440\u0435\u0442\u0438\u0439: \u0442\u0430\u0439\u043c\u0430\u0443\u0442 \u043d\u0435 \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u043f\u043b\u0430\u0442\u0435\u0436 \u043d\u0435 \u043f\u0440\u043e\u0448\u0435\u043b\u0415\u0449\u0435 \u043e\u0434\u0438\u043d \u043d\u0435\u043f\u0440\u0438\u044f\u0442\u043d\u044b\u0439 \u044d\u043f\u0438\u0437\u043e\u0434 \u0431\u044b\u043b \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u043e\u043c.payment-service \u0445\u043e\u0434\u0438\u043b \u0432 acquirer-api \u0441 \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u043e\u043c 3 \u0441\u0435\u043a\u0443\u043d\u0434\u044b. \u041e\u0431\u044b\u0447\u043d\u043e \u043e\u0442\u0432\u0435\u0442 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u043b \u0437\u0430 400-700 \u043c\u0441. \u041d\u043e \u043f\u0430\u0440\u0443 \u0440\u0430\u0437 \u0432 \u0434\u0435\u043d\u044c p99 \u0443\u043b\u0435\u0442\u0430\u043b \u0434\u043e 5-8 \u0441\u0435\u043a\u0443\u043d\u0434.\u0427\u0442\u043e \u0432\u0438\u0434\u0435\u043b\u0438 \u043c\u044b:POST \/charge -&gt; context deadline exceededretry after 500msPOST \/charge -&gt; 200 OK\u0427\u0442\u043e \u0432\u0438\u0434\u0435\u043b \u043a\u043b\u0438\u0435\u043d\u0442 \u0432 \u0431\u0430\u043d\u043a\u0435:\u0421\u043f\u0438\u0441\u0430\u043d\u0438\u0435 12 435.27 KZT\u0421\u043f\u0438\u0441\u0430\u043d\u0438\u0435 12 435.27 KZT\u041f\u0435\u0440\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0435 \u0443\u043c\u0435\u0440. \u041e\u043d \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0442\u0432\u0435\u0442\u0438\u043b \u043f\u043e\u0437\u0436\u0435. \u0410 \u043c\u044b \u0443\u0436\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b\u0438 \u0432\u0442\u043e\u0440\u043e\u0439.\u0412 \u043b\u043e\u0433\u0430\u0445 \u044d\u0442\u043e \u0431\u044b\u043b\u043e \u0442\u0430\u043a:payment_id=pay_9921 attempt=1 timeout_ms=3000 err=deadline_exceededpayment_id=pay_9921 attempt=2 status=authorized acquirer_id=acq_771payment_id=pay_9921 callback attempt=1 status=authorized acquirer_id=acq_770\u041f\u0435\u0440\u0432\u044b\u0439 \u043a\u043e\u0441\u0442\u044b\u043b\u044c: \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u043b\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 retry \u0434\u043b\u044f POST \/charge, \u0435\u0441\u043b\u0438 \u043d\u0435\u0442 \u044f\u0432\u043d\u043e\u0433\u043e \u043e\u0442\u0432\u0435\u0442\u0430 \u043e\u0442 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430. \u042d\u0442\u043e \u0441\u0440\u0430\u0437\u0443 \u0441\u043d\u0438\u0437\u0438\u043b\u043e \u0440\u0438\u0441\u043a \u0434\u0432\u043e\u0439\u043d\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044f, \u043d\u043e \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u043b\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 unknown.\u041f\u043e\u0442\u043e\u043c \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u044d\u0442\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c\u0438 API:idempotency_key \u0441\u0442\u0430\u043b \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c;(merchant_id, idempotency_key) \u043f\u043e\u043b\u0443\u0447\u0438\u043b unique index;callback \u0441\u0442\u0430\u043b \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u0434\u043e\u043c\u0435\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 Authorize;\u0437\u0430\u0432\u0438\u0441\u0448\u0438\u0435 \u043f\u043b\u0430\u0442\u0435\u0436\u0438 \u0443\u0448\u043b\u0438 \u0432 reconciliation job.create unique index payments_idem_uqon payments (merchant_id, idempotency_key);func (p *Payment) Authorize(acquirerID string) error {switch p.Status {case PaymentAuthorized:return nilcase PaymentCreated, PaymentUnknown:p.Status = PaymentAuthorizedp.AcquirerID = acquirerIDreturn nildefault:return ErrInvalidPaymentState}}\u0418 \u0434\u0430, PaymentUnknown &#8212; \u043d\u0435 \u0441\u0430\u043c\u044b\u0439 \u043f\u0440\u0438\u044f\u0442\u043d\u044b\u0439 \u0441\u0442\u0430\u0442\u0443\u0441. \u041d\u043e \u043e\u043d \u0447\u0435\u0441\u0442\u043d\u044b\u0439. \u0412\u043d\u0435\u0448\u043d\u0438\u0439 \u043c\u0438\u0440 \u0438\u043d\u043e\u0433\u0434\u0430 \u043d\u0435 \u0434\u0430\u0435\u0442 \u0442\u0435\u0431\u0435 \u0431\u0438\u043d\u0430\u0440\u043d\u043e\u0433\u043e \u043e\u0442\u0432\u0435\u0442\u0430 \u201c\u0443\u0441\u043f\u0435\u0448\u043d\u043e\/\u043d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e\u201d.reconciliation-worker \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043b\u0441\u044f \u043a\u0430\u0436\u0434\u0443\u044e \u043c\u0438\u043d\u0443\u0442\u0443 \u0438 \u0434\u043e\u0431\u0438\u0440\u0430\u043b \u043f\u043b\u0430\u0442\u0435\u0436\u0438 \u0441\u0442\u0430\u0440\u0448\u0435 90 \u0441\u0435\u043a\u0443\u043d\u0434:select payment_id, acquirer_request_idfrom paymentswhere status = &#8216;unknown&#8217;  and created_at &lt; now() &#8212; interval &#8217;90 seconds&#8217;limit 500;\u042d\u0442\u043e \u043a\u043e\u0441\u0442\u044b\u043b\u044c? \u0427\u0430\u0441\u0442\u0438\u0447\u043d\u043e. \u041d\u043e \u0431\u0435\u0437 \u043d\u0435\u0433\u043e \u043b\u044e\u0431\u0430\u044f \u043a\u0440\u0430\u0441\u0438\u0432\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043b\u043e\u043c\u0430\u043b\u0430\u0441\u044c \u043e\u0431 API, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0438\u043d\u043e\u0433\u0434\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u043b\u043e 502, \u0445\u043e\u0442\u044f \u043f\u043b\u0430\u0442\u0435\u0436 \u0443\u0436\u0435 \u0431\u044b\u043b \u0441\u043e\u0437\u0434\u0430\u043d.\u0411\u0430\u0433 \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u044b\u0439: read model \u043e\u0442\u0441\u0442\u0430\u0435\u0442, \u0441\u0430\u043f\u043f\u043e\u0440\u0442 \u043f\u0430\u043d\u0438\u043a\u0443\u0435\u0442\u041f\u043e\u0441\u043b\u0435 \u0444\u0438\u043a\u0441\u0430 Quote \u043c\u044b \u0434\u0443\u043c\u0430\u043b\u0438, \u0447\u0442\u043e \u0440\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u0438\u0441\u044c. \u041d\u0443 \u0434\u0430, \u043a\u043e\u043d\u0435\u0447\u043d\u043e.\u0427\u0435\u0440\u0435\u0437 \u043d\u0435\u0434\u0435\u043b\u044e \u0441\u0430\u043f\u043f\u043e\u0440\u0442 \u043f\u0438\u0448\u0435\u0442: \u201c\u0443 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0441\u043f\u0438\u0441\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e, \u043d\u043e \u0432 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u0441\u0442\u0430\u0440\u0430\u044f \u0441\u0443\u043c\u043c\u0430\u201d. \u041a\u043b\u0438\u0435\u043d\u0442 \u0443\u0436\u0435 \u043d\u0435\u0440\u0432\u043d\u0438\u0447\u0430\u0435\u0442, \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u0442\u043e\u0436\u0435.\u041e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c, \u0447\u0442\u043e ledger-read-model \u043e\u0442\u0441\u0442\u0430\u0432\u0430\u043b \u043e\u0442 Kafka \u043d\u0430 3-4 \u043c\u0438\u043d\u0443\u0442\u044b \u043f\u043e\u0441\u043b\u0435 \u0434\u0435\u043f\u043b\u043e\u044f. \u0421\u0430\u043c \u043f\u043b\u0430\u0442\u0435\u0436 \u0431\u044b\u043b \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439, \u0437\u0430\u043f\u0438\u0441\u044c \u0432 payments \u0442\u043e\u0436\u0435. \u041d\u043e \u0430\u0434\u043c\u0438\u043d\u043a\u0430 \u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0430 \u043d\u0435 \u0442\u0443\u0434\u0430.\u041c\u0435\u0442\u0440\u0438\u043a\u0430 lag \u0442\u043e\u0433\u0434\u0430 \u0431\u044b\u043b\u0430, \u043d\u043e \u0430\u043b\u0435\u0440\u0442 \u0441\u0442\u043e\u044f\u043b \u043d\u0430 10 \u043c\u0438\u043d\u0443\u0442. \u041f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u201c\u043c\u0435\u043d\u044c\u0448\u0435 \u043d\u0435 \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e\u201d. \u041e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c, \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e, \u0435\u0441\u043b\u0438 \u044d\u0442\u043e\u0439 \u0430\u0434\u043c\u0438\u043d\u043a\u043e\u0439 \u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0430\u043f\u043f\u043e\u0440\u0442 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u043e\u0433\u043e \u0438\u043d\u0446\u0438\u0434\u0435\u043d\u0442\u0430.\u0427\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u0431\u044b\u0441\u0442\u0440\u043e:\u0432 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u0440\u044f\u0434\u043e\u043c \u0441 \u0441\u0443\u043c\u043c\u043e\u0439 \u043f\u043e\u043a\u0430\u0437\u0430\u043b\u0438 quote_id \u0438 rate_version;\u0434\u043b\u044f \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439 \u043c\u043b\u0430\u0434\u0448\u0435 15 \u043c\u0438\u043d\u0443\u0442 \u043d\u0430\u0447\u0430\u043b\u0438 \u0447\u0438\u0442\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0438\u0437 payments;consumer lag \u043f\u043e payment-events \u043e\u043f\u0443\u0441\u0442\u0438\u043b\u0438 \u0432 \u0430\u043b\u0435\u0440\u0442\u0430\u0445 \u0434\u043e 60 \u0441\u0435\u043a\u0443\u043d\u0434.\u0427\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043b\u0438 \u043f\u043e\u0442\u043e\u043c:fresh payment view:  source = payments tablehistorical\/report view:  source = ledger read model\u041d\u0435 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u043e, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0432 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u043f\u043e\u044f\u0432\u0438\u043b\u043e\u0441\u044c \u0434\u0432\u0435 \u0432\u0435\u0442\u043a\u0438 \u0447\u0442\u0435\u043d\u0438\u044f. \u041d\u043e \u044d\u0442\u043e \u043b\u0443\u0447\u0448\u0435, \u0447\u0435\u043c \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0441\u0430\u043f\u043f\u043e\u0440\u0442\u0443 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438 \u0433\u043e\u0432\u043e\u0440\u0438\u0442\u044c \u201c\u043d\u0443 \u043e\u043d\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u0434\u043e\u0435\u0434\u0435\u0442\u201d.\u0413\u0434\u0435 \u0437\u0434\u0435\u0441\u044c DDD, \u0435\u0441\u043b\u0438 \u0431\u0435\u0437 \u0440\u0435\u043b\u0438\u0433\u0438\u0438DDD \u043f\u043e\u043c\u043e\u0433 \u043d\u0435 \u043f\u0430\u043f\u043a\u043e\u0439 domain. \u041f\u0430\u043f\u043a\u0443 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0437\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u0443\u0433\u043e\u0434\u043d\u043e \u0438 \u0432\u0441\u0435 \u0440\u0430\u0432\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043a\u0430\u0448\u0443.\u041f\u043e\u043b\u044c\u0437\u0430 \u0431\u044b\u043b\u0430 \u0432 \u0434\u0440\u0443\u0433\u043e\u043c: \u043c\u044b \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043b\u0438 \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u043a\u0443\u0440\u0441 \u0432\u0430\u043b\u044e\u0442 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0434\u0435\u0442\u0430\u043b\u044c\u044e.\u0420\u0430\u043d\u044c\u0448\u0435 \u043f\u0440\u0430\u0432\u0434\u0430 \u0431\u044b\u043b\u0430 \u0440\u0430\u0437\u043c\u0430\u0437\u0430\u043d\u0430:\u043a\u0443\u0440\u0441 \u0432 Redis\u043a\u0443\u0440\u0441 \u0432 fx-rate-service\u043a\u0443\u0440\u0441 \u0432 Postgres\u0441\u0443\u043c\u043c\u0430 \u0432 billing\u0441\u0443\u043c\u043c\u0430 \u0432 ledger\u041f\u043e\u0441\u043b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u043f\u0440\u0430\u0432\u0434\u0430 \u0441\u0442\u0430\u043b\u0430 \u0431\u043b\u0438\u0436\u0435 \u043a \u043f\u043b\u0430\u0442\u0435\u0436\u0443:type Payment struct {ID&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-476540","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/476540","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=476540"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/476540\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=476540"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=476540"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=476540"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}