Содержание

SERVICE_REQUEST в DONE: финализация оплаты и закрытие

Назначение: Сценарий процесса. Продукт: ShariX Open.

Для кого: Разработчик · Поддержка · Интегратор · Рецензент и приёмщик · Нейросеть.

Темы: Заказы → Финализация оплаты · Заказы → Жизненный цикл.

Область: ShariX Open. Обработчик: open_service_request_done, тип SERVICE_REQUEST (2), статус DONE. 31.10.2026 опубликованные исходники повторно получены из Git. Обе Python-копии заново сопоставлены с результатом генерации Blockly; проверены 11 исходников генераторов. Сам handler не исполнялся. Выбранную версию, конфигурацию и внешние подключения сервера проверяют отдельно.

Назначение и границы

Работа по заказу уже завершена. Handler повторяет платёжную финализацию и отдельно проверяет возможность перевести Ticket DONE→CLOSED. Он не просит исполнителя снова выполнить работу, не повторяет round finish, не пересчитывает фактическое окончание и не создаёт следующий повторный заказ своим processing.

Здесь нужно различать:

BPMN описательные, isExecutable=false. Runtime исполняет выбранный Python, сгенерированный из Blockly; схема Wiki процесс не запускает.

Порядок вызовов (BPMN)

Схема BPMN

Загрузка схемы…

