tap_msg/examples/
invoice_examples.rs

1//! Examples for using the Invoice and Payment functionality.
2
3use crate::didcomm::PlainMessage;
4use crate::error::Result;
5use crate::message::invoice::{Invoice, LineItem, TaxCategory, TaxSubtotal, TaxTotal};
6use crate::message::payment::PaymentBuilder;
7use crate::message::tap_message_trait::TapMessageBody;
8use crate::message::{Participant, Payment};
9use std::collections::HashMap;
10use std::str::FromStr;
11use tap_caip::AssetId;
12
13/// Example of creating a basic invoice with line items
14pub fn create_basic_invoice_example() -> Result<Invoice> {
15    // Create line items
16    let line_items = vec![
17        LineItem {
18            id: "1".to_string(),
19            description: "Widget A".to_string(),
20            quantity: 5.0,
21            unit_code: Some("EA".to_string()),
22            unit_price: 10.0,
23            line_total: 50.0,
24            tax_category: None,
25        },
26        LineItem {
27            id: "2".to_string(),
28            description: "Widget B".to_string(),
29            quantity: 10.0,
30            unit_code: Some("EA".to_string()),
31            unit_price: 5.0,
32            line_total: 50.0,
33            tax_category: None,
34        },
35    ];
36
37    // Calculate the total
38    let total = line_items.iter().map(|item| item.line_total).sum();
39
40    // Create a basic invoice
41    let invoice = Invoice::new(
42        "INV001".to_string(),
43        "2023-09-01".to_string(),
44        "USD".to_string(),
45        line_items,
46        total,
47    );
48
49    // Validate the invoice
50    invoice.validate()?;
51
52    Ok(invoice)
53}
54
55/// Example of creating an invoice with tax information
56pub fn create_invoice_with_tax_example() -> Result<Invoice> {
57    // Create line items
58    let line_items = vec![
59        LineItem {
60            id: "1".to_string(),
61            description: "Widget A".to_string(),
62            quantity: 5.0,
63            unit_code: Some("EA".to_string()),
64            unit_price: 10.0,
65            line_total: 50.0,
66            tax_category: None,
67        },
68        LineItem {
69            id: "2".to_string(),
70            description: "Widget B".to_string(),
71            quantity: 10.0,
72            unit_code: Some("EA".to_string()),
73            unit_price: 5.0,
74            line_total: 50.0,
75            tax_category: None,
76        },
77    ];
78
79    // Calculate the subtotal
80    let sub_total = line_items.iter().map(|item| item.line_total).sum();
81
82    // Create tax information
83    let tax_category = TaxCategory {
84        id: "S".to_string(),
85        percent: 15.0,
86        tax_scheme: "VAT".to_string(),
87    };
88
89    let tax_amount = sub_total * (tax_category.percent / 100.0);
90    let total = sub_total + tax_amount;
91
92    let tax_subtotal = TaxSubtotal {
93        taxable_amount: sub_total,
94        tax_amount,
95        tax_category,
96    };
97
98    let tax_total = TaxTotal {
99        tax_amount,
100        tax_subtotal: Some(vec![tax_subtotal]),
101    };
102
103    // Create the invoice with tax information
104    let invoice = Invoice {
105        id: "INV001".to_string(),
106        issue_date: "2023-09-01".to_string(),
107        currency_code: "USD".to_string(),
108        line_items,
109        tax_total: Some(tax_total),
110        total,
111        sub_total: Some(sub_total),
112        due_date: Some("2023-10-01".to_string()),
113        note: None,
114        payment_terms: Some("NET30".to_string()),
115        accounting_cost: None,
116        order_reference: None,
117        additional_document_reference: None,
118        metadata: HashMap::new(),
119    };
120
121    // Validate the invoice
122    invoice.validate()?;
123
124    Ok(invoice)
125}
126
127/// Example of creating a Payment with an embedded invoice
128pub fn create_payment_request_with_invoice_example(
129    merchant_did: &str,
130    customer_did: Option<&str>,
131) -> Result<PlainMessage> {
132    // Create merchant participant
133    let merchant = Participant {
134        id: merchant_did.to_string(),
135        role: Some("merchant".to_string()),
136        policies: None,
137        leiCode: None,
138        name: None,
139    };
140
141    // Create a merchant agent (e.g., a payment processor)
142    let agent = Participant {
143        id: "did:example:payment_processor".to_string(),
144        role: Some("agent".to_string()),
145        policies: None,
146        leiCode: None,
147        name: None,
148    };
149
150    // Create an invoice with tax
151    let invoice = create_invoice_with_tax_example()?;
152
153    // Create a Payment using the new API
154    let asset =
155        AssetId::from_str("eip155:1/erc20:0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
156
157    // Create transaction ID
158    let transaction_id = uuid::Uuid::new_v4().to_string();
159
160    // Create a customer participant if provided
161    let customer = customer_did.map(|cust_did| Participant {
162        id: cust_did.to_string(),
163        role: Some("customer".to_string()),
164        policies: None,
165        leiCode: None,
166        name: None,
167    });
168
169    // Use the builder pattern to create the payment
170    let mut payment_request = PaymentBuilder::default()
171        .transaction_id(transaction_id)
172        .asset(asset)
173        .amount(format!("{:.2}", invoice.total))
174        .currency_code(invoice.currency_code.clone())
175        .merchant(merchant.clone())
176        .add_agent(agent)
177        .build();
178
179    // Set customer if provided
180    payment_request.customer = customer;
181
182    // Add the invoice directly to the payment
183    payment_request.invoice = Some(invoice);
184
185    // Add expiry (e.g., 30 days)
186    payment_request.expiry = Some("2023-10-01T00:00:00Z".to_string());
187
188    // Convert to a DIDComm message
189    let recipients = if let Some(cust_did) = customer_did {
190        vec![cust_did]
191    } else {
192        vec![]
193    };
194
195    let message =
196        payment_request.to_didcomm_with_route(merchant_did, recipients.iter().copied())?;
197
198    Ok(message)
199}
200
201/// Example of extracting and validating an invoice from a Payment message
202pub fn process_payment_request_with_invoice_example(message: &PlainMessage) -> Result<()> {
203    // Extract the Payment
204    let payment_request = Payment::from_didcomm(message)?;
205
206    // Validate the Payment
207    payment_request.validate()?;
208
209    // Print merchant information
210    println!("Merchant: {}", payment_request.merchant.id);
211
212    // Print customer information if present
213    if let Some(customer) = &payment_request.customer {
214        println!("Customer: {}", customer.id);
215    } else {
216        println!("Customer: Not specified");
217    }
218
219    println!("Amount: {}", payment_request.amount);
220
221    if let Some(currency) = &payment_request.currency_code {
222        println!("Currency: {}", currency);
223    }
224
225    if let Some(asset) = &payment_request.asset {
226        println!("Asset: {}", asset);
227    }
228
229    // Check if it has an invoice directly in the payment
230    if let Some(invoice) = &payment_request.invoice {
231        println!("Invoice ID: {}", invoice.id);
232        println!("Currency: {}", invoice.currency_code);
233        println!("Total amount: {:.2}", invoice.total);
234
235        // Print line items
236        println!("Line items:");
237        for (i, item) in invoice.line_items.iter().enumerate() {
238            println!(
239                "  {}: {} x {} @ {:.2} = {:.2}",
240                i + 1,
241                item.quantity,
242                item.description,
243                item.unit_price,
244                item.line_total
245            );
246        }
247
248        // Print tax information if present
249        if let Some(tax_total) = &invoice.tax_total {
250            println!("Tax amount: {:.2}", tax_total.tax_amount);
251
252            if let Some(tax_subtotals) = &tax_total.tax_subtotal {
253                for (i, subtotal) in tax_subtotals.iter().enumerate() {
254                    println!(
255                        "  Tax {}: {:.2}% {} on {:.2} = {:.2}",
256                        i + 1,
257                        subtotal.tax_category.percent,
258                        subtotal.tax_category.tax_scheme,
259                        subtotal.taxable_amount,
260                        subtotal.tax_amount
261                    );
262                }
263            }
264        }
265
266        println!(
267            "Due date: {}",
268            invoice.due_date.as_deref().unwrap_or("Not specified")
269        );
270    } else {
271        println!("Payment request does not contain an invoice");
272    }
273
274    // Print expiry if present
275    if let Some(expiry) = &payment_request.expiry {
276        println!("Expires: {}", expiry);
277    }
278
279    Ok(())
280}