lib/openagents/settlement/payment_receipt.ex

main at 58e6347eeb72 · 1 KB

defmodule OpenAgents.Settlement.PaymentReceipt do
  @moduledoc """
  The exact evidence of one settled payment.

  One receipt exists per payment intent, and the payment hash is unique across
  the ledger, so a replayed acknowledgement cannot become a second payment.
  """

  use Ecto.Schema
  import Ecto.Changeset

  alias OpenAgents.Settlement.{Claim, PaymentIntent}

  @primary_key {:id, :binary_id, autogenerate: true}
  @timestamps_opts [type: :utc_datetime_usec, updated_at: false]

  @fields ~w(payment_intent_id claim_id amount_sats fee_sats payment_hash preimage_digest
             gateway_ref paid_at receipt_digest)a

  schema "settlement_payment_receipts" do
    field :amount_sats, :integer
    field :fee_sats, :integer
    field :payment_hash, :string
    field :preimage_digest, :string
    field :gateway_ref, :string
    field :paid_at, :utc_datetime_usec
    field :receipt_digest, :string
    belongs_to :payment_intent, PaymentIntent, type: :binary_id
    belongs_to :claim, Claim, type: :binary_id
    timestamps()
  end

  @type t :: %__MODULE__{}

  def changeset(record, attributes) do
    record
    |> cast(attributes, @fields)
    |> validate_required(@fields)
    |> validate_number(:amount_sats, greater_than: 0)
    |> validate_number(:fee_sats, greater_than_or_equal_to: 0)
    |> validate_format(:payment_hash, ~r/\A[0-9a-f]{64}\z/)
    |> validate_format(:preimage_digest, ~r/\A[0-9a-f]{64}\z/)
    |> validate_format(:receipt_digest, ~r/\A[0-9a-f]{64}\z/)
    |> validate_length(:gateway_ref, min: 1, max: 256)
    |> unique_constraint(:payment_intent_id)
    |> unique_constraint(:payment_hash)
    |> foreign_key_constraint(:claim_id)
    |> foreign_key_constraint(:payment_intent_id)
  end
end