Skip to content

Instantly share code, notes, and snippets.

@jagdeepsingh
Last active March 13, 2023 12:38
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jagdeepsingh/166fa03829275cb6131d77abc6d4c148 to your computer and use it in GitHub Desktop.
Save jagdeepsingh/166fa03829275cb6131d77abc6d4c148 to your computer and use it in GitHub Desktop.
Stripe Connect

Stripe Connect

Links:

Contents:

Basic setup

Create an account on stripe.com and note the secret_key and publication_key.

API version: 2017-04-06

Add following to Gemfile.

gem 'stripe', '~> 1.58.0'

Run bundle install. Now, you are ready to use Stripe API through stripe-ruby.

Set global secret_key for Stripe.

require 'stripe'
Stripe.api_key = secret_key

Register your platform on Dashboard. Note down the client_id for all environments.

Back to Top

Create account

Only attribute required to create a managed account on Stripe is country.

account = Stripe::Account.create(country: 'US', managed: true)     # => #<Stripe::Account:0x3ffc2e288ccc id=acct_19oAnaC7ST68TeIn> JSON: { ... }

Save the keys from the response. These keys will not be returned in any further requests.

account.keys.secret         # => "sk_test_3m1beY55s2dgsuu8tSRsaopV"
account.keys.publishable    # => "pk_test_0DhzuYi6lmhHUO9jKoUukY4O"

You can get information about test bank accounts for various countries here:

Also, see all the arguments that you can pass to create a managed account here.

USA

There are different stages of a managed account on Stripe.

If you have all the verification fields needed to fulfill first two stages of managed account initially, you can pass them when creating the account.

external_account = { object: 'bank_account',
                     country: 'US',
                     currency: 'usd',
                     routing_number: '110000000',
                     account_number: '000123456789' }

tos_acceptance = { date: Time.now.to_i, ip: '119.82.78.186' }

dob = { day: 20, month: 1, year: 1990 }

address = { line1: 'Metropolitan Life North Building',
            line2: '11 Madison Ave',
            city: 'New York',
            state: 'CA',
            postal_code: '10010' }

legal_entity = { dob: dob,
                 first_name: 'Lance',
                 last_name: 'Melia',
                 type: 'company',
                 business_name: 'Eleven Madison Park',
                 address: address,
                 ssn_last_4: 1234,
                 business_tax_id: 123456789 }

account = Stripe::Account.create(country: 'US', managed: true,
                                 external_account: external_account,
                                 tos_acceptance: tos_acceptance,
                                 legal_entity: legal_entity)
account.charges_enabled                 # => true
account.transfers_enabled               # => true
account.verification.fields_needed      # => ["legal_entity.personal_id_number", "legal_entity.verification.document"]

Download the test success image. I have also stored the image on S3. Download it to your local and connect it to your Stripe account.

file_obj = Stripe::FileUpload.create({ purpose: 'identity_document',
                                       file: File.new('success.png') },
                                     stripe_account: acc_id)              # => #<Stripe::FileUpload:0x3fd9b48e9ae0 id=file_19pFt6FeV5ZvsmJc3dKeAfaU> JSON: { ... }
file = file_obj.id      # => "file_19pFt6FeV5ZvsmJc3dKeAfaU"

Now, connect this file to your managed account and provide other remaining fields.

account.legal_entity.personal_id_number = 123456789
account.legal_entity.verification.document = file
account.save
account.verification.fields_needed         # => []

This information is needed for both individual and company accounts.

Hong Kong

external_account = { object: 'bank_account',
                     country: 'HK',
                     currency: 'hkd',
                     routing_number: '123456',
                     account_number: '000123-456' }
tos_acceptance = { date: Time.now.to_i, ip: '119.82.78.186' }
dob = { day: 20, month: 1, year: 1990 }
address = { line1: 'Pacific Place', city: 'Admiralty', state: 'Hong Kong', postal_code: '999077' }

personal_address = { line1: 'Four Seasons',
                     line2: '8 Finance St',
                     city: 'Central',
                     state: 'Hong Kong',
                     postal_code: '999077' }

