1extern 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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
23#[allow(non_snake_case)]
24pub struct Participant {
25 #[serde(default)]
27 pub id: String,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 #[serde(default)]
32 pub role: Option<String>,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 #[serde(default)]
37 pub policies: Option<Vec<Policy>>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub leiCode: Option<String>,
42}
43
44impl Participant {
45 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 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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
68pub struct AttachmentData {
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub base64: Option<String>,
72
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub json: Option<serde_json::Value>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
80pub struct Attachment {
81 pub id: String,
83
84 #[serde(rename = "media_type")]
86 pub media_type: String,
87
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub data: Option<AttachmentData>,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95#[serde(rename_all = "lowercase")]
96pub enum AttachmentFormat {
97 Base64,
99
100 Json(serde_json::Value),
102
103 Links { links: Vec<String> },
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct Transfer {
110 pub asset: AssetId,
112
113 #[serde(rename = "originator")]
115 pub originator: Participant,
116
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub beneficiary: Option<Participant>,
120
121 pub amount: String,
123
124 #[serde(default)]
126 pub agents: Vec<Participant>,
127
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub memo: Option<String>,
131
132 #[serde(rename = "settlementId", skip_serializing_if = "Option::is_none")]
134 pub settlement_id: Option<String>,
135
136 #[serde(skip)]
138 pub transaction_id: String,
139
140 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
142 pub metadata: HashMap<String, serde_json::Value>,
143}
144
145impl Transfer {
146 pub fn builder() -> TransferBuilder {
175 TransferBuilder::default()
176 }
177
178 pub fn message_id(&self) -> String {
180 uuid::Uuid::new_v4().to_string()
181 }
182
183 pub fn validate(&self) -> Result<()> {
185 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 if self.originator.id.is_empty() {
193 return Err(Error::Validation("Originator ID is required".to_string()));
194 }
195
196 if self.amount.is_empty() {
198 return Err(Error::Validation("Amount is required".to_string()));
199 }
200
201 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 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#[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 pub fn asset(mut self, asset: AssetId) -> Self {
242 self.asset = Some(asset);
243 self
244 }
245
246 pub fn originator(mut self, originator: Participant) -> Self {
248 self.originator = Some(originator);
249 self
250 }
251
252 pub fn amount(mut self, amount: String) -> Self {
254 self.amount = Some(amount);
255 self
256 }
257
258 pub fn beneficiary(mut self, beneficiary: Participant) -> Self {
260 self.beneficiary = Some(beneficiary);
261 self
262 }
263
264 pub fn settlement_id(mut self, settlement_id: String) -> Self {
266 self.settlement_id = Some(settlement_id);
267 self
268 }
269
270 pub fn memo(mut self, memo: String) -> Self {
272 self.memo = Some(memo);
273 self
274 }
275
276 pub fn transaction_id(mut self, transaction_id: String) -> Self {
278 self.transaction_id = Some(transaction_id);
279 self
280 }
281
282 pub fn add_agent(mut self, agent: Participant) -> Self {
284 self.agents.push(agent);
285 self
286 }
287
288 pub fn agents(mut self, agents: Vec<Participant>) -> Self {
290 self.agents = agents;
291 self
292 }
293
294 pub fn add_metadata(mut self, key: String, value: serde_json::Value) -> Self {
296 self.metadata.insert(key, value);
297 self
298 }
299
300 pub fn metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
302 self.metadata = metadata;
303 self
304 }
305
306 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 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 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 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 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 if self.originator.id.is_empty() {
392 return Err(Error::Validation("Originator ID is required".to_string()));
393 }
394
395 if self.amount.is_empty() {
397 return Err(Error::Validation("Amount is required".to_string()));
398 }
399
400 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 let mut body_json =
419 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
420
421 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 let mut agent_dids = Vec::new();
431
432 agent_dids.push(self.originator.id.clone());
434
435 if let Some(beneficiary) = &self.beneficiary {
437 agent_dids.push(beneficiary.id.clone());
438 }
439
440 for agent in &self.agents {
442 agent_dids.push(agent.id.clone());
443 }
444
445 agent_dids.sort();
447 agent_dids.dedup();
448
449 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 let pthid = self
458 .connection_id()
459 .map(|connect_id| connect_id.to_string());
460
461 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 let mut message = self.to_didcomm(from)?;
487
488 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
507pub struct RequestPresentation {
508 pub transaction_id: String,
510
511 pub presentation_definition: String,
513
514 #[serde(skip_serializing_if = "Option::is_none")]
516 pub description: Option<String>,
517
518 pub challenge: String,
520
521 #[serde(skip_serializing_if = "Option::is_none")]
523 pub for_originator: Option<bool>,
524
525 #[serde(skip_serializing_if = "Option::is_none")]
527 pub for_beneficiary: Option<bool>,
528
529 #[serde(skip_serializing_if = "Option::is_none")]
531 pub note: Option<String>,
532
533 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
535 pub metadata: HashMap<String, serde_json::Value>,
536}
537
538#[derive(Debug, Clone, Serialize, Deserialize)]
540pub struct Presentation {
541 pub challenge: String,
543
544 pub credentials: Vec<serde_json::Value>,
546
547 #[serde(skip_serializing_if = "Option::is_none")]
549 pub transaction_id: Option<String>,
550
551 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
553 pub metadata: HashMap<String, serde_json::Value>,
554}
555
556#[derive(Debug, Clone, Serialize, Deserialize)]
558pub struct Authorize {
559 pub transaction_id: String,
561
562 #[serde(skip_serializing_if = "Option::is_none")]
564 pub note: Option<String>,
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize)]
569pub struct Reject {
570 pub transaction_id: String,
572
573 pub reason: String,
575}
576
577#[derive(Debug, Clone, Serialize, Deserialize)]
579pub struct Settle {
580 pub transaction_id: String,
582
583 pub settlement_id: String,
585
586 #[serde(skip_serializing_if = "Option::is_none")]
588 pub amount: Option<String>,
589}
590
591#[derive(Debug, Clone, Serialize, Deserialize)]
593pub struct Cancel {
594 pub transaction_id: String,
596
597 #[serde(skip_serializing_if = "Option::is_none")]
599 pub reason: Option<String>,
600
601 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
661pub struct Revert {
662 pub transaction_id: String,
664
665 pub settlement_address: String,
667
668 pub reason: String,
670
671 #[serde(skip_serializing_if = "Option::is_none")]
673 pub note: Option<String>,
674}
675
676#[derive(Debug, Clone, Serialize, Deserialize)]
678pub struct AddAgents {
679 #[serde(rename = "transfer_id")]
681 pub transaction_id: String,
682
683 pub agents: Vec<Participant>,
685}
686
687#[derive(Debug, Clone, Serialize, Deserialize)]
691pub struct ReplaceAgent {
692 #[serde(rename = "transfer_id")]
694 pub transaction_id: String,
695
696 pub original: String,
698
699 pub replacement: Participant,
701}
702
703#[derive(Debug, Clone, Serialize, Deserialize)]
707pub struct RemoveAgent {
708 #[serde(rename = "transfer_id")]
710 pub transaction_id: String,
711
712 pub agent: String,
714}
715
716#[derive(Debug, Clone, Serialize, Deserialize)]
720pub struct ConfirmRelationship {
721 #[serde(rename = "transfer_id")]
723 pub transaction_id: String,
724
725 pub agent_id: String,
727
728 #[serde(rename = "for")]
730 pub for_id: String,
731
732 #[serde(skip_serializing_if = "Option::is_none")]
734 pub role: Option<String>,
735}
736
737impl ConfirmRelationship {
738 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 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 let mut body_json =
784 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
785
786 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 }
794
795 let id = uuid::Uuid::new_v4().to_string(); let created_time = Utc::now().timestamp_millis() as u64;
798
799 let to = Some(vec![self.agent_id.clone()]);
801
802 let message = Message {
804 id,
805 typ: "application/didcomm-plain+json".to_string(), type_: Self::message_type().to_string(),
807 from: from_did.map(|s| s.to_string()),
808 to, thid: Some(self.transaction_id.clone()),
810 pthid: None, 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#[derive(Debug, Clone, Serialize, Deserialize)]
902pub struct UpdateParty {
903 pub transaction_id: String,
905
906 #[serde(rename = "partyType")]
908 pub party_type: String,
909
910 pub party: Participant,
912
913 #[serde(skip_serializing_if = "Option::is_none")]
915 pub note: Option<String>,
916
917 #[serde(rename = "@context", skip_serializing_if = "Option::is_none")]
919 pub context: Option<String>,
920}
921
922impl UpdateParty {
923 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 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 let mut body_json =
966 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
967
968 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 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#[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 let mut body_json =
1098 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1099
1100 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 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 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 let mut body_json = message.body.clone();
1142
1143 if let Some(body_obj) = body_json.as_object_mut() {
1145 body_obj.remove("@type");
1146 }
1147
1148 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#[derive(Debug, Clone, Serialize, Deserialize)]
1161pub struct ErrorBody {
1162 pub code: String,
1164
1165 pub description: String,
1167
1168 #[serde(skip_serializing_if = "Option::is_none")]
1170 pub original_message_id: Option<String>,
1171
1172 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
1174 pub metadata: HashMap<String, serde_json::Value>,
1175}
1176
1177pub trait Validate {
1179 fn validate(&self) -> crate::error::Result<()>;
1181}
1182
1183pub trait Authorizable {
1185 fn authorize(&self, note: Option<String>) -> Authorize;
1195
1196 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 fn reject(&self, code: String, description: String) -> Reject;
1227
1228 fn settle(&self, settlement_id: String, amount: Option<String>) -> Settle;
1239
1240 fn update_party(
1253 &self,
1254 transaction_id: String,
1255 party_type: String,
1256 party: Participant,
1257 note: Option<String>,
1258 ) -> UpdateParty;
1259
1260 fn update_policies(&self, transaction_id: String, policies: Vec<Policy>) -> UpdatePolicies;
1271
1272 fn add_agents(&self, transaction_id: String, agents: Vec<Participant>) -> AddAgents;
1283
1284 fn replace_agent(
1296 &self,
1297 transaction_id: String,
1298 original: String,
1299 replacement: Participant,
1300 ) -> ReplaceAgent;
1301
1302 fn remove_agent(&self, transaction_id: String, agent: String) -> RemoveAgent;
1313}
1314
1315impl 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 let mut body_json =
1335 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1336
1337 if let Some(body_obj) = body_json.as_object_mut() {
1339 body_obj.insert(
1341 "@type".to_string(),
1342 serde_json::Value::String(Self::message_type().to_string()),
1343 );
1344 }
1345
1346 let id = uuid::Uuid::new_v4().to_string();
1348 let created_time = Utc::now().timestamp() as u64;
1349
1350 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 let mut body_json =
1397 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1398
1399 if let Some(body_obj) = body_json.as_object_mut() {
1401 body_obj.insert(
1403 "@type".to_string(),
1404 serde_json::Value::String(Self::message_type().to_string()),
1405 );
1406 }
1407
1408 let id = uuid::Uuid::new_v4().to_string();
1410 let created_time = Utc::now().timestamp() as u64;
1411
1412 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 let mut body_json =
1467 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1468
1469 if let Some(body_obj) = body_json.as_object_mut() {
1471 body_obj.insert(
1473 "@type".to_string(),
1474 serde_json::Value::String(Self::message_type().to_string()),
1475 );
1476 }
1477
1478 let id = uuid::Uuid::new_v4().to_string();
1480 let created_time = Utc::now().timestamp() as u64;
1481
1482 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 let mut body_json =
1529 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1530
1531 if let Some(body_obj) = body_json.as_object_mut() {
1533 body_obj.insert(
1535 "@type".to_string(),
1536 serde_json::Value::String(Self::message_type().to_string()),
1537 );
1538 }
1539
1540 let id = uuid::Uuid::new_v4().to_string();
1542 let created_time = Utc::now().timestamp() as u64;
1543
1544 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 let mut body_json =
1597 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1598
1599 if let Some(body_obj) = body_json.as_object_mut() {
1601 body_obj.insert(
1603 "@type".to_string(),
1604 serde_json::Value::String(Self::message_type().to_string()),
1605 );
1606 }
1607
1608 let id = uuid::Uuid::new_v4().to_string();
1610 let created_time = Utc::now().timestamp() as u64;
1611
1612 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 let mut body_json =
1665 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1666
1667 if let Some(body_obj) = body_json.as_object_mut() {
1669 body_obj.insert(
1671 "@type".to_string(),
1672 serde_json::Value::String(Self::message_type().to_string()),
1673 );
1674 }
1675
1676 let id = uuid::Uuid::new_v4().to_string();
1678 let created_time = Utc::now().timestamp() as u64;
1679
1680 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 let mut body_json =
1725 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1726
1727 if let Some(body_obj) = body_json.as_object_mut() {
1729 body_obj.insert(
1731 "@type".to_string(),
1732 serde_json::Value::String(Self::message_type().to_string()),
1733 );
1734 }
1735
1736 let id = uuid::Uuid::new_v4().to_string();
1738 let created_time = Utc::now().timestamp() as u64;
1739
1740 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 let mut body_json =
1787 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
1788
1789 if let Some(body_obj) = body_json.as_object_mut() {
1791 body_obj.insert(
1793 "@type".to_string(),
1794 serde_json::Value::String(Self::message_type().to_string()),
1795 );
1796 }
1797
1798 let id = uuid::Uuid::new_v4().to_string();
1800 let created_time = Utc::now().timestamp() as u64;
1801
1802 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#[derive(Debug, Clone, Serialize, Deserialize)]
1825pub struct PaymentRequest {
1826 #[serde(skip_serializing_if = "Option::is_none")]
1828 pub asset: Option<AssetId>,
1829
1830 #[serde(skip_serializing_if = "Option::is_none")]
1832 pub currency: Option<String>,
1833
1834 pub amount: String,
1836
1837 #[serde(rename = "supportedAssets", skip_serializing_if = "Option::is_none")]
1839 pub supported_assets: Option<Vec<String>>,
1840
1841 #[serde(skip_serializing_if = "Option::is_none")]
1843 pub invoice: Option<crate::message::invoice::Invoice>,
1844
1845 #[serde(skip_serializing_if = "Option::is_none")]
1847 pub expiry: Option<String>,
1848
1849 pub merchant: Participant,
1851
1852 #[serde(skip_serializing_if = "Option::is_none")]
1854 pub customer: Option<Participant>,
1855
1856 pub agents: Vec<Participant>,
1858
1859 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
1861 pub metadata: HashMap<String, serde_json::Value>,
1862}
1863
1864impl PaymentRequest {
1865 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 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 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 pub fn validate(&self) -> Result<()> {
1925 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 if self.amount.trim().is_empty() {
1934 return Err(Error::Validation("Amount cannot be empty".to_string()));
1935 }
1936
1937 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 if self.merchant.id.trim().is_empty() {
1952 return Err(Error::Validation("Merchant ID is required".to_string()));
1953 }
1954
1955 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 if self.agents.is_empty() {
1969 return Err(Error::Validation("Agents cannot be empty".to_string()));
1970 }
1971
1972 if let Some(invoice) = &self.invoice {
1974 invoice.validate()?;
1976
1977 if let Ok(amount_f64) = self.amount.parse::<f64>() {
1979 let difference = (amount_f64 - invoice.total).abs();
1980 if difference > 0.01 {
1981 return Err(Error::Validation(format!(
1983 "Invoice total ({}) does not match payment amount ({})",
1984 invoice.total, amount_f64
1985 )));
1986 }
1987 }
1988
1989 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)] 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 let mut message = self.to_didcomm(from)?;
2010
2011 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 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 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 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 if self.amount.trim().is_empty() {
2060 return Err(Error::Validation("Amount cannot be empty".to_string()));
2061 }
2062
2063 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 if self.merchant.id.trim().is_empty() {
2078 return Err(Error::Validation("Merchant ID is required".to_string()));
2079 }
2080
2081 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 if self.agents.is_empty() {
2095 return Err(Error::Validation("Agents cannot be empty".to_string()));
2096 }
2097
2098 if let Some(invoice) = &self.invoice {
2100 invoice.validate()?;
2102
2103 if let Ok(amount_f64) = self.amount.parse::<f64>() {
2105 let difference = (amount_f64 - invoice.total).abs();
2106 if difference > 0.01 {
2107 return Err(Error::Validation(format!(
2109 "Invoice total ({}) does not match payment amount ({})",
2110 invoice.total, amount_f64
2111 )));
2112 }
2113 }
2114
2115 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 let mut body_json =
2132 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
2133
2134 if let Some(body_obj) = body_json.as_object_mut() {
2136 body_obj.insert(
2138 "@type".to_string(),
2139 serde_json::Value::String(Self::message_type().to_string()),
2140 );
2141 }
2142
2143 let mut agent_dids = Vec::new();
2145
2146 agent_dids.push(self.merchant.id.clone());
2148
2149 if let Some(customer) = &self.customer {
2151 agent_dids.push(customer.id.clone());
2152 }
2153
2154 for agent in &self.agents {
2156 agent_dids.push(agent.id.clone());
2157 }
2158
2159 agent_dids.sort();
2161 agent_dids.dedup();
2162
2163 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 let pthid = self
2172 .connection_id()
2173 .map(|connect_id| connect_id.to_string());
2174
2175 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#[derive(Debug, Clone, Serialize, Deserialize)]
2198pub struct ConnectionConstraints {
2199 #[serde(skip_serializing_if = "Option::is_none")]
2201 pub purposes: Option<Vec<String>>,
2202
2203 #[serde(skip_serializing_if = "Option::is_none")]
2205 pub category_purposes: Option<Vec<String>>,
2206
2207 #[serde(skip_serializing_if = "Option::is_none")]
2209 pub limits: Option<TransactionLimits>,
2210}
2211
2212#[derive(Debug, Clone, Serialize, Deserialize)]
2214pub struct TransactionLimits {
2215 #[serde(rename = "per_transaction", skip_serializing_if = "Option::is_none")]
2217 pub per_transaction: Option<String>,
2218
2219 #[serde(skip_serializing_if = "Option::is_none")]
2221 pub daily: Option<String>,
2222
2223 pub currency: String,
2225}
2226
2227#[derive(Debug, Clone, Serialize, Deserialize)]
2229pub struct Connect {
2230 #[serde(skip_serializing_if = "Option::is_none")]
2232 pub agent: Option<Agent>,
2233
2234 pub for_id: String,
2236
2237 pub constraints: ConnectionConstraints,
2239
2240 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
2242 pub metadata: HashMap<String, serde_json::Value>,
2243}
2244
2245impl Connect {
2246 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 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 pub fn validate(&self) -> Result<()> {
2268 if self.for_id.trim().is_empty() {
2270 return Err(Error::Validation("For ID cannot be empty".to_string()));
2271 }
2272
2273 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 if self.for_id.trim().is_empty() {
2294 return Err(Error::Validation("For ID cannot be empty".to_string()));
2295 }
2296
2297 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 let mut body_json =
2312 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
2313
2314 if let Some(body_obj) = body_json.as_object_mut() {
2316 body_obj.insert(
2318 "@type".to_string(),
2319 serde_json::Value::String(Self::message_type().to_string()),
2320 );
2321 }
2322
2323 let mut agent_dids = Vec::new();
2325
2326 if let Some(agent) = &self.agent {
2328 agent_dids.push(agent.id.clone());
2329 }
2330
2331 if self.for_id.starts_with("did:") {
2333 agent_dids.push(self.for_id.clone());
2334 }
2335
2336 agent_dids.sort();
2338 agent_dids.dedup();
2339
2340 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 let pthid = None;
2349
2350 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#[derive(Debug, Clone, Serialize, Deserialize)]
2373pub struct Agent {
2374 pub id: String,
2376
2377 #[serde(skip_serializing_if = "Option::is_none")]
2379 pub name: Option<String>,
2380
2381 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
2383 pub agent_type: Option<String>,
2384
2385 #[serde(skip_serializing_if = "Option::is_none")]
2387 pub service_url: Option<String>,
2388}
2389
2390#[derive(Debug, Clone, Serialize, Deserialize)]
2392pub struct AuthorizationRequired {
2393 pub authorization_url: String,
2395
2396 pub expires: String,
2398
2399 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
2401 pub metadata: HashMap<String, serde_json::Value>,
2402}
2403
2404impl AuthorizationRequired {
2405 pub fn new(authorization_url: String, expires: String) -> Self {
2407 Self {
2408 authorization_url,
2409 expires,
2410 metadata: HashMap::new(),
2411 }
2412 }
2413
2414 pub fn validate(&self) -> Result<()> {
2416 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 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 let mut body_json =
2478 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
2479
2480 if let Some(body_obj) = body_json.as_object_mut() {
2482 body_obj.insert(
2484 "@type".to_string(),
2485 serde_json::Value::String(Self::message_type().to_string()),
2486 );
2487 }
2488
2489 let id = uuid::Uuid::new_v4().to_string();
2491 let created_time = Utc::now().timestamp() as u64;
2492
2493 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#[derive(Debug, Clone, Serialize, Deserialize)]
2516pub struct OutOfBand {
2517 #[serde(skip_serializing_if = "Option::is_none")]
2519 pub goal_code: Option<String>,
2520
2521 #[serde(skip_serializing_if = "Option::is_none")]
2523 pub goal: Option<String>,
2524
2525 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2527 pub attachments: Vec<Attachment>,
2528
2529 #[serde(skip_serializing_if = "Option::is_none")]
2531 pub accept: Option<Vec<String>>,
2532
2533 #[serde(skip_serializing_if = "Option::is_none")]
2535 pub handshake_protocols: Option<Vec<String>>,
2536
2537 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
2539 pub metadata: HashMap<String, serde_json::Value>,
2540}
2541
2542impl OutOfBand {
2543 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 pub fn validate(&self) -> Result<()> {
2561 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 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 let mut body_json =
2605 serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
2606
2607 if let Some(body_obj) = body_json.as_object_mut() {
2609 body_obj.insert(
2611 "@type".to_string(),
2612 serde_json::Value::String(Self::message_type().to_string()),
2613 );
2614 }
2615
2616 let id = uuid::Uuid::new_v4().to_string();
2618 let created_time = Utc::now().timestamp() as u64;
2619
2620 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#[derive(Debug, Clone, Serialize, Deserialize)]
2696pub struct DIDCommPresentation {
2697 #[serde(skip_serializing_if = "Option::is_none")]
2699 pub thid: Option<String>,
2700
2701 #[serde(skip_serializing_if = "Option::is_none")]
2703 pub comment: Option<String>,
2704
2705 #[serde(skip_serializing_if = "Option::is_none")]
2707 pub goal_code: Option<String>,
2708
2709 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2711 pub attachments: Vec<Attachment>,
2712
2713 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
2715 pub metadata: HashMap<String, serde_json::Value>,
2716}
2717
2718impl DIDCommPresentation {
2719 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 pub fn validate(&self) -> Result<()> {
2761 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 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 if attachment.id.trim().is_empty() {
2778 return Err(Error::Validation(
2779 "Attachment ID cannot be empty".to_string(),
2780 ));
2781 }
2782
2783 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 if attachment.data.is_none() {
2792 return Err(Error::Validation(
2793 "Attachment must include data".to_string(),
2794 ));
2795 }
2796
2797 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 let Some(json_data) = &data.json {
2807 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 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
2828impl 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 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 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 let thid = message.thid.clone();
2869
2870 let comment = body
2872 .get("comment")
2873 .and_then(|v| v.as_str())
2874 .map(ToString::to_string);
2875
2876 let goal_code = body
2878 .get("goal_code")
2879 .and_then(|v| v.as_str())
2880 .map(ToString::to_string);
2881
2882 let attachments = if let Some(didcomm_attachments) = &message.attachments {
2884 didcomm_attachments
2885 .iter()
2886 .filter_map(|a| {
2887 if a.id.is_none() || a.media_type.is_none() {
2889 return None;
2890 }
2891
2892 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 None
2905 }
2906 };
2907
2908 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 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 let mut body = serde_json::Map::new();
2944
2945 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 for (key, value) in &self.metadata {
2962 body.insert(key.clone(), value.clone());
2963 }
2964
2965 let didcomm_attachments = if !self.attachments.is_empty() {
2967 let attachments: Vec<didcomm::Attachment> = self
2968 .attachments
2969 .iter()
2970 .filter_map(|a| {
2971 let didcomm_data = match &a.data {
2973 Some(data) => {
2974 if let Some(base64_data) = &data.base64 {
2975 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 didcomm::AttachmentData::Json {
2985 value: didcomm::JsonAttachmentData {
2986 json: json_data.clone(),
2987 jws: None,
2988 },
2989 }
2990 } else {
2991 return None;
2993 }
2994 }
2995 None => {
2996 return None;
2998 }
2999 };
3000
3001 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 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
3045impl Authorizable for Transfer {
3047 fn authorize(&self, note: Option<String>) -> Authorize {
3057 Authorize {
3058 transaction_id: self.message_id(),
3059 note,
3060 }
3061 }
3062
3063 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 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 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 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 fn update_policies(&self, transaction_id: String, policies: Vec<Policy>) -> UpdatePolicies {
3164 UpdatePolicies {
3165 transaction_id,
3166 policies,
3167 }
3168 }
3169
3170 fn add_agents(&self, transaction_id: String, agents: Vec<Participant>) -> AddAgents {
3181 AddAgents {
3182 transaction_id,
3183 agents,
3184 }
3185 }
3186
3187 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 fn remove_agent(&self, transaction_id: String, agent: String) -> RemoveAgent {
3222 RemoveAgent {
3223 transaction_id,
3224 agent,
3225 }
3226 }
3227}
3228
3229#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3232pub struct Payment {
3233 pub transaction_id: String,
3235 #[serde(skip_serializing_if = "Option::is_none")]
3237 pub thid: Option<String>,
3238 #[serde(skip_serializing_if = "Option::is_none")]
3240 pub pthid: Option<String>,
3241 pub merchant: Participant,
3243 pub customer: Participant,
3245 pub asset: AssetId,
3247 pub amount: String,
3249 #[serde(skip_serializing_if = "Option::is_none")]
3251 pub order_details: Option<HashMap<String, serde_json::Value>>,
3252 pub timestamp: String,
3254 #[serde(skip_serializing_if = "Option::is_none")]
3256 pub expires: Option<String>,
3257 #[serde(skip_serializing_if = "Option::is_none")]
3259 pub note: Option<String>,
3260 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3262 pub agents: Vec<Participant>,
3263 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
3265 pub metadata: HashMap<String, serde_json::Value>,
3266 #[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 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 if self.merchant.id.is_empty() {
3284 return Err(Error::Validation("Merchant ID is required".to_string()));
3285 }
3286
3287 if self.customer.id.is_empty() {
3289 return Err(Error::Validation("Customer ID is required".to_string()));
3290 }
3291
3292 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 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 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 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#[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
3615impl Payment {
3617 pub fn message_id(&self) -> String {
3619 uuid::Uuid::new_v4().to_string()
3620 }
3621
3622 pub fn require_proof_of_control(
3624 &self,
3625 _agent: String,
3626 _challenge: String,
3627 ) -> RequireProofOfControl {
3628 RequireProofOfControl {
3629 from: None, from_role: None, from_agent: None, address_id: String::new(), purpose: Some(String::new()), }
3635 }
3636}