Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: CI

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

defaults:
run:
working-directory: pretex

jobs:
precommit:
name: mix precommit
runs-on: ubuntu-latest

services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: pretex_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

env:
MIX_ENV: test

steps:
- uses: actions/checkout@v4

- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
elixir-version: "1.19"
otp-version: "27"

- name: Restore deps cache
uses: actions/cache@v4
id: deps-cache
with:
path: pretex/deps
key: deps-${{ runner.os }}-${{ hashFiles('pretex/mix.lock') }}
restore-keys: deps-${{ runner.os }}-

- name: Restore build cache
uses: actions/cache@v4
with:
path: pretex/_build
key: build-${{ runner.os }}-${{ hashFiles('pretex/mix.lock') }}
restore-keys: build-${{ runner.os }}-

- name: Install dependencies
if: steps.deps-cache.outputs.cache-hit != 'true'
run: mix deps.get

- name: Run precommit checks
run: mix precommit
11 changes: 8 additions & 3 deletions pretex/lib/pretex/memberships.ex
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ defmodule Pretex.Memberships do
now = DateTime.utc_now(:second)

Membership
|> where([m],
|> where(
[m],
m.customer_id == ^customer.id and
m.organization_id == ^org_id and
m.status == "active" and
Expand All @@ -134,7 +135,8 @@ defmodule Pretex.Memberships do

memberships =
Membership
|> where([m],
|> where(
[m],
m.customer_id == ^customer_id and
m.organization_id == ^org_id and
m.status == "active" and
Expand Down Expand Up @@ -210,7 +212,10 @@ defmodule Pretex.Memberships do
min(value, subtotal_cents)
end

defp compute_benefit_discount(%{benefit_type: "percentage_discount", value: basis_points}, subtotal_cents) do
defp compute_benefit_discount(
%{benefit_type: "percentage_discount", value: basis_points},
subtotal_cents
) do
round(subtotal_cents * basis_points / 10_000)
end

Expand Down
9 changes: 8 additions & 1 deletion pretex/lib/pretex/memberships/membership.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ defmodule Pretex.Memberships.Membership do
:organization_id,
:source_order_id
])
|> validate_required([:starts_at, :expires_at, :status, :membership_type_id, :customer_id, :organization_id])
|> validate_required([
:starts_at,
:expires_at,
:status,
:membership_type_id,
:customer_id,
:organization_id
])
|> validate_inclusion(:status, @statuses)
end
end
38 changes: 36 additions & 2 deletions pretex/lib/pretex/orders.ex
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,32 @@ defmodule Pretex.Orders do
|> Repo.update()
end

@doc """
Persists the attendee name and email on the cart so they survive page refreshes.
"""
def update_cart_attendee_info(%CartSession{} = cart, name, email) do
cart
|> CartSession.attendee_changeset(%{attendee_name: name, attendee_email: email})
|> Repo.update()
end

@doc """
Slides the cart's expiry window 15 minutes from now.

Called on every checkout page visit so the TTL is activity-based rather than
fixed at cart-creation time. A user who spends more than 15 minutes browsing
the summary page no longer hits a spurious "cart expired" error when they
click to place their order.
"""
def extend_cart_expiry(%CartSession{} = cart) do
new_expiry =
DateTime.utc_now() |> DateTime.add(15 * 60, :second) |> DateTime.truncate(:second)

cart
|> Ecto.Changeset.change(expires_at: new_expiry)
|> Repo.update()
end

@doc """
Adds an item to the cart or updates quantity if already present.
Options: [quantity: 1, variation_id: nil]
Expand Down Expand Up @@ -345,11 +371,19 @@ defmodule Pretex.Orders do
order_after_gift_card
|> Repo.preload(order_items: [item: []])
|> Map.get(:order_items, [])
|> Enum.filter(fn oi -> oi.item.item_type == "membership" && oi.item.membership_type_id != nil end)
|> Enum.filter(fn oi ->
oi.item.item_type == "membership" && oi.item.membership_type_id != nil
end)
|> Enum.each(fn oi ->
mt = Pretex.Memberships.get_membership_type!(oi.item.membership_type_id)
customer = Repo.get!(Pretex.Customers.Customer, customer_id)
Pretex.Memberships.activate_membership_from_order(mt, customer, org, order_after_gift_card)

Pretex.Memberships.activate_membership_from_order(
mt,
customer,
org,
order_after_gift_card
)
end)
end

Expand Down
7 changes: 7 additions & 0 deletions pretex/lib/pretex/orders/cart_session.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ defmodule Pretex.Orders.CartSession do
field(:session_token, :string)
field(:expires_at, :utc_datetime)
field(:status, :string, default: "active")
field(:attendee_name, :string)
field(:attendee_email, :string)

belongs_to(:event, Pretex.Events.Event)
has_many(:cart_items, Pretex.Orders.CartItem)
Expand All @@ -24,4 +26,9 @@ defmodule Pretex.Orders.CartSession do
|> validate_inclusion(:status, @statuses)
|> unique_constraint(:session_token)
end

def attendee_changeset(cart, attrs) do
cart
|> cast(attrs, [:attendee_name, :attendee_email])
end
end
9 changes: 9 additions & 0 deletions pretex/lib/pretex/payments.ex
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,15 @@ defmodule Pretex.Payments do
|> Repo.one()
end

@doc """
Updates the transfer_note on a payment (customer-submitted proof for bank transfer).
"""
def update_payment_transfer_note(%Payment{} = payment, note) do
payment
|> Payment.note_changeset(%{transfer_note: note})
|> Repo.update()
end

def list_pending_async_payments do
Payment
|> where([p], p.status == "pending" and p.flow == "async")
Expand Down
8 changes: 8 additions & 0 deletions pretex/lib/pretex/payments/payment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ defmodule Pretex.Payments.Payment do
field(:settled_at, :utc_datetime)
# Human-readable failure reason, if any
field(:failure_reason, :string)
# Customer-submitted proof/note for manual bank transfer payments
field(:transfer_note, :string)

has_many(:refunds, Pretex.Payments.Refund)

Expand Down Expand Up @@ -67,6 +69,12 @@ defmodule Pretex.Payments.Payment do
])
end

def note_changeset(payment, attrs) do
payment
|> cast(attrs, [:transfer_note])
|> validate_length(:transfer_note, max: 1000)
end

def confirm_changeset(payment, attrs \\ %{}) do
now = DateTime.utc_now() |> DateTime.truncate(:second)

Expand Down
2 changes: 1 addition & 1 deletion pretex/lib/pretex_web/controllers/page_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ defmodule PretexWeb.PageController do
use PretexWeb, :controller

def home(conn, _params) do
render(conn, :home)
render(conn, :home, current_scope: conn.assigns.current_scope)
end
end
14 changes: 12 additions & 2 deletions pretex/lib/pretex_web/controllers/page_html/home.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,18 @@
</a>
</div>
<div class="flex items-center gap-2">
<a href="/customers/log-in" class="btn btn-ghost btn-sm">Entrar</a>
<a href="/customers/register" class="btn btn-primary btn-sm">Criar Conta</a>
<%= if @current_scope && @current_scope.customer do %>
<span class="text-sm text-base-content/60 hidden sm:inline">
{@current_scope.customer.email}
</span>
<a href="/account/orders" class="btn btn-ghost btn-sm">Meus Pedidos</a>
<.link href="/customers/log-out" method="delete" class="btn btn-ghost btn-sm">
Sair
</.link>
<% else %>
<a href="/customers/log-in" class="btn btn-ghost btn-sm">Entrar</a>
<a href="/customers/register" class="btn btn-primary btn-sm">Criar Conta</a>
<% end %>
</div>
</div>
</nav>
Expand Down
26 changes: 26 additions & 0 deletions pretex/lib/pretex_web/live/admin/order_live/show.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ defmodule PretexWeb.Admin.OrderLive.Show do
alias Pretex.Events
alias Pretex.Orders
alias Pretex.Organizations
alias Pretex.Payments

@impl true
def mount(%{"org_id" => org_id, "event_id" => event_id, "id" => id}, _session, socket) do
org = Organizations.get_organization!(org_id)
event = Events.get_event!(event_id)
order = Orders.get_order_with_details!(id)
payment = Payments.get_payment_for_order(order)

socket =
socket
|> assign(:org, org)
|> assign(:event, event)
|> assign(:order, order)
|> assign(:payment, payment)
|> assign(:page_title, "Pedido ##{order.confirmation_code}")

{:ok, socket}
Expand Down Expand Up @@ -85,4 +88,27 @@ defmodule PretexWeb.Admin.OrderLive.Show do
{:noreply, put_flash(socket, :error, "Não foi possível cancelar o pedido.")}
end
end

@impl true
def handle_event("confirm_payment", _params, socket) do
case socket.assigns.payment do
nil ->
{:noreply, put_flash(socket, :error, "Nenhum pagamento encontrado para este pedido.")}

payment ->
case Payments.confirm_payment(payment) do
{:ok, confirmed_payment} ->
updated_order = Orders.get_order_with_details!(socket.assigns.order.id)

{:noreply,
socket
|> assign(:order, updated_order)
|> assign(:payment, confirmed_payment)
|> put_flash(:info, "Pagamento confirmado com sucesso.")}

{:error, _reason} ->
{:noreply, put_flash(socket, :error, "Não foi possível confirmar o pagamento.")}
end
end
end
end
Loading
Loading