legal_entity = { dob: dob,
                 first_name: 'Miesha',
                 last_name: 'Lahr',
                 type: 'company',
                 business_name: 'Cafe Grey Deluxe',
                 address: address,
                 personal_address: personal_address,
                 business_tax_id: 123456789 }

account = Stripe::Account.create(country: 'HK', managed: true,
                                 external_account: external_account,
                                 tos_acceptance: tos_acceptance,
                                 legal_entity: legal_entity)

Upload the verification document and connect it to managed account.

account.legal_entity.personal_id_number = 123456789
account.legal_entity.verification.document = file
account.save
account.verification.fields_needed         # => []

Singapore

external_account = { object: 'bank_account',
                     country: 'SG',
                     currency: 'sgd',
                     routing_number: '1100-000',
                     account_number: '000123456' }
tos_acceptance = { date: Time.now.to_i, ip: '119.82.78.186' }

dob = { day: 20, month: 1, year: 1984 }

additional_owners = [{ first_name: 'Lenny', last_name: 'Maurry', dob: dob,
                       address: { line1: '438 Serangoon Rd', postal_code: 218133 } },
                     { first_name: 'Gia', last_name: 'Scott', dob: dob,
                       address: { line1: '7 Raffles Ave', postal_code: 039799 } }]

address = { line1: '1 Cuscaden Rd', postal_code: '249715' }
personal_address = { line1: '10 Dempsey Rd', postal_code: 247700 }
legal_entity = { additional_owners: additional_owners,
                 dob: dob,
                 first_name: 'Shila',
                 last_name: 'Banks',
                 type: 'company',
                 business_name: 'Basilico Restaurant',
                 business_tax_id: 123456789,
                 address: address,
                 personal_address: personal_address }

account = Stripe::Account.create(country: 'SG', managed: true,
                                 external_account: external_account,
                                 tos_acceptance: tos_acceptance,
                                 legal_entity: legal_entity)

Upload the verification document and connect it to managed account.

account.legal_entity.personal_id_number = 123456789
account.legal_entity.verification.document = file

Also upload the verification documents for all the additional owners.

file = Stripe::FileUpload.create({ purpose: 'identity_document', file: File.new('success.png') },
                                 stripe_account: account.id)
