diff --git a/config/dev.exs b/config/dev.exs index ced6bfde6..da96ceb06 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -12,6 +12,8 @@ config :code_corps, CodeCorps.Endpoint, code_reloader: true, check_origin: false +config :code_corps, site_url: "http://localhost:4200" + # Watch static and templates for browser reloading. config :code_corps, CodeCorps.Endpoint, live_reload: [ @@ -56,3 +58,6 @@ config :sentry, config :code_corps, CodeCorps.Mailer, adapter: Bamboo.LocalAdapter + +config :code_corps, + postmark_receipt_template: "123" diff --git a/config/prod.exs b/config/prod.exs index 1b8c073b3..569a18c36 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -18,6 +18,8 @@ config :code_corps, CodeCorps.Endpoint, force_ssl: [rewrite_on: [:x_forwarded_proto]], secret_key_base: System.get_env("SECRET_KEY_BASE") +config :code_corps, site_url: "https://www.codecorps.org" + # Configure your database config :code_corps, CodeCorps.Repo, adapter: Ecto.Adapters.Postgres, @@ -58,6 +60,9 @@ config :code_corps, CodeCorps.Mailer, adapter: Bamboo.PostmarkAdapter, api_key: System.get_env("POSTMARK_API_KEY") +config :code_corps, + postmark_receipt_template: "1255222" + # ## SSL Support # # To get SSL working, you will need to add the `https` key diff --git a/config/remote-development.exs b/config/remote-development.exs index d105d2887..aa469e935 100644 --- a/config/remote-development.exs +++ b/config/remote-development.exs @@ -16,6 +16,8 @@ config :code_corps, CodeCorps.Endpoint, url: [scheme: "http", host: "api.pbqrpbecf-qri.org", port: 80], secret_key_base: System.get_env("SECRET_KEY_BASE") +config :code_corps, site_url: "http://www.pbqrpbecf-qri.org" + # Configure your database config :code_corps, CodeCorps.Repo, adapter: Ecto.Adapters.Postgres, @@ -40,6 +42,9 @@ config :code_corps, :stripe_env, :remote_dev config :code_corps, CodeCorps.Mailer, adapter: Bamboo.LocalAdapter +config :code_corps, + postmark_receipt_template: "123" + # ## SSL Support # # To get SSL working, you will need to add the `https` key diff --git a/config/staging.exs b/config/staging.exs index 7a3f9a27f..3080d83ff 100644 --- a/config/staging.exs +++ b/config/staging.exs @@ -17,6 +17,8 @@ config :code_corps, CodeCorps.Endpoint, url: [scheme: "http", host: "api.pbqrpbecf.org", port: 80], secret_key_base: System.get_env("SECRET_KEY_BASE") +config :code_corps, site_url: "http://www.pbqrpbecf.org" + # Configure your database config :code_corps, CodeCorps.Repo, adapter: Ecto.Adapters.Postgres, @@ -56,6 +58,9 @@ config :code_corps, CodeCorps.Mailer, adapter: Bamboo.PostmarkAdapter, api_key: System.get_env("POSTMARK_API_KEY") +config :code_corps, + postmark_receipt_template: "1252361" + # ## SSL Support # # To get SSL working, you will need to add the `https` key diff --git a/config/test.exs b/config/test.exs index be8c2def0..2a1c8303b 100644 --- a/config/test.exs +++ b/config/test.exs @@ -18,6 +18,8 @@ config :code_corps, CodeCorps.Repo, database: "code_corps_phoenix_test", pool: Ecto.Adapters.SQL.Sandbox +config :code_corps, site_url: "http://localhost:4200" + # speed up password hashing config :comeonin, :bcrypt_log_rounds, 4 config :comeonin, :pbkdf2_rounds, 1 @@ -44,3 +46,6 @@ config :sentry, config :code_corps, CodeCorps.Mailer, adapter: Bamboo.TestAdapter + +config :code_corps, + postmark_receipt_template: "123" diff --git a/emails/receipt.html b/emails/receipt.html new file mode 100644 index 000000000..ebbf5c7cf --- /dev/null +++ b/emails/receipt.html @@ -0,0 +1,137 @@ + + + + + + Your monthly donation on Code Corps + + + + + Your receipt for your monthly donation to {{project_title}}. + + + + +
+ + + + + + + + + + + + +
+ + diff --git a/emails/styles.css b/emails/styles.css new file mode 100644 index 000000000..b50d08720 --- /dev/null +++ b/emails/styles.css @@ -0,0 +1,379 @@ +/* Base ------------------------------ */ + +*:not(br):not(tr):not(html) { + font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; + box-sizing: border-box; +} + +body { + width: 100% !important; + height: 100%; + margin: 0; + line-height: 1.4; + color: #333; + -webkit-text-size-adjust: none; +} + +p, +ul, +ol, +blockquote { + line-height: 1.4; + text-align: left; +} + +a { + color: #08A8FC; + text-decoration: none; +} + +a img { + border: none; +} + +td { + word-break: break-word; +} +/* Layout ------------------------------ */ + +.email-wrapper { + width: 100%; + margin: 0; + padding: 0; + -premailer-width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; +} + +.email-content { + width: 100%; + margin: 0; + padding: 0; + -premailer-width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; +} + +/* Masthead ----------------------- */ + +.email-masthead_inner { + padding: 10px 0; +} + +.email-masthead_logo { + width: 161px; + height: 35px; +} + +/* Body ------------------------------ */ + +.email-body { + width: 100%; + margin: 0; + padding: 0; + -premailer-width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + background-color: #FFFFFF; +} + +.email-body_inner { + width: 570px; + margin: 0 auto; + padding: 0; + border-top: 2px solid #08A8FC; + border-right: 2px solid #EEE; + border-bottom: 2px solid #EEE; + border-left: 2px solid #EEE; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + -premailer-width: 570px; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + background-color: #FFFFFF; +} + +.email-footer { + width: 570px; + margin: 0 auto; + padding: 0; + -premailer-width: 570px; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + text-align: center; +} + +.email-footer p { + color: #AEAEAE; +} + +.body-action { + width: 100%; + margin: 30px auto; + padding: 0; + -premailer-width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + text-align: center; +} + +.body-signature { + width: 100%; + padding: 0; + -premailer-width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; +} + +.body-sub { + width: 100%; + margin-top: 25px; + padding-top: 25px; + border-top: 1px solid #EDEFF2; +} + +.content-cell { + padding: 35px; +} + +.preheader { + display: none !important; +} +/* Attribute list ------------------------------ */ + +.attributes { + margin: 0 0 21px; +} + +.attributes_content { + background-color: #EDEFF2; + padding: 16px; +} + +.attributes_item { + padding: 0; +} +/* Related Items ------------------------------ */ + +.related { + width: 100%; + margin: 0; + padding: 25px 0 0 0; + -premailer-width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; +} + +.related_item { + padding: 10px 0; + color: #333; + font-size: 15px; + line-height: 18px; +} + +.related_item-title { + display: block; + margin: .5em 0 0; +} + +.related_item-thumb { + display: block; + padding-bottom: 10px; +} + +.related_heading { + border-top: 1px solid #EDEFF2; + text-align: center; + padding: 25px 0 10px; +} + +/* Social Icons ------------------------------ */ + +.social { + width: auto; +} + +.social td { + padding: 0; + width: auto; +} + +.social_icon { + height: 20px; + margin: 0 8px 10px 8px; + padding: 0; +} +/* Donation -------------------------------- */ + +.donation_image { + margin-bottom: 30px +} + +.donation_text { + margin-bottom: 30px; +} + +.donation_goal_header { + border-top: 1px solid #EEE; + padding: 20px 10px 0px 10px; + font-size: 12px; + text-align: center; + text-transform: uppercase; + color: #999; + font-weight: bold; +} + +.donation_goal_footer { + font-size: 14px; + border-bottom: 1px solid #EEE; + padding: 0px 10px 20px 10px; + text-align: center; +} + +/* Data table ------------------------------ */ + +.purchase { + width: 100%; + margin: 0; + padding: 35px 0; + -premailer-width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; +} + +.charge_content { + font-size: 14px; + width: 100%; + margin: 0; + padding: 25px 0 0 0; + -premailer-width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; +} + +.charge_item { + padding: 10px 0; + color: #333; + font-size: 14px; + line-height: 18px; +} + +.charge_heading { + padding-bottom: 8px; + border-bottom: 1px solid #EDEFF2; +} + +.charge_heading p { + margin: 0; + color: #9BA2AB; + font-size: 12px; +} + +.charge_footer { + padding-top: 15px; + border-top: 1px solid #EDEFF2; +} + +.charge_total { + margin: 0; + text-align: right; + font-size: 14px; + font-weight: bold; + line-height: 18px; + color: #2F3133; +} + +.charge_total--label { + padding: 0 15px 0 0; + font-size: 12px; + line-height: 18px; +} +/* Utilities ------------------------------ */ + +.align-right { + text-align: right; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} +/*Media Queries ------------------------------ */ + +@media only screen and (max-width: 600px) { + .email-body_inner, + .email-footer { + width: 100% !important; + } +} + +@media only screen and (max-width: 500px) { + .button { + width: 100% !important; + } +} +/* Buttons ------------------------------ */ + +.button { + background-color: #08A8FC; + border-top: 10px solid #08A8FC; + border-right: 18px solid #08A8FC; + border-bottom: 10px solid #08A8FC; + border-left: 18px solid #08A8FC; + display: inline-block; + color: #FFF; + text-decoration: none; + border-radius: 3px; + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); + -webkit-text-size-adjust: none; +} + +/* Type ------------------------------ */ + +h1 { + margin-top: 0; + color: #2F3133; + font-size: 19px; + font-weight: bold; + text-align: left; +} + +h2 { + margin-top: 0; + color: #2F3133; + font-size: 16px; + font-weight: bold; + text-align: left; +} + +h3 { + margin-top: 0; + color: #2F3133; + font-size: 14px; + font-weight: bold; + text-align: left; +} + +p { + margin-top: 0; + color: #333; + font-size: 16px; + line-height: 1.5em; + text-align: left; +} + +p.small { + font-size: 14px; +} + +p.sub { + color: #666; + font-size: 12px; +} + +p.center { + text-align: center; +} diff --git a/lib/code_corps/emails/base_email.ex b/lib/code_corps/emails/base_email.ex new file mode 100644 index 000000000..8a4ac5db6 --- /dev/null +++ b/lib/code_corps/emails/base_email.ex @@ -0,0 +1,8 @@ +defmodule CodeCorps.Emails.BaseEmail do + import Bamboo.Email + + def create do + new_email + |> from("Code Corps") + end +end diff --git a/lib/code_corps/emails/receipt_email.ex b/lib/code_corps/emails/receipt_email.ex new file mode 100644 index 000000000..15a3e75f2 --- /dev/null +++ b/lib/code_corps/emails/receipt_email.ex @@ -0,0 +1,86 @@ +defmodule CodeCorps.Emails.ReceiptEmail do + import Bamboo.Email + import Bamboo.PostmarkHelper + + alias CodeCorps.Emails.BaseEmail + alias CodeCorps.{DonationGoal, Project, Repo, StripeConnectCharge, StripeConnectSubscription} + + def create(%StripeConnectCharge{} = charge, %Stripe.Invoice{} = invoice) do + with %StripeConnectCharge{} = charge <- preload(charge), + %Project{} = project <- get_project(invoice.subscription), + {:ok, %DonationGoal{} = current_donation_goal} <- get_current_donation_goal(project), + template_model <- build_model(charge, project, current_donation_goal) + do + BaseEmail.create + |> to(charge.user.email) + |> template(template_id, template_model) + else + nil -> {:error, :project_not_found} + other -> other + end + end + + defp preload(%StripeConnectCharge{} = charge) do + Repo.preload(charge, :user) + end + + defp get_project(subscription_id_from_stripe) do + with %StripeConnectSubscription{} = subscription <- get_subscription(subscription_id_from_stripe) do + subscription.stripe_connect_plan.project + else + nil -> {:error, :subscription_not_found} + end + end + + defp get_subscription(subscription_id_from_stripe) do + StripeConnectSubscription + |> Repo.get_by(id_from_stripe: subscription_id_from_stripe) + |> Repo.preload(stripe_connect_plan: [project: :organization]) + end + + defp get_current_donation_goal(project) do + case Repo.get_by(DonationGoal, current: true, project_id: project.id) do + nil -> {:error, :donation_goal_not_found} + donation_goal -> {:ok, donation_goal} + end + end + + defp build_model(charge, project, current_donation_goal) do + %{ + charge_amount: charge.amount |> format_amount(), + charge_statement_descriptor: charge.statement_descriptor, + high_five_image_url: high_five_image_url, + project_current_donation_goal_description: current_donation_goal.description, + project_title: project.title, + project_url: project |> url(), + subject: project |> build_subject_line(), + user_first_name: charge.user.first_name + } + end + + defp build_subject_line(project) do + "Your monthly donation to " <> project.title + end + + defp high_five_image_url, do: Enum.random(high_five_image_urls) + + defp high_five_image_urls, do: [ + "https://d3pgew4wbk2vb1.cloudfront.net/emails/images/emoji-1f64c-1f3fb@2x.png", + "https://d3pgew4wbk2vb1.cloudfront.net/emails/images/emoji-1f64c-1f3fc@2x.png", + "https://d3pgew4wbk2vb1.cloudfront.net/emails/images/emoji-1f64c-1f3fd@2x.png", + "https://d3pgew4wbk2vb1.cloudfront.net/emails/images/emoji-1f64c-1f3fe@2x.png", + "https://d3pgew4wbk2vb1.cloudfront.net/emails/images/emoji-1f64c-1f3ff@2x.png" + ] + + defp format_amount(amount) do + Money.to_string(Money.new(amount, :USD)) + end + + defp url(project) do + Application.get_env(:code_corps, :site_url) + |> URI.merge(project.organization.slug <> "/" <> project.slug) + |> URI.to_string + end + + defp template_id, do: Application.get_env(:code_corps, :postmark_receipt_template) +end diff --git a/lib/code_corps/emails/test.ex b/lib/code_corps/emails/test.ex deleted file mode 100644 index 432c09921..000000000 --- a/lib/code_corps/emails/test.ex +++ /dev/null @@ -1,12 +0,0 @@ -defmodule CodeCorps.Emails.Test do - import Bamboo.Email - - def hello_world do - new_email - |> to("test_user@codecorps.org") - |> from("admin@codecorps.org") - |> subject("Hello World!") - |> html_body("Hello World!") - |> text_body("Hello World!") - end -end diff --git a/lib/code_corps/stripe_service/events/connect_charge_succeeded.ex b/lib/code_corps/stripe_service/events/connect_charge_succeeded.ex index bcf261bb0..85c2c40b4 100644 --- a/lib/code_corps/stripe_service/events/connect_charge_succeeded.ex +++ b/lib/code_corps/stripe_service/events/connect_charge_succeeded.ex @@ -1,5 +1,38 @@ defmodule CodeCorps.StripeService.Events.ConnectChargeSucceeded do + alias CodeCorps.StripeService.StripeConnectChargeService + alias CodeCorps.{Emails, Mailer, StripeConnectCharge} + + @api Application.get_env(:code_corps, :stripe) + def handle(%{data: %{object: %{id: id_from_stripe}}, user_id: connect_account_id_from_stripe}) do - CodeCorps.StripeService.StripeConnectChargeService.create(id_from_stripe, connect_account_id_from_stripe) + create_charge(id_from_stripe, connect_account_id_from_stripe) + |> try_create_receipt(connect_account_id_from_stripe) + |> try_send_receipt end + + defp create_charge(id_from_stripe, account_id_from_stripe) do + StripeConnectChargeService.create(id_from_stripe, account_id_from_stripe) + end + + defp try_create_receipt({:ok, %StripeConnectCharge{invoice_id_from_stripe: invoice_id} = charge}, account_id) do + with {:ok, %Stripe.Invoice{} = invoice} <- retrieve_invoice(invoice_id, account_id), + %Bamboo.Email{} = receipt <- Emails.ReceiptEmail.create(charge, invoice) + do + {:ok, charge, receipt} + else + failure -> failure + end + end + defp try_create_receipt(any, _account_id), do: any + + defp retrieve_invoice(invoice_id, account_id) do + @api.Invoice.retrieve(invoice_id, connect_account: account_id) + end + + defp try_send_receipt({:ok, charge, receipt}) do + with %Bamboo.Email{} = email <- receipt |> Mailer.deliver_now do + {:ok, charge, email} + end + end + defp try_send_receipt(other), do: other end diff --git a/lib/code_corps/stripe_service/stripe_connect_charge_service.ex b/lib/code_corps/stripe_service/stripe_connect_charge_service.ex index e286db605..9f58328ad 100644 --- a/lib/code_corps/stripe_service/stripe_connect_charge_service.ex +++ b/lib/code_corps/stripe_service/stripe_connect_charge_service.ex @@ -7,7 +7,7 @@ defmodule CodeCorps.StripeService.StripeConnectChargeService do @api Application.get_env(:code_corps, :stripe) def create(id_from_stripe, connect_account_id_from_stripe) do - with %StripeConnectAccount{} = stripe_connect_account <- Repo.get_by(StripeConnectAccount, id_from_stripe: connect_account_id_from_stripe), + with {:ok, %StripeConnectAccount{} = stripe_connect_account} <- get_connect_account(connect_account_id_from_stripe), {:ok, %Stripe.Charge{} = api_charge} <- @api.Charge.retrieve(id_from_stripe, connect_account: connect_account_id_from_stripe), {:ok, params} = StripeConnectChargeAdapter.to_params(api_charge, stripe_connect_account) do @@ -17,4 +17,11 @@ defmodule CodeCorps.StripeService.StripeConnectChargeService do |> CodeCorps.Analytics.Segment.track(:created) end end + + defp get_connect_account(id_from_stripe) do + case Repo.get_by(StripeConnectAccount, id_from_stripe: id_from_stripe) do + nil -> {:error, :account_not_found} + account -> {:ok, account} + end + end end diff --git a/lib/code_corps/stripe_testing/charge.ex b/lib/code_corps/stripe_testing/charge.ex index 995a367fc..124731f24 100644 --- a/lib/code_corps/stripe_testing/charge.ex +++ b/lib/code_corps/stripe_testing/charge.ex @@ -1,7 +1,7 @@ defmodule CodeCorps.StripeTesting.Charge do import CodeCorps.StripeTesting.Helpers - def retrieve(_id, _opts) do - {:ok, load_fixture(Stripe.Charge, "charge")} + def retrieve(id, _opts) do + {:ok, load_fixture(Stripe.Charge, id)} end end diff --git a/lib/code_corps/stripe_testing/fixtures/charge.json b/lib/code_corps/stripe_testing/fixtures/charge.json index 9c50e169c..d23f02a14 100644 --- a/lib/code_corps/stripe_testing/fixtures/charge.json +++ b/lib/code_corps/stripe_testing/fixtures/charge.json @@ -16,7 +16,7 @@ "failure_code": null, "failure_message": null, "fraud_details": {}, - "invoice": null, + "invoice": "invoice", "livemode": false, "metadata": { }, diff --git a/lib/code_corps/stripe_testing/fixtures/invoice.json b/lib/code_corps/stripe_testing/fixtures/invoice.json new file mode 100644 index 000000000..2c78e36f2 --- /dev/null +++ b/lib/code_corps/stripe_testing/fixtures/invoice.json @@ -0,0 +1,75 @@ +{ + "id": "invoice", + "object": "invoice", + "amount_due": 0, + "application_fee": null, + "attempt_count": 0, + "attempted": false, + "charge": "charge", + "closed": false, + "currency": "usd", + "customer": "cus_9z91zWSKp3YTbK", + "date": 1485236932, + "description": null, + "discount": null, + "ending_balance": null, + "forgiven": false, + "lines": { + "data": [ + { + "id": "sub_9zLsYkireHjH3D", + "object": "line_item", + "amount": 999, + "currency": "usd", + "description": null, + "discountable": true, + "livemode": true, + "metadata": { + }, + "period": { + "start": 1487915332, + "end": 1490334532 + }, + "plan": { + "id": "gold", + "object": "plan", + "amount": 2000, + "created": 1485236932, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": { + }, + "name": "Gold Special", + "statement_descriptor": null, + "trial_period_days": null + }, + "proration": false, + "quantity": 1, + "subscription": null, + "subscription_item": "si_19fNAm2eZvKYlo2Ccq9bDSAx", + "type": "subscription" + } + ], + "total_count": 1, + "object": "list", + "url": "/v1/invoices/in_19fNAmKZ6oB5ZflENv3m1dqt/lines" + }, + "livemode": false, + "metadata": { + }, + "next_payment_attempt": 1485240532, + "paid": false, + "period_end": 1485236932, + "period_start": 1485236932, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "subscription": "subscription", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "webhooks_delivered_at": null +} diff --git a/lib/code_corps/stripe_testing/invoice.ex b/lib/code_corps/stripe_testing/invoice.ex index 9d7945422..1aec9d54f 100644 --- a/lib/code_corps/stripe_testing/invoice.ex +++ b/lib/code_corps/stripe_testing/invoice.ex @@ -1,40 +1,7 @@ defmodule CodeCorps.StripeTesting.Invoice do - def retrieve(id, _) do - {:ok, invoice(id)} - end + import CodeCorps.StripeTesting.Helpers - defp invoice(id) do - %Stripe.Invoice{ - amount_due: 1000, - application_fee: 50, - attempt_count: 1, - attempted: true, - charge: "ch_123", - closed: true, - currency: "usd", - customer: "cus_123", - date: 1_483_553_506, - description: nil, - discount: nil, - ending_balance: 0, - forgiven: false, - id: id, - livemode: false, - metadata: %{}, - next_payment_attempt: nil, - paid: true, - period_end: 1_483_553_506, - period_start: 1_483_553_506, - receipt_number: nil, - starting_balance: 0, - statement_descriptor: nil, - subscription: "sub_123", - subscription_proration_date: nil, - subtotal: 1000, - tax: nil, - tax_percent: nil, - total: 1000, - webhooks_delivered_at: 1_483_553_511 - } + def retrieve(id, _opts) do + {:ok, load_fixture(Stripe.Invoice, id)} end end diff --git a/mix.exs b/mix.exs index dabdb2368..b3cfdd927 100644 --- a/mix.exs +++ b/mix.exs @@ -86,6 +86,7 @@ defmodule CodeCorps.Mixfile do {:ja_resource, "~> 0.2"}, {:ja_serializer, "~> 0.11.0"}, # JSON API {:mix_test_watch, "~> 0.2", only: :dev}, # Test watcher + {:money, "~> 1.2.1"}, {:poison, "~> 2.0"}, {:scrivener_ecto, "~> 1.0"}, # DB query pagination {:segment, "~> 0.1"}, # Segment analytics diff --git a/mix.lock b/mix.lock index 549b1283c..62b591bfa 100644 --- a/mix.lock +++ b/mix.lock @@ -42,6 +42,7 @@ "mime": {:hex, :mime, "1.0.1", "05c393850524767d13a53627df71beeebb016205eb43bfbd92d14d24ec7a1b51", [:mix], []}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, "mix_test_watch": {:hex, :mix_test_watch, "0.2.6", "9fcc2b1b89d1594c4a8300959c19d50da2f0ff13642c8f681692a6e507f92cab", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, optional: false]}]}, + "money": {:hex, :money, "1.2.1", "fdcc7b021b894dbcc2cd0f57d2ccdd2224eced747231337a849424ffaa196b13", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 2.1", [hex: :ecto, optional: true]}, {:phoenix_html, "~> 2.0", [hex: :phoenix_html, optional: true]}]}, "phoenix": {:hex, :phoenix, "1.2.1", "6dc592249ab73c67575769765b66ad164ad25d83defa3492dc6ae269bd2a68ab", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.1", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]}, "phoenix_ecto": {:hex, :phoenix_ecto, "3.2.1", "6cf11d590c61977f50fcb98ad8a10ee90ba8670a82cbf5eaf49cfaee2e95e8b7", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, optional: false]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}]}, "phoenix_html": {:hex, :phoenix_html, "2.9.2", "371160b30cf4e10443b015efce6f03e1f19aae98ff6487620477b13a5b2ef660", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]}, diff --git a/test/lib/code_corps/emails/receipt_email_test.exs b/test/lib/code_corps/emails/receipt_email_test.exs new file mode 100644 index 000000000..2dacb713a --- /dev/null +++ b/test/lib/code_corps/emails/receipt_email_test.exs @@ -0,0 +1,57 @@ +defmodule CodeCorps.Emails.ReceiptEmailTest do + use CodeCorps.ModelCase + use Bamboo.Test + + alias CodeCorps.Emails.ReceiptEmail + + test "receipt email works" do + invoice_fixture = CodeCorps.StripeTesting.Helpers.load_fixture(Stripe.Invoice, "invoice") + + user = insert(:user, email: "jimmy@mail.com", first_name: "Jimmy") + + project = insert(:project, title: "Code Corps") + plan = insert(:stripe_connect_plan, project: project) + subscription = insert( + :stripe_connect_subscription, + id_from_stripe: invoice_fixture.subscription, + stripe_connect_plan: plan, + user: user + ) + + invoice = insert( + :stripe_invoice, + id_from_stripe: invoice_fixture.id, + stripe_connect_subscription: subscription, + user: user + ) + + charge = insert( + :stripe_connect_charge, + amount: 500, + id_from_stripe: invoice.charge_id_from_stripe, + invoice_id_from_stripe: invoice.id_from_stripe, + user: user, + statement_descriptor: "Test descriptor" + ) + + insert(:donation_goal, project: project, current: true, description: "Test goal") + + email = ReceiptEmail.create(charge, invoice_fixture) + assert email.from == "Code Corps" + assert email.to == "jimmy@mail.com" + + template_model = email.private.template_model |> Map.delete(:high_five_image_url) + high_five_image_url = email.private.template_model |> Map.get(:high_five_image_url) + + assert template_model == %{ + charge_amount: "$5.00", + charge_statement_descriptor: "Test descriptor", + project_title: "Code Corps", + project_url: "http://localhost:4200/#{project.organization.slug}/#{project.slug}", + project_current_donation_goal_description: "Test goal", + subject: "Your monthly donation to Code Corps", + user_first_name: "Jimmy" + } + assert high_five_image_url + end +end diff --git a/test/lib/code_corps/mailer_test.exs b/test/lib/code_corps/mailer_test.exs deleted file mode 100644 index 457aa2973..000000000 --- a/test/lib/code_corps/mailer_test.exs +++ /dev/null @@ -1,25 +0,0 @@ -defmodule CodeCorps.MailerTest do - @moduledoc false - - use ExUnit.Case - use Bamboo.Test - - alias CodeCorps.{Emails, Mailer} - - test "hello_world email works" do - email = Emails.Test.hello_world - - assert email.to == "test_user@codecorps.org" - assert email.from == "admin@codecorps.org" - assert email.subject == "Hello World!" - assert email.html_body == "Hello World!" - assert email.text_body == "Hello World!" - end - - test "email can be sent" do - email = Emails.Test.hello_world - email |> Mailer.deliver_now - - assert_delivered_email email - end -end diff --git a/test/lib/code_corps/stripe_service/adapters/stripe_connect_charge_test.exs b/test/lib/code_corps/stripe_service/adapters/stripe_connect_charge_test.exs index f13c90d1e..7474a73d5 100644 --- a/test/lib/code_corps/stripe_service/adapters/stripe_connect_charge_test.exs +++ b/test/lib/code_corps/stripe_service/adapters/stripe_connect_charge_test.exs @@ -32,7 +32,7 @@ defmodule CodeCorps.StripeService.Adapters.StripeConnectChargeTest do failure_code: nil, failure_message: nil, id_from_stripe: "charge", - invoice_id_from_stripe: nil, + invoice_id_from_stripe: "invoice", paid: true, refunded: false, review_id_from_stripe: nil, diff --git a/test/lib/code_corps/stripe_service/events/connect_charge_succeeded_test.exs b/test/lib/code_corps/stripe_service/events/connect_charge_succeeded_test.exs new file mode 100644 index 000000000..c39d8a6ad --- /dev/null +++ b/test/lib/code_corps/stripe_service/events/connect_charge_succeeded_test.exs @@ -0,0 +1,37 @@ +defmodule CodeCorps.StripeService.Events.ConnectChargeSucceededTest do + use CodeCorps.StripeCase + + use Bamboo.Test + + alias CodeCorps.{ + Project, Repo, StripeConnectCharge, StripeTesting + } + + alias CodeCorps.StripeService.Events.ConnectChargeSucceeded + + test "handling event creates charge and sends receipt" do + account = insert(:stripe_connect_account) + + charge_fixture = StripeTesting.Helpers.load_fixture(Stripe.Charge, "charge") + + insert(:stripe_connect_customer, id_from_stripe: charge_fixture.customer) + + invoice_fixture = StripeTesting.Helpers.load_fixture(Stripe.Invoice, charge_fixture.invoice) + insert(:stripe_connect_subscription, id_from_stripe: invoice_fixture.subscription) + + project = Repo.one(Project) + insert(:donation_goal, current: true, project: project) + + event = %Stripe.Event{ + data: %{object: charge_fixture}, + user_id: account.id_from_stripe + } + + Bamboo.SentEmail.start_link() + + assert {:ok, %StripeConnectCharge{}, %Bamboo.Email{} = email} + = ConnectChargeSucceeded.handle(event) + + assert_delivered_email email + end +end diff --git a/test/lib/code_corps/stripe_service/stripe_invoice_service_test.exs b/test/lib/code_corps/stripe_service/stripe_invoice_service_test.exs index bb6e22674..d390b4ed9 100644 --- a/test/lib/code_corps/stripe_service/stripe_invoice_service_test.exs +++ b/test/lib/code_corps/stripe_service/stripe_invoice_service_test.exs @@ -8,34 +8,18 @@ defmodule CodeCorps.StripeService.StripeInvoiceServiceTest do describe "create" do test "creates a StripeInvoice" do - attributes = %{ - "charge" => "ch_123", - "customer" => "cus_123", - "id" => "in_123", - "subscription" => "sub_123" - } + invoice_fixture = CodeCorps.StripeTesting.Helpers.load_fixture(Stripe.Invoice, "invoice") - customer_id = attributes["customer"] - - user = insert(:user) - subscription = insert(:stripe_connect_subscription, id_from_stripe: attributes["subscription"]) - stripe_platform_customer = insert(:stripe_platform_customer, user: user) - insert(:stripe_connect_customer, - id_from_stripe: customer_id, - stripe_platform_customer: stripe_platform_customer, - user: user - ) - - invoice_id_from_stripe = attributes["id"] + subscription = insert(:stripe_connect_subscription, id_from_stripe: invoice_fixture.subscription) + connect_customer = insert(:stripe_connect_customer, id_from_stripe: invoice_fixture.customer) {:ok, %StripeInvoice{} = invoice} = - StripeInvoiceService.create(invoice_id_from_stripe, customer_id) + StripeInvoiceService.create(invoice_fixture.id, invoice_fixture.customer) - assert invoice.id_from_stripe == invoice_id_from_stripe + assert invoice.id_from_stripe == invoice_fixture.id assert invoice.stripe_connect_subscription_id == subscription.id - user_id = user.id - assert invoice.user_id == user_id + assert invoice.user_id == connect_customer.user_id end end end diff --git a/test/lib/code_corps/stripe_service/webhook_processing/event_handler_test.exs b/test/lib/code_corps/stripe_service/webhook_processing/event_handler_test.exs index c8a7a354a..97377a77b 100644 --- a/test/lib/code_corps/stripe_service/webhook_processing/event_handler_test.exs +++ b/test/lib/code_corps/stripe_service/webhook_processing/event_handler_test.exs @@ -6,7 +6,9 @@ defmodule CodeCorps.StripeService.WebhookProcessing.EventHandlerTest do } alias CodeCorps.{ - StripeEvent, StripeInvoice, StripePlatformCard, StripePlatformCustomer + Repo, Project, + StripeEvent, StripeInvoice, StripePlatformCard, StripePlatformCustomer, + StripeTesting } defmodule CodeCorps.StripeService.WebhookProcessing.EventHandlerTest.StubObject do @@ -77,22 +79,51 @@ defmodule CodeCorps.StripeService.WebhookProcessing.EventHandlerTest do assert event.user_id == "acc_123" end - test "handles charge.succeeded" do + test "handles charge.succeeded as processed when everything is in order" do connect_account = insert(:stripe_connect_account) - customer = insert(:stripe_connect_customer, id_from_stripe: "test_customer_for_charge") - event = build_event( - "charge.succeeded", - "charge", - %Stripe.Charge{id: "ch_123", customer: customer.id_from_stripe}, - connect_account.id_from_stripe - ) + + charge_fixture = StripeTesting.Helpers.load_fixture(Stripe.Charge, "charge") + insert(:stripe_connect_customer, id_from_stripe: charge_fixture.customer) + + invoice_fixture = StripeTesting.Helpers.load_fixture(Stripe.Invoice, charge_fixture.invoice) + insert(:stripe_connect_subscription, id_from_stripe: invoice_fixture.subscription) + + project = Repo.one(Project) + insert(:donation_goal, current: true, project: project) + + event = build_event("charge.succeeded", "charge", charge_fixture, connect_account.id_from_stripe) {:ok, event} = EventHandler.handle(event, ConnectEventHandler, connect_account.id_from_stripe) assert event.object_type == "charge" - assert event.object_id == "ch_123" + assert event.object_id == charge_fixture.id assert event.status == "processed" end + test "handles charge.succeeded as errored when something goes wrong with email" do + connect_account = insert(:stripe_connect_account) + charge_fixture = StripeTesting.Helpers.load_fixture(Stripe.Charge, "charge") + + insert(:stripe_connect_customer, id_from_stripe: charge_fixture.customer) + + event = build_event("charge.succeeded", "charge", charge_fixture, connect_account.id_from_stripe) + {:ok, event} = EventHandler.handle(event, ConnectEventHandler, connect_account.id_from_stripe) + + assert event.object_type == "charge" + assert event.object_id == charge_fixture.id + assert event.status == "errored" + end + + test "handles charge.succeeded as errored when something goes wrong with creating a charge" do + charge_fixture = StripeTesting.Helpers.load_fixture(Stripe.Charge, "charge") + + event = build_event("charge.succeeded", "charge", charge_fixture, "bad_account") + {:ok, event} = EventHandler.handle(event, ConnectEventHandler, "bad_account") + + assert event.object_type == "charge" + assert event.object_id == charge_fixture.id + assert event.status == "errored" + end + test "handles customer.subscription.updated" do project = insert(:project) plan = insert(:stripe_connect_plan, project: project) @@ -154,36 +185,29 @@ defmodule CodeCorps.StripeService.WebhookProcessing.EventHandlerTest do end test "handles invoice.payment_succeeded" do + fixture = StripeTesting.Helpers.load_fixture(Stripe.Invoice, "invoice") + + insert(:stripe_connect_subscription, id_from_stripe: fixture.subscription) + user = insert(:user) - # need to hardcode id from stripe, since this is the value StripeTesting returns - subscription = insert(:stripe_connect_subscription, id_from_stripe: "sub_123") stripe_platform_customer = insert(:stripe_platform_customer, user: user) # same with hardcoding customer id from stripe - connect_customer = insert( + insert( :stripe_connect_customer, - id_from_stripe: "cus_123", + id_from_stripe: fixture.customer, stripe_platform_customer: stripe_platform_customer, user: user ) - event = build_event( - "invoice.payment_succeeded", - "invoice", - %Stripe.Invoice{ - id: "ivc_123", - customer: connect_customer.id_from_stripe, - subscription: subscription.id_from_stripe - }, - nil - ) + event = build_event("invoice.payment_succeeded", "invoice", fixture, nil) {:ok, event} = EventHandler.handle(event, ConnectEventHandler, "acc_123") assert event.object_type == "invoice" - assert event.object_id == "ivc_123" + assert event.object_id == fixture.id assert event.status == "processed" assert event.user_id == "acc_123" - assert Repo.get_by(StripeInvoice, id_from_stripe: "ivc_123") + assert Repo.get_by(StripeInvoice, id_from_stripe: fixture.id) end end diff --git a/test/support/factories.ex b/test/support/factories.ex index b5705d0a7..df6a0dffc 100644 --- a/test/support/factories.ex +++ b/test/support/factories.ex @@ -186,6 +186,17 @@ defmodule CodeCorps.Factories do } end + def stripe_invoice_factory do + %CodeCorps.StripeInvoice{ + id_from_stripe: sequence(:id_from_stripe, &"stripe_id_#{&1}"), + charge_id_from_stripe: sequence(:id_from_stripe, &"charge_stripe_id_#{&1}"), + customer_id_from_stripe: sequence(:id_from_stripe, &"customer_stripe_id_#{&1}"), + subscription_id_from_stripe: sequence(:subscription_id_from_stripe, &"subscription_stripe_id_#{&1}"), + stripe_connect_subscription: build(:stripe_connect_subscription), + user: build(:user) + } + end + def stripe_platform_customer_factory do %CodeCorps.StripePlatformCustomer{ created: Timex.now |> Timex.to_unix, diff --git a/web/models/stripe_invoice.ex b/web/models/stripe_invoice.ex index 0f5545c85..e2ef636d8 100644 --- a/web/models/stripe_invoice.ex +++ b/web/models/stripe_invoice.ex @@ -34,7 +34,7 @@ defmodule CodeCorps.StripeInvoice do field :total, :integer field :webhooks_delievered_at, :integer - belongs_to :stripe_connect_subscription, CodeCorps.StripeConnectAccount + belongs_to :stripe_connect_subscription, CodeCorps.StripeConnectSubscription belongs_to :user, CodeCorps.User timestamps()