syrup-rail-postgres 0.5.0

Canonical provider-neutral PostgreSQL schema contract and SQLx orchestration for Syrup Rail
Documentation
use sqlx::{PgConnection, Row, postgres::PgRow};
use syrup_rail::{
    ActorId, BillingScopeId, ChargeAmount, CurrencyCode, ExternalReversalAttestation,
    ExternalReversalKind, ExternalReversalReason, GatewayAccountId, GatewayConfigurationId,
    GatewayOrderId, GatewayTransactionId, Money, PaymentAttempt, PaymentAttemptId,
    PaymentAttemptKind, PaymentResolutionCode, ProcessorCharge, ProcessorChargeId,
    ProcessorChargeProgression, ProcessorChargeRole, ProcessorChargeStateCode,
};
use uuid::Uuid;

use crate::{
    attempts::processor_evidence_from_row,
    operator_review::{INVALID_OPERATOR_STATE, OperatorReviewError},
};

pub(crate) fn processor_charge_from_row(
    row: &PgRow,
) -> Result<ProcessorCharge, OperatorReviewError> {
    let attempt_id = PaymentAttemptId::new(row.try_get("attempt_id")?);
    let currency = CurrencyCode::new(&row.try_get::<String, _>("currency")?)
        .map_err(|_| OperatorReviewError::InvalidState(INVALID_OPERATOR_STATE))?;
    let amount = Money::new(row.try_get("amount_cents")?, currency)
        .map_err(|_| OperatorReviewError::InvalidState(INVALID_OPERATOR_STATE))?;
    let order = row.try_get::<String, _>("gateway_order_id")?;
    let gateway_order_id = GatewayOrderId::from_generated_attempt(&order, attempt_id)
        .or_else(|_| GatewayOrderId::from_correlation(&order))
        .map_err(|_| OperatorReviewError::InvalidState(INVALID_OPERATOR_STATE))?;
    Ok(ProcessorCharge::new(
        ProcessorChargeId::new(row.try_get("id")?),
        attempt_id,
        BillingScopeId::new(row.try_get("billing_scope_id")?),
        GatewayAccountId::new(row.try_get("gateway_account_id")?),
        gateway_order_id,
        parse_kind(&row.try_get::<String, _>("attempt_kind")?)?,
        amount,
        parse_role(&row.try_get::<String, _>("charge_role")?)?,
        parse_progression(&row.try_get::<String, _>("progression_state")?)?,
        row.try_get::<Option<String>, _>("state_code")?
            .as_deref()
            .map(parse_charge_state_code)
            .transpose()?,
        processor_evidence_from_row(row).map_err(|_| {
            OperatorReviewError::InvalidState("operator charge evidence is invalid")
        })?,
        row.try_get("observed_at")?,
    ))
}

pub(crate) async fn attestation_by_charge(
    connection: &mut PgConnection,
    charge_id: Uuid,
) -> Result<Option<ExternalReversalAttestation>, OperatorReviewError> {
    let row = sqlx::query(
        r#"
        SELECT processor_charge_id, attempt_id, actor_id, reversal_kind, reason,
            prior_resolution_code, final_resolution_code,
            gateway_account_id, gateway_configuration_id, gateway_order_id,
            amount_cents, currency, gateway_transaction_id,
            gateway_payment_method_reference, gateway_response, gateway_response_code,
            gateway_response_text, gateway_condition, payment_type, card_brand,
            card_last4, card_exp_month, card_exp_year, attested_at
        FROM billing_external_reversal_attestations
        WHERE processor_charge_id = $1 FOR UPDATE
        "#,
    )
    .bind(charge_id)
    .fetch_optional(connection)
    .await?;
    row.as_ref().map(attestation_from_row).transpose()
}

fn attestation_from_row(row: &PgRow) -> Result<ExternalReversalAttestation, OperatorReviewError> {
    let attempt_id = PaymentAttemptId::new(row.try_get("attempt_id")?);
    let order = row.try_get::<String, _>("gateway_order_id")?;
    let order = GatewayOrderId::from_generated_attempt(&order, attempt_id)
        .or_else(|_| GatewayOrderId::from_correlation(&order))
        .map_err(|_| {
            OperatorReviewError::InvalidState("operator attestation order identity is invalid")
        })?;
    let currency = CurrencyCode::new(&row.try_get::<String, _>("currency")?).map_err(|_| {
        OperatorReviewError::InvalidState("operator attestation currency is invalid")
    })?;
    Ok(ExternalReversalAttestation::new(
        ProcessorChargeId::new(row.try_get("processor_charge_id")?),
        attempt_id,
        ActorId::new(row.try_get("actor_id")?),
        parse_reversal_kind(&row.try_get::<String, _>("reversal_kind")?).map_err(|_| {
            OperatorReviewError::InvalidState("operator attestation reversal kind is invalid")
        })?,
        ExternalReversalReason::new(row.try_get::<String, _>("reason")?).map_err(|_| {
            OperatorReviewError::InvalidState("operator attestation reason is invalid")
        })?,
        row.try_get("prior_resolution_code")?,
        PaymentResolutionCode::try_from(
            row.try_get::<String, _>("final_resolution_code")?.as_str(),
        )
        .map_err(|_| {
            OperatorReviewError::InvalidState("operator attestation final resolution is invalid")
        })?,
        GatewayAccountId::new(row.try_get("gateway_account_id")?),
        GatewayConfigurationId::new(row.try_get("gateway_configuration_id")?),
        order,
        ChargeAmount::new(row.try_get("amount_cents")?, currency).map_err(|_| {
            OperatorReviewError::InvalidState("operator attestation amount is invalid")
        })?,
        GatewayTransactionId::new(row.try_get::<String, _>("gateway_transaction_id")?).map_err(
            |_| {
                OperatorReviewError::InvalidState(
                    "operator attestation transaction identity is invalid",
                )
            },
        )?,
        processor_evidence_from_row(row).map_err(|_| {
            OperatorReviewError::InvalidState("operator attestation evidence is invalid")
        })?,
        row.try_get("attested_at")?,
    ))
}