additional_owner_params = [{ first_name: 'Lenny', last_name: 'Maurry', dob: dob,
                             address: { line1: '438 Serangoon Rd', postal_code: 218133 },
                             verification: { document: file.id } }
account.legal_entity.additional_owners = additional_owner_params
account.save
account.verification.fields_needed       # => []

Japan

external_account = { object: 'bank_account',
                     country: 'JP',
                     currency: 'jpy',
                     account_holder_name: 'An Yutzy',
                     routing_number: '1100000',
                     account_number: '00012345' }

address = { line1: '4-4-13',
            town: 'Shibakoen',
            city: 'Tokyo',
            state: 'Tokyo',
            postal_code: '1050011' }

address_kana = { line1: '27-15 FLAG 3A',
                 town: 'ジングウマエ 3-',
                 city: 'シブヤク',
                 state: 'トウキョウト',
                 postal_code: '1500001' }

address_kanji = { line1: '27-15 FLAG 3A',
                  town: '神宮前 3丁目',
                  city: '渋谷区',
                  state: '東京都',
                  postal_code: '1500001' }

dob = { day: 20, month: 1, year: 1990 }

legal_entity = { type: 'company',
                 first_name_kana: 'アン',
                 first_name_kanji: 'ヤトジー',
                 last_name_kana: '丹几',
                 last_name_kanji: 'と凵卞乙と',
                 gender: 'female',
                 phone_number: '08012345678',
                 business_name: 'Robot Restaurant',
                 business_name_kana: ' 	ローボット・レストロント',
                 business_name_kanji: '尺回日回卞 尺ヨ己卞丹凵尺丹几卞',
                 business_tax_id: 123456789,
                 address_kana: address_kana,
                 address_kanji: address_kanji,
                 personal_address_kana: address_kana,
                 personal_address_kanji: address_kanji,
                 dob: dob }

tos_acceptance = { date: Time.now.to_i, ip: '119.82.78.186' }

account = Stripe::Account.create(country: 'JP', managed: true,
                                 external_account: external_account,
                                 tos_acceptance: tos_acceptance,
                                 legal_entity: legal_entity)

Now, upload the verification document and connect it to managed account. See here.

account.legal_entity.personal_id_number = 123456789
account.legal_entity.verification.document = file
account.save
account.verification.fields_needed         # => []

Retrieve account

account = Stripe::Account.retrieve(account_id)    # => #<Stripe::Account:0x3ffc2e288ccc id=acct_19oAnaC7ST68TeIn> JSON: { ... }

Create charge

purchase

credit_card = { object: 'card',
                number: '4242424242424242',
                exp_year: Time.now.year + 1,
                exp_month: 11,
                cvc: '123',
                name: 'Ammie Core' }
charge = Stripe::Charge.create(source: credit_card,
                               currency: 'JPY',
                               amount: 121843,
                               destination: { account: acc_id })       # => #<Stripe::Charge:0x3fdb74fb30bc id=ch_19ieOgAz1CWwcBCUZbMBRMhh> JSON: { ... }
charge.status           # => "succeeded"
charge.on_behalf_of     # => "acct_19pxWWLqhivFDn6r"

Other ways to create a charge are:

authorize

Pass capture: false in the arguments hash to Stripe::Charge.create along with other options.

capture

You can call capture on charge to capture partial or full amount.

void

refund = Stripe::Refund.create(charge: charge.id, reverse_transfer: true)    # => #<Stripe::Refund:0x3ff40d94c6b8 id=re_19pzDiAz1CWwcBCUeBYfoho0> JSON: { ... }

refund

Same as void.

Delete account

account = Stripe::Charge.retrieve(account_id)
account.delete

Back to Top

Webhooks

You can manage webhooks for Stripe Connect here.

Testing webhooks in development

You will need a public url to access your local application through webhooks.

You can generate a public url using ngrok. Install the ngrok with Homebrew.

$ brew update
$ brew cask install ngrok

Check your ip address by running ifconfig.

Now, open a terminal and run the following command:

$ ngrok http 192.168.1.15:3000
ngrok by @inconshreveable                                                                                                                                                                  (Ctrl+C to quit)
                                                                                                                                                                                                           
Session Status                online                                                                                                                                                                       
Version                       2.1.18                                                                                                                                                                       
Region                        United States (us)                                                                                                                                                           
Web Interface                 http://127.0.0.1:4040                                                                                                                                                        
Forwarding                    http://8289e474.ngrok.io -> 192.168.1.15:3000                                                                                                                                
Forwarding                    https://8289e474.ngrok.io -> 192.168.1.15:3000
...

Your local application is now accessible at http://8289e474.ngrok.io.

You can use this url for creating the webhook on Stripe.

Back to Top

A Standard Stripe account is a conventional Stripe account controlled directly by the account holder (i.e., your platform’s user). A user with a Standard account has a relationship with Stripe, is able to log in to the Dashboard, can process charges on their own, and can disconnect their account from your platform.

You can prompt your users to create Stripe accounts, or allow anyone with an existing Stripe account to connect to your platform.

2.1 Setup

  1. Create a new account on Stripe dashboard.

  1. Under Connect > Settings > Platform Settings, click on "Register your platform".

  1. Under Standard accounts, fill Name, Website URL, and other details.

  1. Enter Redirect URIs and note down the client_id (CLIENT_ID) for both Development and Production environments. Sample Redirect URI: http://localhost:3000/path/to/stripe/oauth_callback.

  2. On dashboard, click on "API" and note down Publishable key (PUBLISHABLE_KEY) and Secret key (SECRET_KEY) for Live and Test environments.

Back to File

Back to Top

2.2 Install gems

# Gemfile
gem 'omniauth-stripe-connect', '~> 2.10.0'

Run bundle install. omniauth-stripe-connect is now installed in your application.

Back to File

Back to Top

2.3 OAuth link

# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :stripe_connect, CLIENT_ID, SECRET_KEY, { callback_path: '/path/to/stripe/oauth/callback' }
end
/
  app/views/path/to/stripe_connect.html.haml
= link_to 'Stripe Connect', '/auth/stripe_connect'

Back to File

Back to Top

2.4 User connects their account

Clicking on "Stripe Connect" button, user will be taken to following page. Click on "Sign in with Stripe to connect".

User can fill the following form and click on "Authorize access to this account", or in development mode, they can click on "Skip this account form".

After this, user will be redirected to your application with oauth response.

Back to File

Back to Top

2.5 OAuth response

# app/controllers/stripe/standard_accounts_controller.rb
> auth = request.env['omniauth.auth']
 => #<OmniAuth::AuthHash credentials=#<OmniAuth::AuthHash expires=false refresh_token="rt_BOI6KtvSxy49piHgu...uTaUuMQodEajHovbqn" token="sk_test_2MKaifd6...E6LNUDGX"> extra=#<OmniAuth::AuthHash extra_info=#<OmniAuth::AuthHash business_logo=nil business_name=nil business_url=nil charges_enabled=false country="US" default_currency="usd" details_submitted=false display_name="Khalsa" email="jagdeepsingh.125k@gmail.com" id="acct_1B1V84CD6zxw0RlV" managed=false metadata=#<OmniAuth::AuthHash> object="account" payouts_enabled=false statement_descriptor=nil support_email=nil support_phone=nil timezone="Asia/Calcutta" type="standard"> raw_info=#<OmniAuth::AuthHash livemode=false scope="read_only" stripe_publishable_key="pk_test_nXq65KTwz...fuFxBhzL" stripe_user_id="acct_1B1V84CD6zxw0RlV" token_type="bearer">> info=#<OmniAuth::AuthHash::InfoHash email="jagdeepsingh.125k@gmail.com" livemode=false name="Khalsa" nickname="Khalsa" scope="read_only" stripe_publishable_key="pk_test_nXq65KTwzo...uFxBhzL"> provider=:stripe_connect uid="acct_1B1V84CD6zxw0RlV">

> auth.uid
 => "acct_1B1V84CD6zxw0RlV"

> auth.credentials.token
 => "sk_test_2MKaifd6CuFqRiggE6LNUDGX"
 
> auth.info
 => #<OmniAuth::AuthHash::InfoHash email="jagdeepsingh.125k@gmail.com" livemode=false name="Khalsa" nickname="Khalsa" scope="read_only" stripe_publishable_key="pk_test_nXq65KTw...uFxBhzL">

> auth.extra.extra_info
 => #<OmniAuth::AuthHash business_logo=nil business_name=nil business_url=nil charges_enabled=false country="US" default_currency="usd" details_submitted=false display_name="Khalsa" email="jagdeepsingh.125k@gmail.com" id="acct_1B1V84CD6zxw0RlV" managed=false metadata=#<OmniAuth::AuthHash> object="account" payouts_enabled=false statement_descriptor=nil support_email=nil support_phone=nil timezone="Asia/Calcutta" type="standard">
 
> auth.extra.raw_info
 => #<OmniAuth::AuthHash livemode=false scope="read_only" stripe_publishable_key="pk_test_nXq65KT...DfuFxBhzL" stripe_user_id="acct_1B1V84CD6zxw0RlV" token_type="bearer">

Note down the value of auth.uid (ACCOUNT_ID).

You can retrieve the account again using ACCOUNT_ID:

account = Stripe::Account.retrieve(ACCOUNT_ID)
 => #<Stripe::Account:0x3fd200bcb378 id=acct_1AIFzJLs43kZFnbc> JSON: {
  "id": "acct_1AIFzJLs43kZFnbc",
  "object": "account",
  "business_logo": null,
  "business_name": "Eleven Madison Park-JD-local",
  "business_url": null,
  "charges_enabled": true,
  "country": "US",
  "debit_negative_balances": false,
  "decline_charge_on": {"avs_failure":false,"cvc_failure":false},
  "default_currency": "usd",
  "details_submitted": true,
  "display_name": null,
  "email": null,
  "external_accounts": {"object":"list","data":[{"id":"ba_1AIFzJLs43kZFnbcecj5H2wh","object":"bank_account","account":"acct_1AIFzJLs43kZFnbc","account_holder_name":null,"account_holder_type":null,"bank_name":"STRIPE TEST BANK","country":"US","currency":"usd","default_for_currency":true,"fingerprint":"tICRtBvxQB6kkjPk","last4":"6789","metadata":{},"routing_number":"110000000","status":"new"}],"has_more":false,"total_count":1,"url":"/v1/accounts/acct_1AIFzJLs43kZFnbc/external_accounts"},
  "legal_entity": {"additional_owners":[],"address":{"city":"New York","country":"US","line1":"Metropolitan Life North Building","line2":"11 Madison Ave","postal_code":"10010","state":"CA"},"business_name":"Eleven Madison Park-JD-local","business_tax_id_provided":true,"dob":{"day":11,"month":5,"year":1992},"first_name":"Sri","last_name":"Vroom","personal_address":{"city":null,"country":"US","line1":null,"line2":null,"postal_code":null,"state":null},"personal_id_number_provided":true,"ssn_last_4_provided":true,"type":"company","verification":{"details":null,"details_code":null,"document":"file_1AIFzPLs43kZFnbckAkgH9Ce","status":"verified"}},
  "metadata": {"payment_account_id":"591452696120b292a800001c","payment_account_name":"PA2-JD-local"},
  "payout_schedule": {"delay_days":2,"interval":"daily"},
  "payout_statement_descriptor": null,
  "payouts_enabled": true,
  "product_description": null,
  "statement_descriptor": null,
  "support_email": null,
  "support_phone": null,
  "timezone": "Etc/UTC",
  "tos_acceptance": {"date":1494441000,"ip":"127.0.0.1","user_agent":""},
  "type": "custom",
  "verification": {"disabled_reason":null,"due_by":null,"fields_needed":[]},
  "managed": true
}

