use std::process;
use clap::{Parser, Subcommand, ValueEnum};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use payrix::{EntityType, Environment, PayrixClient};
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum CliEnvironment {
Test,
#[value(alias = "prod")]
Production,
}
impl From<CliEnvironment> for Environment {
fn from(cli_env: CliEnvironment) -> Self {
match cli_env {
CliEnvironment::Test => Environment::Test,
CliEnvironment::Production => Environment::Production,
}
}
}
#[derive(Parser)]
#[command(name = "payrix")]
#[command(about = "Payrix API command line tool", long_about = None)]
#[command(version)]
struct Cli {
#[arg(short, long, global = true)]
verbose: bool,
#[arg(short, long, global = true, value_enum)]
env: Option<CliEnvironment>,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Lookup {
id: String,
},
}
#[derive(Debug)]
struct ParsedId {
environment: Environment,
entity_type: EntityType,
full_id: String,
}
fn parse_id(id: &str) -> Result<ParsedId, String> {
let id = id.trim();
let parts: Vec<&str> = id.split('_').collect();
if parts.len() < 3 {
return Err(format!(
"Invalid ID format: expected at least 3 parts separated by underscores, got {}",
parts.len()
));
}
let environment = match parts[0] {
"t1" => Environment::Test,
"p1" => Environment::Production,
other => {
return Err(format!(
"Unknown environment prefix '{}'. Expected 't1' (test) or 'p1' (production)",
other
))
}
};
let entity_type = entity_type_from_code(parts[1])?;
Ok(ParsedId {
environment,
entity_type,
full_id: id.to_string(),
})
}
fn entity_type_from_code(code: &str) -> Result<EntityType, String> {
match code {
"acc" => Ok(EntityType::Accounts),
"cst" => Ok(EntityType::Customers),
"ent" => Ok(EntityType::Entities),
"fnd" => Ok(EntityType::Funds),
"mbr" => Ok(EntityType::Members),
"mer" => Ok(EntityType::Merchants),
"org" => Ok(EntityType::Orgs),
"pyt" => Ok(EntityType::Payouts),
"pln" => Ok(EntityType::Plans),
"sub" => Ok(EntityType::Subscriptions),
"stk" => Ok(EntityType::SubscriptionTokens),
"log" => Ok(EntityType::Logins),
"tlg" => Ok(EntityType::TeamLogins),
"tkn" => Ok(EntityType::Tokens),
"txn" => Ok(EntityType::Txns),
"ers" => Ok(EntityType::EntityReserves),
"frl" => Ok(EntityType::FeeRules),
"fee" => Ok(EntityType::Fees),
"oen" => Ok(EntityType::OrgEntities),
"ren" => Ok(EntityType::ReserveEntries),
"rsv" => Ok(EntityType::Reserves),
"vnd" => Ok(EntityType::Vendors),
"avr" => Ok(EntityType::AccountVerifications),
"adj" => Ok(EntityType::Adjustments),
"bch" => Ok(EntityType::Batches),
"cbk" => Ok(EntityType::Chargebacks),
"cbm" => Ok(EntityType::ChargebackMessages),
"cbd" => Ok(EntityType::ChargebackDocuments),
"cmr" => Ok(EntityType::ChargebackMessageResults),
"cbs" => Ok(EntityType::ChargebackStatuses),
"cnt" => Ok(EntityType::Contacts),
"dsb" => Ok(EntityType::Disbursements),
"dse" => Ok(EntityType::DisbursementEntries),
"etr" => Ok(EntityType::Entries),
"pen" => Ok(EntityType::PendingEntries),
"rfd" => Ok(EntityType::Refunds),
"alt" => Ok(EntityType::Alerts),
"ala" => Ok(EntityType::AlertActions),
"atr" => Ok(EntityType::AlertTriggers),
"nte" => Ok(EntityType::Notes),
"ntd" => Ok(EntityType::NoteDocuments),
"hld" => Ok(EntityType::Holds),
other => Err(format!(
"Unknown entity type code '{}'. \n\
Known codes: acc, cst, ent, fnd, mbr, mer, org, pyt, pln, sub, stk, log, tlg, tkn, txn, \
ers, frl, fee, oen, ren, rsv, vnd, avr, adj, bch, cbk, cbm, cbd, cmr, cbs, \
cnt, dsb, dse, etr, pen, rfd, alt, ala, atr, nte, ntd, hld",
other
)),
}
}
fn entity_type_name(entity_type: &EntityType) -> &'static str {
match entity_type {
EntityType::Accounts => "Bank Account",
EntityType::Customers => "Customer",
EntityType::Entities => "Entity",
EntityType::Funds => "Fund",
EntityType::Members => "Member",
EntityType::Merchants => "Merchant",
EntityType::Orgs => "Organization",
EntityType::Payouts => "Payout",
EntityType::Plans => "Plan",
EntityType::Subscriptions => "Subscription",
EntityType::SubscriptionTokens => "Subscription Token",
EntityType::TeamLogins => "Team Login",
EntityType::Tokens => "Token",
EntityType::Txns => "Transaction",
EntityType::EntityReserves => "Entity Reserve",
EntityType::FeeRules => "Fee Rule",
EntityType::Fees => "Fee",
EntityType::OrgEntities => "Org Entity",
EntityType::ReserveEntries => "Reserve Entry",
EntityType::Reserves => "Reserve",
EntityType::Vendors => "Vendor",
EntityType::AccountVerifications => "Account Verification",
EntityType::Adjustments => "Adjustment",
EntityType::Batches => "Batch",
EntityType::Chargebacks => "Chargeback",
EntityType::ChargebackMessages => "Chargeback Message",
EntityType::ChargebackDocuments => "Chargeback Document",
EntityType::ChargebackMessageResults => "Chargeback Message Result",
EntityType::ChargebackStatuses => "Chargeback Status",
EntityType::Contacts => "Contact",
EntityType::Disbursements => "Disbursement",
EntityType::DisbursementEntries => "Disbursement Entry",
EntityType::Entries => "Entry",
EntityType::PendingEntries => "Pending Entry",
EntityType::Refunds => "Refund",
EntityType::Alerts => "Alert",
EntityType::AlertActions => "Alert Action",
EntityType::AlertTriggers => "Alert Trigger",
EntityType::Logins => "Login",
EntityType::Notes => "Note",
EntityType::NoteDocuments => "Note Document",
EntityType::Holds => "Hold",
}
}
async fn lookup(
id: &str,
verbose: bool,
explicit_env: Option<CliEnvironment>,
) -> Result<(), Box<dyn std::error::Error>> {
let parsed = parse_id(id)?;
let environment = match explicit_env {
Some(cli_env) => {
let env = Environment::from(cli_env);
if verbose && env != parsed.environment {
eprintln!(
"Note: --env {:?} overrides ID prefix environment ({:?})",
cli_env, parsed.environment
);
}
env
}
None => parsed.environment,
};
if verbose {
eprintln!(
"Looking up {} in {} environment...",
entity_type_name(&parsed.entity_type),
if environment == Environment::Test {
"test"
} else {
"production"
}
);
}
let api_key = match environment {
Environment::Test => std::env::var("TEST_PAYRIX_API_KEY").map_err(|_| {
"TEST_PAYRIX_API_KEY environment variable not set.\n\
Set it with: export TEST_PAYRIX_API_KEY=your_api_key"
})?,
Environment::Production => std::env::var("PAYRIX_API_KEY").map_err(|_| {
"PAYRIX_API_KEY environment variable not set.\n\
Set it with: export PAYRIX_API_KEY=your_api_key"
})?,
};
let client = PayrixClient::new(&api_key, environment)?;
let result: Option<serde_json::Value> = client
.get_one(parsed.entity_type, &parsed.full_id)
.await?;
match result {
Some(entity) => {
let pretty = serde_json::to_string_pretty(&entity)?;
println!("{}", pretty);
Ok(())
}
None => {
eprintln!(
"Error: {} with ID '{}' not found",
entity_type_name(&parsed.entity_type),
parsed.full_id
);
process::exit(1);
}
}
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
if cli.verbose {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "payrix=debug".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
}
let result = match cli.command {
Commands::Lookup { id } => lookup(&id, cli.verbose, cli.env).await,
};
if let Err(e) = result {
eprintln!("Error: {}", e);
process::exit(1);
}
}