pub(crate) fn attestation_matches_source(
    attestation: &ExternalReversalAttestation,
    attempt: &PaymentAttempt,
    charge: &ProcessorCharge,
) -> bool {
    attestation.processor_charge_id() == charge.id()
        && attestation.attempt_id() == attempt.identity().attempt_id()
        && attestation.gateway_account_id() == attempt.identity().gateway_account_id()
        && attestation.gateway_configuration_id() == attempt.identity().gateway_configuration_id()
        && attestation.gateway_order_id() == attempt.request().gateway_order_id()
        && attestation.amount().money() == charge.amount()
        && attestation.gateway_transaction_id()
            == charge
                .evidence()
                .transaction_id()
                .expect("eligible charge has transaction identity")
        && attestation.processor_evidence() == charge.evidence()
        && attestation.prior_resolution_code() == expected_prior_resolution_code(attempt, charge)
        && attestation.final_resolution_code()
            == expected_final_resolution_code(attempt.kind(), attestation.kind())
}

pub(crate) fn expected_prior_resolution_code(
    attempt: &PaymentAttempt,
    charge: &ProcessorCharge,
) -> &'static str {
    if charge.role() == ProcessorChargeRole::Primary
        && charge.attempt_kind() == PaymentAttemptKind::SubscriptionInitial
        && (attempt.state().resolution_code()
            == Some(PaymentResolutionCode::SubscriptionInitialCurrentGrantConflict)
            || charge.state_code()
                == Some(ProcessorChargeStateCode::PaymentResolution(
                    PaymentResolutionCode::SubscriptionInitialCurrentGrantConflict,
                )))
    {
        PaymentResolutionCode::SubscriptionInitialCurrentGrantConflict.as_str()
    } else {
        "processor_charge_external_reversal_required"
    }
}

pub(crate) fn expected_final_resolution_code(
    attempt_kind: PaymentAttemptKind,
    kind: ExternalReversalKind,
) -> PaymentResolutionCode {
    match (attempt_kind, kind) {
        (PaymentAttemptKind::SubscriptionInitial, ExternalReversalKind::Refund) => {
            PaymentResolutionCode::SubscriptionInitialExternallyRefunded
        }
        (PaymentAttemptKind::SubscriptionInitial, ExternalReversalKind::Void) => {
            PaymentResolutionCode::SubscriptionInitialExternallyVoided
        }
        (_, ExternalReversalKind::Refund) => {
            PaymentResolutionCode::ProcessorChargeExternallyRefunded
        }
        (_, ExternalReversalKind::Void) => PaymentResolutionCode::ProcessorChargeExternallyVoided,
    }
}

pub(crate) fn parse_kind(value: &str) -> Result<PaymentAttemptKind, OperatorReviewError> {
    value
        .parse()
        .map_err(|_| OperatorReviewError::InvalidState(INVALID_OPERATOR_STATE))
}

pub(crate) fn parse_role(value: &str) -> Result<ProcessorChargeRole, OperatorReviewError> {
    match value {
        "primary" => Ok(ProcessorChargeRole::Primary),
        "additional" => Ok(ProcessorChargeRole::Additional),
        _ => Err(OperatorReviewError::InvalidState(INVALID_OPERATOR_STATE)),
    }
}

pub(crate) fn parse_progression(
    value: &str,
) -> Result<ProcessorChargeProgression, OperatorReviewError> {
    match value {
        "pending" => Ok(ProcessorChargeProgression::Pending),
        "reconciliation_required" => Ok(ProcessorChargeProgression::ReconciliationRequired),
        "external_reversal_required" => Ok(ProcessorChargeProgression::ExternalReversalRequired),
        "applied" => Ok(ProcessorChargeProgression::Applied),
        "externally_reversed" => Ok(ProcessorChargeProgression::ExternallyReversed),
        _ => Err(OperatorReviewError::InvalidState(INVALID_OPERATOR_STATE)),
    }
}

pub(crate) fn parse_reversal_kind(
    value: &str,
) -> Result<ExternalReversalKind, OperatorReviewError> {
    match value {
        "refund" => Ok(ExternalReversalKind::Refund),
        "void" => Ok(ExternalReversalKind::Void),
        _ => Err(OperatorReviewError::InvalidState(INVALID_OPERATOR_STATE)),
    }
}

pub(crate) fn parse_charge_state_code(
    value: &str,
) -> Result<ProcessorChargeStateCode, OperatorReviewError> {
    match value {
        "processor_charge_external_reversal_required" => {
            return Ok(ProcessorChargeStateCode::ExternalReversalRequired);
        }
        "additional_approved_charge_identified" => {
            return Ok(ProcessorChargeStateCode::AdditionalApprovedChargeIdentified);
        }
        "processor_charge_transaction_identity_required" => {
            return Ok(ProcessorChargeStateCode::TransactionIdentityRequired);
        }
        "approved_charge_waiting_for_application" => {
            return Ok(ProcessorChargeStateCode::ApprovedChargeWaitingForApplication);
        }
        "zero_amount_additional_approved_charge" => {
            return Ok(ProcessorChargeStateCode::ZeroAmountAdditionalApprovedCharge);
        }
        _ => {}
    }
    PaymentResolutionCode::try_from(value)
        .map(ProcessorChargeStateCode::PaymentResolution)
        .map_err(|_| OperatorReviewError::InvalidState(INVALID_OPERATOR_STATE))
}