Back to File

Back to Top

2.6 Creating charges

Create customer

Use Stripe.js to generate a TOKEN for credit card details.

customer = Stripe::Customer.create({ source: TOKEN }, stripe_account: ACCOUNT_ID)
 => #<Stripe::Customer:0x3fd200bd26c8 id=cus_BOhKqDiVx3bai8> JSON: {
  "id": "cus_BOhKqDiVx3bai8",
  "object": "customer",
  "account_balance": 0,
  "created": 1505382431,
  "currency": null,
  "default_source": "card_1B1twBLs43kZFnbcMvHoXMWF",
  "delinquent": false,
  "description": null,
  "discount": null,
  "email": null,
  "livemode": false,
  "metadata": {},
  "shipping": null,
  "sources": {"object":"list","data":[{"id":"card_1B1twBLs43kZFnbcMvHoXMWF","object":"card","address_city":null,"address_country":null,"address_line1":null,"address_line1_check":null,"address_line2":null,"address_state":null,"address_zip":null,"address_zip_check":null,"brand":"Visa","country":"US","customer":"cus_BOhKqDiVx3bai8","cvc_check":"pass","dynamic_last4":null,"exp_month":10,"exp_year":2017,"fingerprint":"Ofk9JGwSzld0iYjN","funding":"unknown","last4":"1111","metadata":{},"name":"Lisa Holder","tokenization_method":null}],"has_more":false,"total_count":1,"url":"/v1/customers/cus_BOhKqDiVx3bai8/sources"},
  "subscriptions": {"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/customers/cus_BOhKqDiVx3bai8/subscriptions"}
}

