Current section
Files
Jump to
Current section
Files
lib/paper_tiger/resources/payment_intent.ex
defmodule PaperTiger.Resources.PaymentIntent do
@moduledoc """
Handles PaymentIntent resource endpoints.
## Endpoints
- POST /v1/payment_intents - Create payment intent
- GET /v1/payment_intents/:id - Retrieve payment intent
- POST /v1/payment_intents/:id - Update payment intent
- GET /v1/payment_intents - List payment intents
Note: Payment intents cannot be deleted (only canceled).
## PaymentIntent Object
%{
id: "pi_...",
object: "payment_intent",
created: 1234567890,
amount: 2000, # Amount in cents
currency: "usd",
status: "requires_payment_method",
customer: "cus_...",
payment_method: "pm_...",
metadata: %{},
# ... other fields
}
"""
import PaperTiger.Resource
alias PaperTiger.Store.PaymentIntents
require Logger
@doc """
Creates a new payment intent.
## Required Parameters
- amount - Amount in cents (e.g., 2000 for $20.00)
- currency - Three-letter ISO currency code (e.g., "usd")
## Optional Parameters
- customer - Customer ID this payment is for
- payment_method - Payment method ID to use
- metadata - Key-value metadata
- description - Payment description
- statement_descriptor - Descriptor for bank statements
"""
@spec create(Plug.Conn.t()) :: Plug.Conn.t()
def create(conn) do
with {:ok, _params} <- validate_params(conn.params, [:amount, :currency]),
payment_intent = build_payment_intent(conn.params),
{:ok, payment_intent} <- PaymentIntents.insert(payment_intent) do
maybe_store_idempotency(conn, payment_intent)
:telemetry.execute([:paper_tiger, :payment_intent, :created], %{}, %{object: payment_intent})
payment_intent
|> maybe_expand(conn.params)
|> then(&json_response(conn, 200, &1))
else
{:error, :invalid_params, field} ->
error_response(
conn,
PaperTiger.Error.invalid_request("Missing required parameter", field)
)
end
end
@doc """
Retrieves a payment intent by ID.
"""
@spec retrieve(Plug.Conn.t(), String.t()) :: Plug.Conn.t()
def retrieve(conn, id) do
case PaymentIntents.get(id) do
{:ok, payment_intent} ->
payment_intent
|> maybe_expand(conn.params)
|> then(&json_response(conn, 200, &1))
{:error, :not_found} ->
error_response(conn, PaperTiger.Error.not_found("payment_intent", id))
end
end
@doc """
Updates a payment intent.
## Updatable Fields
- amount
- customer
- payment_method
- metadata
- description
- statement_descriptor
"""
@spec update(Plug.Conn.t(), String.t()) :: Plug.Conn.t()
def update(conn, id) do
with {:ok, existing} <- PaymentIntents.get(id),
updated =
merge_updates(existing, conn.params, [
:id,
:object,
:created,
:currency,
:status
]),
{:ok, updated} <- PaymentIntents.update(updated) do
updated
|> maybe_expand(conn.params)
|> then(&json_response(conn, 200, &1))
else
{:error, :not_found} ->
error_response(conn, PaperTiger.Error.not_found("payment_intent", id))
end
end
@doc """
Lists all payment intents with pagination.
## Parameters
- limit - Number of items (default: 10, max: 100)
- starting_after - Cursor for pagination
- ending_before - Reverse cursor
- customer - Filter by customer ID
- status - Filter by status
"""
@spec list(Plug.Conn.t()) :: Plug.Conn.t()
def list(conn) do
pagination_opts = parse_pagination_params(conn.params)
result = PaymentIntents.list(pagination_opts)
json_response(conn, 200, result)
end
## Private Functions
defp build_payment_intent(params) do
%{
id: generate_id("pi"),
object: "payment_intent",
created: PaperTiger.now(),
amount: get_integer(params, :amount),
currency: Map.get(params, :currency),
status: "requires_payment_method",
customer: Map.get(params, :customer),
payment_method: Map.get(params, :payment_method),
metadata: Map.get(params, :metadata, %{}),
# Additional fields
livemode: false,
description: Map.get(params, :description),
statement_descriptor: Map.get(params, :statement_descriptor),
confirmation_method: Map.get(params, :confirmation_method, "automatic"),
setup_future_usage: Map.get(params, :setup_future_usage),
off_session: Map.get(params, :off_session),
receipt_email: Map.get(params, :receipt_email),
capture_method: Map.get(params, :capture_method, "automatic"),
amount_details: Map.get(params, :amount_details),
cancellation_reason: nil,
client_secret: generate_client_secret(),
next_action: nil,
shipping: Map.get(params, :shipping),
on_behalf_of: Map.get(params, :on_behalf_of),
last_payment_error: nil,
processing: nil,
review: nil,
application: nil,
application_fee_amount: nil,
invoice: nil,
mandate: nil,
source: Map.get(params, :source)
}
end
defp maybe_expand(payment_intent, params) do
expand_params = parse_expand_params(params)
PaperTiger.Hydrator.hydrate(payment_intent, expand_params)
end
defp generate_client_secret do
random_part =
:crypto.strong_rand_bytes(24)
|> Base.encode16(case: :lower)
|> binary_part(0, 24)
"pi_secret_#{random_part}"
end
end