Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions proto/customer.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,56 @@ struct ProviderTerminalKey {
2: required domain.TerminalRef terminal_ref
}

/* Terminal affinity — привязка плательщика к терминалу */

/**
* Привязка Customer к терминалу.
*
* У Customer может быть несколько активных привязок. При выборе роута среди
* доступных кандидатов предпочитается привязка, созданная раньше
* (с меньшим bind_seq). См. domain.RoutingAffinity.
*/
struct TerminalAffinity {
1: required domain.ProviderRef provider_ref
2: required domain.TerminalRef terminal_ref
/**
* Порядковый номер привязки, растёт монотонно. Чем меньше, тем раньше
* создана привязка и тем выше её приоритет.
*/
3: required i64 bind_seq
/** Время создания привязки; точка отсчёта для TTL since_bound */
4: required base.Timestamp bound_at
/**
* Время последнего успешного платежа через привязку;
* точка отсчёта для TTL since_last_use.
*/
5: required base.Timestamp last_used_at
}

/**
* Параметры привязки Customer к терминалу.
* Операция идемпотентна: повторный вызов не меняет bind_seq, только last_used_at.
*/
struct TerminalAffinityParams {
1: required CustomerID customer_id
2: required domain.ProviderRef provider_ref
3: required domain.TerminalRef terminal_ref
/**
* Если задан и существующая привязка к этому терминалу по нему уже истекла,
* она снимается, а вместо неё создаётся новая с очередным bind_seq.
*/
4: optional domain.RoutingAffinityTtl ttl
}

/**
* Параметры снятия привязки.
*/
struct ReleaseTerminalAffinityParams {
1: required CustomerID customer_id
2: required ProviderTerminalKey key
3: optional string reason
}

/**
* BankCard — самостоятельная сущность банковской карты.
*/
Expand Down Expand Up @@ -141,6 +191,13 @@ struct Customer {
6: optional domain.Metadata metadata
/** Внешний идентификатор Customer */
7: optional string external_id
/**
* Email плательщика в нормализованном виде (нижний регистр, без пробелов
* по краям). Уникален в рамках party_ref. Служит идентификатором плательщика
* для привязки к терминалу; не путать с external_id — идентификатором,
* который назначает мерчант.
*/
8: optional string email
}

/**
Expand Down Expand Up @@ -182,6 +239,8 @@ struct CustomerParams {
3: optional domain.Metadata metadata
/** Внешний идентификатор Customer */
4: optional string external_id
/** Email плательщика; нормализуется сервисом. Уникален в рамках party_ref */
5: optional string email
}

/**
Expand Down Expand Up @@ -243,6 +302,11 @@ exception CustomerAlreadyExists {
1: required CustomerID id
}

/** Email уже занят другим Customer этой party */
exception CustomerEmailConflict {
1: required CustomerID id
}

exception BankCardNotFound {}