Purchase

charge = Stripe::Charge.create({ customer: customer.id, currency: 'USD', amount: 1000 }, stripe_account: ACCOUNT_ID)
 => #<Stripe::Charge:0x3fd206510230 id=ch_1B1tyzLs43kZFnbcHBwHgr17> JSON: {
  "id": "ch_1B1tyzLs43kZFnbcHBwHgr17",
  "object": "charge",
  "amount": 1000,
  "amount_refunded": 0,
  "application": "ca_A8QuChIaVBzAu4N3oj9g9hdKU8zpbQZ9",
  "application_fee": null,
  "balance_transaction": "txn_1B1tyzLs43kZFnbcx0v1cFKQ",
  "captured": true,
  "created": 1505382605,
  "currency": "usd",
  "customer": "cus_BOhKqDiVx3bai8",
  "description": null,
  "destination": null,
  "dispute": null,
  "failure_code": null,
  "failure_message": null,
  "fraud_details": {},
  "invoice": null,
  "livemode": false,
  "metadata": {},
  "on_behalf_of": null,
  "order": null,
  "outcome": {"network_status":"approved_by_network","reason":null,"risk_level":"normal","seller_message":"Payment complete.","type":"authorized"},
  "paid": true,
  "receipt_email": null,
  "receipt_number": null,
  "refunded": false,
  "refunds": {"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/charges/ch_1B1tyzLs43kZFnbcHBwHgr17/refunds"},
  "review": null,
  "shipping": null,
  "source": {"id":"card_1B1twBLs43kZFnbcMvHoXMWF","object":"card","address_city":null,"address_country":null,"address_line1":null,"address_line1_check":null,"address_line2":null,"address_state":null,"address_zip":null,"address_zip_check":null,"brand":"Visa","country":"US","customer":"cus_BOhKqDiVx3bai8","cvc_check":"pass","dynamic_last4":null,"exp_month":10,"exp_year":2017,"fingerprint":"Ofk9JGwSzld0iYjN","funding":"unknown","last4":"1111","metadata":{},"name":"Lisa Holder","tokenization_method":null},
  "source_transfer": null,
  "statement_descriptor": null,
  "status": "succeeded",
  "transfer_group": null
}

