1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9use tap_caip::AssetId;
10
11use crate::error::{Error, Result};
12use crate::message::agent::TapParticipant;
13use crate::message::tap_message_trait::{TapMessage as TapMessageTrait, TapMessageBody};
14use crate::message::{Agent, Party};
15use crate::settlement_address::SettlementAddress;
16use crate::TapMessage;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
21#[serde(untagged)]
22pub enum SupportedAsset {
23 Simple(AssetId),
25 Priced(AssetPricing),
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct AssetPricing {
35 pub asset: String,
37 pub amount: String,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub expires: Option<String>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46#[serde(untagged)]
47pub enum InvoiceReference {
48 Url(String),
50 Object(Box<crate::message::Invoice>),
52}
53
54impl InvoiceReference {
55 pub fn is_url(&self) -> bool {
57 matches!(self, InvoiceReference::Url(_))
58 }
59
60 pub fn is_object(&self) -> bool {
62 matches!(self, InvoiceReference::Object(_))
63 }
64
65 pub fn as_url(&self) -> Option<&str> {
67 match self {
68 InvoiceReference::Url(url) => Some(url),
69 _ => None,
70 }
71 }
72
73 pub fn as_object(&self) -> Option<&crate::message::Invoice> {
75 match self {
76 InvoiceReference::Object(invoice) => Some(invoice.as_ref()),
77 _ => None,
78 }
79 }
80
81 pub fn validate(&self) -> Result<()> {
83 match self {
84 InvoiceReference::Url(url) => {
85 if url.is_empty() {
87 return Err(Error::Validation("Invoice URL cannot be empty".to_string()));
88 }
89 Ok(())
91 }
92 InvoiceReference::Object(invoice) => {
93 invoice.validate()
95 }
96 }
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
107#[tap(
108 message_type = "https://tap.rsvp/schema/1.0#Payment",
109 initiator,
110 authorizable,
111 transactable
112)]
113pub struct Payment {
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub asset: Option<AssetId>,
117
118 pub amount: String,
120
121 #[serde(rename = "currency", skip_serializing_if = "Option::is_none")]
123 pub currency_code: Option<String>,
124
125 #[serde(rename = "supportedAssets", skip_serializing_if = "Option::is_none")]
128 pub supported_assets: Option<Vec<SupportedAsset>>,
129
130 #[serde(skip_serializing_if = "Option::is_none")]
132 #[tap(participant)]
133 pub customer: Option<Party>,
134
135 #[tap(participant)]
137 pub merchant: Party,
138
139 #[serde(skip)]
141 #[tap(transaction_id)]
142 pub transaction_id: Option<String>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub memo: Option<String>,
147
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub expiry: Option<String>,
151
152 #[serde(skip_serializing_if = "Option::is_none")]
154 pub invoice: Option<InvoiceReference>,
155
156 #[serde(default)]
158 #[tap(participant_list)]
159 pub agents: Vec<Agent>,
160
161 #[serde(skip_serializing_if = "Option::is_none")]
163 #[tap(connection_id)]
164 pub connection_id: Option<String>,
165
166 #[serde(
168 rename = "fallbackSettlementAddresses",
169 skip_serializing_if = "Option::is_none"
170 )]
171 pub fallback_settlement_addresses: Option<Vec<SettlementAddress>>,
172
173 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
175 pub metadata: HashMap<String, serde_json::Value>,
176}
177
178#[derive(Default)]
180pub struct PaymentBuilder {
181 asset: Option<AssetId>,
182 amount: Option<String>,
183 currency_code: Option<String>,
184 supported_assets: Option<Vec<SupportedAsset>>,
185 customer: Option<Party>,
186 merchant: Option<Party>,
187 transaction_id: Option<String>,
188 memo: Option<String>,
189 expiry: Option<String>,
190 invoice: Option<InvoiceReference>,
191 agents: Vec<Agent>,
192 fallback_settlement_addresses: Option<Vec<SettlementAddress>>,
193 metadata: HashMap<String, serde_json::Value>,
194}
195
196impl PaymentBuilder {
197 pub fn asset(mut self, asset: AssetId) -> Self {
199 self.asset = Some(asset);
200 self
201 }
202
203 pub fn amount(mut self, amount: String) -> Self {
205 self.amount = Some(amount);
206 self
207 }
208
209 pub fn currency_code(mut self, currency_code: String) -> Self {
211 self.currency_code = Some(currency_code);
212 self
213 }
214
215 pub fn supported_assets(mut self, supported_assets: Vec<SupportedAsset>) -> Self {
217 self.supported_assets = Some(supported_assets);
218 self
219 }
220
221 pub fn add_supported_asset(mut self, asset: AssetId) -> Self {
223 if let Some(assets) = &mut self.supported_assets {
224 assets.push(SupportedAsset::Simple(asset));
225 } else {
226 self.supported_assets = Some(vec![SupportedAsset::Simple(asset)]);
227 }
228 self
229 }
230
231 pub fn add_priced_asset(mut self, pricing: AssetPricing) -> Self {
233 if let Some(assets) = &mut self.supported_assets {
234 assets.push(SupportedAsset::Priced(pricing));
235 } else {
236 self.supported_assets = Some(vec![SupportedAsset::Priced(pricing)]);
237 }
238 self
239 }
240
241 pub fn customer(mut self, customer: Party) -> Self {
243 self.customer = Some(customer);
244 self
245 }
246
247 pub fn merchant(mut self, merchant: Party) -> Self {
249 self.merchant = Some(merchant);
250 self
251 }
252
253 pub fn transaction_id(mut self, transaction_id: String) -> Self {
255 self.transaction_id = Some(transaction_id);
256 self
257 }
258
259 pub fn memo(mut self, memo: String) -> Self {
261 self.memo = Some(memo);
262 self
263 }
264
265 pub fn expiry(mut self, expiry: String) -> Self {
267 self.expiry = Some(expiry);
268 self
269 }
270
271 pub fn invoice(mut self, invoice: crate::message::Invoice) -> Self {
273 self.invoice = Some(InvoiceReference::Object(Box::new(invoice)));
274 self
275 }
276
277 pub fn invoice_url(mut self, url: String) -> Self {
279 self.invoice = Some(InvoiceReference::Url(url));
280 self
281 }
282
283 pub fn add_agent(mut self, agent: Agent) -> Self {
285 self.agents.push(agent);
286 self
287 }
288
289 pub fn agents(mut self, agents: Vec<Agent>) -> Self {
291 self.agents = agents;
292 self
293 }
294
295 pub fn add_metadata(mut self, key: String, value: serde_json::Value) -> Self {
297 self.metadata.insert(key, value);
298 self
299 }
300
301 pub fn metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
303 self.metadata = metadata;
304 self
305 }
306
307 pub fn add_fallback_settlement_address(mut self, address: SettlementAddress) -> Self {
309 if let Some(addresses) = &mut self.fallback_settlement_addresses {
310 addresses.push(address);
311 } else {
312 self.fallback_settlement_addresses = Some(vec![address]);
313 }
314 self
315 }
316
317 pub fn fallback_settlement_addresses(mut self, addresses: Vec<SettlementAddress>) -> Self {
319 self.fallback_settlement_addresses = Some(addresses);
320 self
321 }
322
323 pub fn build(self) -> Payment {
329 if self.asset.is_none() && self.currency_code.is_none() {
331 panic!("Either asset or currency_code is required");
332 }
333
334 Payment {
335 asset: self.asset,
336 amount: self.amount.expect("Amount is required"),
337 currency_code: self.currency_code,
338 supported_assets: self.supported_assets,
339 customer: self.customer,
340 merchant: self.merchant.expect("Merchant is required"),
341 transaction_id: self.transaction_id,
342 memo: self.memo,
343 expiry: self.expiry,
344 invoice: self.invoice,
345 agents: self.agents,
346 connection_id: None,
347 fallback_settlement_addresses: self.fallback_settlement_addresses,
348 metadata: self.metadata,
349 }
350 }
351}
352
353impl Payment {
354 pub fn builder() -> PaymentBuilder {
356 PaymentBuilder::default()
357 }
358
359 pub fn with_asset(asset: AssetId, amount: String, merchant: Party, agents: Vec<Agent>) -> Self {
361 Self {
362 asset: Some(asset),
363 amount,
364 currency_code: None,
365 supported_assets: None,
366 customer: None,
367 merchant,
368 transaction_id: None,
369 memo: None,
370 expiry: None,
371 invoice: None,
372 agents,
373 connection_id: None,
374 fallback_settlement_addresses: None,
375 metadata: HashMap::new(),
376 }
377 }
378
379 pub fn with_currency(
381 currency_code: String,
382 amount: String,
383 merchant: Party,
384 agents: Vec<Agent>,
385 ) -> Self {
386 Self {
387 asset: None,
388 amount,
389 currency_code: Some(currency_code),
390 supported_assets: None,
391 customer: None,
392 merchant,
393 transaction_id: None,
394 memo: None,
395 expiry: None,
396 invoice: None,
397 agents,
398 connection_id: None,
399 fallback_settlement_addresses: None,
400 metadata: HashMap::new(),
401 }
402 }
403
404 pub fn with_currency_and_assets(
406 currency_code: String,
407 amount: String,
408 supported_assets: Vec<SupportedAsset>,
409 merchant: Party,
410 agents: Vec<Agent>,
411 ) -> Self {
412 Self {
413 asset: None,
414 amount,
415 currency_code: Some(currency_code),
416 supported_assets: Some(supported_assets),
417 customer: None,
418 merchant,
419 transaction_id: None,
420 memo: None,
421 expiry: None,
422 invoice: None,
423 agents,
424 connection_id: None,
425 fallback_settlement_addresses: None,
426 metadata: HashMap::new(),
427 }
428 }
429
430 pub fn validate(&self) -> Result<()> {
432 if self.asset.is_none() && self.currency_code.is_none() {
434 return Err(Error::Validation(
435 "Either asset or currency_code must be provided".to_string(),
436 ));
437 }
438
439 if let Some(asset) = &self.asset {
441 if asset.namespace().is_empty() || asset.reference().is_empty() {
442 return Err(Error::Validation("Asset ID is invalid".to_string()));
443 }
444 }
445
446 if self.amount.is_empty() {
448 return Err(Error::Validation("Amount is required".to_string()));
449 }
450
451 match self.amount.parse::<f64>() {
453 Ok(amount) if !amount.is_finite() => {
454 return Err(Error::Validation(
455 "Amount must be a finite number".to_string(),
456 ));
457 }
458 Ok(amount) if amount <= 0.0 => {
459 return Err(Error::Validation("Amount must be positive".to_string()));
460 }
461 Err(_) => {
462 return Err(Error::Validation(
463 "Amount must be a valid number".to_string(),
464 ));
465 }
466 _ => {}
467 }
468
469 if self.merchant.id().is_empty() {
471 return Err(Error::Validation("Merchant ID is required".to_string()));
472 }
473
474 if let Some(supported_assets) = &self.supported_assets {
476 if supported_assets.is_empty() {
477 return Err(Error::Validation(
478 "Supported assets list cannot be empty".to_string(),
479 ));
480 }
481
482 for (i, supported) in supported_assets.iter().enumerate() {
483 match supported {
484 SupportedAsset::Simple(asset) => {
485 if asset.namespace().is_empty() || asset.reference().is_empty() {
486 return Err(Error::Validation(format!(
487 "Supported asset at index {} is invalid",
488 i
489 )));
490 }
491 }
492 SupportedAsset::Priced(pricing) => {
493 if pricing.asset.is_empty() {
494 return Err(Error::Validation(format!(
495 "Supported asset at index {} has empty asset identifier",
496 i
497 )));
498 }
499 if pricing.amount.is_empty() {
500 return Err(Error::Validation(format!(
501 "Supported asset at index {} has empty amount",
502 i
503 )));
504 }
505 }
506 }
507 }
508 }
509
510 if let Some(invoice) = &self.invoice {
512 if let Err(e) = invoice.validate() {
514 return Err(Error::Validation(format!(
515 "Invoice validation failed: {}",
516 e
517 )));
518 }
519 }
520
521 Ok(())
522 }
523}