Skip to content

Commit 8be9316

Browse files
authored
Merge pull request #596 from code-corps/update-deps
Fix StripeConnectAccount
2 parents 3df5c2d + e44bcdc commit 8be9316

49 files changed

Lines changed: 397 additions & 253 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
AWS_ACCESS_KEY_ID=
2-
AWS_SECRET_ACCESS_KEY=
3-
CLOUDFRONT_DOMAIN=
4-
S3_BUCKET=
5-
SEGMENT_WRITE_KEY=
6-
SENTRY_DSN=
7-
STRIPE_SECRET_KEY=
8-
STRIPE_PLATFORM_CLIENT_ID=
1+
export AWS_ACCESS_KEY_ID=
2+
export AWS_SECRET_ACCESS_KEY=
3+
export CLOUDFRONT_DOMAIN=
4+
export S3_BUCKET=
5+
export SEGMENT_WRITE_KEY=
6+
export SENTRY_DSN=
7+
export STRIPE_SECRET_KEY=
8+
export STRIPE_PLATFORM_CLIENT_ID=

lib/code_corps/map_utils.ex

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,21 @@ defmodule CodeCorps.MapUtils do
55
|> Map.delete(old_key)
66
end
77

8-
def keys_to_string(map) do
9-
for {key, val} <- map, into: %{} do
10-
cond do
11-
is_atom(key) -> {Atom.to_string(key), val}
12-
true -> {key, val}
13-
end
14-
end
8+
def keys_to_string(map), do: stringify_keys(map)
9+
10+
# Goes through a list and stringifies keys of any map member
11+
def stringify_keys(nil), do: nil
12+
def stringify_keys(%DateTime{} = val), do: val
13+
def stringify_keys(map = %{}) do
14+
map
15+
|> Enum.map(fn {k, v} -> {stringify_key(k), stringify_keys(v)} end)
16+
|> Enum.into(%{})
1517
end
18+
def stringify_keys([head | rest]), do: [stringify_keys(head) | stringify_keys(rest)]
19+
# Default
20+
def stringify_keys(not_a_map), do: not_a_map
21+
22+
def stringify_key(k) when is_atom(k), do: Atom.to_string(k)
23+
def stringify_key(k), do: k
24+
1625
end

lib/code_corps/stripe_service/adapters/stripe_connect_account.ex

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountAdapter do
22
import CodeCorps.MapUtils, only: [keys_to_string: 1]
3-
import CodeCorps.StripeService.Util, only: [transform_map: 2]
3+
import CodeCorps.StripeService.Util, only: [transform_attributes: 2, transform_map: 2]
44

5-
@stripe_attributes [
6-
:business_name, :business_url, :charges_enabled, :country, :default_currency, :details_submitted, :email, :id, :managed,
7-
:support_email, :support_phone, :support_url, :transfers_enabled
8-
]
9-
10-
@doc """
11-
Mapping of stripe record attributes to locally stored attributes
12-
Format is {:local_key, [:nesting, :of, :stripe, :keys]}
13-
"""
5+
# Mapping of stripe record attributes to locally stored attributes
6+
# Format is {:local_key, [:nesting, :of, :stripe, :keys]}
147
@stripe_mapping [
158
{:id_from_stripe, [:id]},
169
{:business_name, [:business_name]},
@@ -45,7 +38,9 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountAdapter do
4538
{:legal_entity_personal_address_postal_code, [:legal_entity, :personal_address, :postal_code]},
4639
{:legal_entity_personal_address_state, [:legal_entity, :personal_address, :state]},
4740
{:legal_entity_phone_number, [:legal_entity, :phone_number]},
41+
{:legal_entity_personal_id_number, [:legal_entity, :personal_id_number]},
4842
{:legal_entity_personal_id_number_provided, [:legal_entity, :personal_id_number_provided]},
43+
{:legal_entity_ssn_last_4, [:legal_entity, :ssn_last_4]},
4944
{:legal_entity_ssn_last_4_provided, [:legal_entity, :ssn_last_4_provided]},
5045
{:legal_entity_type, [:legal_entity, :type]},
5146
{:legal_entity_verification_details, [:legal_entity, :verification, :details]},
@@ -62,6 +57,19 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountAdapter do
6257
{:verification_fields_needed, [:verification, :fields_needed]}
6358
]
6459