Retrieve a charge.

Stripe::Charge.retrieve(charge.id, stripe_account: ACCOUNT_ID)

Refund

refund = Stripe::Refund.create({ charge: charge.id, amount: 500, reason: 'requested_by_customer' }, stripe_account: ACCOUNT_ID)
 => #<Stripe::Refund:0x3fd20646d97c id=re_1B1u8fLs43kZFnbc6a3QYdwz> JSON: {
  "id": "re_1B1u8fLs43kZFnbc6a3QYdwz",
  "object": "refund",
  "amount": 500,
  "balance_transaction": "txn_1B1u8gLs43kZFnbc8PdZUYgO",
  "charge": "ch_1B1tyzLs43kZFnbcHBwHgr17",
  "created": 1505383205,
  "currency": "usd",
  "metadata": {},
  "reason": "requested_by_customer",
  "receipt_number": null,
  "status": "succeeded"
}

reason must be one of "duplicate", "fraudulent", or "requested_by_customer".

Back to File

Back to Top

2.7 Revoke access

Docs: https://stripe.com/docs/connect/standard-accounts#revoked-access

An account.application.deauthorized event occurs when a user disconnects your platform from their account. You can watch this event via Webhooks.

You will receive following params in your controller action for above event:

# app/controllers/stripe_connect_controller.rb
> params
 => {"id"=>"evt_1B1tPFAz1CWwcBCU7xDQUrj0", "object"=>"event", "account"=>"acct_19hzNiAz1CWwcBCU", "api_version"=>"2017-04-06", "created"=>1505380389, "data"=>{"object"=>{"id"=>"ca_A8QuChIaVBzAu4N3oj9g9hdKU8zpbRZ9", "object"=>"application", "name"=>"TableSolution"}}, "livemode"=>false, "pending_webhooks"=>1, "request"=>{"id"=>nil, "idempotency_key"=>nil}, "type"=>"account.application.deauthorized", "stripe_managed_account"=>{"id"=>"evt_1B1tPFAz1CWwcBCU7xDQUrj0", "object"=>"event", "account"=>"acct_19hzNiAz1CWwcBCU", "api_version"=>"2017-04-06", "created"=>1505380389, "data"=>{"object"=>{"id"=>"ca_A8QuChIaVBzAu4N3oj9g9hdKU8zpbRZ9", "object"=>"application", "name"=>"TableSolution"}}, "livemode"=>false, "pending_webhooks"=>1, "request"=>{"id"=>nil, "idempotency_key"=>nil}, "type"=>"account.application.deauthorized"}}

Back to File

Back to Top

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment