use payrix_macros::PayrixEntity;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use super::{bool_from_int_default_false, DateYmd, PayrixId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum MerchantType {
#[default]
SoleProprietor = 0,
Corporation = 1,
LimitedLiabilityCorporation = 2,
Partnership = 3,
NonProfitOrganization = 5,
GovernmentOrganization = 6,
CCorporation = 7,
SCorporation = 8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum MerchantStatus {
#[default]
NotReady = 0,
Ready = 1,
Boarded = 2,
Manual = 3,
Closed = 4,
Incomplete = 5,
Pending = 6,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum MerchantEnvironment {
Supermarket,
#[serde(rename = "moto")]
MailOrTelephoneOrder,
CardPresent,
Fuel,
ServiceStation,
Restaurant,
#[default]
#[serde(rename = "ecommerce", alias = "eCommerce")]
Ecommerce,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RiskLevel {
Restricted,
Prohibited,
High,
Medium,
#[default]
Low,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum TaxIdStatus {
#[default]
Pending = 0,
Valid = 1,
Invalid = 2,
NotRequired = 3,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SaqType {
#[serde(rename = "SAQ-A")]
SaqA,
#[serde(rename = "SAQ-A-EP")]
SaqAEp,
#[serde(rename = "SAQ-B")]
SaqB,
#[serde(rename = "SAQ-B-IP")]
SaqBIp,
#[serde(rename = "SAQ-C-VT")]
SaqCVt,
#[serde(rename = "SAQ-C")]
SaqC,
#[serde(rename = "SAQ-P2PE-HW")]
SaqP2PeHw,
#[serde(rename = "SAQ-D")]
SaqD,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum LocationType {
RetailStorefront = 77,
Warehouse = 78,
PrivateResidence = 79,
Others = 80,
PrivateResidenceProfConstruction = 81,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExpressBatchCloseMethod {
TimeInitiated,
MerchantInitiated,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PayrixEntity)]
#[payrix(create = CreateMerchant, update = UpdateMerchant)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
#[serde(rename_all = "camelCase")]
pub struct Merchant {
#[payrix(readonly)]
pub id: PayrixId,
#[payrix(readonly)]
#[serde(default)]
pub created: Option<String>,
#[payrix(readonly)]
#[serde(default)]
pub modified: Option<String>,
#[payrix(readonly)]
#[serde(default)]
pub creator: Option<PayrixId>,
#[payrix(readonly)]
#[serde(default)]
pub modifier: Option<PayrixId>,
#[serde(default)]
pub last_activity: Option<String>,
#[serde(default)]
pub total_approved_sales: Option<i64>,
#[payrix(create_only)]
#[serde(default)]
pub entity: Option<PayrixId>,
#[payrix(create_only)]
#[serde(default)]
pub login: Option<PayrixId>,
#[payrix(mutable)]
#[serde(default)]
pub dba: Option<String>,
#[serde(default, with = "bool_from_int_default_false")]
pub new: bool,
#[serde(default, with = "bool_from_int_default_false")]
pub incremental_auth_supported: bool,
#[serde(default, with = "bool_from_int_default_false")]
pub seasonal: bool,
#[serde(default, with = "bool_from_int_default_false")]
pub advanced_billing: bool,
#[serde(default)]
pub established: Option<DateYmd>,
#[serde(default, rename = "annualCCSales")]
pub annual_cc_sales: Option<i64>,
#[serde(default, rename = "annualCCSaleVolume")]
pub annual_cc_sale_volume: Option<i64>,
#[serde(default, rename = "annualACHSaleVolume")]
pub annual_ach_sale_volume: Option<i64>,
#[serde(default)]
pub amex_volume: Option<i64>,
#[serde(default)]
pub avg_ticket: Option<i64>,
#[serde(default)]
pub total_volume: Option<i64>,
#[serde(default)]
pub amex: Option<String>,
#[serde(default)]
pub discover: Option<String>,
#[serde(default)]
pub mcc: Option<String>,
#[serde(default)]
pub visa_mvv: Option<String>,
#[serde(default, with = "bool_from_int_default_false")]
pub visa_disclosure: bool,
#[payrix(mutable)]
#[serde(default)]
pub environment: Option<MerchantEnvironment>,
#[serde(default)]
pub status: Option<MerchantStatus>,
#[serde(default, with = "bool_from_int_default_false")]
pub auto_boarded: bool,
#[serde(default)]
pub status_reason: Option<String>,
#[serde(default)]
pub account_closure_reason_code: Option<String>,
#[serde(default)]
pub account_closure_reason_date: Option<i32>,
#[serde(default)]
pub risk_level: Option<RiskLevel>,
#[serde(default)]
pub boarded: Option<DateYmd>,
#[serde(default)]
pub credit_ratio: Option<i32>,
#[serde(default)]
pub credit_timeliness: Option<i32>,
#[serde(default)]
pub chargeback_ratio: Option<i32>,
#[serde(default)]
pub ndx_days: Option<i32>,
#[serde(default)]
pub ndx_percentage: Option<i32>,
#[serde(default)]
pub saq_type: Option<SaqType>,
#[serde(default)]
pub saq_date: Option<i32>,
#[serde(default)]
pub qsa: Option<String>,
#[serde(default, with = "bool_from_int_default_false")]
pub letter_status: bool,
#[serde(default)]
pub letter_date: Option<i32>,
#[serde(default, with = "bool_from_int_default_false")]
pub tc_attestation: bool,
#[serde(default)]
pub tmx_session_id: Option<String>,
#[serde(default, rename = "disclosureIP")]
pub disclosure_ip: Option<String>,
#[serde(default)]
pub disclosure_date: Option<i32>,
#[payrix(mutable)]
#[serde(default)]
pub chargeback_notification_email: Option<String>,
#[serde(default)]
pub location_type: Option<LocationType>,
#[serde(default)]
pub percent_keyed: Option<i32>,
#[serde(default)]
pub percent_ecomm: Option<i32>,
#[serde(default)]
pub percent_business: Option<i32>,
#[serde(default)]
pub naics: Option<i32>,
#[serde(default)]
pub naics_description: Option<String>,
#[serde(default, with = "bool_from_int_default_false")]
pub apple_pay_active: bool,
#[serde(default)]
pub apple_pay_status: Option<String>,
#[serde(default, with = "bool_from_int_default_false")]
pub google_pay_active: bool,
#[serde(default)]
pub express_batch_close_method: Option<ExpressBatchCloseMethod>,
#[serde(default)]
pub express_batch_close_time: Option<String>,
#[serde(default, with = "bool_from_int_default_false")]
pub pass_token_enabled: bool,
#[payrix(mutable)]
#[serde(default, with = "bool_from_int_default_false")]
pub inactive: bool,
#[payrix(mutable)]
#[serde(default, with = "bool_from_int_default_false")]
pub frozen: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn merchant_type_serialize_all_variants() {
assert_eq!(serde_json::to_string(&MerchantType::SoleProprietor).unwrap(), "0");
assert_eq!(serde_json::to_string(&MerchantType::Corporation).unwrap(), "1");
assert_eq!(serde_json::to_string(&MerchantType::LimitedLiabilityCorporation).unwrap(), "2");
assert_eq!(serde_json::to_string(&MerchantType::Partnership).unwrap(), "3");
assert_eq!(serde_json::to_string(&MerchantType::NonProfitOrganization).unwrap(), "5");
assert_eq!(serde_json::to_string(&MerchantType::GovernmentOrganization).unwrap(), "6");
assert_eq!(serde_json::to_string(&MerchantType::CCorporation).unwrap(), "7");
assert_eq!(serde_json::to_string(&MerchantType::SCorporation).unwrap(), "8");
}
#[test]
fn merchant_type_deserialize_all_variants() {
assert_eq!(serde_json::from_str::<MerchantType>("0").unwrap(), MerchantType::SoleProprietor);
assert_eq!(serde_json::from_str::<MerchantType>("1").unwrap(), MerchantType::Corporation);
assert_eq!(serde_json::from_str::<MerchantType>("2").unwrap(), MerchantType::LimitedLiabilityCorporation);
assert_eq!(serde_json::from_str::<MerchantType>("3").unwrap(), MerchantType::Partnership);
assert_eq!(serde_json::from_str::<MerchantType>("5").unwrap(), MerchantType::NonProfitOrganization);
assert_eq!(serde_json::from_str::<MerchantType>("6").unwrap(), MerchantType::GovernmentOrganization);
assert_eq!(serde_json::from_str::<MerchantType>("7").unwrap(), MerchantType::CCorporation);
assert_eq!(serde_json::from_str::<MerchantType>("8").unwrap(), MerchantType::SCorporation);
}
#[test]
fn merchant_type_default() {
assert_eq!(MerchantType::default(), MerchantType::SoleProprietor);
}
#[test]
fn merchant_status_serialize_all_variants() {
assert_eq!(serde_json::to_string(&MerchantStatus::NotReady).unwrap(), "0");
assert_eq!(serde_json::to_string(&MerchantStatus::Ready).unwrap(), "1");
assert_eq!(serde_json::to_string(&MerchantStatus::Boarded).unwrap(), "2");
assert_eq!(serde_json::to_string(&MerchantStatus::Manual).unwrap(), "3");
assert_eq!(serde_json::to_string(&MerchantStatus::Closed).unwrap(), "4");
assert_eq!(serde_json::to_string(&MerchantStatus::Incomplete).unwrap(), "5");
assert_eq!(serde_json::to_string(&MerchantStatus::Pending).unwrap(), "6");
}
#[test]
fn merchant_status_deserialize_all_variants() {
assert_eq!(serde_json::from_str::<MerchantStatus>("0").unwrap(), MerchantStatus::NotReady);
assert_eq!(serde_json::from_str::<MerchantStatus>("1").unwrap(), MerchantStatus::Ready);
assert_eq!(serde_json::from_str::<MerchantStatus>("2").unwrap(), MerchantStatus::Boarded);
assert_eq!(serde_json::from_str::<MerchantStatus>("3").unwrap(), MerchantStatus::Manual);
assert_eq!(serde_json::from_str::<MerchantStatus>("4").unwrap(), MerchantStatus::Closed);
assert_eq!(serde_json::from_str::<MerchantStatus>("5").unwrap(), MerchantStatus::Incomplete);
assert_eq!(serde_json::from_str::<MerchantStatus>("6").unwrap(), MerchantStatus::Pending);
}
#[test]
fn merchant_status_default() {
assert_eq!(MerchantStatus::default(), MerchantStatus::NotReady);
}
#[test]
fn merchant_environment_serialize_all_variants() {
assert_eq!(serde_json::to_string(&MerchantEnvironment::Supermarket).unwrap(), "\"supermarket\"");
assert_eq!(serde_json::to_string(&MerchantEnvironment::MailOrTelephoneOrder).unwrap(), "\"moto\"");
assert_eq!(serde_json::to_string(&MerchantEnvironment::CardPresent).unwrap(), "\"cardPresent\"");
assert_eq!(serde_json::to_string(&MerchantEnvironment::Fuel).unwrap(), "\"fuel\"");
assert_eq!(serde_json::to_string(&MerchantEnvironment::ServiceStation).unwrap(), "\"serviceStation\"");
assert_eq!(serde_json::to_string(&MerchantEnvironment::Restaurant).unwrap(), "\"restaurant\"");
assert_eq!(serde_json::to_string(&MerchantEnvironment::Ecommerce).unwrap(), "\"ecommerce\"");
}
#[test]
fn merchant_environment_deserialize_all_variants() {
assert_eq!(serde_json::from_str::<MerchantEnvironment>("\"supermarket\"").unwrap(), MerchantEnvironment::Supermarket);
assert_eq!(serde_json::from_str::<MerchantEnvironment>("\"moto\"").unwrap(), MerchantEnvironment::MailOrTelephoneOrder);
assert_eq!(serde_json::from_str::<MerchantEnvironment>("\"cardPresent\"").unwrap(), MerchantEnvironment::CardPresent);
assert_eq!(serde_json::from_str::<MerchantEnvironment>("\"fuel\"").unwrap(), MerchantEnvironment::Fuel);
assert_eq!(serde_json::from_str::<MerchantEnvironment>("\"serviceStation\"").unwrap(), MerchantEnvironment::ServiceStation);
assert_eq!(serde_json::from_str::<MerchantEnvironment>("\"restaurant\"").unwrap(), MerchantEnvironment::Restaurant);
assert_eq!(serde_json::from_str::<MerchantEnvironment>("\"eCommerce\"").unwrap(), MerchantEnvironment::Ecommerce);
assert_eq!(serde_json::from_str::<MerchantEnvironment>("\"ecommerce\"").unwrap(), MerchantEnvironment::Ecommerce);
}
#[test]
fn merchant_environment_default() {
assert_eq!(MerchantEnvironment::default(), MerchantEnvironment::Ecommerce);
}
#[test]
fn risk_level_serialize_all_variants() {
assert_eq!(serde_json::to_string(&RiskLevel::Restricted).unwrap(), "\"restricted\"");
assert_eq!(serde_json::to_string(&RiskLevel::Prohibited).unwrap(), "\"prohibited\"");
assert_eq!(serde_json::to_string(&RiskLevel::High).unwrap(), "\"high\"");
assert_eq!(serde_json::to_string(&RiskLevel::Medium).unwrap(), "\"medium\"");
assert_eq!(serde_json::to_string(&RiskLevel::Low).unwrap(), "\"low\"");
}
#[test]
fn risk_level_deserialize_all_variants() {
assert_eq!(serde_json::from_str::<RiskLevel>("\"restricted\"").unwrap(), RiskLevel::Restricted);
assert_eq!(serde_json::from_str::<RiskLevel>("\"prohibited\"").unwrap(), RiskLevel::Prohibited);
assert_eq!(serde_json::from_str::<RiskLevel>("\"high\"").unwrap(), RiskLevel::High);
assert_eq!(serde_json::from_str::<RiskLevel>("\"medium\"").unwrap(), RiskLevel::Medium);
assert_eq!(serde_json::from_str::<RiskLevel>("\"low\"").unwrap(), RiskLevel::Low);
}
#[test]
fn risk_level_default() {
assert_eq!(RiskLevel::default(), RiskLevel::Low);
}
#[test]
fn tax_id_status_serialize_all_variants() {
assert_eq!(serde_json::to_string(&TaxIdStatus::Pending).unwrap(), "0");
assert_eq!(serde_json::to_string(&TaxIdStatus::Valid).unwrap(), "1");
assert_eq!(serde_json::to_string(&TaxIdStatus::Invalid).unwrap(), "2");
assert_eq!(serde_json::to_string(&TaxIdStatus::NotRequired).unwrap(), "3");
}
#[test]
fn tax_id_status_deserialize_all_variants() {
assert_eq!(serde_json::from_str::<TaxIdStatus>("0").unwrap(), TaxIdStatus::Pending);
assert_eq!(serde_json::from_str::<TaxIdStatus>("1").unwrap(), TaxIdStatus::Valid);
assert_eq!(serde_json::from_str::<TaxIdStatus>("2").unwrap(), TaxIdStatus::Invalid);
assert_eq!(serde_json::from_str::<TaxIdStatus>("3").unwrap(), TaxIdStatus::NotRequired);
}
#[test]
fn tax_id_status_default() {
assert_eq!(TaxIdStatus::default(), TaxIdStatus::Pending);
}
#[test]
fn merchant_deserialize_full() {
let json = r#"{
"id": "t1_mer_12345678901234567890123",
"created": "2024-01-01 00:00:00.0000",
"modified": "2024-04-01 12:00:00.0000",
"creator": "t1_lgn_creator1234567890123456",
"modifier": "t1_lgn_modifier123456789012345",
"entity": "t1_ent_12345678901234567890123",
"login": "t1_lgn_12345678901234567890123",
"dba": "Acme Widgets",
"status": 2,
"environment": "eCommerce",
"riskLevel": "low",
"new": 1,
"established": "20150101",
"annualCCSales": 50000000,
"avgTicket": 2500,
"mcc": "5734",
"boarded": "20240101",
"chargebackNotificationEmail": "chargeback@example.com",
"inactive": 0,
"frozen": 1
}"#;
let merchant: Merchant = serde_json::from_str(json).unwrap();
assert_eq!(merchant.id.as_str(), "t1_mer_12345678901234567890123");
assert_eq!(merchant.creator.as_ref().unwrap().as_str(), "t1_lgn_creator1234567890123456");
assert_eq!(merchant.modifier.as_ref().unwrap().as_str(), "t1_lgn_modifier123456789012345");
assert_eq!(merchant.entity.unwrap().as_str(), "t1_ent_12345678901234567890123");
assert_eq!(merchant.login.unwrap().as_str(), "t1_lgn_12345678901234567890123");
assert_eq!(merchant.dba.as_deref(), Some("Acme Widgets"));
assert_eq!(merchant.status, Some(MerchantStatus::Boarded));
assert_eq!(merchant.environment, Some(MerchantEnvironment::Ecommerce));
assert_eq!(merchant.risk_level, Some(RiskLevel::Low));
assert!(merchant.new);
assert_eq!(merchant.established.as_ref().unwrap().as_str(), "20150101");
assert_eq!(merchant.annual_cc_sales, Some(50000000));
assert_eq!(merchant.avg_ticket, Some(2500));
assert_eq!(merchant.mcc.as_deref(), Some("5734"));
assert_eq!(merchant.boarded.as_ref().unwrap().as_str(), "20240101");
assert_eq!(merchant.chargeback_notification_email.as_deref(), Some("chargeback@example.com"));
assert!(!merchant.inactive);
assert!(merchant.frozen);
}
#[test]
fn merchant_deserialize_minimal() {
let json = r#"{"id": "t1_mer_12345678901234567890123"}"#;
let merchant: Merchant = serde_json::from_str(json).unwrap();
assert_eq!(merchant.id.as_str(), "t1_mer_12345678901234567890123");
assert!(merchant.entity.is_none());
assert!(merchant.status.is_none());
assert!(!merchant.new);
assert!(!merchant.inactive);
assert!(!merchant.frozen);
}
#[test]
fn merchant_new_fields() {
let json = r#"{
"id": "t1_mer_12345678901234567890123",
"lastActivity": "2024-06-15 10:30:00",
"totalApprovedSales": 1500000,
"incrementalAuthSupported": 1,
"seasonal": 0,
"advancedBilling": 1,
"autoBoarded": 1,
"applePayActive": 1,
"googlePayActive": 0
}"#;
let merchant: Merchant = serde_json::from_str(json).unwrap();
assert_eq!(merchant.last_activity.as_deref(), Some("2024-06-15 10:30:00"));
assert_eq!(merchant.total_approved_sales, Some(1500000));
assert!(merchant.incremental_auth_supported);
assert!(!merchant.seasonal);
assert!(merchant.advanced_billing);
assert!(merchant.auto_boarded);
assert!(merchant.apple_pay_active);
assert!(!merchant.google_pay_active);
}
#[test]
fn merchant_bool_from_int_zero_is_false() {
let json = r#"{"id": "t1_mer_12345678901234567890123", "new": 0, "inactive": 0, "frozen": 0}"#;
let merchant: Merchant = serde_json::from_str(json).unwrap();
assert!(!merchant.new);
assert!(!merchant.inactive);
assert!(!merchant.frozen);
}
#[test]
fn merchant_bool_from_int_one_is_true() {
let json = r#"{"id": "t1_mer_12345678901234567890123", "new": 1, "inactive": 1, "frozen": 1}"#;
let merchant: Merchant = serde_json::from_str(json).unwrap();
assert!(merchant.new);
assert!(merchant.inactive);
assert!(merchant.frozen);
}
}