60+
@doc """
61+
Transforms a set of local attributes into a map of parameters used to
62+
update a `%Stripe.Account{}`.
63+
"""
64+
def from_params(%{} = attributes) do
65+
result =
66+
attributes
67+
|> remove_attributes()
68+
|> transform_attributes(@stripe_mapping)
69+
70+
{:ok, result}
71+
end
72+
6573
@doc """
6674
Transforms a `%Stripe.Account{}` and a set of local attributes into a
6775
map of parameters used to create or update a `StripeConnectAccount` record.
@@ -89,21 +97,23 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectAccountAdapter do
8997
end
9098

9199
defp get_non_stripe_attributes(%{} = attributes) do
92-
attributes
93-
|> Map.take(@non_stripe_attributes)
100+
attributes |> Map.take(@non_stripe_attributes)
94101
end
95102

96103
defp add_to(%{} = attributes, %{} = params) do
97-
params
98-
|> Map.merge(attributes)
104+
params |> Map.merge(attributes)
99105
end
100106

101107
defp add_nested_attributes(map, stripe_account) do
102-
map
103-
|> add_external_account(stripe_account)
104-
end
108+
map |> add_external_account(stripe_account)
109+
end
105110

106111
defp add_external_account(map, %Stripe.Account{external_accounts: %{data: []}}), do: map
107112
defp add_external_account(map, %Stripe.Account{external_accounts: %{data: [head | _]}}), do: map |> do_add_external_account(head)
108113
defp do_add_external_account(map, %{"id" => id}), do: map |> Map.put(:external_account, id)
114+
115+
defp remove_attributes(%{"legal_entity_verification_status" => "verified"} = attributes) do
116+
attributes |> Map.delete("legal_entity_verification_document")
117+
end
118+
defp remove_attributes(attributes), do: attributes
109119
end
Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
defmodule CodeCorps.StripeService.StripeConnectAccountService do
2-
alias CodeCorps.{Repo, StripeConnectAccount, StripeFileUpload}
3-
alias CodeCorps.StripeService.Adapters.{StripeConnectAccountAdapter, StripeFileUploadAdapter}
4-
5-
alias Ecto.Multi
2+
alias CodeCorps.{Repo, StripeConnectAccount}
3+
alias CodeCorps.StripeService.Adapters.{StripeConnectAccountAdapter}
64

75
@api Application.get_env(:code_corps, :stripe)
86

@@ -16,43 +14,17 @@ defmodule CodeCorps.StripeService.StripeConnectAccountService do
1614
end
1715
end
1816

19-
def add_external_account(%StripeConnectAccount{id_from_stripe: stripe_id} = record, external_account) do
20-
with {:ok, %Stripe.Account{} = stripe_account} <- @api.Account.update(stripe_id, %{external_account: external_account}),
21-
{:ok, params} <- StripeConnectAccountAdapter.to_params(stripe_account, %{})
22-
do
23-
record
24-
|> StripeConnectAccount.webhook_update_changeset(params)
25-
|> Repo.update
26-
end
27-
end
28-
29-
def add_vertification_document(
30-
%StripeConnectAccount{id_from_stripe: account_id} = record,
31-
%{"legal_entity_verification_document" => document} = attributes
32-
) do
33-
with {:ok, %Stripe.FileUpload{} = stripe_file_upload} <- Stripe.FileUpload.retrieve(document),
34-
{:ok, %Stripe.Account{} = stripe_account} <- Stripe.Account.update(account_id, %{legal_entity: %{verification: %{document: document}}}),
35-
{:ok, file_upload_params} <- StripeFileUploadAdapter.to_params(stripe_file_upload, attributes),
36-
{:ok, connect_account_params} <- StripeConnectAccountAdapter.to_params(stripe_account, attributes),
37-
account_changeset <- record |> StripeConnectAccount.webhook_update_changeset(connect_account_params),
38-
file_changeset <- %StripeFileUpload{} |> StripeFileUpload.create_changeset(file_upload_params)
17+
def update(%StripeConnectAccount{id_from_stripe: id_from_stripe} = account, %{} = attributes) do
18+
with {:ok, from_params} <- StripeConnectAccountAdapter.from_params(attributes),
19+
{:ok, %Stripe.Account{} = stripe_account} <- @api.Account.update(id_from_stripe, from_params),
20+
{:ok, params} <- StripeConnectAccountAdapter.to_params(stripe_account, attributes),
21+
{:ok, %StripeConnectAccount{} = updated_account} <- account |> StripeConnectAccount.webhook_update_changeset(params) |> Repo.update
3922
do
40-
multi =
41-
Multi.new
42-
|> Multi.update(:stripe_connect_account, account_changeset)
43-
|> Multi.insert(:stripe_file_upload, file_changeset)
44-
45-
case Repo.transaction(multi) do
46-
{:ok, %{stripe_connect_account: account, stripe_file_upload: _}} ->
47-
{:ok, account}
48-
{:error, :stripe_connect_account, %Ecto.Changeset{} = account_changeset, %{}} ->
49-
{:error, account_changeset}
50-
# If creating the file failed due to validation, we add a generic error
51-
# to the account changeset and return that to be rendered.
52-
{:error, :stripe_file_upload, %Ecto.Changeset{} = _, _} ->
53-
account_changeset |> Ecto.Changeset.add_error(:legal_entity_verification_document, "is invalid")
54-
{:error, account_changeset}
55-
end
23+
{:ok, updated_account}
24+
else
25+
{:error, %Ecto.Changeset{} = changeset} -> {:error, changeset}
26+
{:error, %Stripe.APIErrorResponse{} = error} -> {:error, error}
27+
_ -> {:error, :unhandled}
5628
end
5729
end
5830
end

lib/code_corps/stripe_service/stripe_connect_plan.ex

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule CodeCorps.StripeService.StripeConnectPlanService do
77

88
def create(%{"project_id" => project_id} = attributes) do
99
with {:ok, %Project{} = project} <- get_project(project_id) |> ProjectCanEnableDonations.validate,
10-
%{} = create_attributes <- get_create_attributes(),
10+
%{} = create_attributes <- get_create_attributes(project_id),
1111
connect_account_id <- project.organization.stripe_connect_account.id_from_stripe,
1212
{:ok, plan} <- @api.Plan.create(create_attributes, connect_account: connect_account_id),
1313
{:ok, params} <- StripeConnectPlanAdapter.to_params(plan, attributes)
@@ -22,11 +22,12 @@ defmodule CodeCorps.StripeService.StripeConnectPlanService do
2222
end
2323
end
2424

25-
defp get_create_attributes do
25+
@spec get_create_attributes(binary) :: map
26+
defp get_create_attributes(project_id) do
2627
%{
2728
amount: 1, # in cents
2829
currency: "usd",
29-
id: "month",
30+
id: "month_project_" <> to_string(project_id),
3031
interval: "month",
3132
name: "Monthly donation",
3233
statement_descriptor: "CODECORPS.ORG Donation" # No more than 22 chars

lib/code_corps/stripe_service/util.ex

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,53 @@ defmodule CodeCorps.StripeService.Util do
1010
"""
1111
def transform_map(api_map, mapping), do: mapping |> Enum.reduce(%{}, &map_field(&1, &2, api_map))
1212

