tap_msg/message/
types.rs

1//! TAP message types and structures.
2//!
3//! This module defines the structure of all TAP message types according to the specification.
4
5extern crate serde;
6extern crate serde_json;
7
8use didcomm::Message;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12use tap_caip::AssetId;
13
14use crate::error::{Error, Result};
15use crate::impl_tap_message;
16use crate::message::policy::Policy;
17use crate::message::tap_message_trait::{Connectable, TapMessageBody};
18use crate::message::RequireProofOfControl;
19use chrono::Utc;
20
21/// Participant in a transfer (TAIP-3, TAIP-11).
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
23#[allow(non_snake_case)]
24pub struct Participant {
25    /// DID of the participant.
26    #[serde(default)]
27    pub id: String,
28
29    /// Role of the participant (optional).
30    #[serde(skip_serializing_if = "Option::is_none")]
31    #[serde(default)]
32    pub role: Option<String>,
33
34    /// Policies of the participant according to TAIP-7 (optional).
35    #[serde(skip_serializing_if = "Option::is_none")]
36    #[serde(default)]
37    pub policies: Option<Vec<Policy>>,
38
39    /// Legal Entity Identifier (LEI) code of the participant (optional).
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub leiCode: Option<String>,
42}
43
44impl Participant {
45    /// Create a new participant with the given DID.
46    pub fn new(id: &str) -> Self {
47        Self {
48            id: id.to_string(),
49            role: None,
50            policies: None,
51            leiCode: None,
52        }
53    }
54
55    /// Create a new participant with the given DID and role.
56    pub fn with_role(id: &str, role: &str) -> Self {
57        Self {
58            id: id.to_string(),
59            role: Some(role.to_string()),
60            policies: None,
61            leiCode: None,
62        }
63    }
64}
65
66/// Attachment data for a TAP message.
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
68pub struct AttachmentData {
69    /// Base64-encoded data.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub base64: Option<String>,
72
73    /// JSON data.
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub json: Option<serde_json::Value>,
76}
77
78/// Attachment for a TAP message.
79#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
80pub struct Attachment {
81    /// ID of the attachment.
82    pub id: String,
83
84    /// Media type of the attachment.
85    #[serde(rename = "media_type")]
86    pub media_type: String,
87
88    /// Attachment data (optional).
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub data: Option<AttachmentData>,
91}
92
93/// Attachment format enumeration.
94#[derive(Debug, Clone, Serialize, Deserialize)]
95#[serde(rename_all = "lowercase")]
96pub enum AttachmentFormat {
97    /// Base64-encoded data.
98    Base64,
99
100    /// JSON data.
101    Json(serde_json::Value),
102
103    /// External link to data.
104    Links { links: Vec<String> },
105}
106
107/// Transfer message body (TAIP-3).
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct Transfer {
110    /// Network asset identifier (CAIP-19 format).
111    pub asset: AssetId,
112
113    /// Originator information.
114    #[serde(rename = "originator")]
115    pub originator: Participant,
116
117    /// Beneficiary information (optional).
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub beneficiary: Option<Participant>,
120
121    /// Transfer amount.
122    pub amount: String,
123
124    /// Agents involved in the transfer.
125    #[serde(default)]
126    pub agents: Vec<Participant>,
127
128    /// Memo for the transfer (optional).
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub memo: Option<String>,
131
132    /// Settlement identifier (optional).
133    #[serde(rename = "settlementId", skip_serializing_if = "Option::is_none")]
134    pub settlement_id: Option<String>,
135
136    /// Transaction identifier (not stored in the struct but accessible via the TapMessage trait).
137    #[serde(skip)]
138    pub transaction_id: String,
139
140    /// Additional metadata for the transfer.
141    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
142    pub metadata: HashMap<String, serde_json::Value>,
143}
144
145impl Transfer {
146    /// Create a new Transfer
147    ///
148    /// # Example
149    /// ```
150    /// use tap_msg::message::Transfer;
151    /// use tap_caip::{AssetId, ChainId};
152    /// use tap_msg::message::Participant;
153    /// use std::collections::HashMap;
154    ///
155    /// // Create chain ID and asset ID
156    /// let chain_id = ChainId::new("eip155", "1").unwrap();
157    /// let asset = AssetId::new(chain_id, "erc20", "0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
158    ///
159    /// // Create participant
160    /// let originator = Participant {
161    ///     id: "did:example:alice".to_string(),
162    ///     role: Some("originator".to_string()),
163    ///     policies: None,
164    ///     leiCode: None,
165    /// };
166    ///
167    /// // Create a transfer with required fields
168    /// let transfer = Transfer::builder()
169    ///     .asset(asset)
170    ///     .originator(originator)
171    ///     .amount("100".to_string())
172    ///     .build();
173    /// ```
174    pub fn builder() -> TransferBuilder {
175        TransferBuilder::default()
176    }
177
178    /// Generates a unique message ID for authorization, rejection, or settlement
179    pub fn message_id(&self) -> String {
180        uuid::Uuid::new_v4().to_string()
181    }
182
183    /// Validate the Transfer
184    pub fn validate(&self) -> Result<()> {
185        // CAIP-19 asset ID is validated by the AssetId type
186        // Validate asset
187        if self.asset.namespace().is_empty() || self.asset.reference().is_empty() {
188            return Err(Error::Validation("Asset ID is invalid".to_string()));
189        }
190
191        // Validate originator
192        if self.originator.id.is_empty() {
193            return Err(Error::Validation("Originator ID is required".to_string()));
194        }
195
196        // Validate amount
197        if self.amount.is_empty() {
198            return Err(Error::Validation("Amount is required".to_string()));
199        }
200
201        // Validate amount is a positive number
202        match self.amount.parse::<f64>() {
203            Ok(amount) if amount <= 0.0 => {
204                return Err(Error::Validation("Amount must be positive".to_string()));
205            }
206            Err(_) => {
207                return Err(Error::Validation(
208                    "Amount must be a valid number".to_string(),
209                ));
210            }
211            _ => {}
212        }
213
214        // Validate agents (if any are defined)
215        for agent in &self.agents {
216            if agent.id.is_empty() {
217                return Err(Error::Validation("Agent ID cannot be empty".to_string()));
218            }
219        }
220
221        Ok(())
222    }
223}
224
225/// Builder for creating Transfer objects in a more idiomatic way
226#[derive(Default)]
227pub struct TransferBuilder {
228    asset: Option<AssetId>,
229    originator: Option<Participant>,
230    amount: Option<String>,
231    beneficiary: Option<Participant>,
232    settlement_id: Option<String>,
233    memo: Option<String>,
234    transaction_id: Option<String>,
235    agents: Vec<Participant>,
236    metadata: HashMap<String, serde_json::Value>,
237}
238
239impl TransferBuilder {
240    /// Set the asset for this transfer
241    pub fn asset(mut self, asset: AssetId) -> Self {
242        self.asset = Some(asset);
243        self
244    }
245
246    /// Set the originator for this transfer
247    pub fn originator(mut self, originator: Participant) -> Self {
248        self.originator = Some(originator);
249        self
250    }
251
252    /// Set the amount for this transfer
253    pub fn amount(mut self, amount: String) -> Self {
254        self.amount = Some(amount);
255        self
256    }
257
258    /// Set the beneficiary for this transfer
259    pub fn beneficiary(mut self, beneficiary: Participant) -> Self {
260        self.beneficiary = Some(beneficiary);
261        self
262    }
263
264    /// Set the settlement ID for this transfer
265    pub fn settlement_id(mut self, settlement_id: String) -> Self {
266        self.settlement_id = Some(settlement_id);
267        self
268    }
269
270    /// Set the memo for this transfer
271    pub fn memo(mut self, memo: String) -> Self {
272        self.memo = Some(memo);
273        self
274    }
275
276    /// Set the transaction ID for this transfer
277    pub fn transaction_id(mut self, transaction_id: String) -> Self {
278        self.transaction_id = Some(transaction_id);
279        self
280    }
281
282    /// Add an agent to this transfer
283    pub fn add_agent(mut self, agent: Participant) -> Self {
284        self.agents.push(agent);
285        self
286    }
287
288    /// Set all agents for this transfer
289    pub fn agents(mut self, agents: Vec<Participant>) -> Self {
290        self.agents = agents;
291        self
292    }
293
294    /// Add a metadata field
295    pub fn add_metadata(mut self, key: String, value: serde_json::Value) -> Self {
296        self.metadata.insert(key, value);
297        self
298    }
299
300    /// Set all metadata for this transfer
301    pub fn metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
302        self.metadata = metadata;
303        self
304    }
305
306    /// Build the Transfer object
307    ///
308    /// # Panics
309    ///
310    /// Panics if required fields (asset, originator, amount) are not set
311    pub fn build(self) -> Transfer {
312        Transfer {
313            asset: self.asset.expect("Asset is required"),
314            originator: self.originator.expect("Originator is required"),
315            amount: self.amount.expect("Amount is required"),
316            beneficiary: self.beneficiary,
317            settlement_id: self.settlement_id,
318            memo: self.memo,
319            transaction_id: self
320                .transaction_id
321                .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
322            agents: self.agents,
323            metadata: self.metadata,
324        }
325    }
326
327    /// Try to build the Transfer object, returning an error if required fields are missing
328    pub fn try_build(self) -> Result<Transfer> {
329        let asset = self
330            .asset
331            .ok_or_else(|| Error::Validation("Asset is required".to_string()))?;
332        let originator = self
333            .originator
334            .ok_or_else(|| Error::Validation("Originator is required".to_string()))?;
335        let amount = self
336            .amount
337            .ok_or_else(|| Error::Validation("Amount is required".to_string()))?;
338
339        let transfer = Transfer {
340            transaction_id: self
341                .transaction_id
342                .unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
343            asset,
344            originator,
345            amount,
346            beneficiary: self.beneficiary,
347            settlement_id: self.settlement_id,
348            memo: self.memo,
349            agents: self.agents,
350            metadata: self.metadata,
351        };
352
353        // Validate the created transfer
354        transfer.validate()?;
355
356        Ok(transfer)
357    }
358}
359
360impl Connectable for Transfer {
361    fn with_connection(&mut self, connect_id: &str) -> &mut Self {
362        // Store the connect_id in metadata
363        self.metadata.insert(
364            "connect_id".to_string(),
365            serde_json::Value::String(connect_id.to_string()),
366        );
367        self
368    }
369
370    fn has_connection(&self) -> bool {
371        self.metadata.contains_key("connect_id")
372    }
373
374    fn connection_id(&self) -> Option<&str> {
375        self.metadata.get("connect_id").and_then(|v| v.as_str())
376    }
377}
378
379impl TapMessageBody for Transfer {
380    fn message_type() -> &'static str {
381        "https://tap.rsvp/schema/1.0#transfer"
382    }
383
384    fn validate(&self) -> Result<()> {
385        // Validate asset
386        if self.asset.namespace().is_empty() || self.asset.reference().is_empty() {
387            return Err(Error::Validation("Asset ID is invalid".to_string()));
388        }
389
390        // Validate originator
391        if self.originator.id.is_empty() {
392            return Err(Error::Validation("Originator ID is required".to_string()));
393        }
394
395        // Validate amount
396        if self.amount.is_empty() {
397            return Err(Error::Validation("Amount is required".to_string()));
398        }
399
400        // Validate amount is a positive number
401        match self.amount.parse::<f64>() {
402            Ok(amount) if amount <= 0.0 => {
403                return Err(Error::Validation("Amount must be positive".to_string()));
404            }
405            Err(_) => {
406                return Err(Error::Validation(
407                    "Amount must be a valid number".to_string(),
408                ));
409            }
410            _ => {}
411        }
412
413        Ok(())
414    }
415
416    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
417        // Serialize the Transfer to a JSON value
418        let mut body_json =
419            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
420
421        // Ensure the @type field is correctly set in the body
422        if let Some(body_obj) = body_json.as_object_mut() {
423            body_obj.insert(
424                "@type".to_string(),
425                serde_json::Value::String(Self::message_type().to_string()),
426            );
427        }
428
429        // Extract agent DIDs directly from the message
430        let mut agent_dids = Vec::new();
431
432        // Add originator DID
433        agent_dids.push(self.originator.id.clone());
434
435        // Add beneficiary DID if present
436        if let Some(beneficiary) = &self.beneficiary {
437            agent_dids.push(beneficiary.id.clone());
438        }
439
440        // Add DIDs from agents array
441        for agent in &self.agents {
442            agent_dids.push(agent.id.clone());
443        }
444
445        // Remove duplicates
446        agent_dids.sort();
447        agent_dids.dedup();
448
449        // If from_did is provided, remove it from the recipients list to avoid sending to self
450        if let Some(from) = from_did {
451            agent_dids.retain(|did| did != from);
452        }
453
454        let now = Utc::now().timestamp() as u64;
455
456        // Get the connection ID if this message is connected to a previous message
457        let pthid = self
458            .connection_id()
459            .map(|connect_id| connect_id.to_string());
460
461        // Create a new Message with required fields
462        let message = Message {
463            id: uuid::Uuid::new_v4().to_string(),
464            typ: "application/didcomm-plain+json".to_string(),
465            type_: Self::message_type().to_string(),
466            body: body_json,
467            from: from_did.map(|s| s.to_string()),
468            to: Some(agent_dids),
469            thid: None,
470            pthid,
471            created_time: Some(now),
472            expires_time: None,
473            extra_headers: std::collections::HashMap::new(),
474            from_prior: None,
475            attachments: None,
476        };
477
478        Ok(message)
479    }
480
481    fn to_didcomm_with_route<'a, I>(&self, from: Option<&str>, to: I) -> Result<Message>
482    where
483        I: IntoIterator<Item = &'a str>,
484    {
485        // First create a message with the sender, automatically extracting agent DIDs
486        let mut message = self.to_didcomm(from)?;
487
488        // Override with explicitly provided recipients if any
489        let to_vec: Vec<String> = to.into_iter().map(String::from).collect();
490        if !to_vec.is_empty() {
491            message.to = Some(to_vec);
492        }
493
494        // Set the parent thread ID if this message is connected to a previous message
495        if let Some(connect_id) = self.connection_id() {
496            message.pthid = Some(connect_id.to_string());
497        }
498
499        Ok(message)
500    }
501}
502
503impl_tap_message!(Transfer);
504
505/// Request Presentation message body (TAIP-10).
506#[derive(Debug, Clone, Serialize, Deserialize)]
507pub struct RequestPresentation {
508    /// Transfer ID that this request is related to.
509    pub transaction_id: String,
510
511    /// Presentation definition identifier or URI.
512    pub presentation_definition: String,
513
514    /// Description of the request.
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub description: Option<String>,
517
518    /// Challenge to be included in the response.
519    pub challenge: String,
520
521    /// Whether the request is for the originator's information.
522    #[serde(skip_serializing_if = "Option::is_none")]
523    pub for_originator: Option<bool>,
524
525    /// Whether the request is for the beneficiary's information.
526    #[serde(skip_serializing_if = "Option::is_none")]
527    pub for_beneficiary: Option<bool>,
528
529    /// Note for the request.
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub note: Option<String>,
532
533    /// Additional metadata.
534    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
535    pub metadata: HashMap<String, serde_json::Value>,
536}
537
538/// Presentation message body (TAIP-8, TAIP-10).
539#[derive(Debug, Clone, Serialize, Deserialize)]
540pub struct Presentation {
541    /// Challenge from the request.
542    pub challenge: String,
543
544    /// Credential data.
545    pub credentials: Vec<serde_json::Value>,
546
547    /// Transfer ID that this presentation is related to (optional).
548    #[serde(skip_serializing_if = "Option::is_none")]
549    pub transaction_id: Option<String>,
550
551    /// Additional metadata.
552    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
553    pub metadata: HashMap<String, serde_json::Value>,
554}
555
556/// Authorize message body (TAIP-4).
557#[derive(Debug, Clone, Serialize, Deserialize)]
558pub struct Authorize {
559    /// ID of the transaction being authorized.
560    pub transaction_id: String,
561
562    /// Optional note.
563    #[serde(skip_serializing_if = "Option::is_none")]
564    pub note: Option<String>,
565}
566
567/// Reject message body (TAIP-4).
568#[derive(Debug, Clone, Serialize, Deserialize)]
569pub struct Reject {
570    /// ID of the transaction being rejected.
571    pub transaction_id: String,
572
573    /// Reason for rejection.
574    pub reason: String,
575}
576
577/// Settle message body (TAIP-4).
578#[derive(Debug, Clone, Serialize, Deserialize)]
579pub struct Settle {
580    /// ID of the transaction being settled.
581    pub transaction_id: String,
582
583    /// Settlement ID (CAIP-220 identifier of the underlying settlement transaction).
584    pub settlement_id: String,
585
586    /// Optional amount settled. If specified, must be less than or equal to the original amount.
587    #[serde(skip_serializing_if = "Option::is_none")]
588    pub amount: Option<String>,
589}
590
591/// Cancel message body (TAIP-4).
592#[derive(Debug, Clone, Serialize, Deserialize)]
593pub struct Cancel {
594    /// ID of the transfer being cancelled.
595    pub transaction_id: String,
596
597    /// Optional reason for cancellation.
598    #[serde(skip_serializing_if = "Option::is_none")]
599    pub reason: Option<String>,
600
601    /// Optional note.
602    #[serde(skip_serializing_if = "Option::is_none")]
603    pub note: Option<String>,
604}
605
606impl TapMessageBody for Cancel {
607    fn message_type() -> &'static str {
608        "https://tap.rsvp/schema/1.0#cancel"
609    }
610
611    fn validate(&self) -> Result<()> {
612        if self.transaction_id.is_empty() {
613            return Err(Error::Validation(
614                "Cancel message must have a transaction_id".into(),
615            ));
616        }
617        Ok(())
618    }
619
620    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
621        let msg_id = uuid::Uuid::new_v4().to_string();
622
623        let body_json = serde_json::to_value(self)
624            .map_err(|e| Error::SerializationError(format!("Failed to serialize Cancel: {}", e)))?;
625
626        Ok(Message {
627            id: msg_id,
628            typ: "application/didcomm-plain+json".to_string(),
629            type_: Self::message_type().to_string(),
630            body: body_json,
631            from: from_did.map(|s| s.to_string()),
632            to: None,
633            thid: Some(self.transaction_id.clone()),
634            pthid: None,
635            extra_headers: HashMap::new(),
636            attachments: None,
637            created_time: None,
638            expires_time: None,
639            from_prior: None,
640        })
641    }
642
643    fn from_didcomm(msg: &Message) -> Result<Self> {
644        if msg.type_ != Self::message_type() {
645            return Err(Error::InvalidMessageType(format!(
646                "Expected message type {}, got {}",
647                Self::message_type(),
648                msg.type_
649            )));
650        }
651
652        serde_json::from_value(msg.body.clone())
653            .map_err(|e| Error::SerializationError(format!("Failed to deserialize Cancel: {}", e)))
654    }
655}
656
657impl_tap_message!(Cancel);
658
659/// Revert message body (TAIP-4).
660#[derive(Debug, Clone, Serialize, Deserialize)]
661pub struct Revert {
662    /// ID of the transfer being reverted.
663    pub transaction_id: String,
664
665    /// Settlement address in CAIP-10 format to return the funds to.
666    pub settlement_address: String,
667
668    /// Reason for the reversal request.
669    pub reason: String,
670
671    /// Optional note.
672    #[serde(skip_serializing_if = "Option::is_none")]
673    pub note: Option<String>,
674}
675
676/// Add agents message body (TAIP-5).
677#[derive(Debug, Clone, Serialize, Deserialize)]
678pub struct AddAgents {
679    /// ID of the transaction to add agents to.
680    #[serde(rename = "transfer_id")]
681    pub transaction_id: String,
682
683    /// Agents to add.
684    pub agents: Vec<Participant>,
685}
686
687/// Replace agent message body (TAIP-5).
688///
689/// This message type allows replacing an agent with another agent in a transaction.
690#[derive(Debug, Clone, Serialize, Deserialize)]
691pub struct ReplaceAgent {
692    /// ID of the transaction to replace agent in.
693    #[serde(rename = "transfer_id")]
694    pub transaction_id: String,
695
696    /// DID of the original agent to replace.
697    pub original: String,
698
699    /// Replacement agent.
700    pub replacement: Participant,
701}
702
703/// Remove agent message body (TAIP-5).
704///
705/// This message type allows removing an agent from a transaction.
706#[derive(Debug, Clone, Serialize, Deserialize)]
707pub struct RemoveAgent {
708    /// ID of the transaction to remove agent from.
709    #[serde(rename = "transfer_id")]
710    pub transaction_id: String,
711
712    /// DID of the agent to remove.
713    pub agent: String,
714}
715
716/// ConfirmRelationship message body (TAIP-9).
717///
718/// This message type allows confirming a relationship between agents.
719#[derive(Debug, Clone, Serialize, Deserialize)]
720pub struct ConfirmRelationship {
721    /// ID of the transaction related to this message.
722    #[serde(rename = "transfer_id")]
723    pub transaction_id: String,
724
725    /// DID of the agent whose relationship is being confirmed.
726    pub agent_id: String,
727
728    /// DID of the entity that the agent acts on behalf of.
729    #[serde(rename = "for")]
730    pub for_id: String,
731
732    /// Role of the agent in the transaction (optional).
733    #[serde(skip_serializing_if = "Option::is_none")]
734    pub role: Option<String>,
735}
736
737impl ConfirmRelationship {
738    /// Creates a new ConfirmRelationship message body.
739    pub fn new(transaction_id: &str, agent_id: &str, for_id: &str, role: Option<String>) -> Self {
740        Self {
741            transaction_id: transaction_id.to_string(),
742            agent_id: agent_id.to_string(),
743            for_id: for_id.to_string(),
744            role,
745        }
746    }
747
748    /// Validates the ConfirmRelationship message body.
749    pub fn validate(&self) -> Result<()> {
750        if self.transaction_id.is_empty() {
751            return Err(Error::Validation(
752                "Transfer ID is required in ConfirmRelationship".to_string(),
753            ));
754        }
755
756        if self.agent_id.is_empty() {
757            return Err(Error::Validation(
758                "Agent ID is required in ConfirmRelationship".to_string(),
759            ));
760        }
761
762        if self.for_id.is_empty() {
763            return Err(Error::Validation(
764                "For ID is required in ConfirmRelationship".to_string(),
765            ));
766        }
767
768        Ok(())
769    }
770}
771
772impl TapMessageBody for ConfirmRelationship {
773    fn message_type() -> &'static str {
774        "https://tap.rsvp/schema/1.0#confirmrelationship"
775    }
776
777    fn validate(&self) -> Result<()> {
778        self.validate()
779    }
780
781    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
782        // 1. Serialize self to JSON value
783        let mut body_json =
784            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
785
786        // 2. Add/ensure '@type' field
787        if let Some(body_obj) = body_json.as_object_mut() {
788            body_obj.insert(
789                "@type".to_string(),
790                serde_json::Value::String(Self::message_type().to_string()),
791            );
792            // Note: serde handles #[serde(rename = "for")] automatically during serialization
793        }
794
795        // 3. Generate ID and timestamp
796        let id = uuid::Uuid::new_v4().to_string(); // Use new_v4 as per workspace UUID settings
797        let created_time = Utc::now().timestamp_millis() as u64;
798
799        // 4. Explicitly set the recipient using agent_id
800        let to = Some(vec![self.agent_id.clone()]);
801
802        // 5. Create the Message struct
803        let message = Message {
804            id,
805            typ: "application/didcomm-plain+json".to_string(), // Standard type
806            type_: Self::message_type().to_string(),
807            from: from_did.map(|s| s.to_string()),
808            to, // Use the explicitly set 'to' field
809            thid: Some(self.transaction_id.clone()),
810            pthid: None, // Parent Thread ID usually set later
811            created_time: Some(created_time),
812            expires_time: None,
813            extra_headers: std::collections::HashMap::new(),
814            from_prior: None,
815            body: body_json,
816            attachments: None,
817        };
818
819        Ok(message)
820    }
821
822    fn from_didcomm(message: &Message) -> Result<Self> {
823        let body = message
824            .body
825            .as_object()
826            .ok_or_else(|| Error::Validation("Message body is not a JSON object".to_string()))?;
827
828        let transfer_id = body
829            .get("transfer_id")
830            .and_then(|v| v.as_str())
831            .ok_or_else(|| Error::Validation("Missing or invalid transfer_id".to_string()))?;
832
833        let agent_id = body
834            .get("agent_id")
835            .and_then(|v| v.as_str())
836            .ok_or_else(|| Error::Validation("Missing or invalid agent_id".to_string()))?;
837
838        let for_id = body
839            .get("for")
840            .and_then(|v| v.as_str())
841            .ok_or_else(|| Error::Validation("Missing or invalid for".to_string()))?;
842
843        let role = body
844            .get("role")
845            .and_then(|v| v.as_str())
846            .map(ToString::to_string);
847
848        let confirm_relationship = Self {
849            transaction_id: transfer_id.to_string(),
850            agent_id: agent_id.to_string(),
851            for_id: for_id.to_string(),
852            role,
853        };
854
855        confirm_relationship.validate()?;
856
857        Ok(confirm_relationship)
858    }
859}
860
861impl_tap_message!(ConfirmRelationship);
862
863/// UpdateParty message body (TAIP-6).
864///
865/// This message type allows agents to update party information in a transaction.
866/// It enables a participant to modify their details or role within an existing transfer without
867/// creating a new transaction. This is particularly useful for situations where participant
868/// information changes during the lifecycle of a transaction.
869///
870/// # TAIP-6 Specification
871/// The UpdateParty message follows the TAIP-6 specification for updating party information
872/// in a TAP transaction. It includes JSON-LD compatibility with an optional @context field.
873///
874/// # Example
875/// ```
876/// use tap_msg::message::types::UpdateParty;
877/// use tap_msg::Participant;
878/// use std::collections::HashMap;
879///
880/// // Create a participant with updated information
881/// let updated_participant = Participant {
882///     id: "did:key:z6MkpDYxrwJw5WoD1o4YVfthJJgZfxrECpW6Da6QCWagRHLx".to_string(),
883///     role: Some("new_role".to_string()),
884///     policies: None,
885///     leiCode: None,
886/// };
887///
888/// // Create an UpdateParty message
889/// let update_party = UpdateParty::new(
890///     "transfer-123",
891///     "did:key:z6MkpDYxrwJw5WoD1o4YVfthJJgZfxrECpW6Da6QCWagRHLx",
892///     updated_participant
893/// );
894///
895/// // Add an optional note
896/// let update_party_with_note = UpdateParty {
897///     note: Some("Updating role after compliance check".to_string()),
898///     ..update_party
899/// };
900/// ```
901#[derive(Debug, Clone, Serialize, Deserialize)]
902pub struct UpdateParty {
903    /// ID of the transaction this update relates to.
904    pub transaction_id: String,
905
906    /// Type of party being updated (e.g., 'originator', 'beneficiary').
907    #[serde(rename = "partyType")]
908    pub party_type: String,
909
910    /// Updated party information.
911    pub party: Participant,
912
913    /// Optional note regarding the update.
914    #[serde(skip_serializing_if = "Option::is_none")]
915    pub note: Option<String>,
916
917    /// Optional context for the update.
918    #[serde(rename = "@context", skip_serializing_if = "Option::is_none")]
919    pub context: Option<String>,
920}
921
922impl UpdateParty {
923    /// Creates a new UpdateParty message body.
924    pub fn new(transaction_id: &str, party_type: &str, party: Participant) -> Self {
925        Self {
926            transaction_id: transaction_id.to_string(),
927            party_type: party_type.to_string(),
928            party,
929            note: None,
930            context: None,
931        }
932    }
933
934    /// Validates the UpdateParty message body.
935    pub fn validate(&self) -> Result<()> {
936        if self.transaction_id.is_empty() {
937            return Err(Error::Validation(
938                "transaction_id cannot be empty".to_string(),
939            ));
940        }
941
942        if self.party_type.is_empty() {
943            return Err(Error::Validation("partyType cannot be empty".to_string()));
944        }
945
946        if self.party.id.is_empty() {
947            return Err(Error::Validation("party.id cannot be empty".to_string()));
948        }
949
950        Ok(())
951    }
952}
953
954impl TapMessageBody for UpdateParty {
955    fn message_type() -> &'static str {
956        "https://tap.rsvp/schema/1.0#updateparty"
957    }
958
959    fn validate(&self) -> Result<()> {
960        self.validate()
961    }
962
963    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
964        // Serialize the UpdateParty to a JSON value
965        let mut body_json =
966            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
967
968        // Ensure the @type field is correctly set in the body
969        if let Some(body_obj) = body_json.as_object_mut() {
970            body_obj.insert(
971                "@type".to_string(),
972                serde_json::Value::String(Self::message_type().to_string()),
973            );
974        }
975
976        let now = Utc::now().timestamp() as u64;
977
978        // Create a new Message with required fields
979        let message = Message {
980            id: uuid::Uuid::new_v4().to_string(),
981            typ: "application/didcomm-plain+json".to_string(),
982            type_: Self::message_type().to_string(),
983            body: body_json,
984            from: from_did.map(|s| s.to_string()),
985            to: None,
986            thid: Some(self.transaction_id.clone()),
987            pthid: None,
988            created_time: Some(now),
989            expires_time: None,
990            extra_headers: std::collections::HashMap::new(),
991            from_prior: None,
992            attachments: None,
993        };
994
995        Ok(message)
996    }
997
998    fn from_didcomm(message: &Message) -> Result<Self> {
999        let body = message
1000            .body
1001            .as_object()
1002            .ok_or_else(|| Error::Validation("Message body is not a JSON object".to_string()))?;
1003
1004        let transaction_id = body
1005            .get("transaction_id")
1006            .and_then(|v| v.as_str())
1007            .ok_or_else(|| Error::Validation("Missing or invalid transaction_id".to_string()))?;
1008
1009        let party_type = body
1010            .get("partyType")
1011            .and_then(|v| v.as_str())
1012            .ok_or_else(|| Error::Validation("Missing or invalid partyType".to_string()))?;
1013
1014        let party = body
1015            .get("party")
1016            .ok_or_else(|| Error::Validation("Missing party information".to_string()))?;
1017
1018        let party: Participant = serde_json::from_value(party.clone())
1019            .map_err(|e| Error::SerializationError(e.to_string()))?;
1020
1021        let note = body
1022            .get("note")
1023            .and_then(|v| v.as_str())
1024            .map(ToString::to_string);
1025
1026        let context = body
1027            .get("@context")
1028            .and_then(|v| v.as_str())
1029            .map(ToString::to_string);
1030
1031        let update_party = Self {
1032            transaction_id: transaction_id.to_string(),
1033            party_type: party_type.to_string(),
1034            party,
1035            note,
1036            context,
1037        };
1038
1039        update_party.validate()?;
1040
1041        Ok(update_party)
1042    }
1043}
1044
1045impl_tap_message!(UpdateParty);
1046
1047/// UpdatePolicies message body (TAIP-7).
1048///
1049/// This message type allows agents to update their policies for a transaction.
1050#[derive(Debug, Clone, Serialize, Deserialize)]
1051pub struct UpdatePolicies {
1052    #[serde(rename = "transactionId")]
1053    pub transaction_id: String,
1054    pub policies: Vec<Policy>,
1055}
1056
1057impl UpdatePolicies {
1058    pub fn new(transaction_id: &str, policies: Vec<Policy>) -> Self {
1059        Self {
1060            transaction_id: transaction_id.to_string(),
1061            policies,
1062        }
1063    }
1064
1065    pub fn validate(&self) -> Result<()> {
1066        if self.transaction_id.is_empty() {
1067            return Err(Error::Validation(
1068                "UpdatePolicies must have a transaction_id".to_string(),
1069            ));
1070        }
1071
1072        if self.policies.is_empty() {
1073            return Err(Error::Validation(
1074                "UpdatePolicies must have at least one policy".to_string(),
1075            ));
1076        }
1077
1078        for policy in &self.policies {
1079            policy.validate()?;
1080        }
1081
1082        Ok(())
1083    }
1084}
1085
1086impl TapMessageBody for UpdatePolicies {
1087    fn message_type() -> &'static str {
1088        "https://tap.rsvp/schema/1.0#updatepolicies"
1089    }
1090
1091    fn validate(&self) -> Result<()> {
1092        UpdatePolicies::validate(self)
1093    }
1094
1095    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
1096        // Serialize the UpdatePolicies to a JSON value
1097        let mut body_json =
1098            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1099
1100        // Ensure the @type field is correctly set in the body
1101        if let Some(body_obj) = body_json.as_object_mut() {
1102            body_obj.insert(
1103                "@type".to_string(),
1104                serde_json::Value::String(Self::message_type().to_string()),
1105            );
1106        }
1107
1108        let now = Utc::now().timestamp() as u64;
1109
1110        // Create a new Message with required fields
1111        let message = Message {
1112            id: uuid::Uuid::new_v4().to_string(),
1113            typ: "application/didcomm-plain+json".to_string(),
1114            type_: Self::message_type().to_string(),
1115            body: body_json,
1116            from: from_did.map(|s| s.to_string()),
1117            to: None,
1118            thid: Some(self.transaction_id.clone()),
1119            pthid: None,
1120            created_time: Some(now),
1121            expires_time: None,
1122            extra_headers: std::collections::HashMap::new(),
1123            from_prior: None,
1124            attachments: None,
1125        };
1126
1127        Ok(message)
1128    }
1129
1130    fn from_didcomm(message: &Message) -> Result<Self> {
1131        // Verify this is the correct message type
1132        if message.type_ != Self::message_type() {
1133            return Err(Error::InvalidMessageType(format!(
1134                "Expected message type {}, but found {}",
1135                Self::message_type(),
1136                message.type_
1137            )));
1138        }
1139
1140        // Create a copy of the body that we can modify
1141        let mut body_json = message.body.clone();
1142
1143        // Remove the @type field if present as we no longer need it in our struct
1144        if let Some(body_obj) = body_json.as_object_mut() {
1145            body_obj.remove("@type");
1146        }
1147
1148        // Deserialize the body
1149        let update_policies = serde_json::from_value(body_json).map_err(|e| {
1150            Error::SerializationError(format!("Failed to deserialize UpdatePolicies: {}", e))
1151        })?;
1152
1153        Ok(update_policies)
1154    }
1155}
1156
1157impl_tap_message!(UpdatePolicies);
1158
1159/// Error message body.
1160#[derive(Debug, Clone, Serialize, Deserialize)]
1161pub struct ErrorBody {
1162    /// Error code.
1163    pub code: String,
1164
1165    /// Error description.
1166    pub description: String,
1167
1168    /// Original message ID (if applicable).
1169    #[serde(skip_serializing_if = "Option::is_none")]
1170    pub original_message_id: Option<String>,
1171
1172    /// Additional metadata.
1173    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
1174    pub metadata: HashMap<String, serde_json::Value>,
1175}
1176
1177/// Trait for validating TAP message structures.
1178pub trait Validate {
1179    /// Validates the structure and content of a TAP message.
1180    fn validate(&self) -> crate::error::Result<()>;
1181}
1182
1183/// Trait for TAP messages that can be authorized, rejected, or settled
1184pub trait Authorizable {
1185    /// Authorizes this message, creating an Authorize message as a response
1186    ///
1187    /// # Arguments
1188    ///
1189    /// * `note` - Optional note
1190    ///
1191    /// # Returns
1192    ///
1193    /// A new Authorize message body
1194    fn authorize(&self, note: Option<String>) -> Authorize;
1195
1196    /// Confirms a relationship between agents, creating a ConfirmRelationship message as a response
1197    ///
1198    /// # Arguments
1199    ///
1200    /// * `transfer_id` - ID of the transfer related to this message
1201    /// * `agent_id` - DID of the agent whose relationship is being confirmed
1202    /// * `for_id` - DID of the entity that the agent acts on behalf of
1203    /// * `role` - Optional role of the agent in the transaction
1204    ///
1205    /// # Returns
1206    ///
1207    /// A new ConfirmRelationship message body
1208    fn confirm_relationship(
1209        &self,
1210        transaction_id: String,
1211        agent_id: String,
1212        for_id: String,
1213        role: Option<String>,
1214    ) -> ConfirmRelationship;
1215
1216    /// Rejects this message, creating a Reject message as a response
1217    ///
1218    /// # Arguments
1219    ///
1220    /// * `code` - Rejection code
1221    /// * `description` - Description of rejection reason
1222    ///
1223    /// # Returns
1224    ///
1225    /// A new Reject message body
1226    fn reject(&self, code: String, description: String) -> Reject;
1227
1228    /// Settles this message, creating a Settle message as a response
1229    ///
1230    /// # Arguments
1231    ///
1232    /// * `settlement_id` - Settlement ID (CAIP-220 identifier)
1233    /// * `amount` - Optional amount settled
1234    ///
1235    /// # Returns
1236    ///
1237    /// A new Settle message body
1238    fn settle(&self, settlement_id: String, amount: Option<String>) -> Settle;
1239
1240    /// Updates a party in the transaction, creating an UpdateParty message as a response
1241    ///
1242    /// # Arguments
1243    ///
1244    /// * `transfer_id` - ID of the transaction this update relates to
1245    /// * `party_type` - Type of party being updated (e.g., 'originator', 'beneficiary')
1246    /// * `party` - Updated party information
1247    /// * `note` - Optional note about the update
1248    ///
1249    /// # Returns
1250    ///
1251    /// A new UpdateParty message body
1252    fn update_party(
1253        &self,
1254        transaction_id: String,
1255        party_type: String,
1256        party: Participant,
1257        note: Option<String>,
1258    ) -> UpdateParty;
1259
1260    /// Updates policies for this message, creating an UpdatePolicies message as a response
1261    ///
1262    /// # Arguments
1263    ///
1264    /// * `transfer_id` - ID of the transfer being updated
1265    /// * `policies` - Vector of policies to be applied
1266    ///
1267    /// # Returns
1268    ///
1269    /// A new UpdatePolicies message body
1270    fn update_policies(&self, transaction_id: String, policies: Vec<Policy>) -> UpdatePolicies;
1271
1272    /// Adds agents to this message, creating an AddAgents message as a response
1273    ///
1274    /// # Arguments
1275    ///
1276    /// * `transfer_id` - ID of the transfer to add agents to
1277    /// * `agents` - Vector of participants to be added
1278    ///
1279    /// # Returns
1280    ///
1281    /// A new AddAgents message body
1282    fn add_agents(&self, transaction_id: String, agents: Vec<Participant>) -> AddAgents;
1283
1284    /// Replaces an agent in this message, creating a ReplaceAgent message as a response
1285    ///
1286    /// # Arguments
1287    ///
1288    /// * `transfer_id` - ID of the transfer to replace agent in
1289    /// * `original` - DID of the original agent to be replaced
1290    /// * `replacement` - New participant replacing the original agent
1291    ///
1292    /// # Returns
1293    ///
1294    /// A new ReplaceAgent message body
1295    fn replace_agent(
1296        &self,
1297        transaction_id: String,
1298        original: String,
1299        replacement: Participant,
1300    ) -> ReplaceAgent;
1301
1302    /// Removes an agent from this message, creating a RemoveAgent message as a response
1303    ///
1304    /// # Arguments
1305    ///
1306    /// * `transfer_id` - ID of the transfer to remove agent from
1307    /// * `agent` - DID of the agent to remove
1308    ///
1309    /// # Returns
1310    ///
1311    /// A new RemoveAgent message body
1312    fn remove_agent(&self, transaction_id: String, agent: String) -> RemoveAgent;
1313}
1314
1315// Implementation of message type conversion for message body types
1316
1317impl TapMessageBody for Authorize {
1318    fn message_type() -> &'static str {
1319        "https://tap.rsvp/schema/1.0#authorize"
1320    }
1321
1322    fn validate(&self) -> Result<()> {
1323        if self.transaction_id.is_empty() {
1324            return Err(Error::Validation(
1325                "Transaction ID is required in Authorize".to_string(),
1326            ));
1327        }
1328
1329        Ok(())
1330    }
1331
1332    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
1333        // Create a JSON representation of self with explicit type field
1334        let mut body_json =
1335            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1336
1337        // Ensure the @type field is correctly set in the body
1338        if let Some(body_obj) = body_json.as_object_mut() {
1339            // Add or update the @type field with the message type
1340            body_obj.insert(
1341                "@type".to_string(),
1342                serde_json::Value::String(Self::message_type().to_string()),
1343            );
1344        }
1345
1346        // Create a new message with a random ID
1347        let id = uuid::Uuid::new_v4().to_string();
1348        let created_time = Utc::now().timestamp() as u64;
1349
1350        // Create the message
1351        let message = Message {
1352            id,
1353            typ: "application/didcomm-plain+json".to_string(),
1354            type_: Self::message_type().to_string(),
1355            from: from_did.map(|s| s.to_string()),
1356            to: None,
1357            thid: None,
1358            pthid: None,
1359            created_time: Some(created_time),
1360            expires_time: None,
1361            extra_headers: std::collections::HashMap::new(),
1362            from_prior: None,
1363            body: body_json,
1364            attachments: None,
1365        };
1366
1367        Ok(message)
1368    }
1369}
1370
1371impl_tap_message!(Authorize);
1372
1373impl TapMessageBody for Reject {
1374    fn message_type() -> &'static str {
1375        "https://tap.rsvp/schema/1.0#reject"
1376    }
1377
1378    fn validate(&self) -> Result<()> {
1379        if self.transaction_id.is_empty() {
1380            return Err(Error::Validation(
1381                "Transaction ID is required in Reject".to_string(),
1382            ));
1383        }
1384
1385        if self.reason.is_empty() {
1386            return Err(Error::Validation(
1387                "Reason is required in Reject".to_string(),
1388            ));
1389        }
1390
1391        Ok(())
1392    }
1393
1394    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
1395        // Create a JSON representation of self with explicit type field
1396        let mut body_json =
1397            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1398
1399        // Ensure the @type field is correctly set in the body
1400        if let Some(body_obj) = body_json.as_object_mut() {
1401            // Add or update the @type field with the message type
1402            body_obj.insert(
1403                "@type".to_string(),
1404                serde_json::Value::String(Self::message_type().to_string()),
1405            );
1406        }
1407
1408        // Create a new message with a random ID
1409        let id = uuid::Uuid::new_v4().to_string();
1410        let created_time = Utc::now().timestamp() as u64;
1411
1412        // Create the message
1413        let message = Message {
1414            id,
1415            typ: "application/didcomm-plain+json".to_string(),
1416            type_: Self::message_type().to_string(),
1417            from: from_did.map(|s| s.to_string()),
1418            to: None,
1419            thid: None,
1420            pthid: None,
1421            created_time: Some(created_time),
1422            expires_time: None,
1423            extra_headers: std::collections::HashMap::new(),
1424            from_prior: None,
1425            body: body_json,
1426            attachments: None,
1427        };
1428
1429        Ok(message)
1430    }
1431}
1432
1433impl_tap_message!(Reject);
1434
1435impl TapMessageBody for Settle {
1436    fn message_type() -> &'static str {
1437        "https://tap.rsvp/schema/1.0#settle"
1438    }
1439
1440    fn validate(&self) -> Result<()> {
1441        if self.transaction_id.is_empty() {
1442            return Err(Error::Validation(
1443                "Transaction ID is required in Settle".to_string(),
1444            ));
1445        }
1446
1447        if self.settlement_id.is_empty() {
1448            return Err(Error::Validation(
1449                "Settlement ID is required in Settle".to_string(),
1450            ));
1451        }
1452
1453        if let Some(amount) = &self.amount {
1454            if amount.is_empty() {
1455                return Err(Error::Validation(
1456                    "Amount must be a valid number".to_string(),
1457                ));
1458            }
1459        }
1460
1461        Ok(())
1462    }
1463
1464    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
1465        // Create a JSON representation of self with explicit type field
1466        let mut body_json =
1467            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1468
1469        // Ensure the @type field is correctly set in the body
1470        if let Some(body_obj) = body_json.as_object_mut() {
1471            // Add or update the @type field with the message type
1472            body_obj.insert(
1473                "@type".to_string(),
1474                serde_json::Value::String(Self::message_type().to_string()),
1475            );
1476        }
1477
1478        // Create a new message with a random ID
1479        let id = uuid::Uuid::new_v4().to_string();
1480        let created_time = Utc::now().timestamp() as u64;
1481
1482        // Create the message
1483        let message = Message {
1484            id,
1485            typ: "application/didcomm-plain+json".to_string(),
1486            type_: Self::message_type().to_string(),
1487            from: from_did.map(|s| s.to_string()),
1488            to: None,
1489            thid: Some(self.transaction_id.clone()),
1490            pthid: None,
1491            created_time: Some(created_time),
1492            expires_time: None,
1493            extra_headers: std::collections::HashMap::new(),
1494            from_prior: None,
1495            body: body_json,
1496            attachments: None,
1497        };
1498
1499        Ok(message)
1500    }
1501}
1502
1503impl_tap_message!(Settle);
1504
1505impl TapMessageBody for Presentation {
1506    fn message_type() -> &'static str {
1507        "https://tap.rsvp/schema/1.0#presentation"
1508    }
1509
1510    fn validate(&self) -> Result<()> {
1511        if self.challenge.is_empty() {
1512            return Err(Error::Validation(
1513                "Challenge is required in Presentation".to_string(),
1514            ));
1515        }
1516
1517        if self.credentials.is_empty() {
1518            return Err(Error::Validation(
1519                "Credentials are required in Presentation".to_string(),
1520            ));
1521        }
1522
1523        Ok(())
1524    }
1525
1526    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
1527        // Create a JSON representation of self with explicit type field
1528        let mut body_json =
1529            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1530
1531        // Ensure the @type field is correctly set in the body
1532        if let Some(body_obj) = body_json.as_object_mut() {
1533            // Add or update the @type field with the message type
1534            body_obj.insert(
1535                "@type".to_string(),
1536                serde_json::Value::String(Self::message_type().to_string()),
1537            );
1538        }
1539
1540        // Create a new message with a random ID
1541        let id = uuid::Uuid::new_v4().to_string();
1542        let created_time = Utc::now().timestamp() as u64;
1543
1544        // Create the message
1545        let message = Message {
1546            id,
1547            typ: "application/didcomm-plain+json".to_string(),
1548            type_: Self::message_type().to_string(),
1549            from: from_did.map(|s| s.to_string()),
1550            to: None,
1551            thid: None,
1552            pthid: None,
1553            created_time: Some(created_time),
1554            expires_time: None,
1555            extra_headers: std::collections::HashMap::new(),
1556            from_prior: None,
1557            body: body_json,
1558            attachments: None,
1559        };
1560
1561        Ok(message)
1562    }
1563}
1564
1565impl TapMessageBody for AddAgents {
1566    fn message_type() -> &'static str {
1567        "https://tap.rsvp/schema/1.0#addagents"
1568    }
1569
1570    fn validate(&self) -> Result<()> {
1571        if self.transaction_id.is_empty() {
1572            return Err(Error::Validation(
1573                "Transfer ID is required in AddAgents".to_string(),
1574            ));
1575        }
1576
1577        if self.agents.is_empty() {
1578            return Err(Error::Validation(
1579                "At least one agent is required in AddAgents".to_string(),
1580            ));
1581        }
1582
1583        for agent in &self.agents {
1584            if agent.id.is_empty() {
1585                return Err(Error::Validation(
1586                    "Agent ID is required in AddAgents".to_string(),
1587                ));
1588            }
1589        }
1590
1591        Ok(())
1592    }
1593
1594    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
1595        // Create a JSON representation of self with explicit type field
1596        let mut body_json =
1597            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1598
1599        // Ensure the @type field is correctly set in the body
1600        if let Some(body_obj) = body_json.as_object_mut() {
1601            // Add or update the @type field with the message type
1602            body_obj.insert(
1603                "@type".to_string(),
1604                serde_json::Value::String(Self::message_type().to_string()),
1605            );
1606        }
1607
1608        // Create a new message with a random ID
1609        let id = uuid::Uuid::new_v4().to_string();
1610        let created_time = Utc::now().timestamp() as u64;
1611
1612        // Create the message
1613        let message = Message {
1614            id,
1615            typ: "application/didcomm-plain+json".to_string(),
1616            type_: Self::message_type().to_string(),
1617            from: from_did.map(|s| s.to_string()),
1618            to: None,
1619            thid: Some(self.transaction_id.clone()),
1620            pthid: None,
1621            created_time: Some(created_time),
1622            expires_time: None,
1623            extra_headers: std::collections::HashMap::new(),
1624            from_prior: None,
1625            body: body_json,
1626            attachments: None,
1627        };
1628
1629        Ok(message)
1630    }
1631}
1632
1633impl_tap_message!(AddAgents);
1634
1635impl TapMessageBody for ReplaceAgent {
1636    fn message_type() -> &'static str {
1637        "https://tap.rsvp/schema/1.0#replaceagent"
1638    }
1639
1640    fn validate(&self) -> Result<()> {
1641        if self.transaction_id.is_empty() {
1642            return Err(Error::Validation(
1643                "Transfer ID is required in ReplaceAgent".to_string(),
1644            ));
1645        }
1646
1647        if self.original.is_empty() {
1648            return Err(Error::Validation(
1649                "Original agent ID is required in ReplaceAgent".to_string(),
1650            ));
1651        }
1652
1653        if self.replacement.id.is_empty() {
1654            return Err(Error::Validation(
1655                "Replacement agent ID is required in ReplaceAgent".to_string(),
1656            ));
1657        }
1658
1659        Ok(())
1660    }
1661
1662    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
1663        // Create a JSON representation of self with explicit type field
1664        let mut body_json =
1665            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1666
1667        // Ensure the @type field is correctly set in the body
1668        if let Some(body_obj) = body_json.as_object_mut() {
1669            // Add or update the @type field with the message type
1670            body_obj.insert(
1671                "@type".to_string(),
1672                serde_json::Value::String(Self::message_type().to_string()),
1673            );
1674        }
1675
1676        // Create a new message with a random ID
1677        let id = uuid::Uuid::new_v4().to_string();
1678        let created_time = Utc::now().timestamp() as u64;
1679
1680        // Create the message
1681        let message = Message {
1682            id,
1683            typ: "application/didcomm-plain+json".to_string(),
1684            type_: Self::message_type().to_string(),
1685            from: from_did.map(|s| s.to_string()),
1686            to: None,
1687            thid: Some(self.transaction_id.clone()),
1688            pthid: None,
1689            created_time: Some(created_time),
1690            expires_time: None,
1691            extra_headers: std::collections::HashMap::new(),
1692            from_prior: None,
1693            body: body_json,
1694            attachments: None,
1695        };
1696
1697        Ok(message)
1698    }
1699}
1700
1701impl_tap_message!(ReplaceAgent);
1702
1703impl TapMessageBody for RemoveAgent {
1704    fn message_type() -> &'static str {
1705        "https://tap.rsvp/schema/1.0#removeagent"
1706    }
1707
1708    fn validate(&self) -> Result<()> {
1709        if self.transaction_id.is_empty() {
1710            return Err(Error::Validation(
1711                "Transfer ID is required in RemoveAgent".to_string(),
1712            ));
1713        }
1714
1715        if self.agent.is_empty() {
1716            return Err(Error::Validation("Agent DID cannot be empty".to_string()));
1717        }
1718
1719        Ok(())
1720    }
1721
1722    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
1723        // Create a JSON representation of self with explicit type field
1724        let mut body_json =
1725            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1726
1727        // Ensure the @type field is correctly set in the body
1728        if let Some(body_obj) = body_json.as_object_mut() {
1729            // Add or update the @type field with the message type
1730            body_obj.insert(
1731                "@type".to_string(),
1732                serde_json::Value::String(Self::message_type().to_string()),
1733            );
1734        }
1735
1736        // Create a new message with a random ID
1737        let id = uuid::Uuid::new_v4().to_string();
1738        let created_time = Utc::now().timestamp() as u64;
1739
1740        // Create the message
1741        let message = Message {
1742            id,
1743            typ: "application/didcomm-plain+json".to_string(),
1744            type_: Self::message_type().to_string(),
1745            from: from_did.map(|s| s.to_string()),
1746            to: None,
1747            thid: Some(self.transaction_id.clone()),
1748            pthid: None,
1749            created_time: Some(created_time),
1750            expires_time: None,
1751            extra_headers: std::collections::HashMap::new(),
1752            from_prior: None,
1753            body: body_json,
1754            attachments: None,
1755        };
1756
1757        Ok(message)
1758    }
1759}
1760
1761impl_tap_message!(RemoveAgent);
1762
1763impl TapMessageBody for ErrorBody {
1764    fn message_type() -> &'static str {
1765        "https://tap.rsvp/schema/1.0#error"
1766    }
1767
1768    fn validate(&self) -> Result<()> {
1769        if self.code.is_empty() {
1770            return Err(Error::Validation(
1771                "Error code is required in Error message".to_string(),
1772            ));
1773        }
1774
1775        if self.description.is_empty() {
1776            return Err(Error::Validation(
1777                "Error description is required in Error message".to_string(),
1778            ));
1779        }
1780
1781        Ok(())
1782    }
1783
1784    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
1785        // Create a JSON representation of self with explicit type field
1786        let mut body_json =
1787            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1788
1789        // Ensure the @type field is correctly set in the body
1790        if let Some(body_obj) = body_json.as_object_mut() {
1791            // Add or update the @type field with the message type
1792            body_obj.insert(
1793                "@type".to_string(),
1794                serde_json::Value::String(Self::message_type().to_string()),
1795            );
1796        }
1797
1798        // Create a new message with a random ID
1799        let id = uuid::Uuid::new_v4().to_string();
1800        let created_time = Utc::now().timestamp() as u64;
1801
1802        // Create the message
1803        let message = Message {
1804            id,
1805            typ: "application/didcomm-plain+json".to_string(),
1806            type_: Self::message_type().to_string(),
1807            from: from_did.map(|s| s.to_string()),
1808            to: None,
1809            thid: None,
1810            pthid: None,
1811            created_time: Some(created_time),
1812            expires_time: None,
1813            extra_headers: std::collections::HashMap::new(),
1814            from_prior: None,
1815            body: body_json,
1816            attachments: None,
1817        };
1818
1819        Ok(message)
1820    }
1821}
1822
1823/// Payment Request message body (TAIP-14)
1824#[derive(Debug, Clone, Serialize, Deserialize)]
1825pub struct PaymentRequest {
1826    /// Asset identifier in CAIP-19 format (optional if currency is provided)
1827    #[serde(skip_serializing_if = "Option::is_none")]
1828    pub asset: Option<AssetId>,
1829
1830    /// ISO 4217 currency code for fiat amount (optional if asset is provided)
1831    #[serde(skip_serializing_if = "Option::is_none")]
1832    pub currency: Option<String>,
1833
1834    /// Amount requested in the specified asset or currency
1835    pub amount: String,
1836
1837    /// Array of CAIP-19 asset identifiers that can be used to settle a fiat currency amount
1838    #[serde(rename = "supportedAssets", skip_serializing_if = "Option::is_none")]
1839    pub supported_assets: Option<Vec<String>>,
1840
1841    /// Structured invoice information according to TAIP-16
1842    #[serde(skip_serializing_if = "Option::is_none")]
1843    pub invoice: Option<crate::message::invoice::Invoice>,
1844
1845    /// ISO 8601 timestamp when the request expires
1846    #[serde(skip_serializing_if = "Option::is_none")]
1847    pub expiry: Option<String>,
1848
1849    /// Party information for the merchant (beneficiary)
1850    pub merchant: Participant,
1851
1852    /// Party information for the customer (originator) (optional)
1853    #[serde(skip_serializing_if = "Option::is_none")]
1854    pub customer: Option<Participant>,
1855
1856    /// Array of agents involved in the payment request
1857    pub agents: Vec<Participant>,
1858
1859    /// Additional metadata for the payment request
1860    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
1861    pub metadata: HashMap<String, serde_json::Value>,
1862}
1863
1864impl PaymentRequest {
1865    /// Creates a new PaymentRequest message body
1866    pub fn new(amount: String, merchant: Participant, agents: Vec<Participant>) -> Self {
1867        Self {
1868            asset: None,
1869            currency: None,
1870            amount,
1871            supported_assets: None,
1872            invoice: None,
1873            expiry: None,
1874            merchant,
1875            customer: None,
1876            agents,
1877            metadata: HashMap::new(),
1878        }
1879    }
1880
1881    /// Creates a new PaymentRequest message with asset specification
1882    pub fn with_asset(
1883        asset: AssetId,
1884        amount: String,
1885        merchant: Participant,
1886        agents: Vec<Participant>,
1887    ) -> Self {
1888        Self {
1889            asset: Some(asset),
1890            currency: None,
1891            amount,
1892            supported_assets: None,
1893            invoice: None,
1894            expiry: None,
1895            merchant,
1896            customer: None,
1897            agents,
1898            metadata: HashMap::new(),
1899        }
1900    }
1901
1902    /// Creates a new PaymentRequest message with currency specification
1903    pub fn with_currency(
1904        currency: String,
1905        amount: String,
1906        merchant: Participant,
1907        agents: Vec<Participant>,
1908    ) -> Self {
1909        Self {
1910            asset: None,
1911            currency: Some(currency),
1912            amount,
1913            supported_assets: None,
1914            invoice: None,
1915            expiry: None,
1916            merchant,
1917            customer: None,
1918            agents,
1919            metadata: HashMap::new(),
1920        }
1921    }
1922
1923    /// Validates the PaymentRequest message body
1924    pub fn validate(&self) -> Result<()> {
1925        // Check if at least one of asset or currency is specified
1926        if self.asset.is_none() && self.currency.is_none() {
1927            return Err(Error::Validation(
1928                "Either asset or currency must be specified".to_string(),
1929            ));
1930        }
1931
1932        // Validate amount (must be a valid numeric string)
1933        if self.amount.trim().is_empty() {
1934            return Err(Error::Validation("Amount cannot be empty".to_string()));
1935        }
1936
1937        // Validate amount is a positive number
1938        match self.amount.parse::<f64>() {
1939            Ok(amount) if amount <= 0.0 => {
1940                return Err(Error::Validation("Amount must be positive".to_string()));
1941            }
1942            Err(_) => {
1943                return Err(Error::Validation(
1944                    "Amount must be a valid number".to_string(),
1945                ));
1946            }
1947            _ => {}
1948        }
1949
1950        // Validate merchant
1951        if self.merchant.id.trim().is_empty() {
1952            return Err(Error::Validation("Merchant ID is required".to_string()));
1953        }
1954
1955        // Validate expiry date format if provided
1956        if let Some(expiry) = &self.expiry {
1957            if Utc::now().timestamp()
1958                > (Utc::now() + chrono::Duration::seconds(expiry.parse::<i64>().unwrap()))
1959                    .timestamp()
1960            {
1961                return Err(Error::Validation(
1962                    "Expiry must be a valid ISO 8601 timestamp".to_string(),
1963                ));
1964            }
1965        }
1966
1967        // Validate agents field is not empty
1968        if self.agents.is_empty() {
1969            return Err(Error::Validation("Agents cannot be empty".to_string()));
1970        }
1971
1972        // Validate invoice if present
1973        if let Some(invoice) = &self.invoice {
1974            // Validate the invoice structure
1975            invoice.validate()?;
1976
1977            // Validate that invoice total matches payment amount
1978            if let Ok(amount_f64) = self.amount.parse::<f64>() {
1979                let difference = (amount_f64 - invoice.total).abs();
1980                if difference > 0.01 {
1981                    // Allow a small tolerance for floating point calculations
1982                    return Err(Error::Validation(format!(
1983                        "Invoice total ({}) does not match payment amount ({})",
1984                        invoice.total, amount_f64
1985                    )));
1986                }
1987            }
1988
1989            // Validate currency consistency if both are present
1990            if let Some(currency) = &self.currency {
1991                if currency.to_uppercase() != invoice.currency_code.to_uppercase() {
1992                    return Err(Error::Validation(format!(
1993                        "Payment request currency ({}) does not match invoice currency ({})",
1994                        currency, invoice.currency_code
1995                    )));
1996                }
1997            }
1998        }
1999
2000        Ok(())
2001    }
2002
2003    #[allow(dead_code)] // Suppress dead code warning for now
2004    fn to_didcomm_with_route<'a, I>(&self, from: Option<&str>, to: I) -> Result<Message>
2005    where
2006        I: IntoIterator<Item = &'a str>,
2007    {
2008        // First create a message with the sender, automatically extracting agent DIDs
2009        let mut message = self.to_didcomm(from)?;
2010
2011        // Override with explicitly provided recipients if any
2012        let to_vec: Vec<String> = to.into_iter().map(String::from).collect();
2013        if !to_vec.is_empty() {
2014            message.to = Some(to_vec);
2015        }
2016
2017        // Set the parent thread ID if this message is connected to a previous message
2018        if let Some(connect_id) = self.connection_id() {
2019            message.pthid = Some(connect_id.to_string());
2020        }
2021
2022        Ok(message)
2023    }
2024}
2025
2026impl Connectable for PaymentRequest {
2027    fn with_connection(&mut self, connect_id: &str) -> &mut Self {
2028        // Store the connect_id in metadata
2029        self.metadata.insert(
2030            "connect_id".to_string(),
2031            serde_json::Value::String(connect_id.to_string()),
2032        );
2033        self
2034    }
2035
2036    fn has_connection(&self) -> bool {
2037        self.metadata.contains_key("connect_id")
2038    }
2039
2040    fn connection_id(&self) -> Option<&str> {
2041        self.metadata.get("connect_id").and_then(|v| v.as_str())
2042    }
2043}
2044
2045impl TapMessageBody for PaymentRequest {
2046    fn message_type() -> &'static str {
2047        "https://tap.rsvp/schema/1.0#paymentrequest"
2048    }
2049
2050    fn validate(&self) -> Result<()> {
2051        // Check if at least one of asset or currency is specified
2052        if self.asset.is_none() && self.currency.is_none() {
2053            return Err(Error::Validation(
2054                "Either asset or currency must be specified".to_string(),
2055            ));
2056        }
2057
2058        // Validate amount (must be a valid numeric string)
2059        if self.amount.trim().is_empty() {
2060            return Err(Error::Validation("Amount cannot be empty".to_string()));
2061        }
2062
2063        // Validate amount is a positive number
2064        match self.amount.parse::<f64>() {
2065            Ok(amount) if amount <= 0.0 => {
2066                return Err(Error::Validation("Amount must be positive".to_string()));
2067            }
2068            Err(_) => {
2069                return Err(Error::Validation(
2070                    "Amount must be a valid number".to_string(),
2071                ));
2072            }
2073            _ => {}
2074        }
2075
2076        // Validate merchant
2077        if self.merchant.id.trim().is_empty() {
2078            return Err(Error::Validation("Merchant ID is required".to_string()));
2079        }
2080
2081        // Validate expiry date format if provided
2082        if let Some(expiry) = &self.expiry {
2083            if Utc::now().timestamp()
2084                > (Utc::now() + chrono::Duration::seconds(expiry.parse::<i64>().unwrap()))
2085                    .timestamp()
2086            {
2087                return Err(Error::Validation(
2088                    "Expiry must be a valid ISO 8601 timestamp".to_string(),
2089                ));
2090            }
2091        }
2092
2093        // Validate agents field is not empty
2094        if self.agents.is_empty() {
2095            return Err(Error::Validation("Agents cannot be empty".to_string()));
2096        }
2097
2098        // Validate invoice if present
2099        if let Some(invoice) = &self.invoice {
2100            // Validate the invoice structure
2101            invoice.validate()?;
2102
2103            // Validate that invoice total matches payment amount
2104            if let Ok(amount_f64) = self.amount.parse::<f64>() {
2105                let difference = (amount_f64 - invoice.total).abs();
2106                if difference > 0.01 {
2107                    // Allow a small tolerance for floating point calculations
2108                    return Err(Error::Validation(format!(
2109                        "Invoice total ({}) does not match payment amount ({})",
2110                        invoice.total, amount_f64
2111                    )));
2112                }
2113            }
2114
2115            // Validate currency consistency if both are present
2116            if let Some(currency) = &self.currency {
2117                if currency.to_uppercase() != invoice.currency_code.to_uppercase() {
2118                    return Err(Error::Validation(format!(
2119                        "Payment request currency ({}) does not match invoice currency ({})",
2120                        currency, invoice.currency_code
2121                    )));
2122                }
2123            }
2124        }
2125
2126        Ok(())
2127    }
2128
2129    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
2130        // Create a JSON representation of self with explicit type field
2131        let mut body_json =
2132            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
2133
2134        // Ensure the @type field is correctly set in the body
2135        if let Some(body_obj) = body_json.as_object_mut() {
2136            // Add or update the @type field with the message type
2137            body_obj.insert(
2138                "@type".to_string(),
2139                serde_json::Value::String(Self::message_type().to_string()),
2140            );
2141        }
2142
2143        // Extract agent DIDs directly from the message
2144        let mut agent_dids = Vec::new();
2145
2146        // Add merchant DID
2147        agent_dids.push(self.merchant.id.clone());
2148
2149        // Add customer DID if present
2150        if let Some(customer) = &self.customer {
2151            agent_dids.push(customer.id.clone());
2152        }
2153
2154        // Add DIDs from agents array
2155        for agent in &self.agents {
2156            agent_dids.push(agent.id.clone());
2157        }
2158
2159        // Remove duplicates
2160        agent_dids.sort();
2161        agent_dids.dedup();
2162
2163        // If from_did is provided, remove it from the recipients list to avoid sending to self
2164        if let Some(from) = from_did {
2165            agent_dids.retain(|did| did != from);
2166        }
2167
2168        let now = Utc::now().timestamp() as u64;
2169
2170        // Get the connection ID if this message is connected to a previous message
2171        let pthid = self
2172            .connection_id()
2173            .map(|connect_id| connect_id.to_string());
2174
2175        // Create a new Message with required fields
2176        let message = Message {
2177            id: uuid::Uuid::new_v4().to_string(),
2178            typ: "application/didcomm-plain+json".to_string(),
2179            type_: Self::message_type().to_string(),
2180            from: from_did.map(|s| s.to_string()),
2181            to: Some(agent_dids),
2182            thid: None,
2183            pthid,
2184            created_time: Some(now),
2185            expires_time: None,
2186            extra_headers: std::collections::HashMap::new(),
2187            from_prior: None,
2188            body: body_json,
2189            attachments: None,
2190        };
2191
2192        Ok(message)
2193    }
2194}
2195
2196/// Constraints for the connection
2197#[derive(Debug, Clone, Serialize, Deserialize)]
2198pub struct ConnectionConstraints {
2199    /// Array of ISO 20022 purpose codes
2200    #[serde(skip_serializing_if = "Option::is_none")]
2201    pub purposes: Option<Vec<String>>,
2202
2203    /// Array of ISO 20022 category purpose codes
2204    #[serde(skip_serializing_if = "Option::is_none")]
2205    pub category_purposes: Option<Vec<String>>,
2206
2207    /// Transaction limits
2208    #[serde(skip_serializing_if = "Option::is_none")]
2209    pub limits: Option<TransactionLimits>,
2210}
2211
2212/// Transaction limits for connections
2213#[derive(Debug, Clone, Serialize, Deserialize)]
2214pub struct TransactionLimits {
2215    /// Maximum amount per transaction
2216    #[serde(rename = "per_transaction", skip_serializing_if = "Option::is_none")]
2217    pub per_transaction: Option<String>,
2218
2219    /// Maximum daily amount
2220    #[serde(skip_serializing_if = "Option::is_none")]
2221    pub daily: Option<String>,
2222
2223    /// Currency code for limits
2224    pub currency: String,
2225}
2226
2227/// Connect message body (TAIP-15)
2228#[derive(Debug, Clone, Serialize, Deserialize)]
2229pub struct Connect {
2230    /// Agent details
2231    #[serde(skip_serializing_if = "Option::is_none")]
2232    pub agent: Option<Agent>,
2233
2234    /// DID of the party the agent represents
2235    pub for_id: String,
2236
2237    /// Transaction constraints for the connection
2238    pub constraints: ConnectionConstraints,
2239
2240    /// Additional metadata for the connection
2241    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
2242    pub metadata: HashMap<String, serde_json::Value>,
2243}
2244
2245impl Connect {
2246    /// Creates a new Connect message body
2247    pub fn new(for_id: String, constraints: ConnectionConstraints) -> Self {
2248        Self {
2249            agent: None,
2250            for_id,
2251            constraints,
2252            metadata: HashMap::new(),
2253        }
2254    }
2255
2256    /// Creates a new Connect message body with agent details
2257    pub fn with_agent(agent: Agent, for_id: String, constraints: ConnectionConstraints) -> Self {
2258        Self {
2259            agent: Some(agent),
2260            for_id,
2261            constraints,
2262            metadata: HashMap::new(),
2263        }
2264    }
2265
2266    /// Validates the Connect message body
2267    pub fn validate(&self) -> Result<()> {
2268        // Validate for_id field
2269        if self.for_id.trim().is_empty() {
2270            return Err(Error::Validation("For ID cannot be empty".to_string()));
2271        }
2272
2273        // Validate constraints
2274        if let Some(limits) = &self.constraints.limits {
2275            if limits.currency.trim().is_empty() {
2276                return Err(Error::Validation(
2277                    "Currency code must be specified for limits".to_string(),
2278                ));
2279            }
2280        }
2281
2282        Ok(())
2283    }
2284}
2285
2286impl TapMessageBody for Connect {
2287    fn message_type() -> &'static str {
2288        "https://tap.rsvp/schema/1.0#connect"
2289    }
2290
2291    fn validate(&self) -> Result<()> {
2292        // Validate for_id field
2293        if self.for_id.trim().is_empty() {
2294            return Err(Error::Validation("For ID cannot be empty".to_string()));
2295        }
2296
2297        // Validate constraints
2298        if let Some(limits) = &self.constraints.limits {
2299            if limits.currency.trim().is_empty() {
2300                return Err(Error::Validation(
2301                    "Currency code must be specified for limits".to_string(),
2302                ));
2303            }
2304        }
2305
2306        Ok(())
2307    }
2308
2309    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
2310        // Create a JSON representation of self with explicit type field
2311        let mut body_json =
2312            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
2313
2314        // Ensure the @type field is correctly set in the body
2315        if let Some(body_obj) = body_json.as_object_mut() {
2316            // Add or update the @type field with the message type
2317            body_obj.insert(
2318                "@type".to_string(),
2319                serde_json::Value::String(Self::message_type().to_string()),
2320            );
2321        }
2322
2323        // Extract agent DIDs directly from the message
2324        let mut agent_dids = Vec::new();
2325
2326        // Add agent DID if present
2327        if let Some(agent) = &self.agent {
2328            agent_dids.push(agent.id.clone());
2329        }
2330
2331        // Add for_id if it's a DID
2332        if self.for_id.starts_with("did:") {
2333            agent_dids.push(self.for_id.clone());
2334        }
2335
2336        // Remove duplicates
2337        agent_dids.sort();
2338        agent_dids.dedup();
2339
2340        // If from_did is provided, remove it from the recipients list to avoid sending to self
2341        if let Some(from) = from_did {
2342            agent_dids.retain(|did| did != from);
2343        }
2344
2345        let now = Utc::now().timestamp() as u64;
2346
2347        // Connect messages don't have connections, so pthid is always None
2348        let pthid = None;
2349
2350        // Create a new Message with required fields
2351        let message = Message {
2352            id: uuid::Uuid::new_v4().to_string(),
2353            typ: "application/didcomm-plain+json".to_string(),
2354            type_: Self::message_type().to_string(),
2355            from: from_did.map(|s| s.to_string()),
2356            to: Some(agent_dids),
2357            thid: None,
2358            pthid,
2359            created_time: Some(now),
2360            expires_time: None,
2361            extra_headers: std::collections::HashMap::new(),
2362            from_prior: None,
2363            body: body_json,
2364            attachments: None,
2365        };
2366
2367        Ok(message)
2368    }
2369}
2370
2371/// Agent details for Connect message
2372#[derive(Debug, Clone, Serialize, Deserialize)]
2373pub struct Agent {
2374    /// DID of the agent
2375    pub id: String,
2376
2377    /// Name of the agent
2378    #[serde(skip_serializing_if = "Option::is_none")]
2379    pub name: Option<String>,
2380
2381    /// Type of the agent
2382    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
2383    pub agent_type: Option<String>,
2384
2385    /// Service URL for the agent
2386    #[serde(skip_serializing_if = "Option::is_none")]
2387    pub service_url: Option<String>,
2388}
2389
2390/// AuthorizationRequired message body (TAIP-15)
2391#[derive(Debug, Clone, Serialize, Deserialize)]
2392pub struct AuthorizationRequired {
2393    /// URL where the customer can review and approve the connection
2394    pub authorization_url: String,
2395
2396    /// ISO 8601 timestamp when the authorization URL expires
2397    pub expires: String,
2398
2399    /// Additional metadata for the authorization
2400    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
2401    pub metadata: HashMap<String, serde_json::Value>,
2402}
2403
2404impl AuthorizationRequired {
2405    /// Creates a new AuthorizationRequired message body
2406    pub fn new(authorization_url: String, expires: String) -> Self {
2407        Self {
2408            authorization_url,
2409            expires,
2410            metadata: HashMap::new(),
2411        }
2412    }
2413
2414    /// Validates the AuthorizationRequired message body
2415    pub fn validate(&self) -> Result<()> {
2416        // Validate authorization_url field
2417        if self.authorization_url.trim().is_empty() {
2418            return Err(Error::Validation(
2419                "Authorization URL cannot be empty".to_string(),
2420            ));
2421        }
2422
2423        match chrono::DateTime::parse_from_rfc3339(&self.expires) {
2424            Ok(expiry_time) => {
2425                let expiry_time_utc = expiry_time.with_timezone(&Utc);
2426                if expiry_time_utc <= Utc::now() {
2427                    return Err(Error::Validation(
2428                        "Expires timestamp must be in the future".to_string(),
2429                    ));
2430                }
2431            }
2432            Err(_) => {
2433                return Err(Error::Validation(
2434                    "Expires must be a valid ISO 8601 timestamp string".to_string(),
2435                ));
2436            }
2437        }
2438
2439        Ok(())
2440    }
2441}
2442
2443impl TapMessageBody for AuthorizationRequired {
2444    fn message_type() -> &'static str {
2445        "https://tap.rsvp/schema/1.0#authorizationrequired"
2446    }
2447
2448    fn validate(&self) -> Result<()> {
2449        // Validate authorization_url field
2450        if self.authorization_url.trim().is_empty() {
2451            return Err(Error::Validation(
2452                "Authorization URL cannot be empty".to_string(),
2453            ));
2454        }
2455
2456        match chrono::DateTime::parse_from_rfc3339(&self.expires) {
2457            Ok(expiry_time) => {
2458                let expiry_time_utc = expiry_time.with_timezone(&Utc);
2459                if expiry_time_utc <= Utc::now() {
2460                    return Err(Error::Validation(
2461                        "Expires timestamp must be in the future".to_string(),
2462                    ));
2463                }
2464            }
2465            Err(_) => {
2466                return Err(Error::Validation(
2467                    "Expires must be a valid ISO 8601 timestamp string".to_string(),
2468                ));
2469            }
2470        }
2471
2472        Ok(())
2473    }
2474
2475    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
2476        // Create a JSON representation of self with explicit type field
2477        let mut body_json =
2478            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
2479
2480        // Ensure the @type field is correctly set in the body
2481        if let Some(body_obj) = body_json.as_object_mut() {
2482            // Add or update the @type field with the message type
2483            body_obj.insert(
2484                "@type".to_string(),
2485                serde_json::Value::String(Self::message_type().to_string()),
2486            );
2487        }
2488
2489        // Create a new message with a random ID
2490        let id = uuid::Uuid::new_v4().to_string();
2491        let created_time = Utc::now().timestamp() as u64;
2492
2493        // Create the message
2494        let message = Message {
2495            id,
2496            typ: "application/didcomm-plain+json".to_string(),
2497            type_: Self::message_type().to_string(),
2498            from: from_did.map(|s| s.to_string()),
2499            to: None,
2500            thid: None,
2501            pthid: None,
2502            created_time: Some(created_time),
2503            expires_time: None,
2504            extra_headers: std::collections::HashMap::new(),
2505            from_prior: None,
2506            body: body_json,
2507            attachments: None,
2508        };
2509
2510        Ok(message)
2511    }
2512}
2513
2514/// OutOfBand message body
2515#[derive(Debug, Clone, Serialize, Deserialize)]
2516pub struct OutOfBand {
2517    /// Goal code for the out-of-band message
2518    #[serde(skip_serializing_if = "Option::is_none")]
2519    pub goal_code: Option<String>,
2520
2521    /// Human-readable goal for the out-of-band message
2522    #[serde(skip_serializing_if = "Option::is_none")]
2523    pub goal: Option<String>,
2524
2525    /// Array of attachments
2526    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2527    pub attachments: Vec<Attachment>,
2528
2529    /// Accept property for the out-of-band message
2530    #[serde(skip_serializing_if = "Option::is_none")]
2531    pub accept: Option<Vec<String>>,
2532
2533    /// Handshake protocols
2534    #[serde(skip_serializing_if = "Option::is_none")]
2535    pub handshake_protocols: Option<Vec<String>>,
2536
2537    /// Additional metadata
2538    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
2539    pub metadata: HashMap<String, serde_json::Value>,
2540}
2541
2542impl OutOfBand {
2543    /// Creates a new OutOfBand message body
2544    pub fn new(
2545        goal_code: Option<String>,
2546        goal: Option<String>,
2547        attachments: Vec<Attachment>,
2548    ) -> Self {
2549        Self {
2550            goal_code,
2551            goal,
2552            attachments,
2553            accept: None,
2554            handshake_protocols: None,
2555            metadata: HashMap::new(),
2556        }
2557    }
2558
2559    /// Validates the OutOfBand message body
2560    pub fn validate(&self) -> Result<()> {
2561        // Validate attachments if any are provided
2562        for attachment in &self.attachments {
2563            if attachment.id.trim().is_empty() {
2564                return Err(Error::Validation(
2565                    "Attachment ID cannot be empty".to_string(),
2566                ));
2567            }
2568            if attachment.media_type.trim().is_empty() {
2569                return Err(Error::Validation(
2570                    "Attachment media type cannot be empty".to_string(),
2571                ));
2572            }
2573        }
2574
2575        Ok(())
2576    }
2577}
2578
2579impl TapMessageBody for OutOfBand {
2580    fn message_type() -> &'static str {
2581        "https://tap.rsvp/schema/1.0#outofband"
2582    }
2583
2584    fn validate(&self) -> Result<()> {
2585        // Validate attachments if any are provided
2586        for attachment in &self.attachments {
2587            if attachment.id.trim().is_empty() {
2588                return Err(Error::Validation(
2589                    "Attachment ID cannot be empty".to_string(),
2590                ));
2591            }
2592            if attachment.media_type.trim().is_empty() {
2593                return Err(Error::Validation(
2594                    "Attachment media type cannot be empty".to_string(),
2595                ));
2596            }
2597        }
2598
2599        Ok(())
2600    }
2601
2602    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
2603        // Create a JSON representation of self with explicit type field
2604        let mut body_json =
2605            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
2606
2607        // Ensure the @type field is correctly set in the body
2608        if let Some(body_obj) = body_json.as_object_mut() {
2609            // Add or update the @type field with the message type
2610            body_obj.insert(
2611                "@type".to_string(),
2612                serde_json::Value::String(Self::message_type().to_string()),
2613            );
2614        }
2615
2616        // Create a new message with a random ID
2617        let id = uuid::Uuid::new_v4().to_string();
2618        let created_time = Utc::now().timestamp() as u64;
2619
2620        // Create the message
2621        let message = Message {
2622            id,
2623            typ: "application/didcomm-plain+json".to_string(),
2624            type_: Self::message_type().to_string(),
2625            from: from_did.map(|s| s.to_string()),
2626            to: None,
2627            thid: None,
2628            pthid: None,
2629            created_time: Some(created_time),
2630            expires_time: None,
2631            extra_headers: std::collections::HashMap::new(),
2632            from_prior: None,
2633            body: body_json,
2634            attachments: None,
2635        };
2636
2637        Ok(message)
2638    }
2639}
2640
2641/// DIDComm Presentation format (using present-proof protocol)
2642///
2643/// This struct implements the standard DIDComm present-proof protocol format as defined in
2644/// [DIDComm Messaging Present Proof Protocol 3.0](https://github.com/decentralized-identity/waci-didcomm/tree/main/present-proof).
2645///
2646/// It is used for exchanging verifiable presentations between parties in a DIDComm conversation.
2647/// The presentation may contain identity credentials, proof of control, or other verifiable claims.
2648///
2649/// # Compatibility Notes
2650///
2651/// - This implementation is fully compatible with the standard DIDComm present-proof protocol.
2652/// - The message type used is `https://didcomm.org/present-proof/3.0/presentation`.
2653/// - Thread ID (`thid`) is required for proper message correlation.
2654/// - At least one attachment containing a verifiable presentation is required.
2655/// - Attachment data must include either base64 or JSON format data.
2656/// - Verifiable presentations in JSON format must include `@context` and `type` fields.
2657///
2658/// # Example
2659///
2660/// ```
2661/// use std::collections::HashMap;
2662/// use tap_msg::message::{Attachment, AttachmentData, DIDCommPresentation};
2663/// use serde_json::json;
2664///
2665/// // Create a presentation with a verifiable credential
2666/// let presentation = DIDCommPresentation {
2667///     thid: Some("123e4567-e89b-12d3-a456-426614174000".to_string()),
2668///     comment: Some("Proof of identity".to_string()),
2669///     goal_code: Some("kyc.individual".to_string()),
2670///     attachments: vec![
2671///         Attachment {
2672///             id: "credential-1".to_string(),
2673///             media_type: "application/json".to_string(),
2674///             data: Some(AttachmentData {
2675///                 base64: None,
2676///                 json: Some(json!({
2677///                     "@context": ["https://www.w3.org/2018/credentials/v1"],
2678///                     "type": ["VerifiablePresentation"],
2679///                     "verifiableCredential": [{
2680///                         "@context": ["https://www.w3.org/2018/credentials/v1"],
2681///                         "type": ["VerifiableCredential"],
2682///                         "issuer": "did:web:issuer.example",
2683///                         "issuanceDate": "2022-01-01T19:23:24Z",
2684///                         "credentialSubject": {
2685///                             "id": "did:example:ebfeb1f712ebc6f1c276e12ec21"
2686///                         }
2687///                     }]
2688///                 })),
2689///             }),
2690///         }
2691///     ],
2692///     metadata: HashMap::new(),
2693/// };
2694/// ```
2695#[derive(Debug, Clone, Serialize, Deserialize)]
2696pub struct DIDCommPresentation {
2697    /// Reference to a previous message in the thread
2698    #[serde(skip_serializing_if = "Option::is_none")]
2699    pub thid: Option<String>,
2700
2701    /// Optional comment
2702    #[serde(skip_serializing_if = "Option::is_none")]
2703    pub comment: Option<String>,
2704
2705    /// Goal code
2706    #[serde(skip_serializing_if = "Option::is_none")]
2707    pub goal_code: Option<String>,
2708
2709    /// Attachments containing the verifiable presentations
2710    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2711    pub attachments: Vec<Attachment>,
2712
2713    /// Additional metadata
2714    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
2715    pub metadata: HashMap<String, serde_json::Value>,
2716}
2717
2718impl DIDCommPresentation {
2719    /// Creates a new DIDComm Presentation
2720    pub fn new(thid: Option<String>, attachments: Vec<Attachment>) -> Self {
2721        Self {
2722            thid,
2723            comment: None,
2724            goal_code: None,
2725            attachments,
2726            metadata: HashMap::new(),
2727        }
2728    }
2729
2730    /// Validate the DIDComm Presentation
2731    ///
2732    /// This method validates a DIDComm presentation according to the standard protocol requirements.
2733    /// For a presentation to be valid, it must satisfy the following criteria:
2734    ///
2735    /// - Must have a thread ID (`thid`) for message correlation
2736    /// - Must include at least one attachment
2737    /// - Each attachment must have a non-empty ID and media type
2738    /// - Each attachment must include data in either base64 or JSON format
2739    /// - Verifiable presentations in JSON format must include `@context` and `type` fields
2740    ///
2741    /// # Returns
2742    ///
2743    /// - `Ok(())` if the presentation is valid
2744    /// - `Err(Error::Validation)` with a descriptive message if validation fails
2745    ///
2746    /// # Examples
2747    ///
2748    /// ```
2749    /// use tap_msg::message::DIDCommPresentation;
2750    /// use tap_msg::Result;
2751    ///
2752    /// fn check_presentation(presentation: &DIDCommPresentation) -> Result<()> {
2753    ///     // Validate the presentation
2754    ///     presentation.validate()?;
2755    ///     
2756    ///     // If we get here, the presentation is valid
2757    ///     Ok(())
2758    /// }
2759    /// ```
2760    pub fn validate(&self) -> Result<()> {
2761        // Validate thread ID (thid) - required according to test vectors
2762        if self.thid.is_none() {
2763            return Err(Error::Validation(
2764                "Thread ID (thid) is required for presentation message".to_string(),
2765            ));
2766        }
2767
2768        // Validate attachments if any are provided
2769        if self.attachments.is_empty() {
2770            return Err(Error::Validation(
2771                "Presentation must include at least one attachment".to_string(),
2772            ));
2773        }
2774
2775        for attachment in &self.attachments {
2776            // Validate attachment ID
2777            if attachment.id.trim().is_empty() {
2778                return Err(Error::Validation(
2779                    "Attachment ID cannot be empty".to_string(),
2780                ));
2781            }
2782
2783            // Validate media type
2784            if attachment.media_type.trim().is_empty() {
2785                return Err(Error::Validation(
2786                    "Attachment media type cannot be empty".to_string(),
2787                ));
2788            }
2789
2790            // Check for attachment data
2791            if attachment.data.is_none() {
2792                return Err(Error::Validation(
2793                    "Attachment must include data".to_string(),
2794                ));
2795            }
2796
2797            // Check attachment data content
2798            if let Some(data) = &attachment.data {
2799                if data.base64.is_none() && data.json.is_none() {
2800                    return Err(Error::Validation(
2801                        "Attachment data must include either base64 or json".to_string(),
2802                    ));
2803                }
2804
2805                // If JSON data is present, validate required fields in presentation data
2806                if let Some(json_data) = &data.json {
2807                    // Check for @context field in JSON data - required by test vectors
2808                    if json_data.get("@context").is_none() {
2809                        return Err(Error::Validation(
2810                            "Attachment JSON data must include @context field".to_string(),
2811                        ));
2812                    }
2813
2814                    // Check for type field in JSON data - required by test vectors
2815                    if json_data.get("type").is_none() {
2816                        return Err(Error::Validation(
2817                            "Attachment JSON data must include type field".to_string(),
2818                        ));
2819                    }
2820                }
2821            }
2822        }
2823
2824        Ok(())
2825    }
2826}
2827
2828/// Implementation of TapMessageBody for DIDCommPresentation
2829///
2830/// This implementation ensures that DIDCommPresentation can be converted to and from
2831/// didcomm::Message objects, allowing seamless integration with the DIDComm
2832/// messaging protocol.
2833///
2834/// # Details
2835///
2836/// - **Message Type**: Uses `https://didcomm.org/present-proof/3.0/presentation` as specified by the standard protocol
2837/// - **Conversion to DIDComm**: Converts attachments to didcomm::Attachment format with appropriate data representation
2838/// - **Conversion from DIDComm**: Extracts presentation data from DIDComm message, handling both Base64 and JSON formats
2839///
2840/// This implementation follows the [DIDComm Messaging Specification](https://identity.foundation/didcomm-messaging/spec/)
2841/// and the [Present Proof Protocol 3.0](https://github.com/decentralized-identity/waci-didcomm/tree/main/present-proof).
2842impl TapMessageBody for DIDCommPresentation {
2843    fn message_type() -> &'static str {
2844        "https://didcomm.org/present-proof/3.0/presentation"
2845    }
2846
2847    fn validate(&self) -> Result<()> {
2848        self.validate()
2849    }
2850
2851    fn from_didcomm(message: &Message) -> Result<Self> {
2852        // Check if this is the correct message type
2853        if message.type_ != Self::message_type() {
2854            return Err(Error::InvalidMessageType(format!(
2855                "Expected message type {}, but found {}",
2856                Self::message_type(),
2857                message.type_
2858            )));
2859        }
2860
2861        // Extract body and attachments
2862        let body = message
2863            .body
2864            .as_object()
2865            .ok_or_else(|| Error::Validation("Message body is not a JSON object".to_string()))?;
2866
2867        // Extract the thread id
2868        let thid = message.thid.clone();
2869
2870        // Extract comment if present
2871        let comment = body
2872            .get("comment")
2873            .and_then(|v| v.as_str())
2874            .map(ToString::to_string);
2875
2876        // Extract goal_code if present
2877        let goal_code = body
2878            .get("goal_code")
2879            .and_then(|v| v.as_str())
2880            .map(ToString::to_string);
2881
2882        // Extract attachments
2883        let attachments = if let Some(didcomm_attachments) = &message.attachments {
2884            didcomm_attachments
2885                .iter()
2886                .filter_map(|a| {
2887                    // Both id and media_type must be present
2888                    if a.id.is_none() || a.media_type.is_none() {
2889                        return None;
2890                    }
2891
2892                    // Convert the didcomm::AttachmentData to our AttachmentData if present
2893                    let data = match &a.data {
2894                        didcomm::AttachmentData::Base64 { value } => Some(AttachmentData {
2895                            base64: Some(value.base64.clone()),
2896                            json: None,
2897                        }),
2898                        didcomm::AttachmentData::Json { value } => Some(AttachmentData {
2899                            base64: None,
2900                            json: Some(value.json.clone()),
2901                        }),
2902                        didcomm::AttachmentData::Links { .. } => {
2903                            // We don't currently support links in our AttachmentData
2904                            None
2905                        }
2906                    };
2907
2908                    // Create our Attachment
2909                    Some(Attachment {
2910                        id: a.id.as_ref().unwrap().clone(),
2911                        media_type: a.media_type.as_ref().unwrap().clone(),
2912                        data,
2913                    })
2914                })
2915                .collect()
2916        } else {
2917            Vec::new()
2918        };
2919
2920        // Parse metadata (excluding known fields)
2921        let mut metadata = HashMap::new();
2922        for (k, v) in body.iter() {
2923            if !["comment", "goal_code"].contains(&k.as_str()) {
2924                metadata.insert(k.clone(), v.clone());
2925            }
2926        }
2927
2928        let presentation = Self {
2929            thid,
2930            comment,
2931            goal_code,
2932            attachments,
2933            metadata,
2934        };
2935
2936        presentation.validate()?;
2937
2938        Ok(presentation)
2939    }
2940
2941    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
2942        // Create message body
2943        let mut body = serde_json::Map::new();
2944
2945        // Add optional fields if present
2946        if let Some(comment) = &self.comment {
2947            body.insert(
2948                "comment".to_string(),
2949                serde_json::Value::String(comment.clone()),
2950            );
2951        }
2952
2953        if let Some(goal_code) = &self.goal_code {
2954            body.insert(
2955                "goal_code".to_string(),
2956                serde_json::Value::String(goal_code.clone()),
2957            );
2958        }
2959
2960        // Add metadata fields
2961        for (key, value) in &self.metadata {
2962            body.insert(key.clone(), value.clone());
2963        }
2964
2965        // Convert our attachments to didcomm::Attachment
2966        let didcomm_attachments = if !self.attachments.is_empty() {
2967            let attachments: Vec<didcomm::Attachment> = self
2968                .attachments
2969                .iter()
2970                .filter_map(|a| {
2971                    // Convert our AttachmentData to didcomm's if present
2972                    let didcomm_data = match &a.data {
2973                        Some(data) => {
2974                            if let Some(base64_data) = &data.base64 {
2975                                // Create Base64 attachment data
2976                                didcomm::AttachmentData::Base64 {
2977                                    value: didcomm::Base64AttachmentData {
2978                                        base64: base64_data.clone(),
2979                                        jws: None,
2980                                    },
2981                                }
2982                            } else if let Some(json_data) = &data.json {
2983                                // Create JSON attachment data
2984                                didcomm::AttachmentData::Json {
2985                                    value: didcomm::JsonAttachmentData {
2986                                        json: json_data.clone(),
2987                                        jws: None,
2988                                    },
2989                                }
2990                            } else {
2991                                // If neither base64 nor json is present, skip this attachment
2992                                return None;
2993                            }
2994                        }
2995                        None => {
2996                            // If no data is present, skip this attachment
2997                            return None;
2998                        }
2999                    };
3000
3001                    // Create the didcomm Attachment
3002                    Some(didcomm::Attachment {
3003                        id: Some(a.id.clone()),
3004                        media_type: Some(a.media_type.clone()),
3005                        data: didcomm_data,
3006                        filename: None,
3007                        format: None,
3008                        byte_count: None,
3009                        lastmod_time: None,
3010                        description: None,
3011                    })
3012                })
3013                .collect();
3014
3015            if attachments.is_empty() {
3016                None
3017            } else {
3018                Some(attachments)
3019            }
3020        } else {
3021            None
3022        };
3023
3024        // Create the didcomm message
3025        let message = Message {
3026            id: uuid::Uuid::new_v4().to_string(),
3027            typ: "application/didcomm-plain+json".to_string(),
3028            type_: Self::message_type().to_string(),
3029            from: from_did.map(|s| s.to_string()),
3030            to: None,
3031            thid: self.thid.clone(),
3032            pthid: None,
3033            created_time: Some(Utc::now().timestamp() as u64),
3034            expires_time: None,
3035            extra_headers: HashMap::new(),
3036            from_prior: None,
3037            body: serde_json::Value::Object(body),
3038            attachments: didcomm_attachments,
3039        };
3040
3041        Ok(message)
3042    }
3043}
3044
3045/// Implementation of Authorizable for Transfer
3046impl Authorizable for Transfer {
3047    /// Authorizes this message, creating an Authorize message as a response
3048    ///
3049    /// # Arguments
3050    ///
3051    /// * `note` - Optional note
3052    ///
3053    /// # Returns
3054    ///
3055    /// A new Authorize message body
3056    fn authorize(&self, note: Option<String>) -> Authorize {
3057        Authorize {
3058            transaction_id: self.message_id(),
3059            note,
3060        }
3061    }
3062
3063    /// Confirms a relationship between agents, creating a ConfirmRelationship message as a response
3064    ///
3065    /// # Arguments
3066    ///
3067    /// * `transfer_id` - ID of the transfer related to this message
3068    /// * `agent_id` - DID of the agent whose relationship is being confirmed
3069    /// * `for_id` - DID of the entity that the agent acts on behalf of
3070    /// * `role` - Optional role of the agent in the transaction
3071    ///
3072    /// # Returns
3073    ///
3074    /// A new ConfirmRelationship message body
3075    fn confirm_relationship(
3076        &self,
3077        transaction_id: String,
3078        agent_id: String,
3079        for_id: String,
3080        role: Option<String>,
3081    ) -> ConfirmRelationship {
3082        ConfirmRelationship {
3083            transaction_id,
3084            agent_id,
3085            for_id,
3086            role,
3087        }
3088    }
3089
3090    /// Rejects this message, creating a Reject message as a response
3091    ///
3092    /// # Arguments
3093    ///
3094    /// * `code` - Rejection code
3095    /// * `description` - Description of rejection reason
3096    ///
3097    /// # Returns
3098    ///
3099    /// A new Reject message body
3100    fn reject(&self, code: String, description: String) -> Reject {
3101        Reject {
3102            transaction_id: self.message_id(),
3103            reason: format!("{}: {}", code, description),
3104        }
3105    }
3106
3107    /// Settles this message, creating a Settle message as a response
3108    ///
3109    /// # Arguments
3110    ///
3111    /// * `settlement_id` - Settlement ID (CAIP-220 identifier)
3112    /// * `amount` - Optional amount settled
3113    ///
3114    /// # Returns
3115    ///
3116    /// A new Settle message body
3117    fn settle(&self, settlement_id: String, amount: Option<String>) -> Settle {
3118        Settle {
3119            transaction_id: self.message_id(),
3120            settlement_id,
3121            amount,
3122        }
3123    }
3124
3125    /// Updates a party in the transaction, creating an UpdateParty message as a response
3126    ///
3127    /// # Arguments
3128    ///
3129    /// * `transfer_id` - ID of the transaction this update relates to
3130    /// * `party_type` - Type of party being updated (e.g., 'originator', 'beneficiary')
3131    /// * `party` - Updated party information
3132    /// * `note` - Optional note about the update
3133    ///
3134    /// # Returns
3135    ///
3136    /// A new UpdateParty message body
3137    fn update_party(
3138        &self,
3139        transaction_id: String,
3140        party_type: String,
3141        party: Participant,
3142        note: Option<String>,
3143    ) -> UpdateParty {
3144        UpdateParty {
3145            transaction_id,
3146            party_type,
3147            party,
3148            note,
3149            context: None,
3150        }
3151    }
3152
3153    /// Updates policies for this message, creating an UpdatePolicies message as a response
3154    ///
3155    /// # Arguments
3156    ///
3157    /// * `transaction_id` - ID of the transaction being updated
3158    /// * `policies` - Vector of policies to be applied
3159    ///
3160    /// # Returns
3161    ///
3162    /// A new UpdatePolicies message body
3163    fn update_policies(&self, transaction_id: String, policies: Vec<Policy>) -> UpdatePolicies {
3164        UpdatePolicies {
3165            transaction_id,
3166            policies,
3167        }
3168    }
3169
3170    /// Adds agents to this message, creating an AddAgents message as a response
3171    ///
3172    /// # Arguments
3173    ///
3174    /// * `transaction_id` - ID of the transaction to add agents to
3175    /// * `agents` - Vector of participants to be added
3176    ///
3177    /// # Returns
3178    ///
3179    /// A new AddAgents message body
3180    fn add_agents(&self, transaction_id: String, agents: Vec<Participant>) -> AddAgents {
3181        AddAgents {
3182            transaction_id,
3183            agents,
3184        }
3185    }
3186
3187    /// Replaces an agent in this message, creating a ReplaceAgent message as a response
3188    ///
3189    /// # Arguments
3190    ///
3191    /// * `transaction_id` - ID of the transaction to replace agent in
3192    /// * `original` - DID of the original agent to be replaced
3193    /// * `replacement` - New participant replacing the original agent
3194    ///
3195    /// # Returns
3196    ///
3197    /// A new ReplaceAgent message body
3198    fn replace_agent(
3199        &self,
3200        transaction_id: String,
3201        original: String,
3202        replacement: Participant,
3203    ) -> ReplaceAgent {
3204        ReplaceAgent {
3205            transaction_id,
3206            original,
3207            replacement,
3208        }
3209    }
3210
3211    /// Removes an agent from this message, creating a RemoveAgent message as a response
3212    ///
3213    /// # Arguments
3214    ///
3215    /// * `transaction_id` - ID of the transaction to remove agent from
3216    /// * `agent` - DID of the agent to remove
3217    ///
3218    /// # Returns
3219    ///
3220    /// A new RemoveAgent message body
3221    fn remove_agent(&self, transaction_id: String, agent: String) -> RemoveAgent {
3222        RemoveAgent {
3223            transaction_id,
3224            agent,
3225        }
3226    }
3227}
3228
3229/// Represents a TAP Payment message.
3230/// This message type is used to initiate a payment request.
3231#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3232pub struct Payment {
3233    /// Unique identifier for this payment request.
3234    pub transaction_id: String,
3235    /// Identifier for the thread this message belongs to.
3236    #[serde(skip_serializing_if = "Option::is_none")]
3237    pub thid: Option<String>,
3238    /// Identifier for the parent thread, used for replies.
3239    #[serde(skip_serializing_if = "Option::is_none")]
3240    pub pthid: Option<String>,
3241    /// The merchant requesting the payment.
3242    pub merchant: Participant,
3243    /// The customer making the payment.
3244    pub customer: Participant,
3245    /// The asset being transferred (e.g., currency and amount).
3246    pub asset: AssetId,
3247    /// The amount requested for payment.
3248    pub amount: String,
3249    /// Optional details about the order or transaction.
3250    #[serde(skip_serializing_if = "Option::is_none")]
3251    pub order_details: Option<HashMap<String, serde_json::Value>>,
3252    /// Timestamp when the payment request was created (RFC3339).
3253    pub timestamp: String,
3254    /// Optional expiry time for the payment request (RFC3339).
3255    #[serde(skip_serializing_if = "Option::is_none")]
3256    pub expires: Option<String>,
3257    /// Optional note from the merchant.
3258    #[serde(skip_serializing_if = "Option::is_none")]
3259    pub note: Option<String>,
3260    /// Optional list of agents involved in processing the payment.
3261    #[serde(default, skip_serializing_if = "Vec::is_empty")]
3262    pub agents: Vec<Participant>,
3263    /// Optional metadata associated with the payment.
3264    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
3265    pub metadata: HashMap<String, serde_json::Value>,
3266    /// Optional attachments.
3267    #[serde(skip_serializing_if = "Option::is_none")]
3268    pub attachments: Option<Vec<Attachment>>,
3269}
3270
3271impl TapMessageBody for Payment {
3272    fn message_type() -> &'static str {
3273        "payment"
3274    }
3275
3276    fn validate(&self) -> Result<()> {
3277        // Validate asset
3278        if self.asset.namespace().is_empty() || self.asset.reference().is_empty() {
3279            return Err(Error::Validation("Asset ID is invalid".to_string()));
3280        }
3281
3282        // Validate merchant
3283        if self.merchant.id.is_empty() {
3284            return Err(Error::Validation("Merchant ID is required".to_string()));
3285        }
3286
3287        // Validate customer
3288        if self.customer.id.is_empty() {
3289            return Err(Error::Validation("Customer ID is required".to_string()));
3290        }
3291
3292        // Validate amount
3293        if self.amount.is_empty() {
3294            return Err(Error::Validation("Amount is required".to_string()));
3295        }
3296        match self.amount.parse::<f64>() {
3297            Ok(amount) if amount <= 0.0 => {
3298                return Err(Error::Validation("Amount must be positive".to_string()));
3299            }
3300            Err(_) => {
3301                return Err(Error::Validation(
3302                    "Amount must be a valid number".to_string(),
3303                ));
3304            }
3305            _ => {}
3306        }
3307
3308        // Validate timestamp format
3309        if chrono::DateTime::parse_from_rfc3339(&self.timestamp).is_err() {
3310            return Err(Error::Validation(
3311                "Timestamp must be a valid RFC3339 string".to_string(),
3312            ));
3313        }
3314
3315        // Validate expires format if present
3316        if let Some(expiry) = &self.expires {
3317            if chrono::DateTime::parse_from_rfc3339(expiry).is_err() {
3318                return Err(Error::Validation(
3319                    "Expires must be a valid RFC3339 string".to_string(),
3320                ));
3321            }
3322        }
3323
3324        Ok(())
3325    }
3326
3327    // Basic to_didcomm implementation (will be refined later if needed)
3328    fn to_didcomm(&self, from_did: Option<&str>) -> Result<Message> {
3329        let mut body_json =
3330            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
3331
3332        if let Some(body_obj) = body_json.as_object_mut() {
3333            body_obj.insert(
3334                "@type".to_string(),
3335                serde_json::Value::String(Self::message_type().to_string()),
3336            );
3337        }
3338
3339        let mut agent_dids = vec![self.merchant.id.clone(), self.customer.id.clone()];
3340        agent_dids.extend(self.agents.iter().map(|a| a.id.clone()));
3341        agent_dids.sort();
3342        agent_dids.dedup();
3343
3344        if let Some(from) = from_did {
3345            agent_dids.retain(|did| did != from);
3346        }
3347
3348        let didcomm_attachments = self.attachments.as_ref().map(|attachments| {
3349            attachments
3350                .iter()
3351                .filter_map(crate::utils::convert_tap_attachment_to_didcomm)
3352                .collect::<Vec<_>>()
3353        });
3354
3355        let message = Message {
3356            id: uuid::Uuid::new_v4().to_string(),
3357            typ: "application/didcomm-plain+json".to_string(),
3358            type_: Self::message_type().to_string(),
3359            from: from_did.map(|s| s.to_string()),
3360            to: Some(agent_dids),
3361            thid: self.thid.clone(),
3362            pthid: self.pthid.clone(),
3363            created_time: Some(Utc::now().timestamp() as u64),
3364            expires_time: self.expires.as_ref().and_then(|exp| {
3365                chrono::DateTime::parse_from_rfc3339(exp)
3366                    .ok()
3367                    .map(|dt| dt.timestamp() as u64)
3368            }),
3369            extra_headers: HashMap::new(),
3370            from_prior: None,
3371            body: body_json,
3372            attachments: didcomm_attachments,
3373        };
3374
3375        Ok(message)
3376    }
3377}
3378
3379impl_tap_message!(Payment);
3380
3381impl Authorizable for Payment {
3382    fn authorize(&self, note: Option<String>) -> Authorize {
3383        Authorize {
3384            transaction_id: self.message_id(),
3385            note,
3386        }
3387    }
3388
3389    fn confirm_relationship(
3390        &self,
3391        transaction_id: String,
3392        agent_id: String,
3393        for_id: String,
3394        role: Option<String>,
3395    ) -> ConfirmRelationship {
3396        ConfirmRelationship {
3397            transaction_id,
3398            agent_id,
3399            for_id,
3400            role,
3401        }
3402    }
3403
3404    fn reject(&self, code: String, description: String) -> Reject {
3405        Reject {
3406            transaction_id: self.message_id(),
3407            reason: format!("{}: {}", code, description),
3408        }
3409    }
3410
3411    fn settle(&self, settlement_id: String, amount: Option<String>) -> Settle {
3412        Settle {
3413            transaction_id: self.message_id(),
3414            settlement_id,
3415            amount,
3416        }
3417    }
3418
3419    fn update_party(
3420        &self,
3421        transaction_id: String,
3422        party_type: String,
3423        party: Participant,
3424        note: Option<String>,
3425    ) -> UpdateParty {
3426        UpdateParty {
3427            transaction_id,
3428            party_type,
3429            party,
3430            note,
3431            context: None,
3432        }
3433    }
3434
3435    fn update_policies(&self, transaction_id: String, policies: Vec<Policy>) -> UpdatePolicies {
3436        UpdatePolicies {
3437            transaction_id,
3438            policies,
3439        }
3440    }
3441
3442    fn add_agents(&self, transaction_id: String, agents: Vec<Participant>) -> AddAgents {
3443        AddAgents {
3444            transaction_id,
3445            agents,
3446        }
3447    }
3448
3449    fn replace_agent(
3450        &self,
3451        transaction_id: String,
3452        original: String,
3453        replacement: Participant,
3454    ) -> ReplaceAgent {
3455        ReplaceAgent {
3456            transaction_id,
3457            original,
3458            replacement,
3459        }
3460    }
3461
3462    fn remove_agent(&self, transaction_id: String, agent: String) -> RemoveAgent {
3463        RemoveAgent {
3464            transaction_id,
3465            agent,
3466        }
3467    }
3468}
3469
3470/// PaymentBuilder
3471#[derive(Default)]
3472pub struct PaymentBuilder {
3473    transaction_id: Option<String>,
3474    thid: Option<String>,
3475    pthid: Option<String>,
3476    merchant: Option<Participant>,
3477    customer: Option<Participant>,
3478    asset: Option<AssetId>,
3479    amount: Option<String>,
3480    order_details: Option<HashMap<String, serde_json::Value>>,
3481    timestamp: Option<String>,
3482    expires: Option<String>,
3483    note: Option<String>,
3484    agents: Vec<Participant>,
3485    metadata: HashMap<String, serde_json::Value>,
3486    attachments: Option<Vec<Attachment>>,
3487}
3488
3489impl PaymentBuilder {
3490    pub fn new() -> Self {
3491        Self::default()
3492    }
3493
3494    pub fn transaction_id(mut self, transaction_id: String) -> Self {
3495        self.transaction_id = Some(transaction_id);
3496        self
3497    }
3498
3499    pub fn thid(mut self, thid: String) -> Self {
3500        self.thid = Some(thid);
3501        self
3502    }
3503
3504    pub fn pthid(mut self, pthid: String) -> Self {
3505        self.pthid = Some(pthid);
3506        self
3507    }
3508
3509    pub fn merchant(mut self, merchant: Participant) -> Self {
3510        self.merchant = Some(merchant);
3511        self
3512    }
3513
3514    pub fn customer(mut self, customer: Participant) -> Self {
3515        self.customer = Some(customer);
3516        self
3517    }
3518
3519    pub fn asset(mut self, asset: AssetId) -> Self {
3520        self.asset = Some(asset);
3521        self
3522    }
3523
3524    pub fn amount(mut self, amount: String) -> Self {
3525        self.amount = Some(amount);
3526        self
3527    }
3528
3529    pub fn order_details(mut self, order_details: HashMap<String, serde_json::Value>) -> Self {
3530        self.order_details = Some(order_details);
3531        self
3532    }
3533
3534    pub fn timestamp(mut self, timestamp: String) -> Self {
3535        self.timestamp = Some(timestamp);
3536        self
3537    }
3538
3539    pub fn expires(mut self, expires: String) -> Self {
3540        self.expires = Some(expires);
3541        self
3542    }
3543
3544    pub fn note(mut self, note: String) -> Self {
3545        self.note = Some(note);
3546        self
3547    }
3548
3549    pub fn add_agent(mut self, agent: Participant) -> Self {
3550        self.agents.push(agent);
3551        self
3552    }
3553
3554    pub fn set_agents(mut self, agents: Vec<Participant>) -> Self {
3555        self.agents = agents;
3556        self
3557    }
3558
3559    pub fn add_metadata(mut self, key: String, value: serde_json::Value) -> Self {
3560        self.metadata.insert(key, value);
3561        self
3562    }
3563
3564    pub fn set_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
3565        self.metadata = metadata;
3566        self
3567    }
3568
3569    pub fn add_attachment(mut self, attachment: Attachment) -> Self {
3570        self.attachments
3571            .get_or_insert_with(Vec::new)
3572            .push(attachment);
3573        self
3574    }
3575
3576    pub fn set_attachments(mut self, attachments: Vec<Attachment>) -> Self {
3577        self.attachments = Some(attachments);
3578        self
3579    }
3580
3581    pub fn build(self) -> Result<Payment> {
3582        let payment = Payment {
3583            transaction_id: self
3584                .transaction_id
3585                .ok_or_else(|| Error::Validation("Transaction ID is required".to_string()))?,
3586            thid: self.thid,
3587            pthid: self.pthid,
3588            merchant: self
3589                .merchant
3590                .ok_or_else(|| Error::Validation("Merchant participant is required".to_string()))?,
3591            customer: self
3592                .customer
3593                .ok_or_else(|| Error::Validation("Customer participant is required".to_string()))?,
3594            asset: self
3595                .asset
3596                .ok_or_else(|| Error::Validation("Asset ID is required".to_string()))?,
3597            amount: self
3598                .amount
3599                .ok_or_else(|| Error::Validation("Amount is required".to_string()))?,
3600            order_details: self.order_details,
3601            timestamp: self.timestamp.unwrap_or_else(|| Utc::now().to_rfc3339()),
3602            expires: self.expires,
3603            note: self.note,
3604            agents: self.agents,
3605            metadata: self.metadata,
3606            attachments: self.attachments,
3607        };
3608
3609        payment.validate()?;
3610
3611        Ok(payment)
3612    }
3613}
3614
3615/// Helper methods for the Payment struct
3616impl Payment {
3617    /// Generates a unique message ID for authorization, rejection, or settlement
3618    pub fn message_id(&self) -> String {
3619        uuid::Uuid::new_v4().to_string()
3620    }
3621
3622    /// Requires proof of control from a specific agent
3623    pub fn require_proof_of_control(
3624        &self,
3625        _agent: String,
3626        _challenge: String,
3627    ) -> RequireProofOfControl {
3628        RequireProofOfControl {
3629            from: None,                   // Placeholder
3630            from_role: None,              // Placeholder
3631            from_agent: None,             // Placeholder
3632            address_id: String::new(),    // Placeholder
3633            purpose: Some(String::new()), // Placeholder
3634        }
3635    }
3636}