exception InvalidRecurrentParent {
Expand Down Expand Up @@ -280,6 +344,7 @@ service CustomerManagement {
throws (
1: CustomerAlreadyExists already_exists
2: base.InvalidRequest invalid_request
3: CustomerEmailConflict email_conflict
)

/**
Expand Down Expand Up @@ -374,6 +439,60 @@ service CustomerManagement {
3: optional ContinuationToken continuation_token
)
throws (1: CustomerNotFound not_found)

/**
* Найти Customer по email в рамках party или создать, если его нет.
* Операция идемпотентна и безопасна при конкурентных вызовах: единственность
* Customer гарантирует уникальность email в рамках party. Email нормализуется
* сервисом. CustomerEmailConflict не бросает.
*/
Customer FindOrCreateByEmail(
1: domain.PartyConfigRef party_ref,
2: string email
)
throws (1: base.InvalidRequest invalid_request)

/**
* Получить Customer по email и party.
*/
CustomerState GetByEmail(
1: domain.PartyConfigRef party_ref,
2: string email
)
throws (1: CustomerNotFound not_found)

/**
* Получить активные привязки Customer к терминалам.
* Истечение по TTL здесь не проверяется: TTL задаётся в настройках кандидата
* роутинга, и учитывать его должна вызывающая сторона.
*/
list<TerminalAffinity> GetTerminalAffinities(1: CustomerID customer_id)
throws (1: CustomerNotFound not_found)

/**
* Привязать Customer к терминалу.
* Идемпотентно: повторный вызов не меняет bind_seq, только last_used_at.
*/
TerminalAffinity BindTerminalAffinity(1: TerminalAffinityParams params)
throws (
1: CustomerNotFound not_found
2: base.InvalidRequest invalid_request
)

/**
* Снять привязку Customer к терминалу.
*/
void ReleaseTerminalAffinity(1: ReleaseTerminalAffinityParams params)
throws (1: CustomerNotFound not_found)

/**
* Снять привязки всех Customer к терминалу.
* Административная операция, например при выводе терминала из эксплуатации.
*/
void ReleaseTerminalAffinitiesByTerminal(
1: ProviderTerminalKey key,
2: optional string reason
)
}

/**
Expand Down
52 changes: 52 additions & 0 deletions proto/domain.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -589,9 +589,20 @@ struct PaymentRoute {
2: required TerminalRef terminal
}

/**
* Скоры роута. Сравниваются как кортеж в порядке объявления полей, поэтому
* чем выше поле объявлено, тем значимее критерий.
*/
struct PaymentRouteScores {
1: optional i32 availability_condition
2: optional i32 conversion_condition
/**
* Ранг привязки плательщика к терминалу (см. RoutingAffinity).
* 0 — привязки нет; чем больше значение, тем раньше была создана привязка.
* Поле намеренно объявлено выше приоритета и веса: привязка важнее
* настроек роутинга, но уступает критическим состояниям по fault detector.
*/
9: optional i32 terminal_affinity
3: optional i32 terminal_priority_rating
4: optional i32 route_pin
5: optional i32 random_condition
Expand Down Expand Up @@ -2586,13 +2597,54 @@ struct RoutingPin {
1: required set<RoutingPinFeature> features
}

/**
* Привязка плательщика к терминалу.
*
* Плательщик идентифицируется по email через Customer. Терминал, через который
* прошёл первый успешный платёж, запоминается, и в последующих платежах имеет
* преимущество перед приоритетом и весом кандидатов, пока доступен.
* Настройка действует только на кандидатов, у которых она задана: остальные
* не участвуют в привязке ни при выборе роута, ни при её создании.
*/
struct RoutingAffinity {
/** Время жизни привязки; если не задано — бессрочно. */
1: optional RoutingAffinityTtl ttl
}

/**
* Время жизни привязки: каждый вариант задаёт момент истечения.
* Привязка считается истёкшей, как только этот момент наступил.
*/
union RoutingAffinityTtl {
/**
* Абсолютная отсечка: все привязки кандидата истекают в этот момент,
* независимо от того, когда были созданы и когда использовались.
* Например, для плановой переразметки плательщиков по актуальным весам
* начиная с заданной даты.
*/
1: base.Timestamp deadline
/**
* Срок с момента создания привязки: истекает через since_bound секунд
* после bound_at, независимо от того, платит ли плательщик.
* Подходит для периодической переразметки по актуальным весам.
*/
2: base.Timeout since_bound
/**
* Скользящий срок: истекает, если через привязку не было успешных платежей
* дольше since_last_use секунд (считая от last_used_at). Каждый успешный
* платёж продлевает срок.
*/
3: base.Timeout since_last_use
}
Comment thread
ndiezel0 marked this conversation as resolved.

struct RoutingCandidate {
1: optional string description
2: required Predicate allowed
3: required TerminalRef terminal
4: optional i32 priority = CANDIDATE_PRIORITY
5: optional RoutingPin pin
6: optional i32 weight = CANDIDATE_WEIGHT
7: optional RoutingAffinity affinity
}

/* Root config */
Expand Down
15 changes: 15 additions & 0 deletions proto/payment_processing.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ union InvoicePaymentChangePayload {
18: InvoicePaymentShopLimitApplied invoice_payment_shop_limit_applied
19: InvoicePaymentCascadeTokensLoaded invoice_payment_cascade_tokens_loaded
20: InvoicePaymentExchangeContextChanged invoice_payment_exchange_context_changed
21: InvoicePaymentTerminalAffinitiesLoaded invoice_payment_terminal_affinities_loaded
}

/**
Expand Down Expand Up @@ -488,6 +489,15 @@ struct InvoicePaymentCascadeTokensLoaded {
1: required list<customer.RecurrentToken> tokens
}

/**
* Событие о загрузке привязок плательщика к терминалам из хранилища.
* Содержит только действующие (не истёкшие по TTL) привязки. Пустой список —
* тоже событие: оно фиксирует, что загрузка для этого платежа уже выполнена.
*/
struct InvoicePaymentTerminalAffinitiesLoaded {
1: required list<customer.TerminalAffinity> affinities
}

struct InvoicePaymentCaptureStarted {
1: required InvoicePaymentCaptureData data
}
Expand Down Expand Up @@ -900,6 +910,11 @@ typedef map<domain.PaymentRoute, list<TurnoverLimitValue>> RouteLimitContext

struct RouteDecisionContext {
1: optional bool skip_recurrent
/**
* У выбранного кандидата задана настройка привязки к терминалу
* (domain.RoutingAffinity): при успешном платеже привязка будет создана.
*/
2: optional bool terminal_affinity
}

// Exceptions
Expand Down
Loading