13+
def transform_attributes(attributes, mapping) do
14+
attributes_map = map_keys_to_atoms(attributes)
15+
mapping |> Enum.reduce(%{}, &map_attribute(&1, &2, attributes_map))
16+
end
17+
18+
defp map_keys_to_atoms(m) do
19+
result = Enum.map(m, fn
20+
{k, v} when is_binary(k) ->
21+
a = String.to_existing_atom(k)
22+
{a, v}
23+
entry ->
24+
entry
25+
end)
26+
result |> Enum.into(%{})
27+
end
28+
29+
defp map_attribute({source_field, target_path}, target_map, source_map) do
30+
value = source_map |> Map.get(source_field)
31+
list = target_path |> Enum.reverse
32+
result = put_value(list, value, %{})
33+
deep_merge(target_map, result)
34+
end
35+
36+
defp put_value(_, value, map) when is_nil(value), do: map
37+
defp put_value([head | tail], value, map) do
38+
new_value = Map.put(%{}, head, value)
39+
put_value(tail, new_value, map)
40+
end
41+
defp put_value([], new_value, _map), do: new_value
42+
43+
defp deep_merge(left, right) do
44+
Map.merge(left, right, &deep_resolve/3)
45+
end
46+
47+
# Key exists in both maps, and both values are maps as well.
48+
# These can be merged recursively.
49+
defp deep_resolve(_key, left = %{}, right = %{}) do
50+
deep_merge(left, right)
51+
end
52+
53+
# Key exists in both maps, but at least one of the values is
54+
# NOT a map. We fall back to standard merge behavior, preferring
55+
# the value on the right.
56+
defp deep_resolve(_key, _left, right) do
57+
right
58+
end
59+
1360
# Takes a tuple which contains a target field and a source path,
1461
# then puts value on the source path from the source map
1562
# into to target map under the target field name.

