code-corps / code-corps-api

Elixir/Phoenix API for Code Corps.
https://www.codecorps.org
MIT License
234 stars 86 forks source link

Add donations engine with Stripe #10

Closed joshsmith closed 7 years ago

joshsmith commented 8 years ago

Here's how we want to structure this:

joshsmith commented 8 years ago

Some notes here:

Organization owner can create standalone account https://stripe.com/docs/connect/standalone-accounts

OAuth client_id and redirect_uri development client_id for testing

Connect button to authorize_url http://jsfiddle.net/luddep/vM7R7/

Handle connection or error authorization code or access denied make POST request to Stripe send more information to pre-fill fields https://stripe.com/docs/connect/reference#get-authorize-request Store all details from stripe on server

Create a $1 plan on the account

Set quantity higher to make it a larger plan This creates virtual plans We’ll want these to be fixed dollar amounts

Revoked access webhook needs handled

Customer objects get created and stored on platform account This is so we can reuse them across other things

Create subscriptions and plans on connected account and application fee on subscription payments Define application_fee_percent as integer between 1 and 100 (5, in our case)

https://github.com/ride/ember-stripe-service https://stripe.com/docs/subscriptions/guide https://stripe.com/docs/checklist https://github.com/NullVoxPopuli/aeonvera-ui/blob/58ffcb3500926a24ed68915ac4f7109869888d88/app/components/integrations/stripe/connect-button.js#L8 https://stripe.com/docs/subscriptions/guide#setting-quantities

joshsmith commented 8 years ago

Some UI:

screen shot 2016-09-18 at 3 57 21 pm

joshsmith commented 8 years ago

stripe_user[url]

If you will be prefilling this field, we highly recommend that the linked page contain a minimum of a description of the user's products or services and contact information. If we don't have enough information, we'll have to reach out to the user directly before initiating payouts.

We'll need to find a way to add public contact information to a project's page for this purpose.

joshsmith commented 8 years ago

Looks like people accepting payments via Stripe will also have to be a business, so if they don't have one, we should recommend they go through Stripe Atlas to create an organization to start accepting those payments: https://stripe.com/atlas/faq#what-does-atlas-cost

EDIT: Actually, sole proprietor is not a formal entity. Just doing business as a person makes you one.

joshsmith commented 8 years ago

We'll probably want a URL like /webhooks/stripe.

Webhook endpoints may occasionally receive the same event more than once. We advise you to guard against duplicated event receipts by making your event processing idempotent. One way of doing this is logging the events you've processed, and then not processing already-logged events.

We should therefore have a StripeEvent model of some sort.

joshsmith commented 8 years ago

We could consider using https://github.com/gearnode/stripe_eventex for events/webhooks.

joshsmith commented 8 years ago

User goes to project page (Donations) If no account, generate button to create one Stripe.Connect.generate_button_url(csrf_token) If account, button to create new StripePlan to start accepting money for this project User clicks link User enters Stripe details User directed back to site (redirect_uri) w/ auth code or error https://example.com/connect/default/oauth/test?scope=read_write&code=AUTHORIZATION_CODE

Stripe.Connect.oauth_token_callback code
%{
  token_type: "bearer",
  stripe_publishable_key: "PUBLISHABLE_KEY",
  scope: "read_write",
  livemode: false,
  stripe_user_id: "USER_ID",
  refresh_token: "REFRESH_TOKEN",
  access_token: "ACCESS_TOKEN"
}

Store tokens in StripeAccount for Organization Create Stripe Plan for that standalone account for each project the organization has (store as StripePlan) ...(need to also ensure new StripePlan is created any time a new project is created for an organization with a standalone account) Check if account is verified (what webhook?) Payments are enabled now!

User decides to donate Collect card details Create token with Stripe.js Submit token to server w/ amount Create subscription in connected account: create(customer_id, opts, key) where key is the standalone account API key application_fee_percent 5 Create customer if they don’t exist and subscribe them to the new plan Store customer details on server (StripeCustomer) Store donation details on server Listen for webhook events

joshsmith commented 8 years ago

For platforms, we’ve learned that proactively emailing users about the following is helpful:

joshsmith commented 8 years ago

We could actually allow cents, too. Not hard to do. We'll just have $0.01 subscriptions and change the amount there.

Not sure best way to handle on front-end or validate on backend.

joshsmith commented 8 years ago

Card brand can be Visa, American Express, MasterCard, Discover, JCB, Diners Club, or Unknown.

joshsmith commented 8 years ago

I think we want to handle the following events:

account.updated describes an account Occurs whenever an account status or property has changed.

account.application.deauthorized describes an application Occurs whenever a user deauthorizes an application. Sent to the related application only. child parameters

account.external_account.created describes an external account (e.g., card or bank account) Occurs whenever an external account is created.

account.external_account.deleted describes an external account (e.g., card or bank account) Occurs whenever an external account is deleted.

account.external_account.updated describes an external account (e.g., card or bank account) Occurs whenever an external account is updated.

application_fee.created describes an application fee Occurs whenever an application fee is created on a charge.

application_fee.refunded describes an application fee Occurs whenever an application fee is refunded, whether from refunding a charge or from refunding the application fee directly, including partial refunds.

application_fee.refund.updated describes a fee refund Occurs whenever an application fee refund is updated.

