cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit 91e2ea30aa990841a87341ac61bc5c03621dd167
parent 5d00f20e85338067b55f85d5b20085f8c4675305
Author: triesap <tyson@radroots.org>
Date:   Fri, 19 Jun 2026 15:55:18 -0700

runtime: split order SDK status adapter

- move SDK status view mapping into an order submodule
- keep order status command entrypoint SDK-backed
- guard the status adapter against direct relay dependencies
- preserve existing order runtime behavior through validation

Diffstat:
Msrc/runtime/order.rs | 367++++++++++++++++++++-----------------------------------------------------------
Asrc/runtime/order/sdk_status.rs | 273+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/runtime/sdk.rs | 14+++++++++++++-
3 files changed, 379 insertions(+), 275 deletions(-)

diff --git a/src/runtime/order.rs b/src/runtime/order.rs @@ -1,5 +1,7 @@ #![allow(dead_code)] +mod sdk_status; + use std::collections::{HashMap, HashSet}; use std::fs; use std::path::{Path, PathBuf}; @@ -70,17 +72,15 @@ use radroots_replica_db_schema::trade_product::{ use radroots_sdk::{ OrderCancellationEnqueueRequest, OrderCancellationPrepareRequest, OrderCancellationReceipt, OrderDecisionEnqueueRequest, OrderDecisionReceipt, OrderEvidenceIngestRequest, - OrderFulfillmentStatusKind, OrderFulfillmentUpdateEnqueueRequest, - OrderFulfillmentUpdatePrepareRequest, OrderFulfillmentUpdateReceipt, OrderPaymentStateKind, - OrderReceiptRecordEnqueueRequest, OrderReceiptRecordPrepareRequest, OrderReceiptRecordReceipt, - OrderRequestEvidenceIngestRequest, OrderRevisionDecisionEnqueueRequest, - OrderRevisionDecisionPrepareRequest, OrderRevisionDecisionReceipt, - OrderRevisionProposalEnqueueRequest, OrderRevisionProposalPrepareRequest, - OrderRevisionProposalReceipt, OrderSettlementStateKind, OrderStatusKind, OrderStatusReceipt, - OrderStatusRequest, OrderSubmitEnqueueRequest, OrderSubmitPlan, OrderSubmitPrepareRequest, - OrderSubmitReceipt, PushOutboxEventReceipt, PushOutboxEventState, PushOutboxReceipt, - PushOutboxRelayOutcomeKind, PushOutboxRequest, SdkMutationState, SdkOrderStatusIssue, - SdkRelayTargetPolicy, SdkRelayUrlPolicy, + OrderFulfillmentUpdateEnqueueRequest, OrderFulfillmentUpdatePrepareRequest, + OrderFulfillmentUpdateReceipt, OrderReceiptRecordEnqueueRequest, + OrderReceiptRecordPrepareRequest, OrderReceiptRecordReceipt, OrderRequestEvidenceIngestRequest, + OrderRevisionDecisionEnqueueRequest, OrderRevisionDecisionPrepareRequest, + OrderRevisionDecisionReceipt, OrderRevisionProposalEnqueueRequest, + OrderRevisionProposalPrepareRequest, OrderRevisionProposalReceipt, OrderStatusRequest, + OrderSubmitEnqueueRequest, OrderSubmitPlan, OrderSubmitPrepareRequest, OrderSubmitReceipt, + PushOutboxEventReceipt, PushOutboxEventState, PushOutboxReceipt, PushOutboxRelayOutcomeKind, + PushOutboxRequest, SdkMutationState, SdkRelayTargetPolicy, SdkRelayUrlPolicy, }; use radroots_sql_core::SqliteExecutor; use radroots_trade::order::{ @@ -133,6 +133,8 @@ use crate::view::runtime::{ OrderStatusRevisionView, OrderStatusView, OrderSubmitView, OrderSummaryView, RelayFailureView, }; +use self::sdk_status::sdk_order_status_view; + const ORDER_DRAFT_KIND: &str = "order_draft_v1"; const ORDER_SOURCE: &str = "local order drafts ยท local first"; const ORDER_APP_RECORD_SOURCE: &str = "app-authored shared local order records"; @@ -2335,266 +2337,6 @@ fn legacy_order_preflight_relay_status( Ok(view) } -fn sdk_order_status_view(receipt: OrderStatusReceipt) -> OrderStatusView { - let state = sdk_order_status_state(receipt.status).to_owned(); - let reducer_issues = receipt - .issues - .iter() - .map(sdk_order_status_issue_view) - .collect::<Vec<_>>(); - let reason = sdk_order_status_reason(receipt.status, receipt.order_id.as_str()); - let fulfillment = sdk_order_status_fulfillment_view(&receipt, reducer_issues.as_slice()); - let lifecycle = sdk_order_status_lifecycle_view(&receipt, reducer_issues.as_slice()); - let payment = Some(sdk_order_status_payment_view( - &receipt, - reducer_issues.as_slice(), - )); - - OrderStatusView { - state, - source: ORDER_STATUS_SDK_SOURCE.to_owned(), - order_id: receipt.order_id.to_string(), - actor_context_source: ORDER_ACTOR_CONTEXT_SDK_LOCAL.to_owned(), - request_event_id: sdk_event_id_string(receipt.request_event_id.as_ref()), - decision_event_id: sdk_event_id_string(receipt.decision_event_id.as_ref()), - agreement_event_id: sdk_order_status_agreement_event_id(&receipt), - listing_event_id: None, - listing_addr: None, - buyer_pubkey: None, - seller_pubkey: None, - economics: None, - last_event_id: sdk_event_id_string(receipt.last_event_id.as_ref()), - revision: None, - inventory: None, - fulfillment, - lifecycle: Some(lifecycle), - payment, - reducer_issues, - target_relays: Vec::new(), - connected_relays: Vec::new(), - failed_relays: Vec::new(), - fetched_count: 0, - decoded_count: receipt.event_count, - skipped_count: 0, - reason, - actions: Vec::new(), - } -} - -fn sdk_order_status_state(status: OrderStatusKind) -> &'static str { - match status { - OrderStatusKind::Missing => "missing", - OrderStatusKind::Requested => "requested", - OrderStatusKind::Accepted => "accepted", - OrderStatusKind::Declined => "declined", - OrderStatusKind::Cancelled => "cancelled", - OrderStatusKind::Completed => "completed", - OrderStatusKind::Disputed => "disputed", - OrderStatusKind::Invalid => "invalid", - _ => "unknown", - } -} - -fn sdk_order_status_reason(status: OrderStatusKind, order_id: &str) -> Option<String> { - match status { - OrderStatusKind::Missing => Some(format!("no local SDK order events matched `{order_id}`")), - OrderStatusKind::Invalid => Some(format!( - "local SDK order events for `{order_id}` failed reducer validation" - )), - _ => None, - } -} - -fn sdk_order_status_agreement_event_id(receipt: &OrderStatusReceipt) -> Option<String> { - sdk_event_id_string(receipt.agreement_event_id.as_ref()) -} - -fn sdk_order_status_fulfillment_view( - receipt: &OrderStatusReceipt, - issues: &[OrderIssueView], -) -> Option<OrderStatusFulfillmentView> { - let fulfillment_issues = issues - .iter() - .filter(|issue| { - issue.code.starts_with("fulfillment_") || issue.code == "forked_fulfillments" - }) - .cloned() - .collect::<Vec<_>>(); - if !fulfillment_issues.is_empty() { - return Some(OrderStatusFulfillmentView { - state: "invalid".to_owned(), - event_id: sdk_event_id_string(receipt.fulfillment_event_id.as_ref()), - root_event_id: sdk_event_id_string(receipt.request_event_id.as_ref()), - prev_event_id: sdk_event_id_string(receipt.decision_event_id.as_ref()), - terminal: false, - inventory_released: false, - issues: fulfillment_issues, - }); - } - let fulfillment_status = receipt.fulfillment_status?; - Some(OrderStatusFulfillmentView { - state: sdk_fulfillment_status_state(fulfillment_status).to_owned(), - event_id: sdk_event_id_string(receipt.fulfillment_event_id.as_ref()), - root_event_id: sdk_event_id_string(receipt.request_event_id.as_ref()), - prev_event_id: sdk_event_id_string(receipt.decision_event_id.as_ref()), - terminal: matches!( - fulfillment_status, - OrderFulfillmentStatusKind::Delivered | OrderFulfillmentStatusKind::SellerCancelled - ), - inventory_released: matches!( - fulfillment_status, - OrderFulfillmentStatusKind::SellerCancelled - ), - issues: Vec::new(), - }) -} - -fn sdk_order_status_payment_view( - receipt: &OrderStatusReceipt, - issues: &[OrderIssueView], -) -> OrderStatusPaymentView { - let payment_issues = issues - .iter() - .filter(|issue| issue.code.starts_with("payment_") || issue.code.starts_with("settlement_")) - .cloned() - .collect::<Vec<_>>(); - OrderStatusPaymentView { - state: sdk_payment_state(receipt.payment_state).to_owned(), - settlement_state: sdk_settlement_state(receipt.settlement_state).to_owned(), - payment_event_id: None, - settlement_event_id: None, - agreement_event_id: sdk_order_status_agreement_event_id(receipt), - quote_id: None, - quote_version: None, - economics_digest: None, - amount: None, - currency: None, - method: None, - reference: None, - paid_at: None, - reason: None, - issues: payment_issues, - } -} - -fn sdk_order_status_lifecycle_view( - receipt: &OrderStatusReceipt, - issues: &[OrderIssueView], -) -> OrderStatusLifecycleView { - let cancellation = receipt.cancellation_event_id.as_ref().map(|event_id| { - OrderStatusLifecycleCancellationView { - event_id: event_id.to_string(), - root_event_id: sdk_event_id_string(receipt.request_event_id.as_ref()), - prev_event_id: sdk_event_id_string(receipt.decision_event_id.as_ref()), - reason: None, - } - }); - let receipt_view = - receipt - .receipt_event_id - .as_ref() - .map(|event_id| OrderStatusLifecycleReceiptView { - event_id: event_id.to_string(), - root_event_id: sdk_event_id_string(receipt.request_event_id.as_ref()), - prev_event_id: sdk_event_id_string(receipt.fulfillment_event_id.as_ref()), - received: matches!(receipt.status, OrderStatusKind::Completed), - issue: None, - received_at: None, - }); - - OrderStatusLifecycleView { - phase: sdk_order_status_lifecycle_phase(receipt).to_owned(), - terminal: receipt.lifecycle_terminal, - event_id: sdk_event_id_string(receipt.last_event_id.as_ref()), - root_event_id: sdk_event_id_string(receipt.request_event_id.as_ref()), - prev_event_id: None, - cancellation, - receipt: receipt_view, - settlement_required: !matches!( - receipt.settlement_state, - OrderSettlementStateKind::NotRequired - ), - settlement_reason: None, - issues: issues.to_vec(), - } -} - -fn sdk_order_status_lifecycle_phase(receipt: &OrderStatusReceipt) -> &'static str { - match receipt.status { - OrderStatusKind::Missing => "missing", - OrderStatusKind::Requested => "requested", - OrderStatusKind::Accepted => match receipt.fulfillment_status { - Some(OrderFulfillmentStatusKind::Preparing) - | Some(OrderFulfillmentStatusKind::OutForDelivery) => "fulfillment_in_progress", - Some( - OrderFulfillmentStatusKind::ReadyForPickup - | OrderFulfillmentStatusKind::Delivered - | OrderFulfillmentStatusKind::SellerCancelled, - ) => "fulfilled", - Some(OrderFulfillmentStatusKind::AcceptedNotFulfilled) | None => "accepted", - Some(_) => "accepted", - }, - OrderStatusKind::Declined => "declined", - OrderStatusKind::Cancelled => "cancelled", - OrderStatusKind::Completed => "completed", - OrderStatusKind::Disputed => "disputed", - OrderStatusKind::Invalid => "invalid", - _ => "unknown", - } -} - -fn sdk_fulfillment_status_state(status: OrderFulfillmentStatusKind) -> &'static str { - match status { - OrderFulfillmentStatusKind::AcceptedNotFulfilled => "accepted_not_fulfilled", - OrderFulfillmentStatusKind::Preparing => "preparing", - OrderFulfillmentStatusKind::ReadyForPickup => "ready_for_pickup", - OrderFulfillmentStatusKind::OutForDelivery => "out_for_delivery", - OrderFulfillmentStatusKind::Delivered => "delivered", - OrderFulfillmentStatusKind::SellerCancelled => "seller_cancelled", - _ => "unknown", - } -} - -fn sdk_payment_state(state: OrderPaymentStateKind) -> &'static str { - match state { - OrderPaymentStateKind::NotRecorded => "not_recorded", - OrderPaymentStateKind::Recorded => "recorded", - OrderPaymentStateKind::Settled => "settled", - OrderPaymentStateKind::Rejected => "rejected", - OrderPaymentStateKind::Invalid => "invalid", - _ => "unknown", - } -} - -fn sdk_settlement_state(state: OrderSettlementStateKind) -> &'static str { - match state { - OrderSettlementStateKind::NotRequired => "not_required", - OrderSettlementStateKind::Pending => "pending", - OrderSettlementStateKind::Accepted => "accepted", - OrderSettlementStateKind::Rejected => "rejected", - OrderSettlementStateKind::Invalid => "invalid", - _ => "unknown", - } -} - -fn sdk_order_status_issue_view(issue: &SdkOrderStatusIssue) -> OrderIssueView { - let code = issue.code(); - OrderIssueView { - code: code.clone(), - field: "sdk_order_status".to_owned(), - message: format!("SDK order status reported `{code}`"), - event_ids: issue - .event_ids - .iter() - .map(RadrootsEventId::to_string) - .collect(), - } -} - -fn sdk_event_id_string(event_id: Option<&RadrootsEventId>) -> Option<String> { - event_id.map(RadrootsEventId::to_string) -} - enum OrderStatusRecord { Request { listing_event_id: Option<String>, @@ -14006,8 +13748,10 @@ mod tests { use radroots_nostr::prelude::{radroots_event_from_nostr, radroots_nostr_build_event}; use radroots_runtime_paths::RadrootsMigrationReport; use radroots_sdk::{ - OrderPaymentStateKind, OrderSettlementStateKind, OrderStatusKind, OrderStatusReceipt, - OrderSubmitPlan, RadrootsSdkTimestamp, SdkOrderStatusIssue, SdkOrderStatusIssueKind, + OrderPaymentHandoffKind, OrderPaymentStateKind, OrderSettlementStateKind, + OrderStatusEligibility, OrderStatusEvidenceSummary, OrderStatusKind, + OrderStatusNextActionKind, OrderStatusReceipt, OrderSubmitPlan, OrderWorkflowKind, + OrderWorkflowPlan, RadrootsSdkTimestamp, SdkOrderStatusIssue, SdkOrderStatusIssueKind, SdkOrderStatusSource, }; use radroots_secret_vault::RadrootsSecretBackend; @@ -14112,11 +13856,20 @@ mod tests { fixture.buyer_pubkey.as_str(), ) .expect("frozen draft"); + let expected_event_id = test_event_id_char('3'); + let workflow_kind = OrderWorkflowKind::Submit; OrderSubmitPlan { + workflow: OrderWorkflowPlan { + kind: workflow_kind, + operation_kind: workflow_kind.operation_kind(), + contract_id: workflow_kind.contract_id(), + expected_event_id: expected_event_id.clone(), + created_at: RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000), + }, order_id: test_order_id(fixture.order_id.as_str()), listing_addr: test_listing_addr(fixture.listing_addr.as_str()), listing_event_id: test_event_id(fixture.listing_event_id.as_str()), - expected_event_id: test_event_id_char('3'), + expected_event_id, frozen_draft, created_at: RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000), } @@ -15861,6 +15614,28 @@ mod tests { payment_state: OrderPaymentStateKind::NotRecorded, settlement_state: OrderSettlementStateKind::NotRequired, lifecycle_terminal: false, + evidence: OrderStatusEvidenceSummary { + event_count: 2, + limit_applied: 500, + has_request: true, + has_decision: true, + has_agreement: true, + has_pending_revision: false, + has_fulfillment: false, + has_cancellation: false, + has_receipt: false, + has_issues: false, + }, + eligibility: OrderStatusEligibility { + can_decide: false, + can_propose_revision: true, + can_decide_revision: false, + can_cancel: true, + can_update_fulfillment: true, + can_record_receipt: false, + }, + payment_handoff: OrderPaymentHandoffKind::InPersonOrOffPlatformPending, + next_action: OrderStatusNextActionKind::ArrangeInPersonOrOffPlatformPayment, event_ids: vec![request_event_id.clone(), decision_event_id.clone()], request_event_id: Some(request_event_id.clone()), decision_event_id: Some(decision_event_id.clone()), @@ -15923,6 +15698,28 @@ mod tests { payment_state: OrderPaymentStateKind::NotRecorded, settlement_state: OrderSettlementStateKind::NotRequired, lifecycle_terminal: false, + evidence: OrderStatusEvidenceSummary { + event_count: 3, + limit_applied: 500, + has_request: true, + has_decision: true, + has_agreement: true, + has_pending_revision: false, + has_fulfillment: false, + has_cancellation: false, + has_receipt: false, + has_issues: false, + }, + eligibility: OrderStatusEligibility { + can_decide: false, + can_propose_revision: true, + can_decide_revision: false, + can_cancel: true, + can_update_fulfillment: true, + can_record_receipt: false, + }, + payment_handoff: OrderPaymentHandoffKind::InPersonOrOffPlatformPending, + next_action: OrderStatusNextActionKind::ArrangeInPersonOrOffPlatformPayment, event_ids: vec![ request_event_id.clone(), decision_event_id.clone(), @@ -15975,6 +15772,28 @@ mod tests { payment_state: OrderPaymentStateKind::NotRecorded, settlement_state: OrderSettlementStateKind::NotRequired, lifecycle_terminal: false, + evidence: OrderStatusEvidenceSummary { + event_count: 2, + limit_applied: 500, + has_request: false, + has_decision: false, + has_agreement: false, + has_pending_revision: false, + has_fulfillment: false, + has_cancellation: false, + has_receipt: false, + has_issues: true, + }, + eligibility: OrderStatusEligibility { + can_decide: false, + can_propose_revision: false, + can_decide_revision: false, + can_cancel: false, + can_update_fulfillment: false, + can_record_receipt: false, + }, + payment_handoff: OrderPaymentHandoffKind::Invalid, + next_action: OrderStatusNextActionKind::InspectEvidenceIssues, event_ids: vec![request_event_id, fork_event_id.clone()], request_event_id: None, decision_event_id: None, diff --git a/src/runtime/order/sdk_status.rs b/src/runtime/order/sdk_status.rs @@ -0,0 +1,273 @@ +use radroots_events::ids::RadrootsEventId; +use radroots_sdk::{ + OrderFulfillmentStatusKind, OrderPaymentStateKind, OrderSettlementStateKind, OrderStatusKind, + OrderStatusReceipt, SdkOrderStatusIssue, +}; + +use crate::view::runtime::{ + OrderIssueView, OrderStatusFulfillmentView, OrderStatusLifecycleCancellationView, + OrderStatusLifecycleReceiptView, OrderStatusLifecycleView, OrderStatusPaymentView, + OrderStatusView, +}; + +use super::{ORDER_ACTOR_CONTEXT_SDK_LOCAL, ORDER_STATUS_SDK_SOURCE}; + +pub(super) fn sdk_order_status_view(receipt: OrderStatusReceipt) -> OrderStatusView { + let state = sdk_order_status_state(receipt.status).to_owned(); + let reducer_issues = receipt + .issues + .iter() + .map(sdk_order_status_issue_view) + .collect::<Vec<_>>(); + let reason = sdk_order_status_reason(receipt.status, receipt.order_id.as_str()); + let fulfillment = sdk_order_status_fulfillment_view(&receipt, reducer_issues.as_slice()); + let lifecycle = sdk_order_status_lifecycle_view(&receipt, reducer_issues.as_slice()); + let payment = Some(sdk_order_status_payment_view( + &receipt, + reducer_issues.as_slice(), + )); + + OrderStatusView { + state, + source: ORDER_STATUS_SDK_SOURCE.to_owned(), + order_id: receipt.order_id.to_string(), + actor_context_source: ORDER_ACTOR_CONTEXT_SDK_LOCAL.to_owned(), + request_event_id: sdk_event_id_string(receipt.request_event_id.as_ref()), + decision_event_id: sdk_event_id_string(receipt.decision_event_id.as_ref()), + agreement_event_id: sdk_order_status_agreement_event_id(&receipt), + listing_event_id: None, + listing_addr: None, + buyer_pubkey: None, + seller_pubkey: None, + economics: None, + last_event_id: sdk_event_id_string(receipt.last_event_id.as_ref()), + revision: None, + inventory: None, + fulfillment, + lifecycle: Some(lifecycle), + payment, + reducer_issues, + target_relays: Vec::new(), + connected_relays: Vec::new(), + failed_relays: Vec::new(), + fetched_count: 0, + decoded_count: receipt.event_count, + skipped_count: 0, + reason, + actions: Vec::new(), + } +} + +fn sdk_order_status_state(status: OrderStatusKind) -> &'static str { + match status { + OrderStatusKind::Missing => "missing", + OrderStatusKind::Requested => "requested", + OrderStatusKind::Accepted => "accepted", + OrderStatusKind::Declined => "declined", + OrderStatusKind::Cancelled => "cancelled", + OrderStatusKind::Completed => "completed", + OrderStatusKind::Disputed => "disputed", + OrderStatusKind::Invalid => "invalid", + _ => "unknown", + } +} + +fn sdk_order_status_reason(status: OrderStatusKind, order_id: &str) -> Option<String> { + match status { + OrderStatusKind::Missing => Some(format!("no local SDK order events matched `{order_id}`")), + OrderStatusKind::Invalid => Some(format!( + "local SDK order events for `{order_id}` failed reducer validation" + )), + _ => None, + } +} + +fn sdk_order_status_agreement_event_id(receipt: &OrderStatusReceipt) -> Option<String> { + sdk_event_id_string(receipt.agreement_event_id.as_ref()) +} + +fn sdk_order_status_fulfillment_view( + receipt: &OrderStatusReceipt, + issues: &[OrderIssueView], +) -> Option<OrderStatusFulfillmentView> { + let fulfillment_issues = issues + .iter() + .filter(|issue| { + issue.code.starts_with("fulfillment_") || issue.code == "forked_fulfillments" + }) + .cloned() + .collect::<Vec<_>>(); + if !fulfillment_issues.is_empty() { + return Some(OrderStatusFulfillmentView { + state: "invalid".to_owned(), + event_id: sdk_event_id_string(receipt.fulfillment_event_id.as_ref()), + root_event_id: sdk_event_id_string(receipt.request_event_id.as_ref()), + prev_event_id: sdk_event_id_string(receipt.decision_event_id.as_ref()), + terminal: false, + inventory_released: false, + issues: fulfillment_issues, + }); + } + let fulfillment_status = receipt.fulfillment_status?; + Some(OrderStatusFulfillmentView { + state: sdk_fulfillment_status_state(fulfillment_status).to_owned(), + event_id: sdk_event_id_string(receipt.fulfillment_event_id.as_ref()), + root_event_id: sdk_event_id_string(receipt.request_event_id.as_ref()), + prev_event_id: sdk_event_id_string(receipt.decision_event_id.as_ref()), + terminal: matches!( + fulfillment_status, + OrderFulfillmentStatusKind::Delivered | OrderFulfillmentStatusKind::SellerCancelled + ), + inventory_released: matches!( + fulfillment_status, + OrderFulfillmentStatusKind::SellerCancelled + ), + issues: Vec::new(), + }) +} + +fn sdk_order_status_payment_view( + receipt: &OrderStatusReceipt, + issues: &[OrderIssueView], +) -> OrderStatusPaymentView { + let payment_issues = issues + .iter() + .filter(|issue| issue.code.starts_with("payment_") || issue.code.starts_with("settlement_")) + .cloned() + .collect::<Vec<_>>(); + OrderStatusPaymentView { + state: sdk_payment_state(receipt.payment_state).to_owned(), + settlement_state: sdk_settlement_state(receipt.settlement_state).to_owned(), + payment_event_id: None, + settlement_event_id: None, + agreement_event_id: sdk_order_status_agreement_event_id(receipt), + quote_id: None, + quote_version: None, + economics_digest: None, + amount: None, + currency: None, + method: None, + reference: None, + paid_at: None, + reason: None, + issues: payment_issues, + } +} + +fn sdk_order_status_lifecycle_view( + receipt: &OrderStatusReceipt, + issues: &[OrderIssueView], +) -> OrderStatusLifecycleView { + let cancellation = receipt.cancellation_event_id.as_ref().map(|event_id| { + OrderStatusLifecycleCancellationView { + event_id: event_id.to_string(), + root_event_id: sdk_event_id_string(receipt.request_event_id.as_ref()), + prev_event_id: sdk_event_id_string(receipt.decision_event_id.as_ref()), + reason: None, + } + }); + let receipt_view = + receipt + .receipt_event_id + .as_ref() + .map(|event_id| OrderStatusLifecycleReceiptView { + event_id: event_id.to_string(), + root_event_id: sdk_event_id_string(receipt.request_event_id.as_ref()), + prev_event_id: sdk_event_id_string(receipt.fulfillment_event_id.as_ref()), + received: matches!(receipt.status, OrderStatusKind::Completed), + issue: None, + received_at: None, + }); + + OrderStatusLifecycleView { + phase: sdk_order_status_lifecycle_phase(receipt).to_owned(), + terminal: receipt.lifecycle_terminal, + event_id: sdk_event_id_string(receipt.last_event_id.as_ref()), + root_event_id: sdk_event_id_string(receipt.request_event_id.as_ref()), + prev_event_id: None, + cancellation, + receipt: receipt_view, + settlement_required: !matches!( + receipt.settlement_state, + OrderSettlementStateKind::NotRequired + ), + settlement_reason: None, + issues: issues.to_vec(), + } +} + +fn sdk_order_status_lifecycle_phase(receipt: &OrderStatusReceipt) -> &'static str { + match receipt.status { + OrderStatusKind::Missing => "missing", + OrderStatusKind::Requested => "requested", + OrderStatusKind::Accepted => match receipt.fulfillment_status { + Some(OrderFulfillmentStatusKind::Preparing) + | Some(OrderFulfillmentStatusKind::OutForDelivery) => "fulfillment_in_progress", + Some( + OrderFulfillmentStatusKind::ReadyForPickup + | OrderFulfillmentStatusKind::Delivered + | OrderFulfillmentStatusKind::SellerCancelled, + ) => "fulfilled", + Some(OrderFulfillmentStatusKind::AcceptedNotFulfilled) | None => "accepted", + Some(_) => "accepted", + }, + OrderStatusKind::Declined => "declined", + OrderStatusKind::Cancelled => "cancelled", + OrderStatusKind::Completed => "completed", + OrderStatusKind::Disputed => "disputed", + OrderStatusKind::Invalid => "invalid", + _ => "unknown", + } +} + +fn sdk_fulfillment_status_state(status: OrderFulfillmentStatusKind) -> &'static str { + match status { + OrderFulfillmentStatusKind::AcceptedNotFulfilled => "accepted_not_fulfilled", + OrderFulfillmentStatusKind::Preparing => "preparing", + OrderFulfillmentStatusKind::ReadyForPickup => "ready_for_pickup", + OrderFulfillmentStatusKind::OutForDelivery => "out_for_delivery", + OrderFulfillmentStatusKind::Delivered => "delivered", + OrderFulfillmentStatusKind::SellerCancelled => "seller_cancelled", + _ => "unknown", + } +} + +fn sdk_payment_state(state: OrderPaymentStateKind) -> &'static str { + match state { + OrderPaymentStateKind::NotRecorded => "not_recorded", + OrderPaymentStateKind::Recorded => "recorded", + OrderPaymentStateKind::Settled => "settled", + OrderPaymentStateKind::Rejected => "rejected", + OrderPaymentStateKind::Invalid => "invalid", + _ => "unknown", + } +} + +fn sdk_settlement_state(state: OrderSettlementStateKind) -> &'static str { + match state { + OrderSettlementStateKind::NotRequired => "not_required", + OrderSettlementStateKind::Pending => "pending", + OrderSettlementStateKind::Accepted => "accepted", + OrderSettlementStateKind::Rejected => "rejected", + OrderSettlementStateKind::Invalid => "invalid", + _ => "unknown", + } +} + +fn sdk_order_status_issue_view(issue: &SdkOrderStatusIssue) -> OrderIssueView { + let code = issue.code(); + OrderIssueView { + code: code.clone(), + field: "sdk_order_status".to_owned(), + message: format!("SDK order status reported `{code}`"), + event_ids: issue + .event_ids + .iter() + .map(RadrootsEventId::to_string) + .collect(), + } +} + +fn sdk_event_id_string(event_id: Option<&RadrootsEventId>) -> Option<String> { + event_id.map(RadrootsEventId::to_string) +} diff --git a/src/runtime/sdk.rs b/src/runtime/sdk.rs @@ -446,6 +446,18 @@ mod tests { required_tokens: &["OrderStatusRequest::parse", "session.sdk().orders().status"], }, MigratedCliPathGuard { + label: "order SDK status adapter", + path: "src/runtime/order/sdk_status.rs", + start: "pub(super) fn sdk_order_status_view(", + end: "fn sdk_event_id_string(", + required_tokens: &[ + "OrderStatusReceipt", + "OrderStatusView", + "OrderStatusLifecycleView", + "OrderStatusPaymentView", + ], + }, + MigratedCliPathGuard { label: "order submit", path: "src/runtime/order.rs", start: "fn prepare_order_submit_via_sdk(", @@ -472,7 +484,7 @@ mod tests { MigratedCliPathGuard { label: "order lifecycle", path: "src/runtime/order.rs", - start: "fn prepare_order_revision_proposal_dry_run_via_sdk(", + start: "fn publish_order_revision(", end: "fn publish_order_payment(", required_tokens: &[ "prepare_revision_proposal(OrderRevisionProposalPrepareRequest::new",