Исходник схемы
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_service_done_logic" targetNamespace="urn:sharix:knowledge:handlers">
  <bpmn:process id="done_Process_done" name="DONE: повтор финализации оплаты, независимый gate закрытия" isExecutable="false">
    <bpmn:documentation>{"notes": ["BPMN isExecutable=false; Python сверён с каноническим Blockly.", "retry_done_payment false не отменяет следующую попытку close_after_done.", "Решающий gate — ensure_order_can_be_closed, не предыдущий retry success.", "Внешняя платёжная система и выбранный backend зависят от установки."], "sources": [{"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "handlers/open_service_request_done.py", "repository": "sharix-open-handlers-examples", "sha256": "ad03fe7806779ff192cb19fe45d86b6d0ba30e41b9ad22ad5d0aca5732539c21"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "generated_handler_blocks/open_service_request_done.json", "repository": "sharix-open-handlers-examples", "sha256": "5ccca3b88d8f637f81c737be8fca2d5d31def43923c80ac8a432cea19ceca462"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "catalog.py", "repository": "sharix-open-handlers-examples", "sha256": "e3dcb72b7e620e2837adacd7af053f35b04cdff475789496d9c114e17d57dfde"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/blockly_runtime.py", "repository": "sharix-open-handlers-backend", "sha256": "3e222150cbff315077c6c9b160d46e59856cedf6d6591d213e958c63cb2f2428"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/requesters.py", "repository": "sharix-open-handlers-backend", "sha256": "965a348cea477722bbaf4c8c6b06023eb238b71664a332f7ea8c90bfe72ea808"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/order_action_messages.py", "repository": "sharix-open-handlers-backend", "sha256": "046c262c0aabb02104fefca4e887324f99df22731e7726063f614aaf8839ca5c"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_blockly_codegen.py", "repository": "sharix-open-handlers-backend", "sha256": "bce71eab622922f8dd0954222842349799b60e18a0ab6310cc3e5f305603661e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_codegen_contract.json", "repository": "sharix-open-handlers-backend", "sha256": "0bfd2b0ebb07bd380ed3c867386536ab88ef7dfa6e13f1cd8c91d32bb02c1ae9"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "lib/tickets.py", "repository": "sharix-open-tickets", "sha256": "0d260c17947f9a186d07af2749159780e3c7354a95b4bafe9d67493663c96883"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/handler_auth.py", "repository": "sharix-open-tickets", "sha256": "ac76dc45aaa9742e5e1da0b98aae914bb2e287048d33e97b4a245a6997bc8425"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "signals.py", "repository": "sharix-open-tickets", "sha256": "39aad8690a2e7d35cebfb052127a8130a951cbb4eea316c4f767a0a59fc5fa7e"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "services/handler_wakeup.py", "repository": "sharix-open-tickets", "sha256": "c9fdc62a18301fd7c075395c697d5b1c738c77713aa71fcbb5ab097e173d98de"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "apiviews/handler_order_workflow.py", "repository": "sharix-open-backend", "sha256": "35624ded6e67110437a25dd07caef214a04251eb0136194ae939fa32fef17e84"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "apiviews/open_api.py", "repository": "sharix-open-backend", "sha256": "cb8fdda304d29c2cc5c0fc938759a13c60022871b7b93d77883d521820021ad3"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_handler_relay.py", "repository": "sharix-open-backend", "sha256": "e49a7876169403970d995af464357e2134af19a398372c55db2a5b40681d731e"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/handler_order_context.py", "repository": "sharix-open-backend", "sha256": "f3c0731e74d30dbe5e59bd53040a06ee108f14f7aad04771e65d741cffeb7179"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_workflow_policy.py", "repository": "sharix-open-backend", "sha256": "3266c6ecab490a0e19c8bbc02041387b86fe4b14068750e545ba9bde8bf51326"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_actions.py", "repository": "sharix-open-backend", "sha256": "27ce243408a46f1b700d94bf78a25c68ef21d51078bb03627174e4a424d82ef0"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_permissions.py", "repository": "sharix-open-backend", "sha256": "9c1636daa80c4da68c3ae5cf347e279127d142eeff91e969559e8bd54ca3d81d"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "urls.py", "repository": "sharix-open-backend", "sha256": "61ffd2f0563291da2dcc9488a5443cb5e2c0395108740a6700034cfd90548158"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_lifecycle.py", "repository": "sharix-open-backend", "sha256": "66dd2c4db9da3aaf90cb080946c1920a4f9953d62d1e6c9e224dd52564c4d661"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_provider_requirements.py", "repository": "sharix-open-backend", "sha256": "392f1f9180a098a4749b5fe5b7948e34530ccdb2befed646e538cdb7a5ddcada"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/order_rounds.py", "repository": "sharix-open-handlers-backend", "sha256": "e27a9190374c3888930f60e26686c11f69069bbbefd4511d21ad7267301321eb"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "settings_schema.py", "repository": "sharix-open-handlers-backend", "sha256": "8ea8494405c6808834f4be7b171ffb8a969e1dd9b1654d36a8c21fbc9e3599c3"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/payments/facade.py", "repository": "sharix-open-backend", "sha256": "57d0d71939f5bcd391090ca473008f2283e8d7fdb369486ff044e1fcf9cd3876"}]}</bpmn:documentation>
    <bpmn:laneSet id="done_Lanes_done">
      <bpmn:lane id="done_Lane_worker" name="Один проход processing; результат retry не ветвит основной flow">
        <bpmn:flowNodeRef>done_start</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>done_retry</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>done_close</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>done_success</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>done_end</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>done_defer</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>done_wait</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>done_retry_error</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>done_close_error</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>done_error</bpmn:flowNodeRef>
      </bpmn:lane>
    </bpmn:laneSet>
    <bpmn:startEvent id="done_start" name="Свой статус DONE">
      <bpmn:outgoing>done_Flow_0</bpmn:outgoing>
    </bpmn:startEvent>
    <bpmn:serviceTask id="done_retry" name="retry-done-payment; записать результат">
      <bpmn:incoming>done_Flow_0</bpmn:incoming>
      <bpmn:outgoing>done_Flow_1</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:serviceTask id="done_close" name="close-after-done: проверить gate и попытаться CLOSED">
      <bpmn:incoming>done_Flow_1</bpmn:incoming>
      <bpmn:outgoing>done_Flow_2</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:exclusiveGateway id="done_success" name="success=true?">
      <bpmn:incoming>done_Flow_2</bpmn:incoming>
      <bpmn:outgoing>done_Flow_3</bpmn:outgoing>
      <bpmn:outgoing>done_Flow_4</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:endEvent id="done_end" name="Локально CLOSED, clear reason, remove queue">
      <bpmn:incoming>done_Flow_3</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:serviceTask id="done_defer" name="payment_finalization; DONE_PAYMENT_RETRY_SECONDS">
      <bpmn:incoming>done_Flow_4</bpmn:incoming>
      <bpmn:outgoing>done_Flow_5</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:endEvent id="done_wait" name="Worker свободен; следующий проход позже / по событию">
      <bpmn:incoming>done_Flow_5</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:boundaryEvent id="done_retry_error" name="API исключение" attachedToRef="done_retry" cancelActivity="true">
      <bpmn:outgoing>done_Flow_6</bpmn:outgoing>
      <bpmn:errorEventDefinition />
    </bpmn:boundaryEvent>
    <bpmn:boundaryEvent id="done_close_error" name="API исключение" attachedToRef="done_close" cancelActivity="true">
      <bpmn:outgoing>done_Flow_7</bpmn:outgoing>
      <bpmn:errorEventDefinition />
    </bpmn:boundaryEvent>
    <bpmn:endEvent id="done_error" name="processing_error; общий defer">
      <bpmn:incoming>done_Flow_6</bpmn:incoming>
      <bpmn:incoming>done_Flow_7</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:sequenceFlow id="done_Flow_0" sourceRef="done_start" targetRef="done_retry" name="" />
    <bpmn:sequenceFlow id="done_Flow_1" sourceRef="done_retry" targetRef="done_close" name="В том числе retry success=false" />
    <bpmn:sequenceFlow id="done_Flow_2" sourceRef="done_close" targetRef="done_success" name="" />
    <bpmn:sequenceFlow id="done_Flow_3" sourceRef="done_success" targetRef="done_end" name="Да" />
    <bpmn:sequenceFlow id="done_Flow_4" sourceRef="done_success" targetRef="done_defer" name="Нет" />
    <bpmn:sequenceFlow id="done_Flow_5" sourceRef="done_defer" targetRef="done_wait" name="" />
    <bpmn:sequenceFlow id="done_Flow_6" sourceRef="done_retry_error" targetRef="done_error" name="" />
    <bpmn:sequenceFlow id="done_Flow_7" sourceRef="done_close_error" targetRef="done_error" name="" />
  </bpmn:process>
  <bpmn:collaboration id="Collaboration_service_done_logic">
    <bpmn:participant id="done_Pool_done" name="DONE: повтор финализации оплаты, независимый gate закрытия" processRef="done_Process_done" />
  </bpmn:collaboration>
  <bpmndi:BPMNDiagram id="Diagram_service_done_logic">
    <bpmndi:BPMNPlane id="Plane_service_done_logic" bpmnElement="Collaboration_service_done_logic">
      <bpmndi:BPMNShape id="done_DI_Pool_done" bpmnElement="done_Pool_done" isHorizontal="true">
        <dc:Bounds x="0.0" y="0.0" width="1530" height="680" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="done_DI_Lane_worker" bpmnElement="done_Lane_worker" isHorizontal="true">
        <dc:Bounds x="30.0" y="0.0" width="1500" height="680" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="done_DI_start" bpmnElement="done_start">
        <dc:Bounds x="132.0" y="82.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="done_DI_retry" bpmnElement="done_retry">
        <dc:Bounds x="365.0" y="65.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="done_DI_close" bpmnElement="done_close">
        <dc:Bounds x="675.0" y="65.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="done_DI_success" bpmnElement="done_success">
        <dc:Bounds x="1035.0" y="75.0" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="done_DI_end" bpmnElement="done_end">
        <dc:Bounds x="1352.0" y="82.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="done_DI_defer" bpmnElement="done_defer">
        <dc:Bounds x="985.0" y="305.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="done_DI_wait" bpmnElement="done_wait">
        <dc:Bounds x="1352.0" y="322.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="done_DI_retry_error" bpmnElement="done_retry_error">
        <dc:Bounds x="472.0" y="117.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="done_DI_close_error" bpmnElement="done_close_error">
        <dc:Bounds x="782.0" y="117.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="done_DI_error" bpmnElement="done_error">
        <dc:Bounds x="422.0" y="562.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="done_DI_Flow_0" bpmnElement="done_Flow_0">
        <di:waypoint x="168.0" y="100.0" />
        <di:waypoint x="365.0" y="100.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="done_DI_Flow_1" bpmnElement="done_Flow_1">
        <di:waypoint x="515.0" y="100.0" />
        <di:waypoint x="675.0" y="100.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="done_DI_Flow_2" bpmnElement="done_Flow_2">
        <di:waypoint x="825.0" y="100.0" />
        <di:waypoint x="1035.0" y="100.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="done_DI_Flow_3" bpmnElement="done_Flow_3">
        <di:waypoint x="1085.0" y="100.0" />
        <di:waypoint x="1352.0" y="100.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="done_DI_Flow_4" bpmnElement="done_Flow_4">
        <di:waypoint x="1060.0" y="125.0" />
        <di:waypoint x="1060.0" y="305.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="done_DI_Flow_5" bpmnElement="done_Flow_5">
        <di:waypoint x="1135.0" y="340.0" />
        <di:waypoint x="1352.0" y="340.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="done_DI_Flow_6" bpmnElement="done_Flow_6">
        <di:waypoint x="508.0" y="135.0" />
        <di:waypoint x="550.0" y="135.0" />
        <di:waypoint x="600.0" y="135.0" />
        <di:waypoint x="600.0" y="510.0" />
        <di:waypoint x="440.0" y="510.0" />
        <di:waypoint x="440.0" y="562.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="done_DI_Flow_7" bpmnElement="done_Flow_7">
        <di:waypoint x="818.0" y="135.0" />
        <di:waypoint x="860.0" y="135.0" />
        <di:waypoint x="900.0" y="135.0" />
        <di:waypoint x="900.0" y="510.0" />
        <di:waypoint x="440.0" y="510.0" />
        <di:waypoint x="440.0" y="562.0" />
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn:definitions>

В своём статусе handler выполняет:

  1. Вызвать retry_done_payment.
  2. Если первый вызов не бросил исключение — вызвать close_after_done.
  3. При success=true у close — завершить локальную обработку.
  4. При неуспешном close — сохранить payment_finalization и defer по SERVICE_REQUEST_DONE_PAYMENT_RETRY_SECONDS.

Boolean первого вызова не выбирает следующую ветвь: обработанный retry success=false не запрещает попытку close. Решающее условие проверяет отдельный backend gate.

Если close возвращает success=true, runtime локально устанавливает CLOSED, очищает waiting reason и исключает Ticket из очереди. Иначе записывает payment_finalization и назначает defer по SERVICE_REQUEST_DONE_PAYMENT_RETRY_SECONDS.

Обработанный ответ и исключение имеют разные последствия:

В processing нет отдельной загрузки order context и нет раунда ответа: каждый backend action самостоятельно проверяет текущий scope и заказ. При чужом локальном статусе handler запрашивает удаление из очереди и комнаты.

Backend и внешнее платёжное состояние (BPMN)

Схема BPMN

Загрузка схемы…

Исходник схемы
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_service_done_payment" targetNamespace="urn:sharix:knowledge:handlers">
  <bpmn:process id="backend_Process_backend" name="Два scoped действия backend; работа уже DONE, платёж проверяется отдельно" isExecutable="false">
    <bpmn:documentation>{"notes": ["BPMN isExecutable=false; Python сверён с каноническим Blockly.", "retry_done_payment false не отменяет следующую попытку close_after_done.", "Решающий gate — ensure_order_can_be_closed, не предыдущий retry success.", "Внешняя платёжная система и выбранный backend зависят от установки."], "sources": [{"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "handlers/open_service_request_done.py", "repository": "sharix-open-handlers-examples", "sha256": "ad03fe7806779ff192cb19fe45d86b6d0ba30e41b9ad22ad5d0aca5732539c21"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "generated_handler_blocks/open_service_request_done.json", "repository": "sharix-open-handlers-examples", "sha256": "5ccca3b88d8f637f81c737be8fca2d5d31def43923c80ac8a432cea19ceca462"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "catalog.py", "repository": "sharix-open-handlers-examples", "sha256": "e3dcb72b7e620e2837adacd7af053f35b04cdff475789496d9c114e17d57dfde"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/blockly_runtime.py", "repository": "sharix-open-handlers-backend", "sha256": "3e222150cbff315077c6c9b160d46e59856cedf6d6591d213e958c63cb2f2428"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/requesters.py", "repository": "sharix-open-handlers-backend", "sha256": "965a348cea477722bbaf4c8c6b06023eb238b71664a332f7ea8c90bfe72ea808"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/order_action_messages.py", "repository": "sharix-open-handlers-backend", "sha256": "046c262c0aabb02104fefca4e887324f99df22731e7726063f614aaf8839ca5c"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_blockly_codegen.py", "repository": "sharix-open-handlers-backend", "sha256": "bce71eab622922f8dd0954222842349799b60e18a0ab6310cc3e5f305603661e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_codegen_contract.json", "repository": "sharix-open-handlers-backend", "sha256": "0bfd2b0ebb07bd380ed3c867386536ab88ef7dfa6e13f1cd8c91d32bb02c1ae9"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "lib/tickets.py", "repository": "sharix-open-tickets", "sha256": "0d260c17947f9a186d07af2749159780e3c7354a95b4bafe9d67493663c96883"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/handler_auth.py", "repository": "sharix-open-tickets", "sha256": "ac76dc45aaa9742e5e1da0b98aae914bb2e287048d33e97b4a245a6997bc8425"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "signals.py", "repository": "sharix-open-tickets", "sha256": "39aad8690a2e7d35cebfb052127a8130a951cbb4eea316c4f767a0a59fc5fa7e"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "services/handler_wakeup.py", "repository": "sharix-open-tickets", "sha256": "c9fdc62a18301fd7c075395c697d5b1c738c77713aa71fcbb5ab097e173d98de"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "apiviews/handler_order_workflow.py", "repository": "sharix-open-backend", "sha256": "35624ded6e67110437a25dd07caef214a04251eb0136194ae939fa32fef17e84"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "apiviews/open_api.py", "repository": "sharix-open-backend", "sha256": "cb8fdda304d29c2cc5c0fc938759a13c60022871b7b93d77883d521820021ad3"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_handler_relay.py", "repository": "sharix-open-backend", "sha256": "e49a7876169403970d995af464357e2134af19a398372c55db2a5b40681d731e"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/handler_order_context.py", "repository": "sharix-open-backend", "sha256": "f3c0731e74d30dbe5e59bd53040a06ee108f14f7aad04771e65d741cffeb7179"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_workflow_policy.py", "repository": "sharix-open-backend", "sha256": "3266c6ecab490a0e19c8bbc02041387b86fe4b14068750e545ba9bde8bf51326"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_actions.py", "repository": "sharix-open-backend", "sha256": "27ce243408a46f1b700d94bf78a25c68ef21d51078bb03627174e4a424d82ef0"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_permissions.py", "repository": "sharix-open-backend", "sha256": "9c1636daa80c4da68c3ae5cf347e279127d142eeff91e969559e8bd54ca3d81d"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "urls.py", "repository": "sharix-open-backend", "sha256": "61ffd2f0563291da2dcc9488a5443cb5e2c0395108740a6700034cfd90548158"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_lifecycle.py", "repository": "sharix-open-backend", "sha256": "66dd2c4db9da3aaf90cb080946c1920a4f9953d62d1e6c9e224dd52564c4d661"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/order_provider_requirements.py", "repository": "sharix-open-backend", "sha256": "392f1f9180a098a4749b5fe5b7948e34530ccdb2befed646e538cdb7a5ddcada"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/order_rounds.py", "repository": "sharix-open-handlers-backend", "sha256": "e27a9190374c3888930f60e26686c11f69069bbbefd4511d21ad7267301321eb"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "settings_schema.py", "repository": "sharix-open-handlers-backend", "sha256": "8ea8494405c6808834f4be7b171ffb8a969e1dd9b1654d36a8c21fbc9e3599c3"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/payments/facade.py", "repository": "sharix-open-backend", "sha256": "57d0d71939f5bcd391090ca473008f2283e8d7fdb369486ff044e1fcf9cd3876"}]}</bpmn:documentation>
    <bpmn:laneSet id="backend_Lanes_backend">
      <bpmn:lane id="backend_Lane_worker" name="order.retry_done_payment / order.close_after_done">
        <bpmn:flowNodeRef>backend_retry</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_lock</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_result</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_close</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_gate</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_allowed</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_blocked</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_save</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>backend_end</bpmn:flowNodeRef>
      </bpmn:lane>
    </bpmn:laneSet>
    <bpmn:startEvent id="backend_retry" name="retry-done-payment">
      <bpmn:outgoing>backend_Flow_0</bpmn:outgoing>
      <bpmn:messageEventDefinition />
    </bpmn:startEvent>
    <bpmn:serviceTask id="backend_lock" name="Lock + DONE; handle_after_order_done через facade">
      <bpmn:incoming>backend_Flow_0</bpmn:incoming>
      <bpmn:outgoing>backend_Flow_1</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:endEvent id="backend_result" name="Вернуть success/status/message, не менять Ticket stage">
      <bpmn:incoming>backend_Flow_1</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:startEvent id="backend_close" name="close-after-done">
      <bpmn:outgoing>backend_Flow_2</bpmn:outgoing>
      <bpmn:messageEventDefinition />
    </bpmn:startEvent>
    <bpmn:serviceTask id="backend_gate" name="Lock + DONE; ensure_order_can_be_closed">
      <bpmn:incoming>backend_Flow_2</bpmn:incoming>
      <bpmn:outgoing>backend_Flow_3</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:exclusiveGateway id="backend_allowed" name="Hook блокирует переход?">
      <bpmn:incoming>backend_Flow_3</bpmn:incoming>
      <bpmn:outgoing>backend_Flow_4</bpmn:outgoing>
      <bpmn:outgoing>backend_Flow_5</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:endEvent id="backend_blocked" name="payment_blocked; сохранить DONE">
      <bpmn:incoming>backend_Flow_4</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:serviceTask id="backend_save" name="Нет блокировки: канонический CLOSED">
      <bpmn:incoming>backend_Flow_5</bpmn:incoming>
      <bpmn:outgoing>backend_Flow_6</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:endEvent id="backend_end" name="Commit, wakeup; runtime сверяет результат">
      <bpmn:incoming>backend_Flow_6</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:sequenceFlow id="backend_Flow_0" sourceRef="backend_retry" targetRef="backend_lock" name="" />
    <bpmn:sequenceFlow id="backend_Flow_1" sourceRef="backend_lock" targetRef="backend_result" name="" />
    <bpmn:sequenceFlow id="backend_Flow_2" sourceRef="backend_close" targetRef="backend_gate" name="" />
    <bpmn:sequenceFlow id="backend_Flow_3" sourceRef="backend_gate" targetRef="backend_allowed" name="" />
    <bpmn:sequenceFlow id="backend_Flow_4" sourceRef="backend_allowed" targetRef="backend_blocked" name="Да" />
    <bpmn:sequenceFlow id="backend_Flow_5" sourceRef="backend_allowed" targetRef="backend_save" name="Нет" />
    <bpmn:sequenceFlow id="backend_Flow_6" sourceRef="backend_save" targetRef="backend_end" name="" />
  </bpmn:process>
  <bpmn:collaboration id="Collaboration_service_done_payment">
    <bpmn:participant id="backend_Pool_backend" name="Два scoped действия backend; работа уже DONE, платёж проверяется отдельно" processRef="backend_Process_backend" />
    <bpmn:participant id="payment_Participant" name="Выбранный платёжный backend / при необходимости внешний gateway и действие клиента" />
    <bpmn:messageFlow id="Message_service_done_payment_0" sourceRef="backend_lock" targetRef="payment_Participant" name="Повтор финализации; не обещание нового списания" />
    <bpmn:messageFlow id="Message_service_done_payment_1" sourceRef="payment_Participant" targetRef="backend_gate" name="Актуальное платёжное состояние/условия backend" />
  </bpmn:collaboration>
  <bpmndi:BPMNDiagram id="Diagram_service_done_payment">
    <bpmndi:BPMNPlane id="Plane_service_done_payment" bpmnElement="Collaboration_service_done_payment">
      <bpmndi:BPMNShape id="backend_DI_Pool_backend" bpmnElement="backend_Pool_backend" isHorizontal="true">
        <dc:Bounds x="0.0" y="0.0" width="1530" height="740" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_Lane_worker" bpmnElement="backend_Lane_worker" isHorizontal="true">
        <dc:Bounds x="30.0" y="0.0" width="1500" height="740" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_retry" bpmnElement="backend_retry">
        <dc:Bounds x="132.0" y="82.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_lock" bpmnElement="backend_lock">
        <dc:Bounds x="365.0" y="65.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_result" bpmnElement="backend_result">
        <dc:Bounds x="732.0" y="82.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_close" bpmnElement="backend_close">
        <dc:Bounds x="132.0" y="382.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_gate" bpmnElement="backend_gate">
        <dc:Bounds x="365.0" y="365.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_allowed" bpmnElement="backend_allowed">
        <dc:Bounds x="725.0" y="375.0" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_blocked" bpmnElement="backend_blocked">
        <dc:Bounds x="732.0" y="622.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_save" bpmnElement="backend_save">
        <dc:Bounds x="985.0" y="365.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="backend_DI_end" bpmnElement="backend_end">
        <dc:Bounds x="1352.0" y="382.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="backend_DI_Flow_0" bpmnElement="backend_Flow_0">
        <di:waypoint x="168.0" y="100.0" />
        <di:waypoint x="365.0" y="100.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="backend_DI_Flow_1" bpmnElement="backend_Flow_1">
        <di:waypoint x="515.0" y="100.0" />
        <di:waypoint x="732.0" y="100.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="backend_DI_Flow_2" bpmnElement="backend_Flow_2">
        <di:waypoint x="168.0" y="400.0" />
        <di:waypoint x="365.0" y="400.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="backend_DI_Flow_3" bpmnElement="backend_Flow_3">
        <di:waypoint x="515.0" y="400.0" />
        <di:waypoint x="725.0" y="400.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="backend_DI_Flow_4" bpmnElement="backend_Flow_4">
        <di:waypoint x="750.0" y="425.0" />
        <di:waypoint x="750.0" y="622.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="backend_DI_Flow_5" bpmnElement="backend_Flow_5">
        <di:waypoint x="775.0" y="400.0" />
        <di:waypoint x="985.0" y="400.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="backend_DI_Flow_6" bpmnElement="backend_Flow_6">
        <di:waypoint x="1135.0" y="400.0" />
        <di:waypoint x="1352.0" y="400.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="DI_payment_Participant" bpmnElement="payment_Participant" isHorizontal="true">
        <dc:Bounds x="0" y="950" width="1530" height="130" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="DI_Message_service_done_payment_0" bpmnElement="Message_service_done_payment_0">
        <di:waypoint x="440" y="135" />
        <di:waypoint x="440" y="850" />
        <di:waypoint x="650" y="850" />
        <di:waypoint x="650" y="950" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="680" y="815" width="260" height="42" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="DI_Message_service_done_payment_1" bpmnElement="Message_service_done_payment_1">
        <di:waypoint x="1200" y="950" />
        <di:waypoint x="1450" y="950" />
        <di:waypoint x="1450" y="330" />
        <di:waypoint x="440" y="330" />
        <di:waypoint x="440" y="365" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="1140" y="790" width="260" height="42" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn:definitions>

Оба action доступны через HasHandlerAPIKey, собственный capability и текущий Ticket scope. Они блокируют актуальный Orders и требуют DONE; произвольный generic ticket.transition не используется.

retry-done-payment

Требует order.retry_done_payment, вызывает handle_after_order_done через выбранный payment facade и возвращает success, payment_status и сообщение. Сам этот action не меняет стадию Ticket. Если hook отсутствует/вернул None, retry считается успешным с пояснением, что повтор не требуется.

Success здесь не является универсальным доказательством оплаченности. Неуспех также не означает, что работу надо выполнить повторно. Конкретные gateway-операции, идемпотентность внешних запросов и необходимость действия клиента принадлежат выбранному платёжному backend; из одного handler нельзя вывести гарантию нового списания на каждом проходе или обязательный внешний gateway для любой установки.

close-after-done

Требует order.close_after_done, вызывает ensure_order_can_be_closed. Если возвращённый hook имеет should_block_transition=true, backend отвечает payment_blocked и сохраняет DONE. Если блокировки нет, выполняется канонический CLOSED, к сообщению может добавиться информация платёжного hook.

Поэтому previous retry success=true не отменяет запрет gate, а retry success=false сам по себе не запрещает закрытие, если gate его допускает. Это два последовательных запроса с самостоятельной проверкой актуального DONE, а не один неизменяемый снимок и не общая атомарная транзакция runtime.

Если между запросами другой разрешённый процесс изменил статус, последующий scoped запрос может вернуть 404. Runtime очищает устаревшую локальную запись как stale; stale не доказывает, что именно этот handler выполнил CLOSED. Wrapper close считает завершением только success=true. Фактический Ticket нужно перечитать прежде, чем повторять неопределённую запись.

Что ожидается снаружи

Платёжное состояние может меняться из-за gateway, предусмотренного клиентского действия или разрешённой работы оператора в платёжном модуле. Конкретное действие нужно определять по текущему payment state/customer_action и ответу gate, а не угадывать по одному waiting reason.

Сам handler не отправляет в этом processing приглашение оплатить, не открывает клиенту интерфейс и не подтверждает получение gateway callback. Эти участники показаны на BPMN как внешний платёжный контур. Повторный проход заново обращается к backend; изменение состояния не обязано совпасть по времени с XMPP-событием Ticket.

Ожидание, ошибки и восстановление

Назначение Настройка
Обычный платёжный повтор после неуспешного close SERVICE_REQUEST_DONE_PAYMENT_RETRY_SECONDS
Общее исключение DEFERRED_TICKET_RECHECK_SECONDS, processing_error
Сверка состава очереди INTERVAL_TIME_BD_REQ

Defer освобождает worker; событие может сделать Ticket ready раньше. Причина payment_finalization — локальное состояние, не отдельное поле оплаченности в БД. После успешного close причина очищается. Backend commit и доставка wakeup разделены: потеря ticket.changed/ticket.moved не откатывает CLOSED, periodic reconciliation восстанавливает состав очереди.

Порядок диагностики:

  1. Прочитать фактический Ticket: DONE или CLOSED.
  2. Отдельно записать результат retry-done-payment.
  3. Прочитать ответ close-after-done и причину блокировки gate.
  4. Если ответ записи был неопределённым — сверить каноническое платёжное состояние до повтора.

Auth/transport/5xx не приравнивают к ожидаемому платёжному ожиданию. Не менять Ticket на CLOSED ради снятия очереди и не повторять finish уже выполненной работы.

Проверенные источники

Git SHA/SHA-256 и модели закреплены в metadata/BPMN и сверены при упаковке. Handler, gateway и бизнес-API при анализе не исполнялись. Общий автоматический hook Wiki пока не завершён.

Связанные материалы