charge.failed describes a charge Occurs whenever a failed charge attempt occurs.

charge.refunded describes a charge Occurs whenever a charge is refunded, including partial refunds.

charge.succeeded describes a charge Occurs whenever a new charge is created and is successful.

charge.updated describes a charge Occurs whenever a charge description or metadata is updated.

customer.created describes a customer Occurs whenever a new customer is created.

customer.deleted describes a customer Occurs whenever a customer is deleted.

customer.updated describes a customer Occurs whenever any property of a customer changes.

customer.source.created describes a source (e.g., card or Bitcoin receiver) Occurs whenever a new source is created for the customer.

customer.source.deleted describes a source (e.g., card or Bitcoin receiver) Occurs whenever a source is removed from a customer.

customer.source.updated describes a source (e.g., card or Bitcoin receiver) Occurs whenever a source's details are changed.

customer.subscription.created describes a subscription Occurs whenever a customer with no subscription is signed up for a plan.

customer.subscription.deleted describes a subscription Occurs whenever a customer ends their subscription.

customer.subscription.updated describes a subscription Occurs whenever a subscription changes. Examples would include switching from one plan to another, or switching status from trial to active.

invoice.created describes an invoice Occurs whenever a new invoice is created. If you are using webhooks, Stripe will wait one hour after they have all succeeded to attempt to pay the invoice; the only exception here is on the first invoice, which gets created and paid immediately when you subscribe a customer to a plan. If your webhooks do not all respond successfully, Stripe will continue retrying the webhooks every hour and will not attempt to pay the invoice. After 3 days, Stripe will attempt to pay the invoice regardless of whether or not your webhooks have succeeded. See how to respond to a webhook.

invoice.payment_failed describes an invoice Occurs whenever an invoice attempts to be paid, and the payment fails. This can occur either due to a declined payment, or because the customer has no active card. A particular case of note is that if a customer with no active card reaches the end of its free trial, an invoice.payment_failed notification will occur.

invoice.payment_succeeded describes an invoice Occurs whenever an invoice attempts to be paid, and the payment succeeds.

invoice.updated describes an invoice Occurs whenever an invoice changes (for example, the amount could change).

plan.created describes a plan Occurs whenever a plan is created.

plan.deleted describes a plan Occurs whenever a plan is deleted.

plan.updated describes a plan Occurs whenever a plan is updated.

source.canceled describes a source (e.g., card or Bitcoin receiver) Occurs whenever a source is canceled.

source.chargeable describes a source (e.g., card or Bitcoin receiver) Occurs whenever a source transitions to chargeable.

source.failed describes a source (e.g., card or Bitcoin receiver) Occurs whenever a source is failed.

transfer.created describes a transfer Occurs whenever a new transfer is created.

transfer.failed describes a transfer Occurs whenever Stripe attempts to send a transfer and that transfer fails.

transfer.paid describes a transfer Occurs whenever a sent transfer is expected to be available in the destination bank account. If the transfer failed, a transfer.failed webhook will additionally be sent at a later time. Note to Connect users: this event is only created for transfers from your connected Stripe accounts to their bank accounts, not for transfers to the connected accounts themselves.

transfer.reversed describes a transfer Occurs whenever a transfer is reversed, including partial reversals.

transfer.updated describes a transfer Occurs whenever the description or metadata of a transfer is updated.

joshsmith commented 8 years ago

The events above imply models for:

joshsmith commented 8 years ago

Process for copying a customer on the platform to a customer on a connected account:

Collect a token using your main account's publishable API key.

Consume the token with your main account's secret API key to save the payment information as a customer object.

For each connected account that would like to use the stored payment information, create a token referencing the main account's customer ID. The customer object can only be copied using the connected account's access token.

Now that you have a fresh token, use your connected account's access token to either create a charge or create a customer object. This will create an entirely new object (i.e., has its own ID) in the connected account.

https://support.stripe.com/questions/connect-publishable-key-error-with-shared-customers

joshsmith commented 8 years ago

joshsmith commented 8 years ago

Accept donations from project page (as organization owner) Add Stripe if not exists (Stripe button) Otherwise click to add donations Redirect to Stripe UI Come back, present waiting screen until Stripe accepts Send email once Stripe accepts Create goals (must have at least one to start accepting donations) Able to edit goals Allow project to start accepting donations (create plan) Have link for people to start donating (to project page)

See project page (total amount) Click amount or enter some Click continue => new page Edit to update amount if needed Enter credit card information or use existing card Submit form to charge card Get success screen (should be happy!) with link to share to others to donate (and social buttons?) Click to return to project Show your contribution amount instead of donate buttons (with a thanks!)

(Things in italics can maybe wait)

User account page Edit card information See list of all your donations

Project donations page See list of all transfer amounts

begedin commented 8 years ago

It honestly all looks really good and thorough. The only thing I'm concerned about, but I also assume you did more research in that area than I did, is if we're losing on donations by going recurring-only.

Are the pros of us going recurring donations only outweighing the cons?

begedin commented 8 years ago

Also, regarding cents, I really don't think that's necessary, but it's good to know that we can do it.

joshsmith commented 8 years ago

@begedin I think we can go back and add single donations later. For now it's better to do recurring I think and just focus on that.

joshsmith commented 7 years ago

Closing this because we're pretty out of sync with what has been discussed so far.