Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Given a version number MAJOR.MINOR.PATCH, increment:
## [Unreleased]
### Added
- IssuingRule missing parameters
- BrcodePreview resource
### Fixed
- query parameters in post requests

Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ This SDK version is compatible with the Stark Infra API v2.
- [PixInfraction](#create-pixinfractions): Create Pix Infraction reports
- [PixChargeback](#create-pixchargebacks): Create Pix Chargeback requests
- [PixDomain](#query-pixdomains): View registered SPI participants certificates
- [BrcodePreview](#create-brcodepreviews): Preview information from a BR Code before paying it
- [Credit Note](#credit-note)
- [CreditNote](#create-creditnotes): Create credit notes
- [Webhook](#webhook):
Expand Down Expand Up @@ -1286,6 +1287,21 @@ StarkInfra.PixDomain.query!()
|> IO.inspect
```

### Create BrcodePreviews

You can preview a BR Code before paying it by providing the BR Code string and the payer's tax id:

```elixir
StarkInfra.BrcodePreview.create!([
%StarkInfra.BrcodePreview{
id: "00020126580014br.gov.bcb.pix0136a629532e-7693-4846-852d-1bbff817b5a8520400005303986540510.005802BR5908T'Challa6009Sao Paulo62090505123456304B14A",
payer_id: "20.018.183/0001-80"
}
]) |> IO.inspect
```

**Note**: Instead of using BrcodePreview structs, you can also pass each BrcodePreview element in map format

## Credit Note

### Create CreditNotes
Expand Down
166 changes: 166 additions & 0 deletions lib/brcode_preview/brcode_preview.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
defmodule StarkInfra.BrcodePreview do
alias __MODULE__, as: BrcodePreview
alias StarkInfra.Utils.Rest
alias StarkInfra.Utils.Check
alias StarkInfra.Utils.API
alias StarkInfra.BrcodePreview.Subscription
alias StarkInfra.User.Project
alias StarkInfra.User.Organization
alias StarkInfra.Error

@moduledoc """
Groups BrcodePreview related functions
"""

@doc """
The BrcodePreview struct provides information about a BR Code from a Pix payment
before paying it. When you initialize a BrcodePreview, the BR Code is not
automatically parsed by the Stark Infra API. The 'create' function sends the
structs to the Stark Infra API and returns the list of previewed structs.

## Parameters (required):
- `:id` [string]: BR Code string from a Pix payment. Same payload encoded inside a QR Code. ex: "00020126580014br.gov.bcb.pix0136a629532e-7693-4846-852d-1bbff817b5a8520400005303986540510.005802BR5908T'Challa6009Sao Paulo62090505123456304B14A"
- `:payer_id` [string]: tax id (CPF/CNPJ) of the individual or business requesting the BR Code information. Used by the Central Bank to rate-limit consultations. ex: "20.018.183/0001-80"

## Parameters (optional):
- `:end_to_end_id` [string, default nil]: central bank's unique transaction id. ex: "E79457883202101262140HHX553UPqeq"

## Attributes (return-only):
- `:account_number` [string]: payment receiver account number. ex: "1234567"
- `:account_type` [string]: payment receiver account type. ex: "checking", "savings", "salary" or "payment"
- `:amount` [integer]: amount in cents this BR Code is expecting to receive. 0 means any value is accepted. ex: 123 (= R$ 1.23)
- `:amount_type` [string]: whether the BR Code's amount is fixed or freely chosen at payment time. ex: "fixed" or "custom"
- `:bank_code` [string]: payment receiver bank code. ex: "20018183"
- `:branch_code` [string]: payment receiver branch code. ex: "0001"
- `:cash_amount` [integer]: amount in cents to be withdrawn at the cashier (Pix Saque / Pix Troco). ex: 1000 (= R$ 10.00)
- `:cashier_bank_code` [string]: cashier's bank code. ex: "20018183"
- `:cashier_type` [string]: cashier's type. ex: "merchant", "participant" or "other"
- `:discount_amount` [integer]: discount value calculated over nominal_amount. ex: 3000
- `:due` [DateTime]: BR Code due date. ex: ~U[2020-03-26 19:32:35.418698Z]
- `:fine_amount` [integer]: fine value calculated over nominal_amount. ex: 20000
- `:interest_amount` [integer]: interest value calculated over nominal_amount. ex: 10000
- `:key_id` [string]: receiver's Pix key id. ex: "+5511989898989"
- `:name` [string]: payment receiver name. ex: "Tony Stark"
- `:nominal_amount` [integer]: BR Code emission amount, before fines, fees and discounts. ex: 1234 (= R$ 12.34)
- `:reconciliation_id` [string]: reconciliation id linked to this payment. Dynamic BR Codes carry 26-35 alphanumeric chars; static BR Codes carry up to 25. ex: "cd65c78aeb6543eaaa0170f68bd741ee"
- `:reduction_amount` [integer]: reduction value to discount from nominal_amount. ex: 1000
- `:scheduled` [DateTime]: scheduled execution datetime of the payment. ex: ~U[2020-03-26 19:32:35.418698Z]
- `:status` [string]: BR Code lifecycle state. ex: "active", "paid", "canceled" or "unknown"
- `:subscription` [BrcodePreview.Subscription]: embedded subscription snapshot when the BR Code carries Pix-recurring-debit metadata. nil for non-subscription BR Codes.
- `:tax_id` [string]: payment receiver tax id. ex: "012.345.678-90"
"""
@enforce_keys [:id, :payer_id]
defstruct [
:id,
:payer_id,
:end_to_end_id,
:account_number,
:account_type,
:amount,
:amount_type,
:bank_code,
:branch_code,
:cash_amount,
:cashier_bank_code,
:cashier_type,
:discount_amount,
:due,
:fine_amount,
:interest_amount,
:key_id,
:name,
:nominal_amount,
:reconciliation_id,
:reduction_amount,
:scheduled,
:status,
:subscription,
:tax_id
]

@type t() :: %__MODULE__{}

@doc """
Send a list of BrcodePreview structs for creation in the Stark Infra API

## Parameters (required):
- `:previews` [list of BrcodePreview structs]: list of BrcodePreview structs to be previewed in the API

## Options:
- `:user` [Organization/Project, default nil]: Organization or Project struct returned from StarkInfra.project(). Only necessary if default project or organization has not been set in configs.

## Return:
- list of BrcodePreview structs with updated attributes
"""
@spec create(
[BrcodePreview.t() | map()],
user: Project.t() | Organization.t() | nil
) ::
{:ok, [BrcodePreview.t()]} |
{:error, [Error.t()]}
def create(previews, options \\ []) do
Rest.post(
resource(),
previews,
options
)
end

@doc """
Same as create(), but it will unwrap the error tuple and raise in case of errors.
"""
@spec create!(
[BrcodePreview.t() | map()],
user: Project.t() | Organization.t() | nil
) :: any
def create!(previews, options \\ []) do
Rest.post!(
resource(),
previews,
options
)
end

defp parse_subscription(nil), do: nil
defp parse_subscription(value) when value == %{}, do: nil
defp parse_subscription(value), do: API.from_api_json(value, &Subscription.resource_maker/1)

@doc false
def resource() do
{
"BrcodePreview",
&resource_maker/1
}
end

@doc false
def resource_maker(json) do
%BrcodePreview{
id: json[:id],
payer_id: json[:payer_id],
end_to_end_id: json[:end_to_end_id],
account_number: json[:account_number],
account_type: json[:account_type],
amount: json[:amount],
amount_type: json[:amount_type],
bank_code: json[:bank_code],
branch_code: json[:branch_code],
cash_amount: json[:cash_amount],
cashier_bank_code: json[:cashier_bank_code],
cashier_type: json[:cashier_type],
discount_amount: json[:discount_amount],
due: json[:due] |> Check.datetime(),
fine_amount: json[:fine_amount],
interest_amount: json[:interest_amount],
key_id: json[:key_id],
name: json[:name],
nominal_amount: json[:nominal_amount],
reconciliation_id: json[:reconciliation_id],
reduction_amount: json[:reduction_amount],
scheduled: json[:scheduled] |> Check.datetime(),
status: json[:status],
subscription: parse_subscription(json[:subscription]),
tax_id: json[:tax_id]
}
end
end
81 changes: 81 additions & 0 deletions lib/brcode_preview/subscription.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
defmodule StarkInfra.BrcodePreview.Subscription do
alias __MODULE__, as: Subscription
alias StarkInfra.Utils.Check

@moduledoc """
Groups BrcodePreview.Subscription related functions
"""

@doc """
A BrcodePreview.Subscription is a read-only snapshot of a Pix recurring-debit
subscription, embedded inside a BrcodePreview response when the previewed BR Code
carries subscription metadata. It is never persisted by the caller and exposes no
endpoints.

## Attributes (return-only):
- `:amount` [integer]: amount in cents charged per cycle. nil for variable-amount subscriptions. ex: 1000 (= R$ 10.00)
- `:amount_min_limit` [integer]: floor value for the maximum amount the sender can set when approving a variable-amount subscription. nil for fixed-amount subscriptions. ex: 500 (= R$ 5.00)
- `:bacen_id` [string]: Central Bank's unique recurrency id for the subscription.
- `:created` [DateTime]: creation datetime of the subscription. ex: ~U[2020-03-26 19:32:35.418698Z]
- `:description` [string]: additional information delivered to the sender.
- `:installment_end` [DateTime]: end datetime of settlements allowed for this subscription. ex: ~U[2020-03-26 19:32:35.418698Z]
- `:installment_start` [DateTime]: start datetime of settlements allowed for this subscription. ex: ~U[2020-03-26 19:32:35.418698Z]
- `:interval` [string]: cycle definition exposed verbatim from the server. ex: "monthly"
- `:pull_retry_limit` [integer]: max number of retries the receiver may issue for a single failed pull cycle.
- `:receiver_bank_code` [string]: receiver's bank institution code.
- `:receiver_name` [string]: receiver's full name.
- `:receiver_tax_id` [string]: receiver's tax id (CPF or CNPJ).
- `:reference_code` [string]: commercial-relation identifier (contract number, order id, or client code).
- `:sender_final_name` [string]: final sender name when the sender differs from the originating institution.
- `:sender_final_tax_id` [string]: final sender tax id when distinct from the originating sender.
- `:status` [string]: current lifecycle state of the subscription snapshot, verbatim from the server. ex: "created", "active", "canceled" or "failed"
- `:type` [string]: subscription journey type, verbatim from the server. ex: "push", "subscriptionAndPayment"
- `:updated` [DateTime]: latest update datetime of the subscription. ex: ~U[2020-03-26 19:32:35.418698Z]
"""
defstruct [
:amount,
:amount_min_limit,
:bacen_id,
:created,
:description,
:installment_end,
:installment_start,
:interval,
:pull_retry_limit,
:receiver_bank_code,
:receiver_name,
:receiver_tax_id,
:reference_code,
:sender_final_name,
:sender_final_tax_id,
:status,
:type,
:updated
]

@type t() :: %__MODULE__{}

@doc false
def resource_maker(json) do
%Subscription{
amount: json[:amount],
amount_min_limit: json[:amount_min_limit],
bacen_id: json[:bacen_id],
created: json[:created] |> Check.datetime(),
description: json[:description],
installment_end: json[:installment_end] |> Check.datetime(),
installment_start: json[:installment_start] |> Check.datetime(),
interval: json[:interval],
pull_retry_limit: json[:pull_retry_limit],
receiver_bank_code: json[:receiver_bank_code],
receiver_name: json[:receiver_name],
receiver_tax_id: json[:receiver_tax_id],
reference_code: json[:reference_code],
sender_final_name: json[:sender_final_name],
sender_final_tax_id: json[:sender_final_tax_id],
status: json[:status],
type: json[:type],
updated: json[:updated] |> Check.datetime()
}
end
end
12 changes: 12 additions & 0 deletions lib/utils/checks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ defmodule StarkInfra.Utils.Check do
nil
end

def datetime("") do
nil
end

def datetime(data) when is_binary(data) do
{:ok, datetime, _utc_offset} = data |> DateTime.from_iso8601()
datetime
Expand All @@ -35,6 +39,10 @@ defmodule StarkInfra.Utils.Check do
nil
end

def date("") do
nil
end

def date(data) when is_binary(data) do
data |> Date.from_iso8601!()
end
Expand All @@ -47,6 +55,10 @@ defmodule StarkInfra.Utils.Check do
data
end

def date_or_datetime("") do
nil
end

def date_or_datetime(data) do
try do
date(data)
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ defmodule StarkInfra.MixProject do

def application do
[
extra_applications: [:inets]
extra_applications: [:inets, :ssl, :public_key]
]
end

Expand Down
27 changes: 27 additions & 0 deletions test/brcode_preview_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
defmodule StarkInfraTest.BrcodePreview do
use ExUnit.Case

@tag :brcode_preview
test "create brcode preview" do
{:ok, previews} = StarkInfra.BrcodePreview.create([example_brcode_preview()])
preview = previews |> hd

assert !is_nil(preview.id)
end

@tag :brcode_preview
test "create! brcode preview" do
preview =
StarkInfra.BrcodePreview.create!([example_brcode_preview()])
|> hd

assert !is_nil(preview.id)
end

def example_brcode_preview() do
%StarkInfra.BrcodePreview{
id: StarkInfraTest.Utils.StaticBrcode.create_id("+5511989890096"),
payer_id: "20.018.183/0001-80"
}
end
end
4 changes: 3 additions & 1 deletion test/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ ExUnit.start(
# :issuing_transaction_log,
# :issuing_withdrawal,
# :issuing_withdrawal_log,
# :webhook
# :webhook,
# :brcode_preview
]
)

Expand All @@ -48,3 +49,4 @@ Code.require_file("./test/utils/random.exs")
Code.require_file("./test/utils/issuing_holder.exs")
Code.require_file("./test/utils/issuing_withdrawal.exs")
Code.require_file("./test/utils/issuing_invoice.exs")
Code.require_file("./test/utils/static_brcode.exs")
22 changes: 22 additions & 0 deletions test/utils/static_brcode.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule StarkInfraTest.Utils.StaticBrcode do
@moduledoc false

alias StarkInfra.Utils.Request
alias StarkInfra.Utils.JSON

def create_id(key_id) do
{:ok, body} =
Request.fetch(
:post,
"/static-brcode",
payload: %{
brcodes: [
%{name: "Tony Stark", keyId: key_id, city: "Sao Paulo"}
]
}
)

%{"brcodes" => [%{"id" => id} | _]} = JSON.decode!(body)
id
end
end