lib/code_corps/stripe_service/webhook_processing/webhook_processor.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ defmodule CodeCorps.StripeService.WebhookProcessing.WebhookProcessor do
7777
end
7878

7979
defp handle_event(json, event, handler) do
80-
case handler.handle_event(json) |> Tuple.to_list do
80+
case json |> handler.handle_event |> Tuple.to_list do
8181
[:ok, :unhandled_event] -> event |> set_unhandled
8282
[:ok | _results] -> event |> set_processed
8383
[:error | _error] -> event |> set_errored

mix.exs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,15 @@ defmodule CodeCorps.Mixfile do
6666
{:phoenix_live_reload, "~> 1.0", only: :dev},
6767
{:gettext, "~> 0.12"},
6868
{:cowboy, "~> 1.0"},
69-
{:arc, git: "https://github.com/stavro/arc.git", ref: "354d4d2e1b86bcd6285db3528118fe3f5db36cf5", override: true}, # Photo uploads
70-
{:arc_ecto, "~> 0.4.4"},
69+
{:arc, "~> 0.6"}, # Photo uploads
70+
{:arc_ecto, "~> 0.5"},
7171
{:benchfella, "~> 0.3.0", only: :dev},
7272
{:canary, "~> 1.1"}, # Authorization
7373
{:comeonin, "~> 2.0"},
7474
{:corsica, "~> 0.4"}, # CORS
7575
{:credo, "~> 0.5", only: [:dev, :test]}, # Code style suggestions
7676
{:earmark, "~> 1.0"}, # Markdown rendering
77-
{:ex_aws, "~> 0.4"}, # Amazon AWS
77+
{:ex_aws, "~> 1.0"}, # Amazon AWS
7878
{:excoveralls, "~> 0.5", only: :test}, # Test coverage
7979
{:ex_doc, "~> 0.14", only: [:dev, :test]},
8080
{:ex_machina, "~> 1.0", only: :test}, # test factories
@@ -87,9 +87,10 @@ defmodule CodeCorps.Mixfile do
8787
{:mix_test_watch, "~> 0.2", only: :dev}, # Test watcher
8888
{:poison, "~> 2.0"},
8989
{:scrivener_ecto, "~> 1.0"}, # DB query pagination
90-
{:segment, github: "stueccles/analytics-elixir"}, # Segment analytics
90+
{:segment, "~> 0.1"}, # Segment analytics
9191
{:sentry, "~> 2.0"}, # Sentry error tracking
9292
{:stripity_stripe, git: "https://github.com/code-corps/stripity_stripe.git", branch: "2.0"}, # Stripe
93+
{:sweet_xml, "~> 0.5"},
9394
{:timber, "~> 0.4"}, # Logging
9495
{:timex, "~> 3.0"},
9596
{:timex_ecto, "~> 3.0"},

0 commit comments

Comments
 (0)