commit b4852735871eb3fa7deb96eee8f3622656c90c71
parent e8a7ac48433222b97fe9f327b2ab65f4f70efb02
Author: triesap <tyson@radroots.org>
Date: Wed, 6 May 2026 13:28:51 +0000
cli: defer payment commands with not_implemented
- add structured not_implemented output for reserved payment operations
- return deferred payment errors before approval and relay preflight
- reclassify payment routes as non-mutating reserved command surfaces
- cover target and service paths for payment and settlement deferral
Diffstat:
7 files changed, 205 insertions(+), 665 deletions(-)
diff --git a/src/main.rs b/src/main.rs
@@ -353,6 +353,12 @@ fn validate_request_contract(
message: format!("`{}` does not support --dry-run", spec.cli_path),
});
}
+ if deferred_payment_operation(spec.operation_id) {
+ return Err(OperationAdapterError::not_implemented(
+ spec.operation_id,
+ deferred_payment_message(),
+ ));
+ }
validate_signer_mode_contract(request, config)?;
validate_network_contract(request, config)?;
Ok(())
@@ -426,9 +432,6 @@ fn dry_run_requires_network(operation_id: &str) -> bool {
| "order.revision.decline"
| "order.fulfillment.update"
| "order.receipt.record"
- | "order.payment.record"
- | "order.settlement.accept"
- | "order.settlement.reject"
)
}
@@ -451,15 +454,23 @@ fn external_network_operation(operation_id: &str) -> bool {
| "order.revision.decline"
| "order.fulfillment.update"
| "order.receipt.record"
- | "order.payment.record"
- | "order.settlement.accept"
- | "order.settlement.reject"
| "order.status.get"
| "order.event.list"
| "order.event.watch"
)
}
+fn deferred_payment_operation(operation_id: &str) -> bool {
+ matches!(
+ operation_id,
+ "order.payment.record" | "order.settlement.accept" | "order.settlement.reject"
+ )
+}
+
+fn deferred_payment_message() -> String {
+ "payments and settlement are not implemented in this Radroots release; order coordination is available now, and payment support is planned for a future phase".to_owned()
+}
+
fn failure_envelope(
request: &TargetOperationRequest,
error: OperationAdapterError,
diff --git a/src/operation_adapter.rs b/src/operation_adapter.rs
@@ -381,6 +381,11 @@ pub enum OperationAdapterError {
operation_id: String,
message: String,
},
+ #[error("operation `{operation_id}` is not implemented: {message}")]
+ NotImplemented {
+ operation_id: String,
+ message: String,
+ },
#[error("operation `{operation_id}` failed: {message}")]
DetailedFailure {
operation_id: String,
@@ -450,6 +455,13 @@ impl OperationAdapterError {
}
}
+ pub fn not_implemented(operation_id: &str, message: String) -> Self {
+ Self::NotImplemented {
+ operation_id: operation_id.to_owned(),
+ message,
+ }
+ }
+
pub fn network_unavailable_with_detail(
operation_id: &str,
message: String,
@@ -678,6 +690,16 @@ impl OperationAdapterError {
message,
CliExitCode::RuntimeUnavailable,
),
+ Self::NotImplemented {
+ operation_id,
+ message,
+ } => runtime_output_error(
+ "not_implemented",
+ operation_id,
+ "operation",
+ message,
+ CliExitCode::RuntimeUnavailable,
+ ),
Self::DetailedFailure {
operation_id,
code,
@@ -1912,6 +1934,22 @@ mod tests {
}
#[test]
+ fn not_implemented_errors_map_to_structured_exit_code() {
+ let error = OperationAdapterError::not_implemented(
+ "order.payment.record",
+ "coming soon".to_owned(),
+ );
+ let output_error = error.to_output_error();
+
+ assert_eq!(output_error.code, "not_implemented");
+ assert_eq!(output_error.exit_code, 3);
+ assert_eq!(
+ output_error.detail.expect("detail")["operation_id"],
+ "order.payment.record"
+ );
+ }
+
+ #[test]
fn runtime_failures_map_to_specific_machine_codes() {
let cases = [
(
diff --git a/src/operation_order.rs b/src/operation_order.rs
@@ -3,8 +3,8 @@ use serde_json::{Value, json};
use crate::domain::runtime::{
CommandDisposition, OrderCancellationView, OrderDecisionView, OrderFulfillmentView,
- OrderPaymentView, OrderReceiptView, OrderRevisionDecisionView, OrderRevisionProposalView,
- OrderSettlementView, OrderStatusView, OrderSubmitView,
+ OrderReceiptView, OrderRevisionDecisionView, OrderRevisionProposalView, OrderStatusView,
+ OrderSubmitView,
};
use crate::operation_adapter::{
OperationAdapterError, OperationRequest, OperationRequestData, OperationRequestPayload,
@@ -22,9 +22,8 @@ use crate::operation_adapter::{
use crate::runtime::RuntimeError;
use crate::runtime::config::RuntimeConfig;
use crate::runtime_args::{
- OrderCancelArgs, OrderDecisionArg, OrderDecisionArgs, OrderFulfillmentArgs, OrderPaymentArgs,
- OrderReceiptArgs, OrderRevisionDecisionArg, OrderRevisionDecisionArgs,
- OrderRevisionProposeArgs, OrderSettlementArgs, OrderSettlementDecisionArg, OrderStatusArgs,
+ OrderCancelArgs, OrderDecisionArg, OrderDecisionArgs, OrderFulfillmentArgs, OrderReceiptArgs,
+ OrderRevisionDecisionArg, OrderRevisionDecisionArgs, OrderRevisionProposeArgs, OrderStatusArgs,
OrderSubmitArgs, OrderWatchArgs, RecordLookupArgs,
};
@@ -445,64 +444,7 @@ impl OperationService<OrderPaymentRecordRequest> for OrderOperationService<'_> {
&self,
request: OperationRequest<OrderPaymentRecordRequest>,
) -> Result<OperationResult<Self::Result>, OperationAdapterError> {
- let amount = string_input(&request, "amount")
- .map(|amount| amount.trim().to_owned())
- .filter(|amount| !amount.is_empty())
- .ok_or_else(|| {
- invalid_input(
- request.operation_id(),
- "missing required payment amount input".to_owned(),
- )
- })?;
- let currency = string_input(&request, "currency")
- .map(|currency| currency.trim().to_owned())
- .filter(|currency| !currency.is_empty())
- .ok_or_else(|| {
- invalid_input(
- request.operation_id(),
- "missing required payment currency input".to_owned(),
- )
- })?;
- let method = string_input(&request, "method")
- .map(|method| method.trim().to_owned())
- .filter(|method| !method.is_empty())
- .ok_or_else(|| {
- invalid_input(
- request.operation_id(),
- "missing required payment method input".to_owned(),
- )
- })?;
- let reference = string_input(&request, "reference")
- .map(|reference| reference.trim().to_owned())
- .filter(|reference| !reference.is_empty());
- let paid_at = u64_input(&request, "paid_at");
- if request.context.requires_approval_token() {
- return Err(OperationAdapterError::approval_required(
- request.operation_id(),
- ));
- }
-
- let args = OrderPaymentArgs {
- key: required_order_key(&request)?,
- amount,
- currency,
- method,
- reference,
- paid_at,
- idempotency_key: request
- .context
- .idempotency_key
- .clone()
- .or_else(|| string_input(&request, "idempotency_key")),
- };
- let mut config = self.config.clone();
- if request.context.dry_run {
- config.output.dry_run = true;
- }
- let view = crate::runtime::order::payment_record(&config, &args).map_err(|error| {
- OperationAdapterError::runtime_failure(request.operation_id(), error)
- })?;
- payment_result::<OrderPaymentRecordResult>(request.operation_id(), &view)
+ Err(deferred_payment_error(request.operation_id()))
}
}
@@ -513,32 +455,7 @@ impl OperationService<OrderSettlementAcceptRequest> for OrderOperationService<'_
&self,
request: OperationRequest<OrderSettlementAcceptRequest>,
) -> Result<OperationResult<Self::Result>, OperationAdapterError> {
- let payment_event_id = required_payment_event_id(&request)?;
- if request.context.requires_approval_token() {
- return Err(OperationAdapterError::approval_required(
- request.operation_id(),
- ));
- }
-
- let args = OrderSettlementArgs {
- key: required_order_key(&request)?,
- payment_event_id,
- decision: OrderSettlementDecisionArg::Accept,
- reason: None,
- idempotency_key: request
- .context
- .idempotency_key
- .clone()
- .or_else(|| string_input(&request, "idempotency_key")),
- };
- let mut config = self.config.clone();
- if request.context.dry_run {
- config.output.dry_run = true;
- }
- let view = crate::runtime::order::settlement_decision(&config, &args).map_err(|error| {
- OperationAdapterError::runtime_failure(request.operation_id(), error)
- })?;
- settlement_result::<OrderSettlementAcceptResult>(request.operation_id(), &view)
+ Err(deferred_payment_error(request.operation_id()))
}
}
@@ -549,41 +466,7 @@ impl OperationService<OrderSettlementRejectRequest> for OrderOperationService<'_
&self,
request: OperationRequest<OrderSettlementRejectRequest>,
) -> Result<OperationResult<Self::Result>, OperationAdapterError> {
- let payment_event_id = required_payment_event_id(&request)?;
- let reason = string_input(&request, "reason")
- .map(|reason| reason.trim().to_owned())
- .filter(|reason| !reason.is_empty())
- .ok_or_else(|| {
- invalid_input(
- request.operation_id(),
- "missing required settlement rejection reason input".to_owned(),
- )
- })?;
- if request.context.requires_approval_token() {
- return Err(OperationAdapterError::approval_required(
- request.operation_id(),
- ));
- }
-
- let args = OrderSettlementArgs {
- key: required_order_key(&request)?,
- payment_event_id,
- decision: OrderSettlementDecisionArg::Reject,
- reason: Some(reason),
- idempotency_key: request
- .context
- .idempotency_key
- .clone()
- .or_else(|| string_input(&request, "idempotency_key")),
- };
- let mut config = self.config.clone();
- if request.context.dry_run {
- config.output.dry_run = true;
- }
- let view = crate::runtime::order::settlement_decision(&config, &args).map_err(|error| {
- OperationAdapterError::runtime_failure(request.operation_id(), error)
- })?;
- settlement_result::<OrderSettlementRejectResult>(request.operation_id(), &view)
+ Err(deferred_payment_error(request.operation_id()))
}
}
@@ -1142,198 +1025,6 @@ where
}
}
-fn payment_result<R>(
- operation_id: &str,
- view: &OrderPaymentView,
-) -> Result<OperationResult<R>, OperationAdapterError>
-where
- R: OperationResultData,
-{
- match view.disposition() {
- CommandDisposition::Success => serialized_target_result::<R, _>(view),
- CommandDisposition::ValidationFailed => {
- let message = view.reason.clone().unwrap_or_else(|| {
- format!(
- "order payment record failed validation with state `{}`",
- view.state
- )
- });
- Err(OperationAdapterError::validation_failed_with_detail(
- operation_id,
- message,
- order_payment_error_detail(view),
- ))
- }
- disposition => {
- let message = view.reason.clone().unwrap_or_else(|| {
- format!("order payment record finished with state `{}`", view.state)
- });
- if disposition == CommandDisposition::ExternalUnavailable {
- let detail = order_payment_error_detail(view);
- if !view.failed_relays.is_empty() && view.connected_relays.is_empty() {
- Err(OperationAdapterError::network_unavailable_with_detail(
- operation_id,
- message,
- detail,
- ))
- } else {
- Err(OperationAdapterError::operation_unavailable_with_detail(
- operation_id,
- message,
- detail,
- ))
- }
- } else if disposition == CommandDisposition::Unconfigured {
- Err(OperationAdapterError::operation_unavailable_with_detail(
- operation_id,
- message,
- order_payment_error_detail(view),
- ))
- } else {
- Err(OperationAdapterError::from_command_disposition(
- operation_id,
- disposition,
- message,
- ))
- }
- }
- }
-}
-
-fn settlement_result<R>(
- operation_id: &str,
- view: &OrderSettlementView,
-) -> Result<OperationResult<R>, OperationAdapterError>
-where
- R: OperationResultData,
-{
- match view.disposition() {
- CommandDisposition::Success => serialized_target_result::<R, _>(view),
- CommandDisposition::ValidationFailed => {
- let message = view.reason.clone().unwrap_or_else(|| {
- format!(
- "order settlement decision failed validation with state `{}`",
- view.state
- )
- });
- Err(OperationAdapterError::validation_failed_with_detail(
- operation_id,
- message,
- order_settlement_error_detail(view),
- ))
- }
- disposition => {
- let message = view.reason.clone().unwrap_or_else(|| {
- format!(
- "order settlement decision finished with state `{}`",
- view.state
- )
- });
- if disposition == CommandDisposition::ExternalUnavailable {
- let detail = order_settlement_error_detail(view);
- if !view.failed_relays.is_empty() && view.connected_relays.is_empty() {
- Err(OperationAdapterError::network_unavailable_with_detail(
- operation_id,
- message,
- detail,
- ))
- } else {
- Err(OperationAdapterError::operation_unavailable_with_detail(
- operation_id,
- message,
- detail,
- ))
- }
- } else if disposition == CommandDisposition::Unconfigured {
- Err(OperationAdapterError::operation_unavailable_with_detail(
- operation_id,
- message,
- order_settlement_error_detail(view),
- ))
- } else {
- Err(OperationAdapterError::from_command_disposition(
- operation_id,
- disposition,
- message,
- ))
- }
- }
- }
-}
-
-fn order_payment_error_detail(view: &OrderPaymentView) -> Value {
- json!({
- "state": &view.state,
- "order_id": &view.order_id,
- "listing_addr": &view.listing_addr,
- "request_event_id": &view.request_event_id,
- "agreement_event_id": &view.agreement_event_id,
- "root_event_id": &view.root_event_id,
- "prev_event_id": &view.prev_event_id,
- "event_id": &view.event_id,
- "event_kind": view.event_kind,
- "buyer_pubkey": &view.buyer_pubkey,
- "seller_pubkey": &view.seller_pubkey,
- "quote_id": &view.quote_id,
- "quote_version": view.quote_version,
- "economics_digest": &view.economics_digest,
- "amount": &view.amount,
- "currency": &view.currency,
- "method": &view.method,
- "reference": &view.reference,
- "paid_at": &view.paid_at,
- "dry_run": view.dry_run,
- "target_relays": &view.target_relays,
- "connected_relays": &view.connected_relays,
- "acknowledged_relays": &view.acknowledged_relays,
- "failed_relays": &view.failed_relays,
- "fetched_count": view.fetched_count,
- "decoded_count": view.decoded_count,
- "skipped_count": view.skipped_count,
- "idempotency_key": &view.idempotency_key,
- "signer_mode": &view.signer_mode,
- "issues": &view.issues,
- "actions": &view.actions,
- })
-}
-
-fn order_settlement_error_detail(view: &OrderSettlementView) -> Value {
- json!({
- "state": &view.state,
- "order_id": &view.order_id,
- "listing_addr": &view.listing_addr,
- "request_event_id": &view.request_event_id,
- "agreement_event_id": &view.agreement_event_id,
- "root_event_id": &view.root_event_id,
- "prev_event_id": &view.prev_event_id,
- "payment_event_id": &view.payment_event_id,
- "event_id": &view.event_id,
- "event_kind": view.event_kind,
- "buyer_pubkey": &view.buyer_pubkey,
- "seller_pubkey": &view.seller_pubkey,
- "quote_id": &view.quote_id,
- "quote_version": view.quote_version,
- "economics_digest": &view.economics_digest,
- "amount": &view.amount,
- "currency": &view.currency,
- "decision": &view.decision,
- "settlement_reason": &view.settlement_reason,
- "reason": &view.reason,
- "dry_run": view.dry_run,
- "target_relays": &view.target_relays,
- "connected_relays": &view.connected_relays,
- "acknowledged_relays": &view.acknowledged_relays,
- "failed_relays": &view.failed_relays,
- "fetched_count": view.fetched_count,
- "decoded_count": view.decoded_count,
- "skipped_count": view.skipped_count,
- "idempotency_key": &view.idempotency_key,
- "signer_mode": &view.signer_mode,
- "issues": &view.issues,
- "actions": &view.actions,
- })
-}
-
fn order_receipt_error_detail(view: &OrderReceiptView) -> Value {
json!({
"state": &view.state,
@@ -1503,15 +1194,6 @@ where
})
}
-fn required_payment_event_id<P>(
- request: &OperationRequest<P>,
-) -> Result<String, OperationAdapterError>
-where
- P: OperationRequestPayload + OperationRequestData,
-{
- required_string_input(request, "payment_event_id")
-}
-
fn required_string_input<P>(
request: &OperationRequest<P>,
key: &str,
@@ -1584,6 +1266,13 @@ fn map_runtime<T>(result: Result<T, RuntimeError>) -> Result<T, OperationAdapter
result.map_err(|error| OperationAdapterError::Runtime(error.to_string()))
}
+fn deferred_payment_error(operation_id: &str) -> OperationAdapterError {
+ OperationAdapterError::not_implemented(
+ operation_id,
+ "payments and settlement are not implemented in this Radroots release; order coordination is available now, and payment support is planned for a future phase".to_owned(),
+ )
+}
+
fn invalid_input(operation_id: &str, message: String) -> OperationAdapterError {
OperationAdapterError::InvalidInput {
operation_id: operation_id.to_owned(),
@@ -1948,141 +1637,44 @@ mod tests {
}
#[test]
- fn order_payment_record_requires_amount_before_approval() {
+ fn deferred_payment_commands_return_not_implemented_before_input_or_approval() {
let dir = tempdir().expect("tempdir");
let config = sample_config(dir.path());
let service = OperationAdapter::new(OrderOperationService::new(&config));
- let payment = OperationRequest::new(
- OperationContext::default(),
- OrderPaymentRecordRequest::from_data(data(&[
- ("order_id", "ord_pending"),
- ("currency", "USD"),
- ("method", "cash"),
- ])),
- )
- .expect("order payment request");
- let error = service.execute(payment).expect_err("amount required");
- let output_error = error.to_output_error();
-
- assert_eq!(output_error.code, "invalid_input");
- assert!(output_error.message.contains("amount"));
- }
-
- #[test]
- fn order_payment_record_requires_method_before_approval() {
- let dir = tempdir().expect("tempdir");
- let config = sample_config(dir.path());
- let service = OperationAdapter::new(OrderOperationService::new(&config));
- let payment = OperationRequest::new(
- OperationContext::default(),
- OrderPaymentRecordRequest::from_data(data(&[
- ("order_id", "ord_pending"),
- ("amount", "12"),
- ("currency", "USD"),
- ])),
- )
- .expect("order payment request");
- let error = service.execute(payment).expect_err("method required");
- let output_error = error.to_output_error();
-
- assert_eq!(output_error.code, "invalid_input");
- assert!(output_error.message.contains("method"));
- }
- #[test]
- fn order_payment_record_requires_approval_token() {
- let dir = tempdir().expect("tempdir");
- let config = sample_config(dir.path());
- let service = OperationAdapter::new(OrderOperationService::new(&config));
let payment = OperationRequest::new(
OperationContext::default(),
- OrderPaymentRecordRequest::from_data(data(&[
- ("order_id", "ord_pending"),
- ("amount", "12"),
- ("currency", "USD"),
- ("method", "cash"),
- ])),
+ OrderPaymentRecordRequest::from_data(data(&[("order_id", "ord_pending")])),
)
.expect("order payment request");
- let error = service.execute(payment).expect_err("approval required");
+ let payment_error = service.execute(payment).expect_err("payment deferred");
+ assert_eq!(payment_error.to_output_error().code, "not_implemented");
- assert_eq!(error.to_output_error().code, "approval_required");
- }
-
- #[test]
- fn order_settlement_accept_requires_payment_event_before_approval() {
- let dir = tempdir().expect("tempdir");
- let config = sample_config(dir.path());
- let service = OperationAdapter::new(OrderOperationService::new(&config));
- let settlement = OperationRequest::new(
+ let settlement_accept = OperationRequest::new(
OperationContext::default(),
OrderSettlementAcceptRequest::from_data(data(&[("order_id", "ord_pending")])),
)
.expect("order settlement accept request");
- let error = service
- .execute(settlement)
- .expect_err("payment event required");
- let output_error = error.to_output_error();
-
- assert_eq!(output_error.code, "invalid_input");
- assert!(output_error.message.contains("payment_event_id"));
- }
-
- #[test]
- fn order_settlement_accept_requires_approval_token() {
- let dir = tempdir().expect("tempdir");
- let config = sample_config(dir.path());
- let service = OperationAdapter::new(OrderOperationService::new(&config));
- let settlement = OperationRequest::new(
- OperationContext::default(),
- OrderSettlementAcceptRequest::from_data(data(&[
- ("order_id", "ord_pending"),
- ("payment_event_id", "pay_pending"),
- ])),
- )
- .expect("order settlement accept request");
- let error = service.execute(settlement).expect_err("approval required");
-
- assert_eq!(error.to_output_error().code, "approval_required");
- }
-
- #[test]
- fn order_settlement_reject_requires_reason_before_approval() {
- let dir = tempdir().expect("tempdir");
- let config = sample_config(dir.path());
- let service = OperationAdapter::new(OrderOperationService::new(&config));
- let settlement = OperationRequest::new(
- OperationContext::default(),
- OrderSettlementRejectRequest::from_data(data(&[
- ("order_id", "ord_pending"),
- ("payment_event_id", "pay_pending"),
- ])),
- )
- .expect("order settlement reject request");
- let error = service.execute(settlement).expect_err("reason required");
- let output_error = error.to_output_error();
-
- assert_eq!(output_error.code, "invalid_input");
- assert!(output_error.message.contains("reason"));
- }
+ let settlement_accept_error = service
+ .execute(settlement_accept)
+ .expect_err("settlement accept deferred");
+ assert_eq!(
+ settlement_accept_error.to_output_error().code,
+ "not_implemented"
+ );
- #[test]
- fn order_settlement_reject_requires_approval_token() {
- let dir = tempdir().expect("tempdir");
- let config = sample_config(dir.path());
- let service = OperationAdapter::new(OrderOperationService::new(&config));
- let settlement = OperationRequest::new(
+ let settlement_reject = OperationRequest::new(
OperationContext::default(),
- OrderSettlementRejectRequest::from_data(data(&[
- ("order_id", "ord_pending"),
- ("payment_event_id", "pay_pending"),
- ("reason", "reference mismatch"),
- ])),
+ OrderSettlementRejectRequest::from_data(data(&[("order_id", "ord_pending")])),
)
.expect("order settlement reject request");
- let error = service.execute(settlement).expect_err("approval required");
-
- assert_eq!(error.to_output_error().code, "approval_required");
+ let settlement_reject_error = service
+ .execute(settlement_reject)
+ .expect_err("settlement reject deferred");
+ assert_eq!(
+ settlement_reject_error.to_output_error().code,
+ "not_implemented"
+ );
}
#[test]
diff --git a/src/operation_registry.rs b/src/operation_registry.rs
@@ -998,11 +998,11 @@ pub const OPERATION_REGISTRY: &[OperationSpec] = &[
"order_payment_record",
"OrderPaymentRecordRequest",
"OrderPaymentRecordResult",
- "Record buyer manual payment.",
+ "Reserved future buyer manual payment command.",
Buyer,
- true,
- Required,
- High,
+ false,
+ None,
+ Low,
false,
true
),
@@ -1013,11 +1013,11 @@ pub const OPERATION_REGISTRY: &[OperationSpec] = &[
"order_settlement_accept",
"OrderSettlementAcceptRequest",
"OrderSettlementAcceptResult",
- "Accept seller settlement of a recorded payment.",
+ "Reserved future seller settlement acceptance command.",
Seller,
- true,
- Required,
- High,
+ false,
+ None,
+ Low,
false,
true
),
@@ -1028,11 +1028,11 @@ pub const OPERATION_REGISTRY: &[OperationSpec] = &[
"order_settlement_reject",
"OrderSettlementRejectRequest",
"OrderSettlementRejectResult",
- "Reject seller settlement of a recorded payment.",
+ "Reserved future seller settlement rejection command.",
Seller,
- true,
- Required,
- High,
+ false,
+ None,
+ Low,
false,
true
),
@@ -1211,9 +1211,6 @@ mod tests {
"order.revision.decline",
"order.fulfillment.update",
"order.receipt.record",
- "order.payment.record",
- "order.settlement.accept",
- "order.settlement.reject",
];
const INTENTIONALLY_UNSUPPORTED_MUTATING_DRY_RUN_OPERATION_IDS: &[&str] = &[];
@@ -1281,9 +1278,6 @@ mod tests {
"order.revision.decline",
"order.fulfillment.update",
"order.receipt.record",
- "order.payment.record",
- "order.settlement.accept",
- "order.settlement.reject",
]
.into_iter()
.collect::<BTreeSet<_>>();
diff --git a/src/runtime/order.rs b/src/runtime/order.rs
@@ -1,3 +1,5 @@
+#![allow(dead_code)]
+
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
diff --git a/src/runtime_args.rs b/src/runtime_args.rs
@@ -245,6 +245,7 @@ pub struct OrderReceiptArgs {
}
#[derive(Debug, Clone)]
+#[allow(dead_code)]
pub struct OrderPaymentArgs {
pub key: String,
pub amount: String,
@@ -301,12 +302,14 @@ pub struct OrderRevisionDecisionArgs {
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[allow(dead_code)]
pub enum OrderSettlementDecisionArg {
Accept,
Reject,
}
#[derive(Debug, Clone)]
+#[allow(dead_code)]
pub struct OrderSettlementArgs {
pub key: String,
pub payment_event_id: String,
diff --git a/tests/target_cli.rs b/tests/target_cli.rs
@@ -262,6 +262,104 @@ fn seller_order_decision_and_status_commands_are_public() {
}
#[test]
+fn payment_commands_return_not_implemented_before_mutation_preflight() {
+ let sandbox = RadrootsCliSandbox::new();
+
+ for (operation_id, args) in [
+ (
+ "order.payment.record",
+ [
+ "--format",
+ "json",
+ "order",
+ "payment",
+ "record",
+ "ord_pending",
+ ]
+ .as_slice(),
+ ),
+ (
+ "order.payment.record",
+ [
+ "--format",
+ "json",
+ "--offline",
+ "order",
+ "payment",
+ "record",
+ "ord_pending",
+ ]
+ .as_slice(),
+ ),
+ (
+ "order.settlement.accept",
+ [
+ "--format",
+ "json",
+ "order",
+ "settlement",
+ "accept",
+ "ord_pending",
+ ]
+ .as_slice(),
+ ),
+ (
+ "order.settlement.accept",
+ [
+ "--format",
+ "json",
+ "--online",
+ "order",
+ "settlement",
+ "accept",
+ "ord_pending",
+ ]
+ .as_slice(),
+ ),
+ (
+ "order.settlement.reject",
+ [
+ "--format",
+ "json",
+ "order",
+ "settlement",
+ "reject",
+ "ord_pending",
+ ]
+ .as_slice(),
+ ),
+ (
+ "order.settlement.reject",
+ [
+ "--format",
+ "json",
+ "--offline",
+ "--dry-run",
+ "order",
+ "settlement",
+ "reject",
+ "ord_pending",
+ ]
+ .as_slice(),
+ ),
+ ] {
+ let (output, value) = sandbox.json_output(args);
+
+ assert!(!output.status.success());
+ assert_eq!(output.status.code(), Some(3));
+ assert_eq!(value["operation_id"], operation_id);
+ assert_eq!(value["result"], Value::Null);
+ assert_eq!(value["errors"][0]["code"], "not_implemented");
+ assert_eq!(value["errors"][0]["exit_code"], 3);
+ let message = value["errors"][0]["message"].as_str().expect("message");
+ assert!(message.contains("not implemented"));
+ assert!(message.contains("future phase"));
+ assert!(!message.contains("approval_token"));
+ assert!(!message.contains("relay"));
+ }
+}
+
+#[test]
fn target_outputs_do_not_suggest_removed_command_families() {
let sandbox = RadrootsCliSandbox::new();
@@ -655,57 +753,6 @@ fn offline_forbids_external_network_operations() {
]
.as_slice(),
),
- (
- "order.payment.record",
- [
- "--format",
- "json",
- "--offline",
- "order",
- "payment",
- "record",
- "ord_offline_payment",
- "--amount",
- "12",
- "--currency",
- "USD",
- "--method",
- "cash",
- ]
- .as_slice(),
- ),
- (
- "order.settlement.accept",
- [
- "--format",
- "json",
- "--offline",
- "order",
- "settlement",
- "accept",
- "ord_offline_settlement",
- "--payment-event-id",
- "pay_event",
- ]
- .as_slice(),
- ),
- (
- "order.settlement.reject",
- [
- "--format",
- "json",
- "--offline",
- "order",
- "settlement",
- "reject",
- "ord_offline_settlement",
- "--payment-event-id",
- "pay_event",
- "--reason",
- "reference mismatch",
- ]
- .as_slice(),
- ),
] {
let output = radroots()
.args(args)
@@ -875,60 +922,6 @@ fn offline_rejects_order_decision_dry_run() {
]
.as_slice(),
),
- (
- "order.payment.record",
- [
- "--format",
- "json",
- "--offline",
- "--dry-run",
- "order",
- "payment",
- "record",
- "ord_offline_decision",
- "--amount",
- "12",
- "--currency",
- "USD",
- "--method",
- "manual_transfer",
- ]
- .as_slice(),
- ),
- (
- "order.settlement.accept",
- [
- "--format",
- "json",
- "--offline",
- "--dry-run",
- "order",
- "settlement",
- "accept",
- "ord_offline_decision",
- "--payment-event-id",
- "pay_event",
- ]
- .as_slice(),
- ),
- (
- "order.settlement.reject",
- [
- "--format",
- "json",
- "--offline",
- "--dry-run",
- "order",
- "settlement",
- "reject",
- "ord_offline_decision",
- "--payment-event-id",
- "pay_event",
- "--reason",
- "reference mismatch",
- ]
- .as_slice(),
- ),
] {
let output = radroots()
.args(args)
@@ -1126,57 +1119,6 @@ fn online_requires_relay_for_external_network_operations() {
]
.as_slice(),
),
- (
- "order.payment.record",
- [
- "--format",
- "json",
- "--online",
- "order",
- "payment",
- "record",
- "ord_missing",
- "--amount",
- "12",
- "--currency",
- "USD",
- "--method",
- "cash",
- ]
- .as_slice(),
- ),
- (
- "order.settlement.accept",
- [
- "--format",
- "json",
- "--online",
- "order",
- "settlement",
- "accept",
- "ord_missing",
- "--payment-event-id",
- "pay_event",
- ]
- .as_slice(),
- ),
- (
- "order.settlement.reject",
- [
- "--format",
- "json",
- "--online",
- "order",
- "settlement",
- "reject",
- "ord_missing",
- "--payment-event-id",
- "pay_event",
- "--reason",
- "reference mismatch",
- ]
- .as_slice(),
- ),
] {
let output = radroots()
.args(args)
@@ -1681,48 +1623,6 @@ fn required_approval_token_rejects_absent_empty_and_whitespace_values() {
"order.receipt.record",
&["order", "receipt", "record", "ord_pending", "--received"],
);
- assert_required_approval_token_rejected(
- &sandbox,
- "order.payment.record",
- &[
- "order",
- "payment",
- "record",
- "ord_pending",
- "--amount",
- "12",
- "--currency",
- "USD",
- "--method",
- "cash",
- ],
- );
- assert_required_approval_token_rejected(
- &sandbox,
- "order.settlement.accept",
- &[
- "order",
- "settlement",
- "accept",
- "ord_pending",
- "--payment-event-id",
- "pay_pending",
- ],
- );
- assert_required_approval_token_rejected(
- &sandbox,
- "order.settlement.reject",
- &[
- "order",
- "settlement",
- "reject",
- "ord_pending",
- "--payment-event-id",
- "pay_pending",
- "--reason",
- "reference mismatch",
- ],
- );
}
fn assert_required_approval_token_rejected(