Skip to content

Commit

Permalink
complete basic functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
manuel-rubio committed May 10, 2024
1 parent 4435418 commit 2942d4f
Show file tree
Hide file tree
Showing 24 changed files with 1,422 additions and 21 deletions.
14 changes: 14 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# These are supported funding model platforms

github: [altenwald, manuel-rubio]
patreon: altenwald
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
39 changes: 39 additions & 0 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Elixir CI

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

permissions:
contents: read

jobs:
build:

name: Build and test
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up Elixir
uses: erlef/setup-beam@61e01a43a562a89bfc54c7f9a378ff67b03e4a21 # v1.16.0
with:
elixir-version: '1.15.2' # [Required] Define the Elixir version
otp-version: '26.0' # [Required] Define the Erlang/OTP version
- name: Restore dependencies cache
uses: actions/cache@v3
with:
path: deps
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: ${{ runner.os }}-mix-
- name: Install dependencies
run: mix deps.get
- name: Run tests
run: mix check
18 changes: 0 additions & 18 deletions lib/paypal.ex

This file was deleted.

18 changes: 18 additions & 0 deletions lib/paypal/auth/access.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
defmodule Paypal.Auth.Access do
@moduledoc """
The access structure has the information for accessing to the rest of
the requests and the information about the expiration of the token.
"""
use TypedEctoSchema
import Ecto.Changeset
import Paypal.EctoHelpers

@primary_key false

@typedoc """
The information stored inside of the access structure is the following:
- `scope` is a list of URLs we can access or use with the access token.
- `access_token` is the hash we need for the other requests.
- `token_type` is the type of the token generated. Usually it's `Bearer`.
- `app_id` is the ID of the application.
- `expires_in` is the number of seconds for expiring the token.
- `nonce` is the nonce used.
"""
typed_embedded_schema do
field(:scope, :string)
field(:access_token, :string)
Expand All @@ -16,6 +30,10 @@ defmodule Paypal.Auth.Access do

@fields ~w[scope access_token token_type app_id expires_in nonce]a

@doc """
Perform the transformation of the input data from the Paypal server to the
access struct.
"""
def cast(model \\ %__MODULE__{}, params) do
model
|> cast(params, @fields)
Expand Down
5 changes: 4 additions & 1 deletion lib/paypal/auth/request.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defmodule Paypal.Auth.Request do
require Logger

plug(Tesla.Middleware.Logger,
format: "$method /api/domains$url?$query ===> $status / time=$time",
format: "$method $url ===> $status / time=$time",
log_level: :debug
)

Expand All @@ -27,6 +27,9 @@ defmodule Paypal.Auth.Request do

plug(Tesla.Middleware.DecodeJson)

@doc """
Perform the authorization and retrieve the response.
"""
def auth do
with {:ok, %_{body: response}} <- post("/v1/oauth2/token", "grant_type=client_credentials") do
{:ok, response}
Expand Down
34 changes: 34 additions & 0 deletions lib/paypal/common/currency_value.ex
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
defmodule Paypal.Common.CurrencyValue do
@moduledoc """
Most of the currencies in the Paypal requests and responses are handled
as a JSON object that is including `currency_code` and `value`. But it's
even more complex in other requests.
This struct contains the possibilites for all of these requests/responses.
"""
use TypedEctoSchema
import Ecto.Changeset

@derive Jason.Encoder

@primary_key false

@typedoc """
The type is composed by the following:
- `currency_code` is the currency code based on ISO-4217, i.e. EUR
- `value` is the decimal or integer value for the currency.
- `breakdown` is expressing information for the money.
About the breakdown, we could find that if it's provided, it could include
information like this one:
```elixir
%{
"item_total" => %{
"currency_code" => "EUR",
"value" => "12.00"
},
"shipping" => %{
"currency_code" => "EUR",
"value" => "2.00"
},
"discount" => {
"currency_code" => "EUR",
"value" => "5.00"
}
}
```
"""
typed_embedded_schema do
field(:currency_code, :string)
field(:value, :decimal)
Expand Down
16 changes: 16 additions & 0 deletions lib/paypal/common/error.ex
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
defmodule Paypal.Common.Error do
@moduledoc """
When something goes wrong, Paypal is replying us with an error message
and this is the struct for retrieving this kind of errors.
"""
use TypedEctoSchema
alias Paypal.Common.Link

@primary_key false

@typedoc """
The information given by Paypal for each error is as follows:
- `debug_id` is the ID for debugging the error.
- `details` is a list of details about the errors.
- `links` is the list of links (HATEOAS).
- `message` is the error message to try to understand why it failed.
- `name` is the error name.
"""
typed_embedded_schema do
field(:debug_id, :string, primary_key: true)

embeds_many :details, Details, primary_key: false do
@moduledoc false
@typedoc false

field(:field, :string)
field(:description, :string)
field(:issue, :string)
Expand Down
13 changes: 13 additions & 0 deletions lib/paypal/common/link.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
defmodule Paypal.Common.Link do
@moduledoc """
The link is accumulating all of the required information for handling the
HATEOAS links.
"""
use TypedEctoSchema

@primary_key false
Expand All @@ -14,6 +18,15 @@ defmodule Paypal.Common.Link do
patch: "PATCH"
]

@typedoc """
The possible values for the links are:
- `enc_type` is the kind of encoding the request is providing or requiring.
It's not compulsory for most of the links.
- `href` is the URL for the link.
- `rel` is the name for the link. The name is based on the RFC-8288.
- `method` is the HTTP method that is needed for the request.
"""
typed_embedded_schema do
field(:enc_type, :string, source: :encType)
field(:href, :string)
Expand Down
10 changes: 10 additions & 0 deletions lib/paypal/common/operation.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
defmodule Paypal.Common.Operation do
@moduledoc """
The operation is the struct where we store the information for the operations
performed in orders and payments. Most of the requests that are performing an
action is returning an operation.
"""
use TypedEctoSchema

alias Paypal.Common.Link
Expand All @@ -14,6 +19,11 @@ defmodule Paypal.Common.Operation do

@primary_key false

@typedoc """
The information provided is an `id` for the operation (order or payment),
the `links` for performing other actions based on the return and the
`status` of the operation.
"""
typed_embedded_schema do
field(:id, :string, primary_key: true)
embeds_many(:links, Link)
Expand Down
8 changes: 8 additions & 0 deletions lib/paypal/ecto_helpers.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
defmodule Paypal.EctoHelpers do
@moduledoc """
Ecto Helpers is a module that is ensuring we have the common functions in
use for most of the schemas or modules that uses Ecto.
"""
import Ecto.Changeset

@doc """
Traverse errors is a way to retrieve in a plain format the full list of
errors for all of the schemas and embedded schemas under the main one.
"""
def traverse_errors(changeset) do
traverse_errors(changeset, fn {msg, opts} ->
Regex.replace(~r"%{(\w+)}", msg, fn _, key ->
Expand Down
6 changes: 6 additions & 0 deletions lib/paypal/order.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ defmodule Paypal.Order do
alias Paypal.Auth
alias Paypal.Common.Error, as: OrderError
alias Paypal.Order.Authorized
alias Paypal.Order.Authorized.PurchaseUnit
alias Paypal.Order.Create
alias Paypal.Order.ExperienceContext
alias Paypal.Order.Info
Expand Down Expand Up @@ -88,6 +89,11 @@ defmodule Paypal.Order do
]
end

@doc """
Create an order.
"""
@spec create(:capture | :authorize, PurchaseUnit.t(), ExperienceContext.t()) ::
{:ok, Info.t()} | {:error, OrderError.t() | String.t()}
def create(intent, purchase_units, experience_context) do
with {:ok, data} <- Create.changeset(%{intent: intent, purchase_units: purchase_units}),
{:ok, context} <- ExperienceContext.changeset(experience_context),
Expand Down
13 changes: 13 additions & 0 deletions lib/paypal/order/authorization.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
defmodule Paypal.Order.Authorization do
@moduledoc """
Authorization is the information embeded into the
`Paypal.Order.Authorized` for getting all of the information for the
authorized payment.
"""
use TypedEctoSchema

alias Paypal.Common.CurrencyValue
Expand All @@ -16,6 +21,11 @@ defmodule Paypal.Order.Authorization do

@primary_key false

@typedoc """
The information about the authorization performed on an order.
The important information here is the `id` because it will be
important to perform actions using `Paypal.Payment` functions.
"""
typed_embedded_schema do
field(:id, :string, primary_key: true)
field(:status, Ecto.Enum, values: @statuses, embed_as: :dumped)
Expand All @@ -29,6 +39,9 @@ defmodule Paypal.Order.Authorization do
field(:network_transaction_reference, :map)

embeds_one :seller_protection, SellerProtection, primary_key: false do
@moduledoc false
@typedoc false

@seller_protection_statuses [
eligible: "ELIGIBLE",
partially_eligible: "PARTIALLY_ELIGIBLE",
Expand Down
14 changes: 14 additions & 0 deletions lib/paypal/order/authorized.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
defmodule Paypal.Order.Authorized do
@moduledoc """
The authorized struct is the response performed by `Paypal.Order.authorize/1`
where we can see the status of the authorization and other information
related to the request.
"""
use TypedEctoSchema

alias Paypal.Common.Link
Expand All @@ -7,16 +12,25 @@ defmodule Paypal.Order.Authorized do

@primary_key false

@typedoc """
The information related to the request is returning the order `id`, the final
`status` for the order and the information for the authorization in the path
`purchase_units/payments/authorizations`.
"""
typed_embedded_schema do
field(:id, :string, primary_key: true)
field(:status, Ecto.Enum, values: Order.statuses(), embed_as: :dumped)
# TODO
field(:payment_source, :map)

embeds_many :purchase_units, PurchaseUnit, primary_key: false do
@moduledoc false
@typedoc false
field(:reference_id, :string, primary_key: true)

embeds_one :payments, Payment, primary_key: false do
@moduledoc false
@typedoc false
embeds_many(:authorizations, Authorization)
end
end
Expand Down
10 changes: 10 additions & 0 deletions lib/paypal/order/create.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
defmodule Paypal.Order.Create do
@moduledoc """
Create an order. It contains the information for creating an order.
"""
use TypedEctoSchema

import Ecto.Changeset
Expand All @@ -11,6 +14,13 @@ defmodule Paypal.Order.Create do

@primary_key false

@typedoc """
The information for creating an order is based on two principal data:
- `intent` that could be `:capture` or `:authorize`.
- `purchase_units` that is the information for the payment.
See `Paypal.Order.PurchaseUnit`.
"""
typed_embedded_schema do
field(:intent, Ecto.Enum, values: Order.intents(), embed_as: :dumped)
embeds_many(:purchase_units, PurchaseUnit)
Expand Down
Loading

0 comments on commit 2942d4f

Please sign in to comment.