1use stateset_core::{
54 A2APurchase, A2APurchaseFilter, AgentCard, AgentCardFilter, CreateA2APurchase, CreateA2AQuote,
55 CreateAgentCard, CreateX402PaymentIntent, PurchaseStatus, QuoteStatus, Result,
56 SignX402PaymentIntent, SkillQuote, SkillQuoteFilter, TrustLevel, UpdateAgentCard, X402Asset,
57 X402CreditAccount, X402CreditAdjustment, X402CreditDirection, X402CreditTransaction,
58 X402CreditTransactionFilter, X402IntentStatus, X402Network, X402PaymentIntent,
59 X402PaymentIntentFilter,
60};
61use stateset_db::Database;
62use std::sync::Arc;
63use uuid::Uuid;
64
65pub struct X402 {
70 db: Arc<dyn Database>,
71}
72
73impl std::fmt::Debug for X402 {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.debug_struct("X402").finish_non_exhaustive()
76 }
77}
78
79impl X402 {
80 pub fn new(db: Arc<dyn Database>) -> Self {
82 Self { db }
83 }
84
85 pub fn create_intent(&self, input: CreateX402PaymentIntent) -> Result<X402PaymentIntent> {
108 self.db.x402_payment_intents().create(input)
109 }
110
111 pub fn get_intent(&self, id: Uuid) -> Result<Option<X402PaymentIntent>> {
113 self.db.x402_payment_intents().get(id)
114 }
115
116 pub fn sign_intent(&self, id: Uuid, input: SignX402PaymentIntent) -> Result<X402PaymentIntent> {
136 self.db.x402_payment_intents().sign(id, input)
137 }
138
139 pub fn mark_sequenced(
144 &self,
145 id: Uuid,
146 sequence_number: u64,
147 batch_id: Uuid,
148 ) -> Result<X402PaymentIntent> {
149 self.db.x402_payment_intents().mark_sequenced(id, sequence_number, batch_id)
150 }
151
152 pub fn mark_settled(
167 &self,
168 id: Uuid,
169 tx_hash: &str,
170 block_number: u64,
171 ) -> Result<X402PaymentIntent> {
172 self.db.x402_payment_intents().mark_settled(id, tx_hash, block_number)
173 }
174
175 pub fn mark_failed(&self, id: Uuid, reason: &str) -> Result<X402PaymentIntent> {
180 self.db.x402_payment_intents().mark_failed(id, reason)
181 }
182
183 pub fn mark_expired(&self, id: Uuid) -> Result<X402PaymentIntent> {
187 self.db.x402_payment_intents().mark_expired(id)
188 }
189
190 pub fn cancel_intent(&self, id: Uuid) -> Result<X402PaymentIntent> {
195 self.db.x402_payment_intents().cancel(id)
196 }
197
198 pub fn intents_for_cart(&self, cart_id: Uuid) -> Result<Vec<X402PaymentIntent>> {
200 self.db.x402_payment_intents().for_cart(cart_id)
201 }
202
203 pub fn intents_for_order(&self, order_id: Uuid) -> Result<Vec<X402PaymentIntent>> {
205 self.db.x402_payment_intents().for_order(order_id)
206 }
207
208 pub fn get_next_nonce(&self, payer_address: &str) -> Result<u64> {
213 self.db.x402_payment_intents().get_next_nonce(payer_address)
214 }
215
216 pub fn list_intents(&self, filter: X402PaymentIntentFilter) -> Result<Vec<X402PaymentIntent>> {
218 self.db.x402_payment_intents().list(filter)
219 }
220
221 pub fn count_intents(&self, filter: X402PaymentIntentFilter) -> Result<u64> {
223 self.db.x402_payment_intents().count(filter)
224 }
225
226 pub fn expire_stale_intents(&self) -> Result<u64> {
230 self.db.x402_payment_intents().expire_stale_intents()
231 }
232
233 pub fn intents_by_status(&self, status: X402IntentStatus) -> Result<Vec<X402PaymentIntent>> {
235 self.list_intents(X402PaymentIntentFilter { status: Some(status), ..Default::default() })
236 }
237
238 pub fn pending_intents(&self) -> Result<Vec<X402PaymentIntent>> {
240 self.intents_by_status(X402IntentStatus::Created)
241 }
242
243 pub fn signed_intents(&self) -> Result<Vec<X402PaymentIntent>> {
245 self.intents_by_status(X402IntentStatus::Signed)
246 }
247
248 pub fn settled_intents(&self) -> Result<Vec<X402PaymentIntent>> {
250 self.intents_by_status(X402IntentStatus::Settled)
251 }
252
253 pub fn create_quote(&self, input: CreateA2AQuote) -> Result<SkillQuote> {
259 self.db.a2a_quotes().create_quote(input)
260 }
261
262 pub fn get_quote(&self, id: Uuid) -> Result<Option<SkillQuote>> {
264 self.db.a2a_quotes().get_quote(id)
265 }
266
267 pub fn get_quote_by_number(&self, quote_number: &str) -> Result<Option<SkillQuote>> {
269 self.db.a2a_quotes().get_quote_by_number(quote_number)
270 }
271
272 pub fn update_quote_status(&self, id: Uuid, status: QuoteStatus) -> Result<SkillQuote> {
274 self.db.a2a_quotes().update_quote_status(id, status)
275 }
276
277 pub fn list_quotes(&self, filter: SkillQuoteFilter) -> Result<Vec<SkillQuote>> {
279 self.db.a2a_quotes().list_quotes(filter)
280 }
281
282 pub fn count_quotes(&self, filter: SkillQuoteFilter) -> Result<u64> {
284 self.db.a2a_quotes().count_quotes(filter)
285 }
286
287 pub fn create_purchase(&self, input: CreateA2APurchase) -> Result<A2APurchase> {
289 self.db.a2a_purchases().create_purchase(input)
290 }
291
292 pub fn get_purchase(&self, id: Uuid) -> Result<Option<A2APurchase>> {
294 self.db.a2a_purchases().get_purchase(id)
295 }
296
297 pub fn get_purchase_by_number(&self, purchase_number: &str) -> Result<Option<A2APurchase>> {
299 self.db.a2a_purchases().get_purchase_by_number(purchase_number)
300 }
301
302 pub fn update_purchase_status(&self, id: Uuid, status: PurchaseStatus) -> Result<A2APurchase> {
304 self.db.a2a_purchases().update_purchase_status(id, status)
305 }
306
307 pub fn link_purchase_to_order(&self, purchase_id: Uuid, order_id: Uuid) -> Result<A2APurchase> {
309 self.db.a2a_purchases().link_purchase_to_order(purchase_id, order_id)
310 }
311
312 pub fn confirm_delivery(
314 &self,
315 purchase_id: Uuid,
316 signature: &str,
317 rating: Option<u8>,
318 feedback: Option<&str>,
319 ) -> Result<A2APurchase> {
320 self.db.a2a_purchases().confirm_delivery(purchase_id, signature, rating, feedback)
321 }
322
323 pub fn list_purchases(&self, filter: A2APurchaseFilter) -> Result<Vec<A2APurchase>> {
325 self.db.a2a_purchases().list_purchases(filter)
326 }
327
328 pub fn count_purchases(&self, filter: A2APurchaseFilter) -> Result<u64> {
330 self.db.a2a_purchases().count_purchases(filter)
331 }
332
333 pub fn get_credit_account(
339 &self,
340 payer_address: &str,
341 asset: X402Asset,
342 network: X402Network,
343 ) -> Result<Option<X402CreditAccount>> {
344 self.db.x402_credits().get_account(payer_address, asset, network)
345 }
346
347 pub fn get_or_create_credit_account(
349 &self,
350 payer_address: &str,
351 asset: X402Asset,
352 network: X402Network,
353 ) -> Result<X402CreditAccount> {
354 self.db.x402_credits().get_or_create_account(payer_address, asset, network)
355 }
356
357 pub fn get_credit_balance(
359 &self,
360 payer_address: &str,
361 asset: X402Asset,
362 network: X402Network,
363 ) -> Result<u64> {
364 self.db.x402_credits().get_balance(payer_address, asset, network)
365 }
366
367 pub fn adjust_credit_balance(
369 &self,
370 input: X402CreditAdjustment,
371 ) -> Result<X402CreditTransaction> {
372 self.db.x402_credits().adjust_balance(input)
373 }
374
375 #[allow(clippy::too_many_arguments)]
377 pub fn credit_account(
378 &self,
379 payer_address: &str,
380 asset: X402Asset,
381 network: X402Network,
382 amount: u64,
383 reason: Option<String>,
384 reference_id: Option<String>,
385 metadata: Option<String>,
386 ) -> Result<X402CreditTransaction> {
387 self.adjust_credit_balance(X402CreditAdjustment {
388 payer_address: payer_address.to_string(),
389 asset,
390 network,
391 direction: X402CreditDirection::Credit,
392 amount,
393 reason,
394 reference_id,
395 metadata,
396 })
397 }
398
399 #[allow(clippy::too_many_arguments)]
401 pub fn debit_account(
402 &self,
403 payer_address: &str,
404 asset: X402Asset,
405 network: X402Network,
406 amount: u64,
407 reason: Option<String>,
408 reference_id: Option<String>,
409 metadata: Option<String>,
410 ) -> Result<X402CreditTransaction> {
411 self.adjust_credit_balance(X402CreditAdjustment {
412 payer_address: payer_address.to_string(),
413 asset,
414 network,
415 direction: X402CreditDirection::Debit,
416 amount,
417 reason,
418 reference_id,
419 metadata,
420 })
421 }
422
423 pub fn list_credit_transactions(
425 &self,
426 filter: X402CreditTransactionFilter,
427 ) -> Result<Vec<X402CreditTransaction>> {
428 self.db.x402_credits().list_transactions(filter)
429 }
430
431 pub fn register_agent(&self, input: CreateAgentCard) -> Result<AgentCard> {
457 self.db.agent_cards().create(input)
458 }
459
460 pub fn get_agent(&self, id: Uuid) -> Result<Option<AgentCard>> {
462 self.db.agent_cards().get(id)
463 }
464
465 pub fn get_agent_by_wallet(&self, wallet_address: &str) -> Result<Option<AgentCard>> {
467 self.db.agent_cards().get_by_wallet(wallet_address)
468 }
469
470 pub fn update_agent(&self, id: Uuid, input: UpdateAgentCard) -> Result<AgentCard> {
472 self.db.agent_cards().update(id, input)
473 }
474
475 pub fn delete_agent(&self, id: Uuid) -> Result<()> {
477 self.db.agent_cards().delete(id)
478 }
479
480 pub fn list_agents(&self, filter: AgentCardFilter) -> Result<Vec<AgentCard>> {
482 self.db.agent_cards().list(filter)
483 }
484
485 pub fn count_agents(&self, filter: AgentCardFilter) -> Result<u64> {
487 self.db.agent_cards().count(filter)
488 }
489
490 pub fn verify_agent(&self, id: Uuid) -> Result<AgentCard> {
494 self.db.agent_cards().verify(id, TrustLevel::Verified, "system")
495 }
496
497 pub fn suspend_agent(&self, id: Uuid, reason: &str) -> Result<AgentCard> {
501 self.db.agent_cards().suspend(id, reason)
502 }
503
504 pub fn reactivate_agent(&self, id: Uuid) -> Result<AgentCard> {
506 self.db.agent_cards().reactivate(id)
507 }
508
509 pub fn discover_agents(
527 &self,
528 network: Option<X402Network>,
529 asset: Option<X402Asset>,
530 skill: Option<stateset_core::A2ASkill>,
531 min_trust_level: Option<TrustLevel>,
532 ) -> Result<Vec<AgentCard>> {
533 self.db.agent_cards().discover(AgentCardFilter {
534 network,
535 asset,
536 skill,
537 trust_level: None,
538 min_trust_level,
539 active: Some(true),
540 ..Default::default()
541 })
542 }
543
544 pub fn active_agents(&self) -> Result<Vec<AgentCard>> {
546 self.list_agents(AgentCardFilter { active: Some(true), ..Default::default() })
547 }
548
549 pub fn agents_by_trust_level(&self, level: TrustLevel) -> Result<Vec<AgentCard>> {
551 self.list_agents(AgentCardFilter {
552 trust_level: Some(level),
553 active: Some(true),
554 ..Default::default()
555 })
556 }
557
558 pub fn verified_agents(&self) -> Result<Vec<AgentCard>> {
560 self.agents_by_trust_level(TrustLevel::Verified)
561 }
562
563 pub fn create_cart_payment(
572 &self,
573 cart_id: Uuid,
574 payer_address: &str,
575 payee_address: &str,
576 amount: rust_decimal::Decimal,
577 network: X402Network,
578 asset: X402Asset,
579 ) -> Result<X402PaymentIntent> {
580 use stateset_core::to_smallest_unit;
581 self.create_intent(CreateX402PaymentIntent {
582 payer_address: payer_address.to_string(),
583 payee_address: payee_address.to_string(),
584 amount: to_smallest_unit(amount, asset),
585 asset,
586 network,
587 cart_id: Some(cart_id),
588 ..Default::default()
589 })
590 }
591
592 pub fn active_intent_for_cart(&self, cart_id: Uuid) -> Result<Option<X402PaymentIntent>> {
596 let intents = self.intents_for_cart(cart_id)?;
597 Ok(intents.into_iter().find(|i| {
598 matches!(
599 i.status,
600 X402IntentStatus::Created
601 | X402IntentStatus::Signed
602 | X402IntentStatus::Sequenced
603 | X402IntentStatus::Settled
604 )
605 }))
606 }
607
608 pub fn is_ready_for_settlement(&self, id: Uuid) -> Result<bool> {
612 if let Some(intent) = self.get_intent(id)? {
613 let now = chrono::Utc::now().timestamp() as u64;
614 Ok(intent.status == X402IntentStatus::Signed && intent.valid_until > now)
615 } else {
616 Ok(false)
617 }
618 }
619
620 pub fn has_valid_signature(&self, id: Uuid) -> Result<bool> {
624 if let Some(intent) = self.get_intent(id)? {
625 if !intent.is_signed() {
626 return Ok(false);
627 }
628 Ok(intent.verify_signature().unwrap_or(false))
629 } else {
630 Ok(false)
631 }
632 }
633}
634
635#[cfg(all(test, feature = "sqlite"))]
636mod tests {
637 use super::*;
638 use stateset_core::{
639 A2ASkill, CurrencyCode, ItemAvailability, QuotedItem, X402_DEFAULT_SIGNATURE_SCHEME,
640 X402SignatureScheme,
641 };
642 use stateset_crypto::pqc::generate_hybrid_signing_keypair;
643
644 fn setup_commerce() -> crate::Commerce {
645 crate::Commerce::in_memory().unwrap()
646 }
647
648 fn sign_locally_with_hybrid(intent: &mut X402PaymentIntent) {
649 let keypair = generate_hybrid_signing_keypair().unwrap();
650 intent.sign_with_hybrid(&keypair).unwrap();
651 }
652
653 #[test]
654 fn test_create_payment_intent() {
655 let commerce = setup_commerce();
656
657 let intent = commerce
659 .x402()
660 .create_intent(CreateX402PaymentIntent {
661 payer_address: "0xPayer123".into(),
662 payee_address: "0xPayee456".into(),
663 amount: 100_000_000,
664 asset: X402Asset::Usdc,
665 network: X402Network::SetChain,
666 ..Default::default()
667 })
668 .unwrap();
669
670 assert_eq!(intent.payer_address, "0xPayer123");
671 assert_eq!(intent.payee_address, "0xPayee456");
672 assert_eq!(intent.amount, 100_000_000);
673 assert_eq!(intent.asset, X402Asset::Usdc);
674 assert_eq!(intent.network, X402Network::SetChain);
675 assert_eq!(intent.status, X402IntentStatus::Created);
676 assert_eq!(intent.payer_signature_scheme, Some(X402_DEFAULT_SIGNATURE_SCHEME));
677 assert!(intent.signing_hash.is_some());
678 }
679
680 #[test]
681 fn test_sign_payment_intent() {
682 let commerce = setup_commerce();
683
684 let intent = commerce
686 .x402()
687 .create_intent(CreateX402PaymentIntent {
688 payer_address: "0xPayer123".into(),
689 payee_address: "0xPayee456".into(),
690 amount: 50_000_000,
691 ..Default::default()
692 })
693 .unwrap();
694
695 let mut locally_signed = commerce.x402().get_intent(intent.id).unwrap().unwrap();
696 sign_locally_with_hybrid(&mut locally_signed);
697 let signature = locally_signed.payer_signature.clone().unwrap();
698 let public_key = locally_signed.payer_public_key.clone().unwrap();
699 let signature_bundle = locally_signed.payer_signature_bundle.clone();
700 let public_key_bundle = locally_signed.payer_public_key_bundle.clone();
701
702 let signed = commerce
703 .x402()
704 .sign_intent(
705 intent.id,
706 SignX402PaymentIntent {
707 intent_id: intent.id,
708 signature_scheme: None,
709 signature: signature.clone(),
710 public_key: public_key.clone(),
711 signature_bundle,
712 public_key_bundle,
713 },
714 )
715 .unwrap();
716
717 assert_eq!(signed.status, X402IntentStatus::Signed);
718 assert_eq!(signed.payer_signature_scheme, Some(X402_DEFAULT_SIGNATURE_SCHEME));
719 assert_eq!(signed.payer_signature, Some(signature));
720 assert_eq!(signed.payer_public_key, Some(public_key));
721 assert!(signed.payer_signature_bundle.is_some());
722 assert!(signed.payer_public_key_bundle.is_some());
723 }
724
725 #[test]
726 fn test_sign_payment_intent_rejects_ed25519_downgrade_for_new_intents() {
727 let commerce = setup_commerce();
728
729 let intent = commerce
730 .x402()
731 .create_intent(CreateX402PaymentIntent {
732 payer_address: "0xHybridSigner".into(),
733 payee_address: "0xPayee456".into(),
734 amount: 50_000_000,
735 ..Default::default()
736 })
737 .unwrap();
738
739 let mut locally_signed = commerce.x402().get_intent(intent.id).unwrap().unwrap();
740 locally_signed.sign_with_ed25519(&[21u8; 32]).unwrap();
741
742 let err = commerce
743 .x402()
744 .sign_intent(
745 intent.id,
746 SignX402PaymentIntent {
747 intent_id: intent.id,
748 signature_scheme: Some(X402SignatureScheme::Ed25519),
749 signature: locally_signed.payer_signature.unwrap(),
750 public_key: locally_signed.payer_public_key.unwrap(),
751 signature_bundle: None,
752 public_key_bundle: None,
753 },
754 )
755 .unwrap_err();
756
757 assert!(err.to_string().contains("ed25519_ml_dsa65"));
758 }
759
760 #[test]
761 fn test_has_valid_signature_true_for_hybrid_signature() {
762 let commerce = setup_commerce();
763
764 let intent = commerce
765 .x402()
766 .create_intent(CreateX402PaymentIntent {
767 payer_address: "0xSigner".into(),
768 payee_address: "0xPayee".into(),
769 amount: 1_000_000,
770 ..Default::default()
771 })
772 .unwrap();
773
774 let mut to_sign = commerce.x402().get_intent(intent.id).unwrap().unwrap();
775 sign_locally_with_hybrid(&mut to_sign);
776
777 let signed = commerce
778 .x402()
779 .sign_intent(
780 intent.id,
781 SignX402PaymentIntent {
782 intent_id: intent.id,
783 signature_scheme: None,
784 signature: to_sign.payer_signature.clone().unwrap(),
785 public_key: to_sign.payer_public_key.clone().unwrap(),
786 signature_bundle: to_sign.payer_signature_bundle.clone(),
787 public_key_bundle: to_sign.payer_public_key_bundle.clone(),
788 },
789 )
790 .unwrap();
791
792 assert_eq!(signed.status, X402IntentStatus::Signed);
793 assert!(commerce.x402().has_valid_signature(intent.id).unwrap());
794 }
795
796 #[test]
797 fn test_sign_intent_rejects_malformed_signature() {
798 let commerce = setup_commerce();
799
800 let intent = commerce
801 .x402()
802 .create_intent(CreateX402PaymentIntent {
803 payer_address: "0xSigner".into(),
804 payee_address: "0xPayee".into(),
805 amount: 1_000_000,
806 ..Default::default()
807 })
808 .unwrap();
809
810 let result = commerce.x402().sign_intent(
811 intent.id,
812 SignX402PaymentIntent {
813 intent_id: intent.id,
814 signature_scheme: None,
815 signature: "not-hex-signature".into(),
816 public_key: "not-hex-public-key".into(),
817 signature_bundle: None,
818 public_key_bundle: None,
819 },
820 );
821
822 assert!(result.is_err());
823 }
824
825 #[test]
826 fn test_sign_intent_rejects_mismatched_intent_id() {
827 let commerce = setup_commerce();
828
829 let intent = commerce
830 .x402()
831 .create_intent(CreateX402PaymentIntent {
832 payer_address: "0xSigner".into(),
833 payee_address: "0xPayee".into(),
834 amount: 1_000_000,
835 ..Default::default()
836 })
837 .unwrap();
838
839 let mut locally_signed = commerce.x402().get_intent(intent.id).unwrap().unwrap();
840 sign_locally_with_hybrid(&mut locally_signed);
841
842 let result = commerce.x402().sign_intent(
843 intent.id,
844 SignX402PaymentIntent {
845 intent_id: Uuid::new_v4(),
846 signature_scheme: None,
847 signature: locally_signed.payer_signature.clone().unwrap(),
848 public_key: locally_signed.payer_public_key.clone().unwrap(),
849 signature_bundle: locally_signed.payer_signature_bundle.clone(),
850 public_key_bundle: locally_signed.payer_public_key_bundle.clone(),
851 },
852 );
853
854 assert!(result.is_err());
855 }
856
857 #[test]
858 fn test_mark_settled() {
859 let commerce = setup_commerce();
860
861 let intent = commerce
863 .x402()
864 .create_intent(CreateX402PaymentIntent {
865 payer_address: "0xPayer123".into(),
866 payee_address: "0xPayee456".into(),
867 amount: 25_000_000,
868 ..Default::default()
869 })
870 .unwrap();
871
872 let mut locally_signed = commerce.x402().get_intent(intent.id).unwrap().unwrap();
874 sign_locally_with_hybrid(&mut locally_signed);
875
876 commerce
877 .x402()
878 .sign_intent(
879 intent.id,
880 SignX402PaymentIntent {
881 intent_id: intent.id,
882 signature_scheme: None,
883 signature: locally_signed.payer_signature.clone().unwrap(),
884 public_key: locally_signed.payer_public_key.clone().unwrap(),
885 signature_bundle: locally_signed.payer_signature_bundle.clone(),
886 public_key_bundle: locally_signed.payer_public_key_bundle.clone(),
887 },
888 )
889 .unwrap();
890
891 let sequenced = commerce.x402().mark_sequenced(intent.id, 7, Uuid::new_v4()).unwrap();
892 assert_eq!(sequenced.status, X402IntentStatus::Sequenced);
893
894 let settled = commerce.x402().mark_settled(intent.id, "0xTxHash123", 12345).unwrap();
896
897 assert_eq!(settled.status, X402IntentStatus::Settled);
898 assert_eq!(settled.tx_hash, Some("0xTxHash123".to_string()));
899 assert_eq!(settled.block_number, Some(12345));
900 }
901
902 #[test]
903 fn test_mark_settled_rejects_unsequenced_intent() {
904 let commerce = setup_commerce();
905
906 let intent = commerce
907 .x402()
908 .create_intent(CreateX402PaymentIntent {
909 payer_address: "0xPayer123".into(),
910 payee_address: "0xPayee456".into(),
911 amount: 25_000_000,
912 ..Default::default()
913 })
914 .unwrap();
915
916 let mut locally_signed = commerce.x402().get_intent(intent.id).unwrap().unwrap();
917 sign_locally_with_hybrid(&mut locally_signed);
918
919 commerce
920 .x402()
921 .sign_intent(
922 intent.id,
923 SignX402PaymentIntent {
924 intent_id: intent.id,
925 signature_scheme: None,
926 signature: locally_signed.payer_signature.clone().unwrap(),
927 public_key: locally_signed.payer_public_key.clone().unwrap(),
928 signature_bundle: locally_signed.payer_signature_bundle.clone(),
929 public_key_bundle: locally_signed.payer_public_key_bundle.clone(),
930 },
931 )
932 .unwrap();
933
934 let err = commerce.x402().mark_settled(intent.id, "0xTxHash123", 12345).unwrap_err();
935 assert!(err.to_string().contains("Cannot settle intent in signed status"));
936 }
937
938 #[test]
939 fn test_register_agent_card() {
940 let commerce = setup_commerce();
941
942 let card = commerce
943 .x402()
944 .register_agent(CreateAgentCard {
945 name: "Test Agent".into(),
946 wallet_address: "0xAgent123".into(),
947 public_key: "test_pubkey".into(),
948 supported_networks: Some(vec![X402Network::SetChain]),
949 supported_assets: Some(vec![X402Asset::Usdc]),
950 a2a_skills: Some(vec![A2ASkill::Sell, A2ASkill::Quote]),
951 endpoint_url: Some("https://api.example.com".into()),
952 ..Default::default()
953 })
954 .unwrap();
955
956 assert_eq!(card.name, "Test Agent");
957 assert_eq!(card.wallet_address, "0xAgent123");
958 assert!(card.active);
959 assert_eq!(card.trust_level, TrustLevel::Standard);
960 }
961
962 #[test]
963 fn test_discover_agents() {
964 let commerce = setup_commerce();
965
966 commerce
968 .x402()
969 .register_agent(CreateAgentCard {
970 name: "Seller 1".into(),
971 wallet_address: "0xSeller1".into(),
972 public_key: "pk1".into(),
973 supported_networks: Some(vec![X402Network::SetChain]),
974 supported_assets: Some(vec![X402Asset::Usdc]),
975 a2a_skills: Some(vec![A2ASkill::Sell]),
976 ..Default::default()
977 })
978 .unwrap();
979
980 commerce
981 .x402()
982 .register_agent(CreateAgentCard {
983 name: "Buyer 1".into(),
984 wallet_address: "0xBuyer1".into(),
985 public_key: "pk2".into(),
986 supported_networks: Some(vec![X402Network::Base]),
987 supported_assets: Some(vec![X402Asset::Usdc]),
988 a2a_skills: Some(vec![A2ASkill::Buy]),
989 ..Default::default()
990 })
991 .unwrap();
992
993 let set_chain_agents =
995 commerce.x402().discover_agents(Some(X402Network::SetChain), None, None, None).unwrap();
996
997 assert_eq!(set_chain_agents.len(), 1);
998 assert_eq!(set_chain_agents[0].name, "Seller 1");
999 }
1000
1001 #[test]
1002 fn test_verify_agent() {
1003 let commerce = setup_commerce();
1004
1005 let card = commerce
1006 .x402()
1007 .register_agent(CreateAgentCard {
1008 name: "Verified Agent".into(),
1009 wallet_address: "0xVerified".into(),
1010 public_key: "pk".into(),
1011 supported_networks: Some(vec![X402Network::SetChain]),
1012 supported_assets: Some(vec![X402Asset::Usdc]),
1013 ..Default::default()
1014 })
1015 .unwrap();
1016
1017 assert_eq!(card.trust_level, TrustLevel::Standard);
1018
1019 let verified = commerce.x402().verify_agent(card.id).unwrap();
1020 assert_eq!(verified.trust_level, TrustLevel::Verified);
1021 }
1022
1023 #[test]
1024 fn test_create_and_track_cart_payment() {
1025 let commerce = setup_commerce();
1026 let cart_id = Uuid::new_v4();
1027
1028 let intent = commerce
1029 .x402()
1030 .create_cart_payment(
1031 cart_id,
1032 "0xPayerCart",
1033 "0xPayeeCart",
1034 rust_decimal_macros::dec!(12.50),
1035 X402Network::SetChain,
1036 X402Asset::Usdc,
1037 )
1038 .unwrap();
1039
1040 assert_eq!(intent.cart_id, Some(cart_id));
1041 assert_eq!(intent.status, X402IntentStatus::Created);
1042
1043 let intents = commerce.x402().intents_for_cart(cart_id).unwrap();
1044 assert_eq!(intents.len(), 1);
1045 assert_eq!(intents[0].id, intent.id);
1046
1047 let active = commerce.x402().active_intent_for_cart(cart_id).unwrap();
1048 assert!(active.is_some());
1049 assert_eq!(active.expect("active intent").id, intent.id);
1050 }
1051
1052 #[test]
1053 fn test_a2a_quote_and_purchase_flow() {
1054 let commerce = setup_commerce();
1055
1056 let seller = commerce
1057 .x402()
1058 .register_agent(CreateAgentCard {
1059 name: "A2A Seller".into(),
1060 wallet_address: "0xSellerA2A".into(),
1061 public_key: "seller_pub".into(),
1062 supported_networks: Some(vec![X402Network::SetChain]),
1063 supported_assets: Some(vec![X402Asset::Usdc]),
1064 a2a_skills: Some(vec![A2ASkill::Sell]),
1065 trust_level: Some(TrustLevel::Verified),
1066 endpoint_url: Some("https://agent.example.com/".into()),
1067 ..Default::default()
1068 })
1069 .unwrap();
1070
1071 let buyer_id = Uuid::new_v4();
1072 let quote = commerce
1073 .x402()
1074 .create_quote(CreateA2AQuote {
1075 buyer_agent_id: buyer_id,
1076 seller_agent_id: seller.id,
1077 items: vec![QuotedItem {
1078 line_number: 1,
1079 sku: Some("SKU-1".to_string()),
1080 name: "Service Plan".to_string(),
1081 quantity: 1,
1082 unit_price: rust_decimal_macros::dec!(19.99),
1083 total: rust_decimal_macros::dec!(19.99),
1084 availability: ItemAvailability::InStock,
1085 lead_time_days: Some(1),
1086 }],
1087 subtotal: rust_decimal_macros::dec!(19.99),
1088 total: rust_decimal_macros::dec!(19.99),
1089 currency: Some(CurrencyCode::USD),
1090 tax_amount: Some(rust_decimal::Decimal::ZERO),
1091 shipping_amount: Some(rust_decimal::Decimal::ZERO),
1092 discount_amount: Some(rust_decimal::Decimal::ZERO),
1093 valid_until: chrono::Utc::now() + chrono::Duration::hours(1),
1094 payment_network: Some(X402Network::SetChain),
1095 payment_asset: Some(X402Asset::Usdc),
1096 notes: Some("unit test quote".into()),
1097 ..Default::default()
1098 })
1099 .unwrap();
1100
1101 assert_eq!(quote.status, QuoteStatus::Pending);
1102
1103 let quoted = commerce.x402().update_quote_status(quote.id, QuoteStatus::Quoted).unwrap();
1104 assert_eq!(quoted.status, QuoteStatus::Quoted);
1105
1106 let no_op_quote =
1107 commerce.x402().update_quote_status(quote.id, QuoteStatus::Quoted).unwrap();
1108 assert_eq!(no_op_quote.status, QuoteStatus::Quoted);
1109
1110 let purchase = commerce
1111 .x402()
1112 .create_purchase(CreateA2APurchase {
1113 buyer_agent_id: buyer_id,
1114 seller_agent_id: seller.id,
1115 quote_id: Some(quoted.id),
1116 items: quoted.items,
1117 total: quoted.total,
1118 currency: Some(quoted.currency),
1119 fulfillment_type: Some("digital".to_string()),
1120 notes: Some("unit test purchase".into()),
1121 metadata: None,
1122 payment_intent_id: None,
1123 })
1124 .unwrap();
1125
1126 assert_eq!(purchase.status, PurchaseStatus::Initiated);
1127 assert_eq!(purchase.quote_id, Some(quoted.id));
1128
1129 let payment_pending = commerce
1130 .x402()
1131 .update_purchase_status(purchase.id, PurchaseStatus::PaymentPending)
1132 .unwrap();
1133 assert_eq!(payment_pending.status, PurchaseStatus::PaymentPending);
1134
1135 let shipped =
1136 commerce.x402().update_purchase_status(purchase.id, PurchaseStatus::Shipped).unwrap();
1137 assert_eq!(shipped.status, PurchaseStatus::Shipped);
1138
1139 let refreshed_quote = commerce.x402().get_quote(quote.id).unwrap();
1140 assert!(
1141 matches!(refreshed_quote.as_ref(), Some(value) if value.status == QuoteStatus::Purchased)
1142 );
1143
1144 let listed_quotes = commerce
1145 .x402()
1146 .list_quotes(SkillQuoteFilter { buyer_agent_id: Some(buyer_id), ..Default::default() })
1147 .unwrap();
1148 assert!(listed_quotes.iter().any(|item| item.id == quoted.id));
1149
1150 let quote_by_number = commerce
1151 .x402()
1152 .get_quote_by_number("ed.quote_number)
1153 .unwrap()
1154 .expect("quote by number");
1155 assert_eq!(quote_by_number.id, quoted.id);
1156
1157 let counted_quotes = commerce
1158 .x402()
1159 .count_quotes(SkillQuoteFilter {
1160 seller_agent_id: Some(seller.id),
1161 ..Default::default()
1162 })
1163 .unwrap();
1164 assert!(counted_quotes >= 1);
1165
1166 let completed = commerce
1167 .x402()
1168 .confirm_delivery(purchase.id, "delivery_signature", Some(5), Some("good"))
1169 .unwrap();
1170 assert_eq!(completed.status, PurchaseStatus::Completed);
1171 assert_eq!(
1172 completed.delivery_confirmation_signature,
1173 Some("delivery_signature".to_string())
1174 );
1175
1176 let no_op_purchase =
1177 commerce.x402().update_purchase_status(purchase.id, PurchaseStatus::Completed).unwrap();
1178 assert_eq!(no_op_purchase.status, PurchaseStatus::Completed);
1179
1180 let listed_purchases = commerce
1181 .x402()
1182 .list_purchases(A2APurchaseFilter {
1183 buyer_agent_id: Some(buyer_id),
1184 ..Default::default()
1185 })
1186 .unwrap();
1187 assert!(listed_purchases.iter().any(|item| item.id == purchase.id));
1188
1189 let counted_purchases = commerce
1190 .x402()
1191 .count_purchases(A2APurchaseFilter {
1192 buyer_agent_id: Some(buyer_id),
1193 ..Default::default()
1194 })
1195 .unwrap();
1196 assert!(counted_purchases >= 1);
1197 }
1198
1199 #[test]
1200 fn test_a2a_quote_and_purchase_state_guards() {
1201 let commerce = setup_commerce();
1202
1203 let seller = commerce
1204 .x402()
1205 .register_agent(CreateAgentCard {
1206 name: "A2A Guard Seller".into(),
1207 wallet_address: "0xSellerA2AGuard".into(),
1208 public_key: "seller_guard_pub".into(),
1209 supported_networks: Some(vec![X402Network::SetChain]),
1210 supported_assets: Some(vec![X402Asset::Usdc]),
1211 a2a_skills: Some(vec![A2ASkill::Sell]),
1212 trust_level: Some(TrustLevel::Verified),
1213 endpoint_url: Some("https://agent.example.com/guard".into()),
1214 ..Default::default()
1215 })
1216 .unwrap();
1217
1218 let other_seller = commerce
1219 .x402()
1220 .register_agent(CreateAgentCard {
1221 name: "A2A Wrong Seller".into(),
1222 wallet_address: "0xSellerWrong".into(),
1223 public_key: "wrong_seller_pub".into(),
1224 supported_networks: Some(vec![X402Network::SetChain]),
1225 supported_assets: Some(vec![X402Asset::Usdc]),
1226 a2a_skills: Some(vec![A2ASkill::Sell]),
1227 ..Default::default()
1228 })
1229 .unwrap();
1230
1231 let buyer_id = Uuid::new_v4();
1232 let quote = commerce
1233 .x402()
1234 .create_quote(CreateA2AQuote {
1235 buyer_agent_id: buyer_id,
1236 seller_agent_id: seller.id,
1237 items: vec![QuotedItem {
1238 line_number: 1,
1239 sku: Some("SKU-2".to_string()),
1240 name: "Guarded service".to_string(),
1241 quantity: 1,
1242 unit_price: rust_decimal_macros::dec!(30.00),
1243 total: rust_decimal_macros::dec!(30.00),
1244 availability: ItemAvailability::InStock,
1245 lead_time_days: Some(2),
1246 }],
1247 subtotal: rust_decimal_macros::dec!(30.00),
1248 total: rust_decimal_macros::dec!(30.00),
1249 currency: Some(CurrencyCode::USD),
1250 tax_amount: Some(rust_decimal::Decimal::ZERO),
1251 shipping_amount: Some(rust_decimal::Decimal::ZERO),
1252 discount_amount: Some(rust_decimal::Decimal::ZERO),
1253 valid_until: chrono::Utc::now() + chrono::Duration::hours(1),
1254 payment_network: Some(X402Network::SetChain),
1255 payment_asset: Some(X402Asset::Usdc),
1256 notes: Some("guard quote".into()),
1257 ..Default::default()
1258 })
1259 .unwrap();
1260
1261 assert!(commerce.x402().update_quote_status(quote.id, QuoteStatus::Accepted).is_err());
1262
1263 let quoted = commerce.x402().update_quote_status(quote.id, QuoteStatus::Quoted).unwrap();
1264
1265 assert!(
1266 commerce
1267 .x402()
1268 .create_purchase(CreateA2APurchase {
1269 buyer_agent_id: buyer_id,
1270 seller_agent_id: other_seller.id,
1271 quote_id: Some(quoted.id),
1272 items: quoted.items.clone(),
1273 total: quoted.total,
1274 currency: Some(CurrencyCode::USD),
1275 fulfillment_type: Some("digital".to_string()),
1276 notes: Some("mismatched seller".into()),
1277 metadata: None,
1278 payment_intent_id: None,
1279 })
1280 .is_err()
1281 );
1282
1283 assert!(
1284 commerce
1285 .x402()
1286 .create_purchase(CreateA2APurchase {
1287 buyer_agent_id: buyer_id,
1288 seller_agent_id: seller.id,
1289 quote_id: Some(quoted.id),
1290 items: quoted.items.clone(),
1291 total: quoted.total,
1292 currency: Some(CurrencyCode::EUR),
1293 fulfillment_type: Some("digital".to_string()),
1294 notes: Some("mismatched currency".into()),
1295 metadata: None,
1296 payment_intent_id: None,
1297 })
1298 .is_err()
1299 );
1300
1301 assert!(
1302 commerce
1303 .x402()
1304 .create_purchase(CreateA2APurchase {
1305 buyer_agent_id: buyer_id,
1306 seller_agent_id: seller.id,
1307 quote_id: Some(quoted.id),
1308 items: quoted.items.clone(),
1309 total: quoted.total + rust_decimal::Decimal::ONE,
1310 currency: Some(CurrencyCode::USD),
1311 fulfillment_type: Some("digital".to_string()),
1312 notes: Some("mismatched total".into()),
1313 metadata: None,
1314 payment_intent_id: None,
1315 })
1316 .is_err()
1317 );
1318
1319 let purchase = commerce
1320 .x402()
1321 .create_purchase(CreateA2APurchase {
1322 buyer_agent_id: buyer_id,
1323 seller_agent_id: seller.id,
1324 quote_id: Some(quoted.id),
1325 items: quoted.items,
1326 total: quoted.total,
1327 currency: Some(quoted.currency),
1328 fulfillment_type: Some("digital".to_string()),
1329 notes: Some("valid purchase".into()),
1330 metadata: None,
1331 payment_intent_id: None,
1332 })
1333 .unwrap();
1334
1335 assert_eq!(purchase.status, PurchaseStatus::Initiated);
1336
1337 assert!(
1338 commerce.x402().update_purchase_status(purchase.id, PurchaseStatus::Completed).is_err()
1339 );
1340
1341 assert!(
1342 commerce
1343 .x402()
1344 .confirm_delivery(purchase.id, "delivery_signature", Some(5), Some("blocked"))
1345 .is_err()
1346 );
1347 }
1348
1349 #[test]
1350 fn test_a2a_purchase_rejected_for_expired_quote() {
1351 let commerce = setup_commerce();
1352
1353 let seller = commerce
1354 .x402()
1355 .register_agent(CreateAgentCard {
1356 name: "Expired Quote Seller".into(),
1357 wallet_address: "0xSellerA2AExpired".into(),
1358 public_key: "expired_seller_pub".into(),
1359 supported_networks: Some(vec![X402Network::SetChain]),
1360 supported_assets: Some(vec![X402Asset::Usdc]),
1361 a2a_skills: Some(vec![A2ASkill::Sell]),
1362 trust_level: Some(TrustLevel::Verified),
1363 endpoint_url: Some("https://agent.example.com/".into()),
1364 ..Default::default()
1365 })
1366 .unwrap();
1367
1368 let buyer_id = Uuid::new_v4();
1369 assert!(
1370 commerce
1371 .x402()
1372 .create_quote(CreateA2AQuote {
1373 buyer_agent_id: buyer_id,
1374 seller_agent_id: seller.id,
1375 items: vec![QuotedItem {
1376 line_number: 1,
1377 sku: Some("SKU-EX".to_string()),
1378 name: "Expired service".to_string(),
1379 quantity: 1,
1380 unit_price: rust_decimal_macros::dec!(15.00),
1381 total: rust_decimal_macros::dec!(15.00),
1382 availability: ItemAvailability::InStock,
1383 lead_time_days: Some(1),
1384 }],
1385 subtotal: rust_decimal_macros::dec!(15.00),
1386 total: rust_decimal_macros::dec!(15.00),
1387 currency: Some(CurrencyCode::USD),
1388 tax_amount: Some(rust_decimal::Decimal::ZERO),
1389 shipping_amount: Some(rust_decimal::Decimal::ZERO),
1390 discount_amount: Some(rust_decimal::Decimal::ZERO),
1391 valid_until: chrono::Utc::now() - chrono::Duration::hours(1),
1392 payment_network: Some(X402Network::SetChain),
1393 payment_asset: Some(X402Asset::Usdc),
1394 notes: Some("expired quote".into()),
1395 ..Default::default()
1396 })
1397 .is_err()
1398 );
1399
1400 let quote = commerce
1401 .x402()
1402 .create_quote(CreateA2AQuote {
1403 buyer_agent_id: buyer_id,
1404 seller_agent_id: seller.id,
1405 items: vec![QuotedItem {
1406 line_number: 1,
1407 sku: Some("SKU-EX".to_string()),
1408 name: "Expired service".to_string(),
1409 quantity: 1,
1410 unit_price: rust_decimal_macros::dec!(15.00),
1411 total: rust_decimal_macros::dec!(15.00),
1412 availability: ItemAvailability::InStock,
1413 lead_time_days: Some(1),
1414 }],
1415 subtotal: rust_decimal_macros::dec!(15.00),
1416 total: rust_decimal_macros::dec!(15.00),
1417 currency: Some(CurrencyCode::USD),
1418 tax_amount: Some(rust_decimal::Decimal::ZERO),
1419 shipping_amount: Some(rust_decimal::Decimal::ZERO),
1420 discount_amount: Some(rust_decimal::Decimal::ZERO),
1421 valid_until: chrono::Utc::now() + chrono::Duration::hours(1),
1422 payment_network: Some(X402Network::SetChain),
1423 payment_asset: Some(X402Asset::Usdc),
1424 notes: Some("expired quote".into()),
1425 ..Default::default()
1426 })
1427 .unwrap();
1428
1429 let quoted = commerce.x402().update_quote_status(quote.id, QuoteStatus::Quoted).unwrap();
1430 let expired = commerce.x402().update_quote_status(quoted.id, QuoteStatus::Expired).unwrap();
1431 assert_eq!(quoted.status, QuoteStatus::Quoted);
1432 assert_eq!(expired.status, QuoteStatus::Expired);
1433
1434 assert!(
1435 commerce
1436 .x402()
1437 .create_purchase(CreateA2APurchase {
1438 buyer_agent_id: buyer_id,
1439 seller_agent_id: seller.id,
1440 quote_id: Some(expired.id),
1441 items: expired.items,
1442 total: expired.total,
1443 currency: Some(expired.currency),
1444 fulfillment_type: Some("digital".to_string()),
1445 notes: Some("expired quote blocked".into()),
1446 metadata: None,
1447 payment_intent_id: None,
1448 })
1449 .is_err()
1450 );
1451 }
1452
1453 #[test]
1454 fn test_a2a_purchase_state_lifecycle_controls() {
1455 let commerce = setup_commerce();
1456
1457 let seller = commerce
1458 .x402()
1459 .register_agent(CreateAgentCard {
1460 name: "A2A Lifecycle Seller".into(),
1461 wallet_address: "0xSellerLifecycle".into(),
1462 public_key: "lifecycle_pub".into(),
1463 supported_networks: Some(vec![X402Network::SetChain]),
1464 supported_assets: Some(vec![X402Asset::Usdc]),
1465 a2a_skills: Some(vec![A2ASkill::Sell]),
1466 trust_level: Some(TrustLevel::Verified),
1467 endpoint_url: Some("https://agent.example.com/lifecycle".into()),
1468 ..Default::default()
1469 })
1470 .unwrap();
1471
1472 let buyer_id = Uuid::new_v4();
1473 let now = chrono::Utc::now();
1474
1475 let make_quote = |buyer_id: Uuid, seller_id: Uuid| CreateA2AQuote {
1476 buyer_agent_id: buyer_id,
1477 seller_agent_id: seller_id,
1478 items: vec![QuotedItem {
1479 line_number: 1,
1480 sku: Some("SKU-LC-1".to_string()),
1481 name: "Lifecycle service".to_string(),
1482 quantity: 1,
1483 unit_price: rust_decimal_macros::dec!(12.00),
1484 total: rust_decimal_macros::dec!(12.00),
1485 availability: ItemAvailability::InStock,
1486 lead_time_days: Some(1),
1487 }],
1488 subtotal: rust_decimal_macros::dec!(12.00),
1489 tax_amount: Some(rust_decimal::Decimal::ZERO),
1490 shipping_amount: Some(rust_decimal::Decimal::ZERO),
1491 discount_amount: Some(rust_decimal::Decimal::ZERO),
1492 total: rust_decimal_macros::dec!(12.00),
1493 currency: Some(CurrencyCode::USD),
1494 payment_network: Some(X402Network::SetChain),
1495 payment_asset: Some(X402Asset::Usdc),
1496 shipping_address: None,
1497 valid_until: now + chrono::Duration::hours(1),
1498 notes: Some("lifecycle quote".to_string()),
1499 metadata: None,
1500 };
1501
1502 let cancelled_quote =
1503 commerce.x402().create_quote(make_quote(buyer_id, seller.id)).unwrap();
1504 let cancelled_quote =
1505 commerce.x402().update_quote_status(cancelled_quote.id, QuoteStatus::Quoted).unwrap();
1506
1507 let cancelled_purchase = commerce
1508 .x402()
1509 .create_purchase(CreateA2APurchase {
1510 buyer_agent_id: buyer_id,
1511 seller_agent_id: seller.id,
1512 quote_id: Some(cancelled_quote.id),
1513 items: cancelled_quote.items.clone(),
1514 total: cancelled_quote.total,
1515 currency: Some(cancelled_quote.currency),
1516 fulfillment_type: Some("digital".to_string()),
1517 notes: Some("cancel path".into()),
1518 metadata: None,
1519 payment_intent_id: None,
1520 })
1521 .unwrap();
1522
1523 let cancelled = commerce
1524 .x402()
1525 .update_purchase_status(cancelled_purchase.id, PurchaseStatus::Cancelled)
1526 .unwrap();
1527 assert_eq!(cancelled.status, PurchaseStatus::Cancelled);
1528
1529 let no_op = commerce
1530 .x402()
1531 .update_purchase_status(cancelled_purchase.id, PurchaseStatus::Cancelled)
1532 .unwrap();
1533 assert_eq!(no_op.status, PurchaseStatus::Cancelled);
1534
1535 assert!(
1536 commerce
1537 .x402()
1538 .update_purchase_status(cancelled_purchase.id, PurchaseStatus::PaymentPending)
1539 .is_err()
1540 );
1541 assert!(
1542 commerce
1543 .x402()
1544 .confirm_delivery(cancelled_purchase.id, "sig", Some(4), Some("should fail"))
1545 .is_err()
1546 );
1547
1548 let disputed_quote =
1549 commerce.x402().create_quote(make_quote(Uuid::new_v4(), seller.id)).unwrap();
1550 let disputed_quote =
1551 commerce.x402().update_quote_status(disputed_quote.id, QuoteStatus::Quoted).unwrap();
1552
1553 let disputed_purchase = commerce
1554 .x402()
1555 .create_purchase(CreateA2APurchase {
1556 buyer_agent_id: disputed_quote.buyer_agent_id,
1557 seller_agent_id: seller.id,
1558 quote_id: Some(disputed_quote.id),
1559 items: disputed_quote.items.clone(),
1560 total: disputed_quote.total,
1561 currency: Some(disputed_quote.currency),
1562 fulfillment_type: Some("digital".to_string()),
1563 notes: Some("dispute path".into()),
1564 metadata: None,
1565 payment_intent_id: None,
1566 })
1567 .unwrap();
1568
1569 let disputed = commerce
1570 .x402()
1571 .update_purchase_status(disputed_purchase.id, PurchaseStatus::Disputed)
1572 .unwrap();
1573 assert_eq!(disputed.status, PurchaseStatus::Disputed);
1574
1575 assert!(
1576 commerce
1577 .x402()
1578 .update_purchase_status(disputed_purchase.id, PurchaseStatus::Shipped)
1579 .is_err()
1580 );
1581 assert!(
1582 commerce
1583 .x402()
1584 .confirm_delivery(disputed_purchase.id, "sig", Some(4), Some("blocked"))
1585 .is_err()
1586 );
1587 assert!(
1588 commerce
1589 .x402()
1590 .update_purchase_status(disputed_purchase.id, PurchaseStatus::Disputed)
1591 .is_ok()
1592 );
1593 }
1594}