{"id":482774,"date":"2026-06-08T08:43:43","date_gmt":"2026-06-08T08:43:43","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=482774"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=482774","title":{"rendered":"\u0412\u0435\u0431\u0445\u0443\u043a\u0438 \u043e\u043f\u043b\u0430\u0442\u044b \u042eKassa, IP-check, event log, idempotency \u0438 \u0430\u0432\u0430\u0440\u0438\u0439\u043d\u044b\u0439 capture"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0439 \u043a\u043e\u0434 \u043e\u0431\u044b\u0447\u043d\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0440\u043e\u0432\u043d\u044b\u043c \u0440\u043e\u0432\u043d\u043e \u0434\u043e \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u0431\u043e\u044f. \u041f\u043e\u043a\u0430 \u043f\u043b\u0430\u0442\u0435\u0436\u0438 \u0438\u0434\u0443\u0442 \u043f\u043e \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u043c\u0443 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044e, \u043a\u0430\u0436\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043e\u043f\u043b\u0430\u0442\u0443, \u0434\u043e\u0436\u0434\u0430\u0442\u044c\u0441\u044f \u0432\u0435\u0431\u0445\u0443\u043a\u0430 \u0438 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u0442\u0430\u0442\u0443\u0441. \u041d\u043e \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0435\u0431\u0445\u0443\u043a \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e, \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043f\u043e\u0437\u0436\u0435 \u043d\u0443\u0436\u043d\u043e\u0433\u043e, \u043f\u0440\u0438\u043b\u0435\u0442\u0430\u0435\u0442 \u043e\u0442 \u043d\u0435 \u0442\u043e\u0433\u043e IP, \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0443\u0436\u0435 \u0436\u0438\u0432\u0435\u0442 \u0432 \u043e\u0434\u043d\u043e\u043c \u0441\u0442\u0430\u0442\u0443\u0441\u0435, \u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u0431\u0430\u0437\u0430 \u0432 \u0434\u0440\u0443\u0433\u043e\u043c, \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u044f\u0441\u043d\u043e, \u0447\u0442\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0443\u0440 \u0431\u0435\u0437 \u0437\u0430\u0449\u0438\u0442 \u043f\u043e\u0447\u0442\u0438 \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u0440\u0435\u0442.<\/p>\n<p>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u0432\u0435\u0431\u0445\u0443\u043a \u043d\u0435\u043b\u044c\u0437\u044f \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u0438\u0441\u0442\u0438\u043d\u043e\u0439 \u0431\u0435\u0437 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438, \u043d\u0435\u043b\u044c\u0437\u044f \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u0431\u0435\u0437 \u0436\u0443\u0440\u043d\u0430\u043b\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u043d\u0435\u043b\u044c\u0437\u044f \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0442\u044c capture \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u043c \u043a\u043b\u044e\u0447\u043e\u043c, \u0438 \u043d\u0435\u043b\u044c\u0437\u044f \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0431\u0435\u0437 \u0430\u0432\u0430\u0440\u0438\u0439\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438, \u0435\u0441\u043b\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0433\u0434\u0435-\u0442\u043e \u0440\u0430\u0437\u043e\u0448\u0435\u043b\u0441\u044f.<\/p>\n<p>\u0412 \u043e\u0434\u043d\u043e\u043c \u0438\u0437 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u044d\u0442\u043e\u0442 \u0443\u0437\u0435\u043b \u0431\u044b\u043b \u0441\u043e\u0431\u0440\u0430\u043d \u0442\u0430\u043a, \u043f\u0435\u0440\u0432\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0441 <code>capture=False<\/code>, \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0439 webhook \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043f\u043e IP, \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0438\u0448\u0435\u0442\u0441\u044f \u0432 \u0436\u0443\u0440\u043d\u0430\u043b, \u043f\u043e\u0442\u043e\u043c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, capture \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442\u0441\u044f \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u044b\u043c idempotence key, \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043f\u043e \u0441\u0443\u043c\u043c\u0435, \u0432\u0430\u043b\u044e\u0442\u0435 \u0438 metadata, \u0430 \u043d\u0430 \u0441\u043b\u0443\u0447\u0430\u0439 \u0440\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0440\u0443\u0447\u043d\u043e\u0439 confirm, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043c\u0435\u0435\u0442 \u0434\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u0442\u0430\u0442\u0443\u0441 \u0438\u0437 \u042eKassa \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u0431\u0430\u0437\u0443.<\/p>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c \u0437\u0430\u0434\u0430\u0447\u0430 \u0442\u0443\u0442 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0440\u0438\u043d\u044f\u0442\u044c webhook, \u0430 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0443\u0440, \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u0432\u0435\u0440\u0438\u0442\u044c.<\/p>\n<h3>\u0413\u0434\u0435 \u043e\u0431\u044b\u0447\u043d\u043e \u043b\u043e\u043c\u0430\u0435\u0442\u0441\u044f \u043d\u0430\u0438\u0432\u043d\u0430\u044f \u0441\u0445\u0435\u043c\u0430<\/h3>\n<p>\u0421\u0430\u043c\u044b\u0439 \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0439 \u043f\u0443\u0442\u044c \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a. \u0424\u0440\u043e\u043d\u0442 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043f\u043b\u0430\u0442\u0435\u0436, \u042eKassa \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0435\u0442 webhook, backend \u043f\u043e\u043c\u0435\u0447\u0430\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u044c \u043a\u0430\u043a \u0443\u0441\u043f\u0435\u0448\u043d\u0443\u044e. \u041d\u0430 \u0434\u0435\u043c\u043e \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e. \u0412 \u0436\u0438\u0432\u043e\u043c \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0435 \u043d\u0435\u0442.<\/p>\n<p>Webhook \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0439\u0442\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437. \u041c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0439\u0442\u0438 \u043f\u043e\u0437\u0436\u0435. \u041c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0439\u0442\u0438 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 <code>waiting_for_capture<\/code>, \u0430 \u043d\u0435 <code>succeeded<\/code>. \u041c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u0442\u043e\u0433\u043e \u0436\u0435 <code>payment.succeeded<\/code>. \u041c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0441\u0431\u043e\u0439 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u043c\u0435\u0436\u0434\u0443 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435\u043c webhook \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438. \u041c\u043e\u0436\u0435\u0442 \u0441\u043b\u0443\u0447\u0438\u0442\u044c\u0441\u044f \u0442\u0430\u043a, \u0447\u0442\u043e \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0443\u0436\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d, \u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u0431\u0430\u0437\u0430 \u043e\u0431 \u044d\u0442\u043e\u043c \u043d\u0435 \u0437\u043d\u0430\u0435\u0442.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432 \u0442\u0430\u043a\u043e\u0439 \u0441\u0445\u0435\u043c\u0435 \u043d\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0436\u0443\u0440\u043d\u0430\u043b\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u043d\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438, \u0447\u0442\u043e webhook \u0432\u043e\u043e\u0431\u0449\u0435 \u043f\u0440\u0438\u0448\u0435\u043b \u043e\u0442 \u042eKassa, \u043d\u0435\u0442 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0433\u043e capture \u0438 \u043d\u0435\u0442 \u0440\u0443\u0447\u043d\u043e\u0433\u043e rescue-\u043f\u0443\u0442\u0438, \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0443\u0440 \u0431\u044b\u0441\u0442\u0440\u043e \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043d\u0435\u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u044b\u043c. \u0412 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u043e\u0434\u043d\u043e, \u0432 API \u0434\u0440\u0443\u0433\u043e\u0435, \u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0442\u0440\u0435\u0442\u044c\u0435.<\/p>\n<h3>\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043f\u043b\u0430\u0442\u0435\u0436\u0430 \u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0436\u0443\u0440\u043d\u0430\u043b \u0441\u043e\u0431\u044b\u0442\u0438\u0439<\/h3>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u0434\u0432\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438. \u041f\u0435\u0440\u0432\u0430\u044f \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0441\u0430\u043c \u043f\u043b\u0430\u0442\u0435\u0436 \u0438 \u0435\u0433\u043e \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u0439 \u0446\u0438\u043a\u043b. \u0412\u0442\u043e\u0440\u0430\u044f \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0436\u0443\u0440\u043d\u0430\u043b \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439.<\/p>\n<p>\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043f\u043b\u0430\u0442\u0435\u0436 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 <code>KassaPayment<\/code>, \u0430 \u0436\u0443\u0440\u043d\u0430\u043b \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 <code>PaymentEventLog<\/code>.<\/p>\n<pre><code class=\"python\"># payment\/models.pyclass KassaPayment(models.Model):    user = models.ForeignKey(User, related_name='kassa_payments', on_delete=models.CASCADE)    kassa_payment_id = models.CharField(max_length=250, blank=True, null=True)    amount = models.DecimalField(max_digits=10, decimal_places=2)    subscription_type = models.CharField(        choices=[('monthly', 'Monthly'), ('yearly', 'Yearly'), ('forever', 'Forever')],        max_length=10    )    status = models.CharField(        choices=[('pending', 'Pending'), ('completed', 'Completed'), ('failed', 'Failed'),                 ('refund', 'Refund'), ('refund_failed', 'Refund Failed')],        max_length=20,        default='pending'    )    kassa_payment_status = models.CharField(        choices=[('waiting_for_capture', 'Waiting for capture'),                 ('succeeded', 'Succeeded'),                 ('failed', 'Failed'),                 ('canceled', 'Canceled'),                 ('refund_succeeded', 'Refund Succeeded')],        max_length=20,        default='waiting_for_capture'    )    income_amount = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)    capture_idem_key = models.CharField(max_length=64, blank=True, null=True)class PaymentEventLog(models.Model):    event_id = models.CharField(max_length=64, db_index=True)    event_type = models.CharField(max_length=64, db_index=True)    payload = models.JSONField()    received_at = models.DateTimeField(auto_now_add=True)    applied = models.BooleanField(default=False)    note = models.TextField(blank=True, null=True)<\/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>\u042d\u0442\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u0435\u0442 \u0441\u0440\u0430\u0437\u0443 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u043e\u0444\u0438\u0442\u043e\u0432. \u0421\u0430\u043c \u043f\u043b\u0430\u0442\u0435\u0436 \u0445\u0440\u0430\u043d\u0438\u0442 \u0438\u0442\u043e\u0433\u043e\u0432\u043e\u0435 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435. \u0416\u0443\u0440\u043d\u0430\u043b \u0445\u0440\u0430\u043d\u0438\u0442 \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0439 webhook \u043a\u0430\u043a \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0444\u0430\u043a\u0442. \u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0434\u0443\u0431\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f, \u0436\u0443\u0440\u043d\u0430\u043b \u044d\u0442\u043e \u043f\u043e\u043a\u0430\u0436\u0435\u0442. \u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u0435 \u0441\u043c\u043e\u0433 \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f, \u0441\u0430\u043c \u0444\u0430\u043a\u0442 \u043f\u0440\u0438\u0445\u043e\u0434\u0430 \u043d\u0435 \u0442\u0435\u0440\u044f\u0435\u0442\u0441\u044f. \u041f\u043e\u0442\u043e\u043c \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 \u0440\u0430\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u0431\u043e\u0435\u0432 \u0447\u0442\u0435\u043d\u0438\u0435\u043c \u0438\u0441\u0442\u043e\u0440\u0438\u0438.<\/p>\n<h3>\u041f\u043e\u0447\u0435\u043c\u0443 \u043f\u0435\u0440\u0432\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u043b\u0443\u0447\u0448\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u0442\u044c \u043d\u0430 waiting_for_capture \u0438 succeeded<\/h3>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043d\u0435 \u043a\u0430\u043a \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043d\u044b\u0439, \u0430 \u043a\u0430\u043a <code>capture=False<\/code>. \u0422\u043e \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e, \u0430 \u0437\u0430\u0442\u0435\u043c \u0443\u0436\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0434\u0435\u043b\u0430\u0435\u0442 capture.<\/p>\n<p>\u0412 \u043a\u043e\u0434\u0435 \u044d\u0442\u043e \u0432\u0438\u0434\u043d\u043e \u0432 \u0441\u0431\u043e\u0440\u043a\u0435 \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445:<\/p>\n<pre><code class=\"python\"># payment\/utils.pydef create_payment_data(amount, description, local_payment_id, subscription_type, receipt, payment_method_id=None):    save_pm = (subscription_type in ('monthly', 'yearly'))    data = {        \"amount\": {\"value\": str(amount), \"currency\": \"RUB\"},        \"capture\": False,        \"confirmation\": {\"type\": \"redirect\", \"return_url\": f\"{settings.FRONT_URL}\/payment\/success\"},        \"description\": description,        \"receipt\": receipt,        \"metadata\": {\"payment_id\": local_payment_id, \"subscription_type\": subscription_type},    }    if save_pm:        data[\"save_payment_method\"] = True    if payment_method_id:        data[\"payment_method_id\"] = payment_method_id        data.pop(\"confirmation\", None)        data[\"capture\"] = True    return data<\/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\u043b\u044f \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u0430 \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u043f\u043e \u0434\u0432\u0443\u043c \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c. \u0412\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u0443\u0435\u043c\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u043c\u0435\u0436\u0434\u0443 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0438 \u043e\u043a\u043e\u043d\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0437\u0430\u0445\u0432\u0430\u0442\u043e\u043c \u0441\u0440\u0435\u0434\u0441\u0442\u0432. \u0412\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, webhook <code>payment.waiting_for_capture<\/code> \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u043c \u044d\u0442\u0430\u043f\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0438 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0441\u0432\u0430\u043b\u0438\u0442\u044c <code>waiting_for_capture<\/code> \u0438 <code>succeeded<\/code> \u0432 \u043e\u0434\u043d\u0443 \u043a\u0443\u0447\u0443, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0442\u0435\u0440\u044f\u0435\u0442 \u043e\u0434\u0438\u043d \u0432\u0430\u0436\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f. \u0421\u043d\u0430\u0440\u0443\u0436\u0438 \u043a\u0430\u0436\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u0432 \u043f\u0440\u043e\u0441\u0442\u043e \u0434\u0432\u0430. \u041d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u044d\u0442\u043e \u0434\u0432\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u044d\u0442\u0430\u043f\u0430 \u0441 \u0440\u0430\u0437\u043d\u043e\u0439 \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u043e\u0439.<\/p>\n<h3>Capture \u043d\u0435\u043b\u044c\u0437\u044f \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0442\u044c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u043c \u043a\u043b\u044e\u0447\u043e\u043c<\/h3>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 <code>payment.waiting_for_capture<\/code>, backend \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u043f\u043b\u0430\u0442\u0435\u0436. \u0418 \u0432\u043e\u0442 \u0437\u0434\u0435\u0441\u044c \u0447\u0430\u0441\u0442\u043e \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430. \u0415\u0441\u043b\u0438 \u0434\u0435\u043b\u0430\u0442\u044c capture \u0441 \u043d\u043e\u0432\u044b\u043c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u043c <code>idempotence_key<\/code> \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u043f\u043e\u0432\u0442\u043e\u0440\u0435, \u0442\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u043e\u0434\u043d\u043e\u0433\u043e \u0438 \u0442\u043e\u0433\u043e \u0436\u0435 webhook \u0443\u0436\u0435 \u043d\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0439.<\/p>\n<p>\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u044b\u0439 \u043a\u043b\u044e\u0447 \u0432\u0438\u0434\u0430 <code>capture:{payment_id}<\/code> \u0438 \u043e\u043d \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 \u0441\u0430\u043c\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u043f\u043b\u0430\u0442\u0435\u0436\u0430.<\/p>\n<pre><code class=\"python\"># payment\/utils.pydef confirm_payment_in_kassa(payment_id, amount=None):    try:        kp = KassaPayment.objects.filter(kassa_payment_id=payment_id).first()        if kp:            if not kp.capture_idem_key:                kp.capture_idem_key = f\"capture:{payment_id}\"                kp.save(update_fields=[\"capture_idem_key\"])            value = str(kp.amount)            idem_key = kp.capture_idem_key        else:            if amount is None:                return None            value = str(amount)            idem_key = f\"capture:{payment_id}\"        resp = Payment.capture(            payment_id,            {\"amount\": {\"value\": value, \"currency\": \"RUB\"}},            idem_key,        )        return resp    except Exception:        return None<\/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\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0434\u0435\u0442\u0430\u043b\u044c \u0440\u0435\u0448\u0430\u0435\u0442 \u0432\u0430\u0436\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443. \u041e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0432\u0441\u0435\u0433\u0434\u0430 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442\u0441\u044f \u043e\u0434\u043d\u0438\u043c \u0438 \u0442\u0435\u043c \u0436\u0435 \u043a\u043b\u044e\u0447\u043e\u043c. \u0417\u043d\u0430\u0447\u0438\u0442, \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0435 \u0432\u044b\u0437\u043e\u0432\u044b \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u044e\u0442 \u043d\u043e\u0432\u044b\u0439 \u0441\u043c\u044b\u0441\u043b, \u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0442 \u0441\u0442\u0430\u0440\u044b\u0439.<\/p>\n<p>\u0412\u043e\u043a\u0440\u0443\u0433 \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u043d\u0443\u0436\u043d\u0430 \u043a\u0430\u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u0445\u043e\u0432\u043a\u0430 \u043e\u0442 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0439 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438.  <\/p>\n<h3>Webhook \u043d\u0443\u0436\u043d\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043e \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0438<\/h3>\n<p>\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0441\u043b\u043e\u0439 \u0434\u043e\u0432\u0435\u0440\u0438\u044f \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0435\u0449\u0435 \u0434\u043e \u0440\u0430\u0437\u0431\u043e\u0440\u0430 payload. \u0412\u0435\u0431\u0445\u0443\u043a \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u0440\u043e\u0439\u0442\u0438 IP-check. \u0414\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043e\u0441\u043b\u0430\u0431\u0438\u0442\u044c, \u0434\u043b\u044f \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0430 \u043d\u0435\u0442.<\/p>\n<pre><code class=\"python\"># payment\/utils.pydef is_valid_webhook_signature(request):    ALLOWED_IP_RANGES = [        '185.71.76.0\/27',        '185.71.77.0\/27',        '77.75.153.0\/25',        '77.75.156.11',        '77.75.156.35',        '77.75.154.128\/25',        '2a02:5180::\/32',    ]    if getattr(settings, 'DJANGO_ENV', 'local') == 'local':        return True    raw_xff = (request.META.get('HTTP_X_FORWARDED_FOR') or '').strip()    x_real = (request.META.get('HTTP_X_REAL_IP') or '').strip()    remote = (request.META.get('REMOTE_ADDR') or '').strip()    ip = raw_xff.split(',')[0].strip() if raw_xff else (x_real or remote)    if ip.startswith('::ffff:'):        ip = ip[7:]    try:        ip_obj = ipaddress.ip_address(ip)    except ValueError:        return False    for rng in ALLOWED_IP_RANGES:        if '\/' in rng and ip_obj in ipaddress.ip_network(rng, strict=False):            return True        if ip == rng:            return True    return False<\/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>\u0415\u0441\u043b\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0435\u0449\u0435 \u043d\u0435 \u043f\u0440\u043e\u0448\u0435\u043b \u0441\u0435\u0442\u0435\u0432\u0443\u044e \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443, \u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0434\u043e \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u044b\u043c webhook \u0438 \u0442\u0430\u0449\u0438\u0442\u044c \u0432 \u0431\u0438\u0437\u043d\u0435\u0441-\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443. \u0427\u0442\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442\u0441\u044f \u0438 \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u0412 \u043f\u0440\u043e\u0434-\u0440\u0435\u0436\u0438\u043c\u0435 \u0437\u0430\u043f\u0440\u043e\u0441 \u0431\u0435\u0437 \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0433\u043e IP \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 <code>403<\/code>, \u0430 \u0437\u0430\u043f\u0438\u0441\u044c \u0432 <code>PaymentEventLog<\/code> \u0434\u0430\u0436\u0435 \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f. \u0422\u043e \u0435\u0441\u0442\u044c \u0434\u043e \u0436\u0443\u0440\u043d\u0430\u043b\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0434\u043e\u0445\u043e\u0434\u0438\u0442 \u0443\u0436\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e, \u0447\u0442\u043e \u043f\u0440\u043e\u0448\u043b\u043e \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0444\u0438\u043b\u044c\u0442\u0440 \u0434\u043e\u0432\u0435\u0440\u0438\u044f.<\/p>\n<h3>\u0416\u0443\u0440\u043d\u0430\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043b\u0443\u0447\u0448\u0435 \u0434\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0438 \u043f\u043e\u0441\u043b\u0435 \u043d\u0435\u0433\u043e<\/h3>\n<p>\u041f\u043e\u0441\u043b\u0435 IP-check webhook \u043d\u0435 \u043d\u0430\u0434\u043e \u0441\u0440\u0430\u0437\u0443 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u043a \u0431\u0430\u0437\u0435. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043b\u0443\u0447\u0448\u0435 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043a\u0430\u043a \u0444\u0430\u043a\u0442. \u041f\u043e\u0442\u043e\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0435\u0433\u043e \u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a. \u041f\u043e\u0442\u043e\u043c \u0443\u0436\u0435 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c \u043e\u043d\u043e \u0438\u043b\u0438 \u043d\u0435\u0442.<\/p>\n<p>\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 webhook \u0443\u0441\u0442\u0440\u043e\u0435\u043d \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\"># payment\/views.py@csrf_exempt@api_view(['POST'])@permission_classes([AllowAny])def kassa_webhook(request):    if not is_valid_webhook_signature(request):        return Response(status=403)    try:        data = json.loads(request.body.decode('utf-8'))    except Exception:        return Response(status=400)    event = (data.get('event') or '').strip()    obj = data.get('object', {}) or {}    obj_id = obj.get('id') or ''    log = PaymentEventLog.objects.create(        event_id=obj_id,        event_type=event,        payload=data,        applied=False,        note=None,    )    if event == 'payment.waiting_for_capture':        resp = webhook_waiting_for_capture(data)    elif event == 'payment.succeeded':        resp = webhook_succeeded(data)    elif event == 'payment.canceled':        resp = webhook_canceled(data)    elif event == 'refund.succeeded':        resp = webhook_refund(data)    else:        PaymentEventLog.objects.filter(pk=log.pk).update(note='unknown_event')        return Response(status=200)    status_code = getattr(resp, 'status_code', 200)    if status_code == 200:        PaymentEventLog.objects.filter(pk=log.pk).update(applied=True)    else:        PaymentEventLog.objects.filter(pk=log.pk).update(note=f'handler_status={status_code}')    return resp<\/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 \u0434\u0430\u0435\u0442 \u0443\u0434\u043e\u0431\u043d\u0443\u044e \u0434\u0432\u0443\u0445\u0448\u0430\u0433\u043e\u0432\u0443\u044e \u043a\u0430\u0440\u0442\u0438\u043d\u0443. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0432\u0438\u0434\u043d\u043e, \u0447\u0442\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043f\u0440\u0438\u0448\u043b\u043e. \u041f\u043e\u0442\u043e\u043c \u0432\u0438\u0434\u043d\u043e, \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043b\u0438 \u0435\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c. \u0415\u0441\u043b\u0438 \u043d\u0435\u0442, \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f <code>note<\/code>. \u0415\u0441\u043b\u0438 \u0434\u0430, <code>applied=True<\/code>.<\/p>\n<p>\u0414\u043b\u044f \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c \u044d\u0442\u043e \u043b\u0443\u0447\u0448\u0435, \u0447\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 stdout. \u0412 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u0438 \u0431\u0430\u0437\u0435 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u043e\u0436\u043d\u043e \u0447\u0438\u0442\u0430\u0442\u044c \u0438 \u0430\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c.<\/p>\n<h3>waiting_for_capture \u0438 succeeded \u0434\u043e\u043b\u0436\u043d\u044b \u0436\u0438\u0442\u044c \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u0445<\/h3>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u0441\u0432\u043e\u044f \u0440\u0430\u0431\u043e\u0442\u0430. \u0414\u043b\u044f <code>payment.waiting_for_capture<\/code> \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u0440\u043e\u0441\u0442\u0430: \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0435\u0449\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 <code>waiting_for_capture<\/code>, \u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u044b\u0439 capture.<\/p>\n<pre><code class=\"python\"># payment\/hooks.pydef webhook_waiting_for_capture(data):    obj = data.get('object', {}) or {}    payment_id = obj.get('id')    amount_value = (obj.get('amount', {}) or {}).get('value')    if not payment_id:        return Response(status=200)    kp = KassaPayment.objects.filter(kassa_payment_id=payment_id).first()    if not kp:        return Response(status=200)    if kp.kassa_payment_status != 'waiting_for_capture':        return Response(status=200)    if confirm_payment_in_kassa(payment_id, amount_value):        return Response(status=200)    return Response({\"error\": \"Error confirming payment\"}, status=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\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u0442\u0430\u0442\u0443\u0441 \u0432\u0430\u0436\u043d\u0430. \u0415\u0441\u043b\u0438 webhook \u043f\u0440\u0438\u0448\u0435\u043b \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0438\u043b\u0438 \u0441 \u043e\u043f\u043e\u0437\u0434\u0430\u043d\u0438\u0435\u043c, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0442\u044c \u0442\u043e, \u0447\u0442\u043e \u0443\u0436\u0435 \u0443\u0448\u043b\u043e \u0434\u0430\u043b\u044c\u0448\u0435 \u043f\u043e \u0446\u0435\u043f\u043e\u0447\u043a\u0435.<\/p>\n<p>\u0414\u043b\u044f <code>payment.succeeded<\/code> \u043b\u043e\u0433\u0438\u043a\u0430 \u0443\u0436\u0435 \u0434\u0440\u0443\u0433\u0430\u044f. \u0422\u0443\u0442 \u043d\u0435\u043b\u044c\u0437\u044f \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c completed \u0438 \u0440\u0430\u0437\u043e\u0439\u0442\u0438\u0441\u044c. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u0441\u0443\u043c\u043c\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0435\u0442, \u0432\u0430\u043b\u044e\u0442\u0430 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u0430\u044f, \u0430 metadata \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043d\u0430 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0443\u044e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c.<\/p>\n<pre><code class=\"python\"># payment\/hooks.pydef webhook_succeeded(data):    obj = data.get('object', {}) or {}    payment_id = obj.get('id')    amount_obj = obj.get('amount', {}) or {}    amount_value = amount_obj.get('value')    currency = amount_obj.get('currency')    meta = obj.get('metadata', {}) or {}    kp = KassaPayment.objects.filter(kassa_payment_id=payment_id).first()    if not kp:        return Response(status=200)    if kp.kassa_payment_status in ('succeeded', 'canceled', 'refund_succeeded'):        return Response(status=200)    amt = Decimal(str(amount_value))    if amt != kp.amount:        return Response(status=200)    if currency != 'RUB':        return Response(status=200)    if str(meta.get('payment_id')) != str(kp.id):        return Response(status=200)    kassa_resp = KPayment.find_one(payment_id)    updated = update_payment_status(payment_id, kassa_resp)<\/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\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0435 \u0434\u0430\u044e\u0442 webhook \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044c\u0441\u044f \u0432 \u0441\u043b\u0435\u043f\u043e\u0439 \u0442\u0440\u0438\u0433\u0433\u0435\u0440. \u0414\u043e \u0442\u0435\u0445 \u043f\u043e\u0440 \u043f\u043e\u043a\u0430 \u0441\u0443\u043c\u043c\u0430, \u0432\u0430\u043b\u044e\u0442\u0430 \u0438 metadata \u043d\u0435 \u0441\u043e\u0432\u043f\u0430\u043b\u0438 \u0441 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u044c\u044e, \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u0443\u0441\u043f\u0435\u0445 \u043d\u0435\u043b\u044c\u0437\u044f.<\/p>\n<h3>\u0423\u0441\u043f\u0435\u0448\u043d\u044b\u0439 webhook \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043b\u0430\u0442\u0435\u0436, \u043d\u043e \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0443<\/h3>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u043f\u043b\u0430\u0442\u0435\u0436 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e, monthly \u0438 yearly-\u043f\u043b\u0430\u043d\u044b \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u0435\u0449\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u0448\u0430\u0433\u0430. \u041d\u0443\u0436\u043d\u043e \u043b\u0438\u0431\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0443, \u043b\u0438\u0431\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e. \u041f\u0440\u0438\u0447\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438 \u042eKassa \u0432\u0435\u0440\u043d\u0443\u043b\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0439 <code>payment_method_id<\/code>, \u043f\u0440\u0438\u0433\u043e\u0434\u043d\u044b\u0439 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439.<\/p>\n<p>\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u044d\u0442\u043e\u0442 \u043a\u043e\u043d\u0442\u0443\u0440 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\"># payment\/hooks.pysub_type = (meta.get('subscription_type') or '').lower()if updated and sub_type in ('monthly','yearly'):    pm = getattr(kassa_resp, 'payment_method', None)    pm_id = getattr(pm, 'id', None)    pm_saved = getattr(pm, 'saved', False)    if pm_id and pm_saved:        sub, _ = Subscription.objects.get_or_create(            user=kp.user,            plan=sub_type,            defaults={                'status': 'active',                'amount': kp.amount,                'currency': 'RUB',                'payment_method_id': pm_id,                'fails_count': 0,                'last_payment_id': kp.kassa_payment_id,            }        )        sub.payment_method_id = pm_id        sub.amount = kp.amount        sub.currency = 'RUB'        sub.status = 'active'        sub.fails_count = 0        sub.last_payment_id = kp.kassa_payment_id        sub.schedule_next()        sub.save()    else:        kp.information_payment = (kp.information_payment or '') + \" [autopay=off]\"        kp.save(update_fields=['information_payment'])<\/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 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436. \u041f\u043e\u0442\u043e\u043c \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u0438\u043b\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0430. \u041f\u043e\u0442\u043e\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f <code>payment_method_id<\/code>, \u0435\u0441\u043b\u0438 \u043e\u043d \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d \u0432 \u042eKassa. \u0415\u0441\u043b\u0438 \u043c\u0435\u0442\u043e\u0434 \u043e\u043f\u043b\u0430\u0442\u044b \u043d\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d, \u044d\u0442\u043e \u043d\u0435 \u043f\u043e\u0432\u043e\u0434 \u0432\u0430\u043b\u0438\u0442\u044c \u043f\u043b\u0430\u0442\u0435\u0436, \u043d\u043e \u044d\u0442\u043e \u043f\u043e\u0432\u043e\u0434 \u044f\u0432\u043d\u043e \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u0430\u0432\u0442\u043e\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0439 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442.<\/p>\n<p>\u0422\u0430\u043a\u043e\u0439 \u043a\u043e\u043d\u0442\u0443\u0440 \u043f\u043e\u0442\u043e\u043c \u0434\u0430\u0435\u0442 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0443\u044e \u043e\u0441\u043d\u043e\u0432\u0443 \u0434\u043b\u044f recurring.<\/p>\n<h3>\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0430\u0432\u0430\u0440\u0438\u0439\u043d\u044b\u0439 confirm<\/h3>\n<p>\u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0432\u044b\u0441\u0442\u0440\u043e\u0435\u043d \u0445\u043e\u0440\u043e\u0448\u043e, \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0443\u0440 \u0436\u0435\u043b\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0441\u043d\u0430\u0431\u0434\u0438\u0442\u044c \u0430\u0432\u0430\u0440\u0438\u0439\u043d\u044b\u043c \u0440\u0443\u0447\u043d\u044b\u043c \u0441\u043b\u043e\u0435\u043c. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, webhook \u043c\u043e\u0433 \u043f\u043e\u0442\u0435\u0440\u044f\u0442\u044c\u0441\u044f \u043f\u043e \u0434\u043e\u0440\u043e\u0433\u0435, \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u043c\u043e\u0433\u043b\u0430 \u043d\u0435 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f, capture \u043c\u043e\u0433 \u0437\u0430\u0432\u0438\u0441\u043d\u0443\u0442\u044c \u0432 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438.<\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0435\u0441\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 admin endpoint, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u0435\u043b\u0430\u0435\u0442 \u0442\u0440\u0438 \u0432\u0435\u0449\u0438. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0447\u0438\u0442\u0430\u0435\u0442 \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u0442\u0430\u0442\u0443\u0441 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u0430 \u0438\u0437 \u042eKassa. \u041f\u043e\u0442\u043e\u043c, \u0435\u0441\u043b\u0438 \u0442\u0430\u043c <code>waiting_for_capture<\/code>, \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u044b\u0439 capture. \u0415\u0441\u043b\u0438 \u0442\u0430\u043c \u0443\u0436\u0435 <code>succeeded<\/code>, \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c. \u0412\u043e \u0432\u0441\u0435\u0445 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u043d\u0435 \u043b\u043e\u043c\u0430\u0435\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u0443, \u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u0441\u0442\u0430\u0442\u0443\u0441.<\/p>\n<pre><code class=\"python\"># payment\/views.py@api_view(['POST'])@permission_classes([IsAdminUser])def confirm_payment(request):    payment_id = request.data.get('payment_id')    if not payment_id:        return Response({\"error\": \"payment_id required\"}, status=400)    kp = KassaPayment.objects.filter(kassa_payment_id=payment_id).first()    if not kp:        return Response({\"error\": \"Payment not found in DB\"}, status=404)    remote = Payment.find_one(payment_id)    remote_status = getattr(remote, \"status\", None)    if remote_status == \"waiting_for_capture\":        capture_resp = confirm_payment_in_kassa(payment_id, kp.amount)        if not capture_resp:            return Response({\"error\": \"Kassa capture error\"}, status=502)        if update_payment_status(payment_id, capture_resp):            return Response({\"status\": \"captured_and_synced\"}, status=200)    if remote_status == \"succeeded\":        if update_payment_status(payment_id, remote):            return Response({\"status\": \"already_captured_synced\"}, status=200)    return Response({\"status\": \"not_capturable\", \"remote_status\": remote_status}, status=409)<\/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\u043b\u043e\u0439 \u043d\u0435 \u043d\u0443\u0436\u0435\u043d \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e. \u041e\u043d \u043d\u0443\u0436\u0435\u043d \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u043a\u0430\u043a \u0441\u0442\u0440\u0430\u0445\u043e\u0432\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0443\u0440. \u0425\u043e\u0440\u043e\u0448\u0438\u0439 \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u0443\u043c\u0435\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0434\u0442\u0438 \u043f\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c\u0443 \u043f\u0443\u0442\u0438, \u043d\u043e \u0438 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u043e\u0441\u043b\u0435 \u0440\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0439.<\/p>\n<h3>\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u0440\u0435\u043a\u0443\u0440\u0440\u0435\u043d\u0442\u043a\u0430 \u0442\u043e\u0436\u0435 \u0434\u043e\u043b\u0436\u043d\u0430 \u0436\u0438\u0442\u044c \u043f\u043e \u043f\u0440\u0435\u0434\u0441\u043a\u0430\u0437\u0443\u0435\u043c\u044b\u043c \u043f\u0440\u0430\u0432\u0438\u043b\u0430\u043c<\/h3>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u0430 monthly \u0438 yearly-\u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442 <code>payment_method_id<\/code>, \u0430 \u0434\u0430\u043b\u044c\u0448\u0435 \u043c\u043e\u0433\u0443\u0442 \u0441\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438. \u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0435\u0441\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0430 <code>charge_subscriptions<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438\u0449\u0435\u0442 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u0441 \u043d\u0430\u0441\u0442\u0443\u043f\u0438\u0432\u0448\u0438\u043c <code>next_charge_at<\/code>, \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0440\u0435\u043a\u0443\u0440\u0440\u0435\u043d\u0442\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0432 \u042eKassa.<\/p>\n<p>\u0418\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u0437\u0434\u0435\u0441\u044c \u0442\u043e, \u0447\u0442\u043e \u0438 \u044d\u0442\u043e\u0442 \u043a\u043e\u043d\u0442\u0443\u0440 \u0442\u043e\u0436\u0435 \u043e\u043f\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u0438 \u043d\u0430 webhook-\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044e. \u0414\u043b\u044f \u0440\u0435\u043a\u0443\u0440\u0440\u0435\u043d\u0442\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u0430 \u0441\u0442\u0440\u043e\u0438\u0442\u0441\u044f \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 \u0438\u0437 <code>sub:{subscription_id}:{due_key}:{fingerprint}<\/code>. \u0415\u0441\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u0441\u0442\u0430\u0442\u0443\u0441 \u0443\u0436\u0435 \u0441\u0440\u0430\u0437\u0443 <code>succeeded<\/code>, \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043d\u0435\u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e. \u0415\u0441\u043b\u0438 \u043d\u0435\u0442, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0441\u043f\u043e\u043a\u043e\u0439\u043d\u043e \u0436\u0434\u0435\u0442 webhook.<\/p>\n<pre><code class=\"python\"># payment\/management\/commands\/charge_subscriptions.pypayload_fingerprint = hashlib.sha1(    json.dumps(payment_data, sort_keys=True, ensure_ascii=False, separators=(\",\", \":\")).encode(\"utf-8\")).hexdigest()[:12]due_key = (s.next_charge_at or now).isoformat()idem = f\"sub:{s.id}:{due_key}:{payload_fingerprint}\"remote = KPayment.create(payment_data, idem)if getattr(remote, \"status\", None) == \"succeeded\":    update_payment_status(remote.id, remote)    s.fails_count = 0    s.schedule_next()    s.save(update_fields=['fails_count', 'next_charge_at', 'updated_at'])else:    # \u0436\u0434\u0451\u043c \u0432\u0435\u0431\u0445\u0443\u043a\u043e\u0432    ...<\/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\u0430\u0436\u0435 \u0440\u0435\u043a\u0443\u0440\u0440\u0435\u043d\u0442\u043a\u0430 \u043d\u0435 \u0434\u0435\u043b\u0430\u0435\u0442 \u0432\u0438\u0434, \u0447\u0442\u043e \u0443 \u043d\u0435\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u043e\u0434\u0438\u043d \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439. \u041e\u043d\u0430 \u0443\u043c\u0435\u0435\u0442 \u043b\u0438\u0431\u043e \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0443\u0441\u043f\u0435\u0445 \u0441\u0440\u0430\u0437\u0443, \u043b\u0438\u0431\u043e \u0436\u0434\u0430\u0442\u044c \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043f\u043e webhook.<\/p>\n<h3>\u041f\u043e\u0447\u0435\u043c\u0443 \u0436\u0443\u0440\u043d\u0430\u043b \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u043b\u0443\u0447\u0448\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e print<\/h3>\n<p>\u0418\u043d\u043e\u0433\u0434\u0430 \u043a\u0430\u0436\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0434\u043b\u044f webhook \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u0438\u043d\u0442\u043e\u0432 \u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0445 \u043b\u043e\u0433\u043e\u0432. \u041d\u043e \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0443\u0436\u0435\u043d \u043e\u0442\u0432\u0435\u0442 \u043d\u0430 \u0432\u043e\u043f\u0440\u043e\u0441 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u043b\u043e \u043b\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0432\u043e\u043e\u0431\u0449\u0435, \u043f\u0440\u0438\u0448\u043b\u043e \u043b\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e, \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c \u043b\u0438 \u043e\u043d\u043e, \u043f\u043e \u043a\u0430\u043a\u043e\u043c\u0443 id, \u0441 \u043a\u0430\u043a\u0438\u043c payload, \u0442\u0430\u043a\u0438\u0435 \u043b\u043e\u0433\u0438 \u0431\u044b\u0441\u0442\u0440\u043e \u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0442\u0441\u044f \u043d\u0435\u0443\u0434\u043e\u0431\u043d\u044b\u043c\u0438.<\/p>\n<p><code>PaymentEventLog<\/code> \u0445\u043e\u0440\u043e\u0448 \u0442\u0435\u043c, \u0447\u0442\u043e \u0445\u0440\u0430\u043d\u0438\u0442 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0444\u0430\u043a\u0442 \u043f\u0440\u0438\u0445\u043e\u0434\u0430, \u043d\u043e \u0438 \u044d\u0442\u0430\u043f \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f. \u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u043e\u0434\u043d\u043e\u0433\u043e \u0438 \u0442\u043e\u0433\u043e \u0436\u0435 <code>payment.succeeded<\/code> \u043d\u0435 \u0440\u0430\u0437\u043c\u043d\u043e\u0436\u0430\u0435\u0442 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0443, \u043d\u043e \u043f\u0438\u0448\u0435\u0442 \u043e\u0431\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0432 \u0436\u0443\u0440\u043d\u0430\u043b. \u0412\u0435\u0431\u0445\u0443\u043a \u0441 \u043d\u0435\u0432\u0435\u0440\u043d\u044b\u043c IP \u0440\u0435\u0436\u0435\u0442\u0441\u044f \u0434\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0437\u0430\u043f\u0438\u0441\u0438. \u042d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c \u0431\u0430\u0437\u0430 \u0434\u043e\u0432\u0435\u0440\u0438\u044f \u043a \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u043e\u043c\u0443 \u043a\u043e\u043d\u0442\u0443\u0440\u0443.<\/p>\n<p>\u0412 \u0442\u0430\u043a\u0438\u0445 \u0432\u0435\u0449\u0430\u0445 \u043d\u0430\u0434\u0435\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0431\u044b\u0447\u043d\u043e \u0441\u0442\u0440\u043e\u0438\u0442\u0441\u044f \u043d\u0435 \u043e\u0434\u043d\u043e\u0439 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0438\u0434\u0435\u0435\u0439, \u0430 \u0434\u0435\u0441\u044f\u0442\u043a\u043e\u043c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0439, \u043a\u0430\u0436\u0434\u043e\u0435 \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0431\u0438\u0440\u0430\u0435\u0442 \u0441\u0432\u043e\u0439 \u043a\u043b\u0430\u0441\u0441 \u0441\u0431\u043e\u0435\u0432.<\/p>\n<h3>\u041d\u0430\u0434\u0435\u0436\u043d\u043e\u0441\u0442\u044c\u00a0\u043a\u043e\u043d\u0442\u0443\u0440\u0430<\/h3>\n<p>\u041d\u0430\u0434\u0435\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0441\u043b\u043e\u0435\u0432 \u0437\u0430\u0449\u0438\u0442\u044b. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 IP-check. \u041f\u043e\u0442\u043e\u043c pre-apply \u0436\u0443\u0440\u043d\u0430\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435. \u041f\u043e\u0442\u043e\u043c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u043e \u0442\u0438\u043f\u0443 \u0441\u043e\u0431\u044b\u0442\u0438\u044f. \u041f\u043e\u0442\u043e\u043c \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u044b\u0439 capture \u0434\u043b\u044f <code>waiting_for_capture<\/code>. \u041f\u043e\u0442\u043e\u043c \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0441\u0443\u043c\u043c\u044b, \u0432\u0430\u043b\u044e\u0442\u044b \u0438 metadata \u0434\u043b\u044f <code>payment.succeeded<\/code>. \u041f\u043e\u0442\u043e\u043c \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u0430 \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438. \u041f\u043e\u0442\u043e\u043c \u0430\u0432\u0430\u0440\u0438\u0439\u043d\u044b\u0439 \u0440\u0443\u0447\u043d\u043e\u0439 confirm, \u0435\u0441\u043b\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u043a\u0430 \u0440\u0430\u0437\u043e\u0448\u043b\u0430\u0441\u044c \u0441 \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043c \u0432 \u042eKassa.<\/p>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0441\u043b\u043e\u0439 \u043f\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u0440\u043e\u0441\u0442\u044b\u043c. \u0412\u043c\u0435\u0441\u0442\u0435 \u043e\u043d\u0438 \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u044e\u0442 \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0443\u0440 \u0438\u0437 \u043e\u043f\u0442\u0438\u043c\u0438\u0441\u0442\u0438\u0447\u043d\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443, \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u043e\u0436\u043d\u043e \u0432\u0435\u0440\u0438\u0442\u044c.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u044d\u0442\u043e\u0433\u043e \u043d\u0435\u0442, \u043f\u0440\u043e\u0434\u0443\u043a\u0442 \u043b\u0435\u0433\u043a\u043e \u0436\u0438\u0432\u0435\u0442 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u043a\u0430\u0436\u0435\u0442\u0441\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u043c, \u043f\u043e\u043a\u0430 \u043f\u0435\u0440\u0432\u044b\u0439 \u0434\u0443\u0431\u043b\u044c webhook \u0438\u043b\u0438 \u043f\u0435\u0440\u0432\u044b\u0439 \u043f\u043e\u0434\u0432\u0438\u0441\u0448\u0438\u0439 capture \u043d\u0435 \u043f\u043e\u043a\u0430\u0436\u0435\u0442 \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0435.<\/p>\n<p>\u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u0436\u0438\u0432\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 <a href=\"https:\/\/ai-chat-frontend-wy6h.onrender.com\/\" rel=\"noopener noreferrer nofollow\">AI-Chat<\/a>. \u041e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0432\u0438\u0442\u0440\u0438\u043d\u0430 \u043d\u0430 <a href=\"https:\/\/lemon1964.github.io\/ai-chat-pages\/\" rel=\"noopener noreferrer nofollow\">GitHub Pages<\/a> \u043f\u0438\u043d\u0433\u0443\u0435\u0442 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430 \u0441\u043f\u044f\u0449\u0435\u043c Render \u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435. \u0412 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0435 \u0441 \u0444\u0440\u043e\u043d\u0442\u043e\u043c, \u0431\u044d\u043a\u0435\u043d\u0434\u043e\u043c \u0438 \u0438\u0445 \u0441\u0442\u044b\u043a\u043e\u0432\u043a\u043e\u0439 \u044d\u0442\u043e\u0442 \u043a\u043e\u043d\u0442\u0443\u0440 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 <a href=\"https:\/\/stepik.org\/a\/253246\" rel=\"noopener noreferrer nofollow\">Stepik \u043a\u0443\u0440\u0441\u0435 LLM-Chat + Mermaid + \u042eKassa<\/a>.<\/p>\n<p> \u200b<\/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\/1044796\/\">https:\/\/habr.com\/ru\/articles\/1044796\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u041f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0439 \u043a\u043e\u0434 \u043e\u0431\u044b\u0447\u043d\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0440\u043e\u0432\u043d\u044b\u043c \u0440\u043e\u0432\u043d\u043e \u0434\u043e \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u0431\u043e\u044f. \u041f\u043e\u043a\u0430 \u043f\u043b\u0430\u0442\u0435\u0436\u0438 \u0438\u0434\u0443\u0442 \u043f\u043e \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u043c\u0443 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044e, \u043a\u0430\u0436\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043e\u043f\u043b\u0430\u0442\u0443, \u0434\u043e\u0436\u0434\u0430\u0442\u044c\u0441\u044f \u0432\u0435\u0431\u0445\u0443\u043a\u0430 \u0438 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u0442\u0430\u0442\u0443\u0441. \u041d\u043e \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0435\u0431\u0445\u0443\u043a \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e, \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043f\u043e\u0437\u0436\u0435 \u043d\u0443\u0436\u043d\u043e\u0433\u043e, \u043f\u0440\u0438\u043b\u0435\u0442\u0430\u0435\u0442 \u043e\u0442 \u043d\u0435 \u0442\u043e\u0433\u043e IP, \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0443\u0436\u0435 \u0436\u0438\u0432\u0435\u0442 \u0432 \u043e\u0434\u043d\u043e\u043c \u0441\u0442\u0430\u0442\u0443\u0441\u0435, \u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u0431\u0430\u0437\u0430 \u0432 \u0434\u0440\u0443\u0433\u043e\u043c, \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u044f\u0441\u043d\u043e, \u0447\u0442\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0443\u0440 \u0431\u0435\u0437 \u0437\u0430\u0449\u0438\u0442 \u043f\u043e\u0447\u0442\u0438 \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u0440\u0435\u0442.\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u0432\u0435\u0431\u0445\u0443\u043a \u043d\u0435\u043b\u044c\u0437\u044f \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u0438\u0441\u0442\u0438\u043d\u043e\u0439 \u0431\u0435\u0437 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438, \u043d\u0435\u043b\u044c\u0437\u044f \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u0431\u0435\u0437 \u0436\u0443\u0440\u043d\u0430\u043b\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u043d\u0435\u043b\u044c\u0437\u044f \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0442\u044c capture \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u043c \u043a\u043b\u044e\u0447\u043e\u043c, \u0438 \u043d\u0435\u043b\u044c\u0437\u044f \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0431\u0435\u0437 \u0430\u0432\u0430\u0440\u0438\u0439\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438, \u0435\u0441\u043b\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0433\u0434\u0435-\u0442\u043e \u0440\u0430\u0437\u043e\u0448\u0435\u043b\u0441\u044f.\u0412 \u043e\u0434\u043d\u043e\u043c \u0438\u0437 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u044d\u0442\u043e\u0442 \u0443\u0437\u0435\u043b \u0431\u044b\u043b \u0441\u043e\u0431\u0440\u0430\u043d \u0442\u0430\u043a, \u043f\u0435\u0440\u0432\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0441 capture=False, \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0439 webhook \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043f\u043e IP, \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0438\u0448\u0435\u0442\u0441\u044f \u0432 \u0436\u0443\u0440\u043d\u0430\u043b, \u043f\u043e\u0442\u043e\u043c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, capture \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442\u0441\u044f \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u044b\u043c idempotence key, \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043f\u043e \u0441\u0443\u043c\u043c\u0435, \u0432\u0430\u043b\u044e\u0442\u0435 \u0438 metadata, \u0430 \u043d\u0430 \u0441\u043b\u0443\u0447\u0430\u0439 \u0440\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0440\u0443\u0447\u043d\u043e\u0439 confirm, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043c\u0435\u0435\u0442 \u0434\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u0442\u0430\u0442\u0443\u0441 \u0438\u0437 \u042eKassa \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u0431\u0430\u0437\u0443.\u0422\u043e \u0435\u0441\u0442\u044c \u0437\u0430\u0434\u0430\u0447\u0430 \u0442\u0443\u0442 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0440\u0438\u043d\u044f\u0442\u044c webhook, \u0430 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0443\u0440, \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u0432\u0435\u0440\u0438\u0442\u044c.\u0413\u0434\u0435 \u043e\u0431\u044b\u0447\u043d\u043e \u043b\u043e\u043c\u0430\u0435\u0442\u0441\u044f \u043d\u0430\u0438\u0432\u043d\u0430\u044f \u0441\u0445\u0435\u043c\u0430\u0421\u0430\u043c\u044b\u0439 \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0439 \u043f\u0443\u0442\u044c \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a. \u0424\u0440\u043e\u043d\u0442 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043f\u043b\u0430\u0442\u0435\u0436, \u042eKassa \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0435\u0442 webhook, backend \u043f\u043e\u043c\u0435\u0447\u0430\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u044c \u043a\u0430\u043a \u0443\u0441\u043f\u0435\u0448\u043d\u0443\u044e. \u041d\u0430 \u0434\u0435\u043c\u043e \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e. \u0412 \u0436\u0438\u0432\u043e\u043c \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0435 \u043d\u0435\u0442.Webhook \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0439\u0442\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437. \u041c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0439\u0442\u0438 \u043f\u043e\u0437\u0436\u0435. \u041c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0439\u0442\u0438 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 waiting_for_capture, \u0430 \u043d\u0435 succeeded. \u041c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u0442\u043e\u0433\u043e \u0436\u0435 payment.succeeded. \u041c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0441\u0431\u043e\u0439 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u043c\u0435\u0436\u0434\u0443 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435\u043c webhook \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438. \u041c\u043e\u0436\u0435\u0442 \u0441\u043b\u0443\u0447\u0438\u0442\u044c\u0441\u044f \u0442\u0430\u043a, \u0447\u0442\u043e \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0443\u0436\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d, \u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u0431\u0430\u0437\u0430 \u043e\u0431 \u044d\u0442\u043e\u043c \u043d\u0435 \u0437\u043d\u0430\u0435\u0442.\u0415\u0441\u043b\u0438 \u0432 \u0442\u0430\u043a\u043e\u0439 \u0441\u0445\u0435\u043c\u0435 \u043d\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0436\u0443\u0440\u043d\u0430\u043b\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u043d\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438, \u0447\u0442\u043e webhook \u0432\u043e\u043e\u0431\u0449\u0435 \u043f\u0440\u0438\u0448\u0435\u043b \u043e\u0442 \u042eKassa, \u043d\u0435\u0442 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0433\u043e capture \u0438 \u043d\u0435\u0442 \u0440\u0443\u0447\u043d\u043e\u0433\u043e rescue-\u043f\u0443\u0442\u0438, \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0443\u0440 \u0431\u044b\u0441\u0442\u0440\u043e \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043d\u0435\u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u044b\u043c. \u0412 \u0430\u0434\u043c\u0438\u043d\u043a\u0435 \u043e\u0434\u043d\u043e, \u0432 API \u0434\u0440\u0443\u0433\u043e\u0435, \u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0442\u0440\u0435\u0442\u044c\u0435.\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043f\u043b\u0430\u0442\u0435\u0436\u0430 \u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0436\u0443\u0440\u043d\u0430\u043b \u0441\u043e\u0431\u044b\u0442\u0438\u0439\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u0434\u0432\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438. \u041f\u0435\u0440\u0432\u0430\u044f \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0441\u0430\u043c \u043f\u043b\u0430\u0442\u0435\u0436 \u0438 \u0435\u0433\u043e \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u0439 \u0446\u0438\u043a\u043b. \u0412\u0442\u043e\u0440\u0430\u044f \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0436\u0443\u0440\u043d\u0430\u043b \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439.\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043f\u043b\u0430\u0442\u0435\u0436 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 KassaPayment, \u0430 \u0436\u0443\u0440\u043d\u0430\u043b \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 PaymentEventLog.# payment\/models.pyclass KassaPayment(models.Model):    user = models.ForeignKey(User, related_name=&#8217;kassa_payments&#8217;, on_delete=models.CASCADE)    kassa_payment_id = models.CharField(max_length=250, blank=True, null=True)    amount = models.DecimalField(max_digits=10, decimal_places=2)    subscription_type = models.CharField(        choices=[(&#8216;monthly&#8217;, &#8216;Monthly&#8217;), (&#8216;yearly&#8217;, &#8216;Yearly&#8217;), (&#8216;forever&#8217;, &#8216;Forever&#8217;)],        max_length=10    )    status = models.CharField(        choices=[(&#8216;pending&#8217;, &#8216;Pending&#8217;), (&#8216;completed&#8217;, &#8216;Completed&#8217;), (&#8216;failed&#8217;, &#8216;Failed&#8217;),                 (&#8216;refund&#8217;, &#8216;Refund&#8217;), (&#8216;refund_failed&#8217;, &#8216;Refund Failed&#8217;)],        max_length=20,        default=&#8217;pending&#8217;    )    kassa_payment_status = models.CharField(        choices=[(&#8216;waiting_for_capture&#8217;, &#8216;Waiting for capture&#8217;),                 (&#8216;succeeded&#8217;, &#8216;Succeeded&#8217;),                 (&#8216;failed&#8217;, &#8216;Failed&#8217;),                 (&#8216;canceled&#8217;, &#8216;Canceled&#8217;),                 (&#8216;refund_succeeded&#8217;, &#8216;Refund Succeeded&#8217;)],        max_length=20,        default=&#8217;waiting_for_capture&#8217;    )    income_amount = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)    capture_idem_key = models.CharField(max_length=64, blank=True, null=True)class PaymentEventLog(models.Model):    event_id = models.CharField(max_length=64, db_index=True)    event_type = models.CharField(max_length=64, db_index=True)    payload = models.JSONField()    received_at = models.DateTimeField(auto_now_add=True)    applied = models.BooleanField(default=False)    note = models.TextField(blank=True, null=True)\u042d\u0442\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u0435\u0442 \u0441\u0440\u0430\u0437\u0443 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u043e\u0444\u0438\u0442\u043e\u0432. \u0421\u0430\u043c \u043f\u043b\u0430\u0442\u0435\u0436 \u0445\u0440\u0430\u043d\u0438\u0442 \u0438\u0442\u043e\u0433\u043e\u0432\u043e\u0435 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435. \u0416\u0443\u0440\u043d\u0430\u043b \u0445\u0440\u0430\u043d\u0438\u0442 \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0439 webhook \u043a\u0430\u043a \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0444\u0430\u043a\u0442. \u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0434\u0443\u0431\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f, \u0436\u0443\u0440\u043d\u0430\u043b \u044d\u0442\u043e \u043f\u043e\u043a\u0430\u0436\u0435\u0442. \u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u0435 \u0441\u043c\u043e\u0433 \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f, \u0441\u0430\u043c \u0444\u0430\u043a\u0442 \u043f\u0440\u0438\u0445\u043e\u0434\u0430 \u043d\u0435 \u0442\u0435\u0440\u044f\u0435\u0442\u0441\u044f. \u041f\u043e\u0442\u043e\u043c \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 \u0440\u0430\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u0431\u043e\u0435\u0432 \u0447\u0442\u0435\u043d\u0438\u0435\u043c \u0438\u0441\u0442\u043e\u0440\u0438\u0438.\u041f\u043e\u0447\u0435\u043c\u0443 \u043f\u0435\u0440\u0432\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u043b\u0443\u0447\u0448\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u0442\u044c \u043d\u0430 waiting_for_capture \u0438 succeeded\u041f\u0435\u0440\u0432\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043d\u0435 \u043a\u0430\u043a \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043d\u044b\u0439, \u0430 \u043a\u0430\u043a capture=False. \u0422\u043e \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e, \u0430 \u0437\u0430\u0442\u0435\u043c \u0443\u0436\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0434\u0435\u043b\u0430\u0435\u0442 capture.\u0412 \u043a\u043e\u0434\u0435 \u044d\u0442\u043e \u0432\u0438\u0434\u043d\u043e \u0432 \u0441\u0431\u043e\u0440\u043a\u0435 \u043f\u043b\u0430\u0442\u0435\u0436\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445:# payment\/utils.pydef create_payment_data(amount, description, local_payment_id, subscription_type, receipt, payment_method_id=None):    save_pm = (subscription_type in (&#8216;monthly&#8217;, &#8216;yearly&#8217;))    data = {        &#171;amount&#187;: {&#171;value&#187;: str(amount), &#171;currency&#187;: &#171;RUB&#187;},        &#171;capture&#187;: False,        &#171;confirmation&#187;: {&#171;type&#187;: &#171;redirect&#187;, &#171;return_url&#187;: f&#187;{settings.FRONT_URL}\/payment\/success&#187;},        &#171;description&#187;: description,        &#171;receipt&#187;: receipt,        &#171;metadata&#187;: {&#171;payment_id&#187;: local_payment_id, &#171;subscription_type&#187;: subscription_type},    }    if save_pm:        data[&#171;save_payment_method&#187;] = True    if payment_method_id:        data[&#171;payment_method_id&#187;] = payment_method_id        data.pop(&#171;confirmation&#187;, None)        data[&#171;capture&#187;] = True    return data\u0414\u043b\u044f \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u043b\u0430\u0442\u0435\u0436\u0430 \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u043f\u043e \u0434\u0432\u0443\u043c \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c. \u0412\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u0443\u0435\u043c\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u043c\u0435\u0436\u0434\u0443 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0438 \u043e\u043a\u043e\u043d\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0437\u0430\u0445\u0432\u0430\u0442\u043e\u043c \u0441\u0440\u0435\u0434\u0441\u0442\u0432. \u0412\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, webhook payment.waiting_for_capture \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u043c \u044d\u0442\u0430\u043f\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0438 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c.\u0415\u0441\u043b\u0438 \u0441\u0432\u0430\u043b\u0438\u0442\u044c waiting_for_capture \u0438 succeeded \u0432 \u043e\u0434\u043d\u0443 \u043a\u0443\u0447\u0443, \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0442\u0435\u0440\u044f\u0435\u0442 \u043e\u0434\u0438\u043d \u0432\u0430\u0436\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f. \u0421\u043d\u0430\u0440\u0443\u0436\u0438 \u043a\u0430\u0436\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u0432 \u043f\u0440\u043e\u0441\u0442\u043e \u0434\u0432\u0430. \u041d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u044d\u0442\u043e \u0434\u0432\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u044d\u0442\u0430\u043f\u0430 \u0441 \u0440\u0430\u0437\u043d\u043e\u0439 \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u043e\u0439.Capture \u043d\u0435\u043b\u044c\u0437\u044f \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0442\u044c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u043c \u043a\u043b\u044e\u0447\u043e\u043c\u041a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 payment.waiting_for_capture, backend \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u043f\u043b\u0430\u0442\u0435\u0436. \u0418 \u0432\u043e\u0442 \u0437\u0434\u0435\u0441\u044c \u0447\u0430\u0441\u0442\u043e \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430. \u0415\u0441\u043b\u0438 \u0434\u0435\u043b\u0430\u0442\u044c capture \u0441 \u043d\u043e\u0432\u044b\u043c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u043c idempotence_key \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u043f\u043e\u0432\u0442\u043e\u0440\u0435, \u0442\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u043e\u0434\u043d\u043e\u0433\u043e \u0438 \u0442\u043e\u0433\u043e \u0436\u0435 webhook \u0443\u0436\u0435 \u043d\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0439.\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u044b\u0439 \u043a\u043b\u044e\u0447 \u0432\u0438\u0434\u0430 capture:{payment_id} \u0438 \u043e\u043d \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 \u0441\u0430\u043c\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u043f\u043b\u0430\u0442\u0435\u0436\u0430.# payment\/utils.pydef confirm_payment_in_kassa(payment_id, amount=None):    try:        kp = KassaPayment.objects.filter(kassa_payment_id=payment_id).first()        if kp:            if not kp.capture_idem_key:                kp.capture_idem_key = f&#187;capture:{payment_id}&#187;                kp.save(update_fields=[&#171;capture_idem_key&#187;])            value = str(kp.amount)            idem_key = kp.capture_idem_key        else:            if amount is None:                return None            value = str(amount)            idem_key = f&#187;capture:{payment_id}&#187;        resp = Payment.capture(            payment_id,            {&#171;amount&#187;: {&#171;value&#187;: value, &#171;currency&#187;: &#171;RUB&#187;}},            idem_key,        )        return resp    except Exception:        return None\u041d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0434\u0435\u0442\u0430\u043b\u044c \u0440\u0435\u0448\u0430\u0435\u0442 \u0432\u0430\u0436\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443. \u041e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u0432\u0441\u0435\u0433\u0434\u0430 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442\u0441\u044f \u043e\u0434\u043d\u0438\u043c \u0438 \u0442\u0435\u043c \u0436\u0435 \u043a\u043b\u044e\u0447\u043e\u043c. \u0417\u043d\u0430\u0447\u0438\u0442, \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0435 \u0432\u044b\u0437\u043e\u0432\u044b \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u044e\u0442 \u043d\u043e\u0432\u044b\u0439 \u0441\u043c\u044b\u0441\u043b, \u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0442 \u0441\u0442\u0430\u0440\u044b\u0439.\u0412\u043e\u043a\u0440\u0443\u0433 \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u043d\u0443\u0436\u043d\u0430 \u043a\u0430\u043a \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u0445\u043e\u0432\u043a\u0430 \u043e\u0442 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0439 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438.  Webhook \u043d\u0443\u0436\u043d\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043e \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0438\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0441\u043b\u043e\u0439 \u0434\u043e\u0432\u0435\u0440\u0438\u044f \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0435\u0449\u0435 \u0434\u043e \u0440\u0430\u0437\u0431\u043e\u0440\u0430 payload. \u0412\u0435\u0431\u0445\u0443\u043a \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u0440\u043e\u0439\u0442\u0438 IP-check. \u0414\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043e\u0441\u043b\u0430\u0431\u0438\u0442\u044c, \u0434\u043b\u044f \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0430 \u043d\u0435\u0442.# payment\/utils.pydef is_valid_webhook_signature(request):    ALLOWED_IP_RANGES = [        &#8216;185.71.76.0\/27&#8217;,        &#8216;185.71.77.0\/27&#8217;,        &#8216;77.75.153.0\/25&#8217;,        &#8216;77.75.156.11&#8217;,        &#8216;77.75.156.35&#8217;,        &#8216;77.75.154.128\/25&#8217;,        &#8216;2a02:5180::\/32&#8217;,    ]    if getattr(settings, &#8216;DJANGO_ENV&#8217;, &#8216;local&#8217;) == &#8216;local&#8217;:        return True    raw_xff = (request.META.get(&#8216;HTTP_X_FORWARDED_FOR&#8217;) or &#187;).strip()    x_real = (request.META.get(&#8216;HTTP_X_REAL_IP&#8217;) or &#187;).strip()    remote = (request.META.get(&#8216;REMOTE_ADDR&#8217;) or &#187;).strip()    ip = raw_xff.split(&#8216;,&#8217;)[0].strip() if raw_xff else (x_real or remote)    if ip.startswith(&#8216;::ffff:&#8217;):        ip = ip[7:]    try:        ip_obj = ipaddress.ip_address(ip)    except ValueError:        return False    for rng in ALLOWED_IP_RANGES:        if &#8216;\/&#8217; in rng and ip_obj in ipaddress.ip_network(rng, strict=False):            return True        if ip == rng:            return True    return False\u0415\u0441\u043b\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0435\u0449\u0435 \u043d\u0435 \u043f\u0440\u043e\u0448\u0435\u043b \u0441\u0435\u0442\u0435\u0432\u0443\u044e \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443, \u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0434\u043e \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u044b\u043c webhook \u0438 \u0442\u0430\u0449\u0438\u0442\u044c \u0432 \u0431\u0438\u0437\u043d\u0435\u0441-\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443. \u0427\u0442\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442\u0441\u044f \u0438 \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u0412 \u043f\u0440\u043e\u0434-\u0440\u0435\u0436\u0438\u043c\u0435 \u0437\u0430\u043f\u0440\u043e\u0441 \u0431\u0435\u0437 \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0433\u043e IP \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 403, \u0430 \u0437\u0430\u043f\u0438\u0441\u044c \u0432 PaymentEventLog \u0434\u0430\u0436\u0435 \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f. \u0422\u043e \u0435\u0441\u0442\u044c \u0434\u043e \u0436\u0443\u0440\u043d\u0430\u043b\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0434\u043e\u0445\u043e\u0434\u0438\u0442 \u0443\u0436\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e, \u0447\u0442\u043e \u043f\u0440\u043e\u0448\u043b\u043e \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0444\u0438\u043b\u044c\u0442\u0440 \u0434\u043e\u0432\u0435\u0440\u0438\u044f.\u0416\u0443\u0440\u043d\u0430\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043b\u0443\u0447\u0448\u0435 \u0434\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0438 \u043f\u043e\u0441\u043b\u0435 \u043d\u0435\u0433\u043e\u041f\u043e\u0441\u043b\u0435 IP-check webhook \u043d\u0435 \u043d\u0430\u0434\u043e \u0441\u0440\u0430\u0437\u0443 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u043a \u0431\u0430\u0437\u0435. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043b\u0443\u0447\u0448\u0435 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043a\u0430\u043a \u0444\u0430\u043a\u0442. \u041f\u043e\u0442\u043e\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0435\u0433\u043e \u0432 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a. \u041f\u043e\u0442\u043e\u043c \u0443\u0436\u0435 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c \u043e\u043d\u043e \u0438\u043b\u0438 \u043d\u0435\u0442.\u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 webhook \u0443\u0441\u0442\u0440\u043e\u0435\u043d \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a:# payment\/views.py@csrf_exempt@api_view([&#8216;POST&#8217;])@permission_classes([AllowAny])def kassa_webhook(request):    if not is_valid_webhook_signature(request):        return Response(status=403)    try:        data = json.loads(request.body.decode(&#8216;utf-8&#8217;))    except Exception:        return Response(status=400)    event = (data.get(&#8216;event&#8217;) or &#187;).strip()    obj = data.get(&#8216;object&#8217;, {}) or {}    obj_id = obj.get(&#8216;id&#8217;) or &#187;    log = PaymentEventLog.objects.create(        event_id=obj_id,        event_type=event,        payload=data,        applied=False,        note=None,    )    if event == &#8216;payment.waiting_for_capture&#8217;:        resp = webhook_waiting_for_capture(data)    elif event == &#8216;payment.succeeded&#8217;:        resp = webhook_succeeded(data)    elif event == &#8216;payment.canceled&#8217;:        resp = webhook_canceled(data)    elif event == &#8216;refund.succeeded&#8217;:        resp = webhook_refund(data)    else:        PaymentEventLog.objects.filter(pk=log.pk).update(note=&#8217;unknown_event&#8217;)        return Response(status=200)    status_code = getattr(resp,&#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-482774","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/482774","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=482774"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/482774\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=482774"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=482774"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=482774"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}