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
+
+
+
+
+
+
+
+
+
+
+ |
+
+ |
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+ {{user_first_name}}, thanks for your monthly donation to {{project_title}}.
+
+ |
+ |
+
+
+ |
+ |
+
+
+
+
+
+
+
+
+ |
+ Description
+ |
+
+ Amount
+ |
+
+
+ | Monthly Donation to {{project_title}} |
+ {{charge_amount}} |
+
+
+
+
+
+
+ |
+
+
+
+
+ |
+
+ Questions? Feedback?
+ Visit our Help Center or just reply to this email.
+
+ Thanks again!
+ The Code Corps Team
+
+ |
+
+
+
+
+
+ |
+ The charge will appear as {{charge_statement_descriptor}} on your statement.
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+ |
+
+
+ |
+
+
+
+
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()