use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
use tap_agent::agent::{Agent, TapAgent};
use tap_agent::agent_key_manager::AgentKeyManagerBuilder;
use tap_agent::config::AgentConfig;
use tap_agent::key_manager::{Secret, SecretMaterial, SecretType};
use tap_caip::AssetId;
use tap_msg::message::payment::InvoiceReference;
use tap_msg::message::{Authorize, Settle};
use tap_msg::{Invoice, LineItem, Party, Payment, TaxCategory, TaxSubtotal, TaxTotal};
fn main() -> Result<(), Box<dyn std::error::Error>> {
tokio_test::block_on(async {
println!("=== TAIP-14 Payment Flow with TAIP-16 Invoice ===\n");
let (merchant_agent, merchant_did) = create_agent(
"did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
"11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"nWGxne/9WmC6hEr+BQh+uDpW6n7dZsN4c4C9rFfIz3Yh",
)
.await;
let (customer_agent, customer_did) = create_agent(
"did:key:z6MkhFvVnYxkqLNEiWQmUwhQuVpXiCfNmRUVi5yZ4Cg9w15k",
"8zYZK2vvsAyVYpNpnYzTnUPjBuWdWpYmPpQmwErV9XQg",
"8zYZK2vvsAyVYpNpnYzTnUPjBuWdWpYmPpQmwErV9XQg",
)
.await;
println!("Created merchant agent with DID: {}", merchant_did);
println!("Created customer agent with DID: {}\n", customer_did);
let settlement_address = "did:pkh:eip155:1:0x1234a96D359eC26a11e2C2b3d8f8B8942d5Bfcdb";
println!("Step 1: Merchant creates a payment request with an invoice");
let transaction_id = uuid::Uuid::new_v4().to_string();
let invoice_id = format!(
"INV-{}",
uuid::Uuid::new_v4().to_string().split('-').next().unwrap()
);
let payment = create_payment_message_with_invoice(
&merchant_did,
&customer_did,
settlement_address,
&invoice_id,
&transaction_id,
);
println!("Payment details:");
println!(" Asset: {}", payment.asset.as_ref().unwrap());
println!(" Amount: {}", payment.amount);
println!(" Merchant: {}", payment.merchant.id);
if let Some(customer) = &payment.customer {
println!(" Customer: {}", customer.id);
}
if let Some(invoice_ref) = &payment.invoice {
match invoice_ref {
InvoiceReference::Object(invoice) => {
println!(" Invoice ID: {}", invoice.id);
println!(
" Invoice Total: {} {}",
invoice.total, invoice.currency_code
);
println!(" Line Items: {}", invoice.line_items.len());
for (i, item) in invoice.line_items.iter().enumerate() {
println!(
" Item {}: {} x {} @ {} = {} {}",
i + 1,
item.quantity,
item.description,
item.unit_price,
item.line_total,
invoice.currency_code
);
}
}
InvoiceReference::Url(url) => {
println!(" Invoice URL: {}", url);
}
}
}
println!();
let (packed_payment, _delivery_results) = merchant_agent
.send_message(&payment, vec![&customer_did], false)
.await?;
println!("Merchant sends the payment request with invoice to the customer\n");
println!("Step 2: Customer receives and processes the payment request");
let plain_message = customer_agent.receive_message(&packed_payment).await?;
let received_payment: Payment = serde_json::from_value(plain_message.body)?;
println!("Customer received payment request:");
println!(" Asset: {}", received_payment.asset.as_ref().unwrap());
println!(" Amount: {}", received_payment.amount);
println!(" Merchant: {}", received_payment.merchant.id);
if let Some(customer) = &received_payment.customer {
println!(" Customer: {}", customer.id);
}
if let Some(invoice_ref) = &received_payment.invoice {
match invoice_ref {
InvoiceReference::Object(invoice) => {
println!(" Invoice ID: {}", invoice.id);
println!(
" Invoice Total: {} {}",
invoice.total, invoice.currency_code
);
println!(" Line Items: {}", invoice.line_items.len());
for (i, item) in invoice.line_items.iter().enumerate() {
println!(
" Item {}: {} x {} @ {} = {} {}",
i + 1,
item.quantity,
item.description,
item.unit_price,
item.line_total,
invoice.currency_code
);
}
}
InvoiceReference::Url(url) => {
println!(" Invoice URL: {}", url);
}
}
}
println!();
println!("Step 3: Customer authorizes the payment");
let authorize = Authorize {
transaction_id: transaction_id.clone(),
settlement_address: None,
expiry: None,
};
let (packed_authorize, _delivery_results) = customer_agent
.send_message(&authorize, vec![&merchant_did], false)
.await?;
println!("Customer sends authorization to the merchant\n");
println!("Step 4: Merchant receives the authorization");
let plain_message = merchant_agent.receive_message(&packed_authorize).await?;
let received_authorize: Authorize = serde_json::from_value(plain_message.body)?;
println!("Merchant received authorization:");
println!(" Payment ID: {}", received_authorize.transaction_id);
println!("Step 5: Customer settles the payment");
let settlement_id =
"eip155:1:tx/0x3edb98c24d46d148eb926c714f4fbaa117c47b0c0821f38bfce9763604457c33";
let settle = Settle {
transaction_id: transaction_id.clone(),
settlement_id: Some(settlement_id.to_string()),
amount: Some(payment.amount.clone()),
};
let (packed_settle, _delivery_results) = customer_agent
.send_message(&settle, vec![&merchant_did], false)
.await?;
println!("Customer sends settlement confirmation to the merchant");
println!(" Settlement ID: {}\n", settlement_id);
println!("Step 6: Merchant receives the settlement confirmation");
let plain_message = merchant_agent.receive_message(&packed_settle).await?;
let received_settle: Settle = serde_json::from_value(plain_message.body)?;
println!("Merchant received settlement confirmation:");
println!(" Payment ID: {}", received_settle.transaction_id);
println!(" Settlement ID: {:?}", received_settle.settlement_id);
if let Some(amount) = &received_settle.amount {
println!(" Amount: {}\n", amount);
}
println!("Step 7: Merchant fulfills the order (out of band)");
println!(" Merchant has received the payment and will now fulfill the order.\n");
println!("=== Payment flow with invoice completed successfully ===");
Ok(())
})
}
async fn create_agent(did: &str, public_key: &str, private_key: &str) -> (Arc<TapAgent>, String) {
let agent_config = AgentConfig::new(did.to_string());
let mut builder = AgentKeyManagerBuilder::new();
let secret = Secret {
id: format!("{}#keys-1", did),
type_: SecretType::JsonWebKey2020,
secret_material: SecretMaterial::JWK {
private_key_jwk: serde_json::json!({
"kty": "OKP",
"crv": "Ed25519",
"x": public_key,
"d": private_key
}),
},
};
builder = builder.add_secret(did.to_string(), secret);
let key_manager = builder.build().expect("Failed to build key manager");
let agent = TapAgent::new(agent_config, Arc::new(key_manager));
(Arc::new(agent), did.to_string())
}
fn create_payment_message_with_invoice(
merchant_did: &str,
customer_did: &str,
settlement_address: &str,
invoice_id: &str,
transaction_id: &str,
) -> Payment {
let merchant = Party::new(merchant_did);
let customer = Party::new(customer_did);
let settlement_agent =
tap_msg::Agent::new(settlement_address, "SettlementAddress", merchant_did);
let line_items = vec![
LineItem {
id: "1".to_string(),
description: "Widget A".to_string(),
quantity: 5.0,
unit_code: Some("EA".to_string()),
unit_price: 10.0,
line_total: 50.0,
tax_category: Some(TaxCategory {
id: "S".to_string(),
percent: 15.0,
tax_scheme: "VAT".to_string(),
}),
name: None,
image: None,
url: None,
},
LineItem {
id: "2".to_string(),
description: "Widget B".to_string(),
quantity: 10.0,
unit_code: Some("EA".to_string()),
unit_price: 5.0,
line_total: 50.0,
tax_category: Some(TaxCategory {
id: "S".to_string(),
percent: 15.0,
tax_scheme: "VAT".to_string(),
}),
name: None,
image: None,
url: None,
},
];
let sub_total = line_items.iter().map(|item| item.line_total).sum::<f64>();
let tax_amount = sub_total * 0.15;
let tax_subtotal = vec![TaxSubtotal {
taxable_amount: sub_total,
tax_amount,
tax_category: TaxCategory {
id: "S".to_string(),
percent: 15.0,
tax_scheme: "VAT".to_string(),
},
}];
let tax_total = TaxTotal {
tax_amount,
tax_subtotal: Some(tax_subtotal),
};
let invoice = Invoice {
id: invoice_id.to_string(),
issue_date: chrono::Utc::now().date_naive().to_string(),
currency_code: "USD".to_string(),
line_items,
tax_total: Some(tax_total),
total: sub_total + tax_amount,
sub_total: Some(sub_total),
due_date: Some(
(chrono::Utc::now() + chrono::Duration::days(30))
.date_naive()
.to_string(),
),
note: Some("Please pay within 30 days".to_string()),
payment_terms: Some("Net 30".to_string()),
accounting_cost: None,
order_reference: None,
additional_document_reference: None,
metadata: HashMap::new(),
};
Payment {
asset: Some(
AssetId::from_str("eip155:1/erc20:0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(),
),
currency_code: None,
amount: "115.0".to_string(), supported_assets: None,
invoice: Some(InvoiceReference::Object(Box::new(invoice))),
expiry: None,
merchant,
customer: Some(customer),
agents: vec![settlement_agent],
connection_id: None,
metadata: HashMap::new(),
transaction_id: Some(transaction_id.to_string()),
memo: Some("Payment with invoice".to_string()),
fallback_settlement_addresses: None,
}
}