Packages
phoenix_kit
1.7.21
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/modules/billing/providers/provider.ex
defmodule PhoenixKit.Modules.Billing.Providers.Provider do
@moduledoc """
Behaviour for payment providers.
Defines a unified interface for all payment systems (Stripe, PayPal, Razorpay).
Each provider implements this behaviour to handle payments, refunds, and webhooks.
## Provider Architecture
PhoenixKit uses Internal Subscription Control - subscriptions are managed
in our database, not by providers. Providers only handle:
- One-time payments (checkout sessions)
- Saving payment methods for recurring billing
- Charging saved payment methods
- Processing refunds
## Hosted Checkout Flow
1. User clicks "Pay with Stripe" on invoice
2. Backend calls create_checkout_session/2
3. User redirected to provider's checkout page
4. Provider processes payment
5. Provider sends webhook
6. WebhookProcessor updates invoice status
## Implementation Example
defmodule PhoenixKit.Modules.Billing.Providers.Stripe do
@behaviour PhoenixKit.Modules.Billing.Providers.Provider
@impl true
def provider_name, do: :stripe
@impl true
def available? do
config = get_config()
config && config.enabled && config.api_key
end
@impl true
def create_checkout_session(invoice, opts) do
# Implementation
end
# ... other callbacks
end
"""
@type checkout_session :: %{
id: String.t(),
url: String.t(),
provider: atom(),
expires_at: DateTime.t() | nil,
metadata: map()
}
@type setup_session :: %{
id: String.t(),
url: String.t(),
provider: atom(),
metadata: map()
}
@type webhook_event :: %{
type: String.t(),
event_id: String.t(),
data: map(),
provider: atom(),
raw_payload: map()
}
@type payment_method :: %{
id: String.t(),
provider: atom(),
provider_payment_method_id: String.t(),
provider_customer_id: String.t() | nil,
type: String.t(),
brand: String.t() | nil,
last4: String.t() | nil,
exp_month: integer() | nil,
exp_year: integer() | nil,
metadata: map()
}
@type charge_result :: %{
id: String.t(),
provider_transaction_id: String.t(),
amount: Decimal.t(),
currency: String.t(),
status: String.t(),
metadata: map()
}
@type refund_result :: %{
id: String.t(),
provider_refund_id: String.t(),
amount: Decimal.t(),
status: String.t(),
metadata: map()
}
@doc """
Returns the provider name as an atom.
## Examples
iex> Stripe.provider_name()
:stripe
iex> PayPal.provider_name()
:paypal
"""
@callback provider_name() :: atom()
@doc """
Checks if the provider is configured and available for use.
Returns `true` if:
- Provider is enabled in settings
- API credentials are configured
- Provider passed verification (if applicable)
## Examples
iex> Stripe.available?()
true
"""
@callback available?() :: boolean()
@doc """
Creates a checkout session for one-time payment.
This is used for paying invoices. The user is redirected to the
provider's hosted checkout page where they enter payment details.
## Parameters
- `invoice` - The invoice to pay (must include amount, currency, line_items)
- `opts` - Options:
- `:success_url` - URL to redirect after successful payment
- `:cancel_url` - URL to redirect if user cancels
- `:save_payment_method` - Whether to save card for future use (default: false)
## Returns
- `{:ok, checkout_session}` - Session created, redirect user to `session.url`
- `{:error, reason}` - Failed to create session
"""
@callback create_checkout_session(invoice :: map(), opts :: keyword()) ::
{:ok, checkout_session()} | {:error, term()}
@doc """
Creates a setup session to save a payment method without charging.
Used when a user wants to add a payment method for future subscriptions
without making an immediate payment.
## Parameters
- `user` - The user to save payment method for
- `opts` - Options:
- `:success_url` - URL to redirect after success
- `:cancel_url` - URL to redirect if user cancels
## Returns
- `{:ok, setup_session}` - Session created
- `{:error, reason}` - Failed to create session
"""
@callback create_setup_session(user :: map(), opts :: keyword()) ::
{:ok, setup_session()} | {:error, term()}
@doc """
Charges a saved payment method.
Used for subscription renewals. The payment method was previously
saved during checkout or setup session.
## Parameters
- `payment_method` - The saved payment method record
- `amount` - Amount to charge (Decimal)
- `opts` - Options:
- `:currency` - Currency code (default: from payment method)
- `:description` - Description for the charge
- `:invoice_id` - Associated invoice ID
- `:metadata` - Additional metadata
## Returns
- `{:ok, charge_result}` - Charge successful
- `{:error, :card_declined}` - Card was declined
- `{:error, :payment_method_expired}` - Payment method expired
- `{:error, reason}` - Other error
"""
@callback charge_payment_method(
payment_method :: map(),
amount :: Decimal.t(),
opts :: keyword()
) :: {:ok, charge_result()} | {:error, term()}
@doc """
Verifies webhook signature to ensure request is from the provider.
## Parameters
- `payload` - Raw request body as binary
- `signature` - Signature from request headers
- `secret` - Webhook secret for this provider
## Returns
- `:ok` - Signature is valid
- `{:error, :invalid_signature}` - Signature verification failed
"""
@callback verify_webhook_signature(
payload :: binary(),
signature :: String.t(),
secret :: String.t()
) :: :ok | {:error, :invalid_signature}
@doc """
Handles and normalizes a webhook event payload.
Converts provider-specific event format to a normalized format
that can be processed by WebhookProcessor.
## Parameters
- `payload` - Decoded JSON payload from webhook
## Returns
- `{:ok, webhook_event}` - Event parsed successfully
- `{:error, :unknown_event}` - Event type not recognized
- `{:error, reason}` - Failed to parse event
"""
@callback handle_webhook_event(payload :: map()) ::
{:ok, webhook_event()} | {:error, term()}
@doc """
Creates a refund for a transaction.
## Parameters
- `provider_transaction_id` - The provider's transaction/charge ID
- `amount` - Amount to refund (Decimal, nil for full refund)
- `opts` - Options:
- `:reason` - Reason for refund
- `:metadata` - Additional metadata
## Returns
- `{:ok, refund_result}` - Refund created
- `{:error, :already_refunded}` - Transaction already refunded
- `{:error, reason}` - Refund failed
"""
@callback create_refund(
provider_transaction_id :: String.t(),
amount :: Decimal.t() | nil,
opts :: keyword()
) :: {:ok, refund_result()} | {:error, term()}
@doc """
Gets details of a saved payment method.
## Parameters
- `provider_payment_method_id` - The provider's payment method ID
## Returns
- `{:ok, payment_method}` - Payment method details
- `{:error, :not_found}` - Payment method not found
- `{:error, reason}` - Failed to get details
"""
@callback get_payment_method_details(provider_payment_method_id :: String.t()) ::
{:ok, payment_method()} | {:error, term()}
@doc """
Detaches/removes a saved payment method from the provider.
## Parameters
- `provider_payment_method_id` - The provider's payment method ID
## Returns
- `:ok` - Payment method removed
- `{:error, :not_found}` - Payment method not found
- `{:error, reason}` - Failed to remove
"""
@callback detach_payment_method(provider_payment_method_id :: String.t()) ::
:ok | {:error, term()}
@optional_callbacks detach_payment_method: 1
end