Refactoring StripeHelper into service objects

This commit is contained in:
Rob Harrington
2017-07-07 11:48:57 +10:00
parent e6eb45b453
commit ae2d3d3fd9
16 changed files with 463 additions and 288 deletions

View File

@@ -1,4 +1,6 @@
require 'open_food_network/referer_parser'
require 'stripe/account_connector'
require 'stripe/oauth'
module Admin
class EnterprisesController < ResourceController
@@ -23,7 +25,6 @@ module Admin
helper 'spree/products'
include ActionView::Helpers::TextHelper
include OrderCyclesHelper
include Admin::StripeHelper
def index
respond_to do |format|
@@ -115,48 +116,19 @@ module Admin
end
def stripe_connect
redirect_to authorize_stripe(params[:enterprise_id])
redirect_to Stripe::OAuth.authorize_url(params[:enterprise_id])
end
def stripe_connect_callback
if params["code"]
state = jwt_decode(params["state"])
unless state.keys.include? "enterprise_id"
redirect_to '/unauthorized' and return
end
# Get the Enterprise
@enterprise = Enterprise.find_by_permalink(state["enterprise_id"])
# Get the deets from Stripe
response_params = get_stripe_token(params["code"]).params
# In case of a problem, need to also issue a request to disconnect the account from Stripe
if !(spree_current_user.enterprises.include? @enterprise) && !(spree_current_user.admin?)
deauthorize_request_for_stripe_id(response_params["stripe_user_id"])
redirect_to '/unauthorized' and return
end
stripe_account = StripeAccount.new(stripe_user_id: response_params["stripe_user_id"], stripe_publishable_key: response_params["stripe_publishable_key"], enterprise: @enterprise)
if stripe_account.save
respond_to do |format|
format.html { redirect_to main_app.edit_admin_enterprise_path(@enterprise), notice: "Stripe account connected successfully."}
format.json { render json: stripe_account }
end
else
render text: "Failed to save Stripe token", status: 500
end
connector = Stripe::AccountConnector.new(spree_current_user, params)
if connector.create_account
flash[:success] = t('admin.controllers.enterprises.stripe_connect_success')
redirect_to main_app.edit_admin_enterprise_path(@enterprise)
else
render text: params["error_description"], status: 500
end
end
def stripe_disconnect
if deauthorize_stripe(params[:account_id])
respond_to do |format|
format.html { redirect_to main_app.edit_admin_enterprise_path(@enterprise), notice: "Stripe account disconnected."}
format.json { render json: "Disconnected" }
end
render text: t('admin.controllers.enterprises.stripe_connect_fail'), status: 500
end
rescue Stripe::StripeError => e
render text: e.message, status: 500
end
protected

View File

@@ -1,30 +1,33 @@
module Admin
class StripeAccountsController < BaseController
include Admin::StripeHelper
protect_from_forgery except: :destroy_from_webhook
def destroy
if deauthorize_stripe(params[:id])
respond_to do |format|
format.html { redirect_to main_app.edit_admin_enterprise_path(params[:enterprise_id]), notice: "Stripe account disconnected."}
format.json { render json: stripe_account }
end
stripe_account = StripeAccount.find(params[:id])
authorize! :destroy, stripe_account
if stripe_account.deauthorize_and_destroy
flash[:success] = "Stripe account disconnected."
else
respond_to do |format|
format.html { redirect_to main_app.edit_admin_enterprise_path(params[:enterprise_id]), notice: "Failed to disconnect Stripe."}
format.json { render json: stripe_account }
end
flash[:error] = "Failed to disconnect Stripe."
end
redirect_to main_app.edit_admin_enterprise_path(stripe_account.enterprise)
rescue ActiveRecord::RecordNotFound
flash[:error] = "Failed to disconnect Stripe."
redirect_to spree.admin_path
end
def destroy_from_webhook
# Fetch the event again direct from stripe for extra security
event = fetch_event_from_stripe(request)
if event.type == "account.application.deauthorized"
StripeAccount.where(stripe_user_id: event.user_id).map{ |account| account.destroy }
# TODO is there a sensible way to confirm this webhook call is actually from Stripe?
event = Stripe::Event.construct_from(params)
return render nothing: true, status: 400 unless event.type == "account.application.deauthorized"
destroyed = StripeAccount.where(stripe_user_id: event.user_id).destroy_all
if destroyed.any?
render text: "Account #{event.user_id} deauthorized", status: 200
else
render json: nil, status: 501
render nothing: true, status: 400
end
end

View File

@@ -208,19 +208,19 @@ class CheckoutController < Spree::CheckoutController
def construct_saved_card_attributes
existing_card_id = params[:order].delete(:existing_card)
if existing_card_id.present?
credit_card = Spree::CreditCard.find(existing_card_id)
if credit_card.try(:user_id).blank? || credit_card.user_id != spree_current_user.try(:id)
raise Spree::Core::GatewayError.new I18n.t(:invalid_credit_card)
end
return if existing_card_id.blank?
# Not currently supported but maybe we should add it...?
credit_card.verification_value = params[:cvc_confirm] if params[:cvc_confirm].present?
params[:order][:payments_attributes].first[:source] = credit_card
params[:order][:payments_attributes].first[:payment_method_id] = credit_card.payment_method_id
params[:order][:payments_attributes].first.delete :source_attributes
credit_card = Spree::CreditCard.find(existing_card_id)
if credit_card.try(:user_id).blank? || credit_card.user_id != spree_current_user.try(:id)
raise Spree::Core::GatewayError, I18n.t(:invalid_credit_card)
end
# Not currently supported but maybe we should add it...?
credit_card.verification_value = params[:cvc_confirm] if params[:cvc_confirm].present?
params[:order][:payments_attributes].first[:source] = credit_card
params[:order][:payments_attributes].first[:payment_method_id] = credit_card.payment_method_id
params[:order][:payments_attributes].first.delete :source_attributes
end
def rescue_from_spree_gateway_error(error)

View File

@@ -1,75 +0,0 @@
# require File.join(Rails.root, '/lib/oauth2/strategy/deauthorize')
# require File.join(Rails.root, '/lib/oauth2/client')
# require 'oauth2'
module Admin
module StripeHelper
class << self
attr_accessor :client, :options
end
@options = {
:site => 'https://connect.stripe.com',
:authorize_url => '/oauth/authorize',
:deauthorize_url => '/oauth/deauthorize',
:token_url => '/oauth/token'
}
@client = OAuth2::Client.new(
ENV['STRIPE_CLIENT_ID'],
ENV['STRIPE_INSTANCE_SECRET_KEY'],
options
)
def get_stripe_token(code, options={})
StripeHelper.client.auth_code.get_token(code, options)
end
def authorize_stripe(enterprise_id, options={})
options = options.merge({enterprise_id: enterprise_id})
jwt = jwt_encode options
# State param will be passed back after auth
StripeHelper.client.auth_code.authorize_url(state: jwt, scope: 'read_write')
end
def deauthorize_stripe(account_id)
stripe_account = StripeAccount.find(account_id)
if stripe_account
# If the account is only connected to one Enterprise, make a request to remove it on the Stripe side
if StripeAccount.where(stripe_user_id: stripe_account.stripe_user_id).size == 1
response = deauthorize_request_for_stripe_id(stripe_account.stripe_user_id)
if response # Response from OAuth2 only returned if successful
stripe_account.destroy
end
else
stripe_account.destroy
end
end
end
def fetch_event_from_stripe(request)
event_json = JSON.parse(request.body.read)
# If the application has been deauthorised, we are no longer authorised to retrieve events for that account
# Left here in case it's useful for other webhooks
unless event_json["type"] == "account.application.deauthorized"
acct_param = event_json["user_id"] ? {"Stripe-Account" => event_json["user_id"]} : nil
Stripe::Event.retrieve(event_json["id"],acct_param)
else
Stripe::Event.construct_from(event_json)
end
end
def deauthorize_request_for_stripe_id(id)
StripeHelper.client.deauthorize(id).deauthorize_request
end
private
def jwt_encode payload
JWT.encode(payload, Openfoodnetwork::Application.config.secret_token, 'HS256')
end
def jwt_decode token
JWT.decode(token, Openfoodnetwork::Application.config.secret_token, true, algorithm: 'HS256')[0] # only returns the original payload
end
end
end

View File

@@ -121,6 +121,10 @@ class AbilityDecorator
# Not sure how to restrict these methods to a non-resource Controller
can [:stripe_connect, :stripe_connect_callback, :stripe_disconnect], :all
can [:destroy], StripeAccount do |stripe_account|
user.enterprises.include? stripe_account.enterprise
end
end
def add_product_management_abilities(user)

View File

@@ -123,16 +123,17 @@ module Spree
end
def token_from_card_profile_ids(creditcard)
if token_or_card_id = creditcard.gateway_payment_profile_id
if customer = creditcard.gateway_customer_profile_id
# Assume the gateway_payment_profile_id is a Stripe card_id
# So generate a new token, using the customer_id and card_id
return tokenize_instance_customer_card(customer, token_or_card_id)
end
token_or_card_id = creditcard.gateway_payment_profile_id
customer = creditcard.gateway_customer_profile_id
# Assume the gateway_payment_profile_id is a token generated by StripeJS
return token_or_card_id
end
return nil if token_or_card_id.blank?
# Assume the gateway_payment_profile_id is a token generated by StripeJS
return token_or_card_id if customer.blank?
# Assume the gateway_payment_profile_id is a Stripe card_id
# So generate a new token, using the customer_id and card_id
tokenize_instance_customer_card(customer, token_or_card_id)
end
def tokenize_instance_customer_card(customer, card)

View File

@@ -2,4 +2,15 @@ class StripeAccount < ActiveRecord::Base
belongs_to :enterprise
validates_presence_of :stripe_user_id, :stripe_publishable_key
validates_uniqueness_of :enterprise_id
def deauthorize_and_destroy
accounts = StripeAccount.where(stripe_user_id: stripe_user_id)
# Only deauthorize the user if it is not linked to multiple accounts
if accounts.count > 1 || Stripe::OAuth.deauthorize(stripe_user_id)
destroy
else
false
end
end
end

View File

@@ -0,0 +1,43 @@
# Encapsulation of logic used to handle the response from Stripe following an
# attempt to connect an account to the instance using the OAuth Connection Flow
# https://stripe.com/docs/connect/standard-accounts#oauth-flow
module Stripe
class AccountConnector
attr_reader :oauth_response, :enterprise, :user, :params
def initialize(user, params)
@user = user
@params = params
raise StripeError, params["error_description"] unless params["code"]
raise CanCan::AccessDenied unless state.keys.include? "enterprise_id"
# Request an access token based on the code provided
@oauth_response = OAuth.request_access_token(params["code"])
# Find the Enterprise
@enterprise = Enterprise.find_by_permalink(state["enterprise_id"])
return if user.enterprises.include?(enterprise) || user.admin?
# Local authorisation issue, so request disconnection from Stripe
OAuth.deauthorize(oauth_response["stripe_user_id"])
raise CanCan::AccessDenied
end
def create_account
StripeAccount.create(
stripe_user_id: oauth_response["stripe_user_id"],
stripe_publishable_key: oauth_response["stripe_publishable_key"],
enterprise: enterprise
)
end
private
def state
OAuth.send(:jwt_decode, params["state"])
end
end
end

51
lib/stripe/oauth.rb Normal file
View File

@@ -0,0 +1,51 @@
module Stripe
class OAuth
class << self
attr_accessor :client, :options
end
@options = {
:site => 'https://connect.stripe.com',
:authorize_url => '/oauth/authorize',
:deauthorize_url => '/oauth/deauthorize',
:token_url => '/oauth/token'
}
@client = OAuth2::Client.new(
ENV['STRIPE_CLIENT_ID'],
ENV['STRIPE_INSTANCE_SECRET_KEY'],
options
)
def self.authorize_url(enterprise_id, options = {})
options[:enterprise_id] = enterprise_id
jwt = jwt_encode(options)
# State param will be passed back after auth
client.auth_code.authorize_url(state: jwt, scope: 'read_write')
end
def self.request_access_token(auth_code)
# Fetch and return the account details from Stripe
client.auth_code.get_token(auth_code).params
end
def self.deauthorize(stripe_user_id)
client.deauthorize(stripe_user_id).deauthorize_request
end
private
def self.secret_token
Openfoodnetwork::Application.config.secret_token
end
def self.jwt_encode(payload)
JWT.encode(payload, secret_token, 'HS256')
end
def self.jwt_decode(token)
# Returns the original payload
JWT.decode(token, secret_token, true, algorithm: 'HS256')[0]
end
end
end

View File

@@ -144,67 +144,71 @@ module Admin
expect(distributor.users).to_not include user
end
describe "stripe connect" do
it "redirects to Stripe" do
controller.stub spree_current_user: distributor_manager
Admin::StripeHelper.client.stub id: "abc"
describe "#stripe_connect" do
before do
allow(controller).to receive(:spree_current_user) { distributor_manager }
allow(::Stripe::OAuth).to receive(:authorize_url) { "some_url" }
end
it "redirects to Stripe Authorization url constructed OAuth" do
spree_get :stripe_connect
['https://connect.stripe.com/oauth/authorize',
'response_type=code',
'state=',
'client_id=abc'].each{|element| response.location.should match element}
expect(response).to redirect_to "some_url"
end
end
context "#stripe_connect_callback" do
let(:params) { { id: distributor.permalink } }
before { allow(controller).to receive(:spree_current_user) { distributor_manager } }
context "when the connector raises a StripeError" do
before do
allow(Stripe::AccountConnector).to receive(:new).and_raise Stripe::StripeError, "some error"
end
it "returns a 500 error" do
spree_get :stripe_connect_callback, params
response.status.should be 500
end
end
it "returns 500 on callback if the response code is not provided" do
controller.stub spree_current_user: distributor_manager
spree_get :stripe_connect_callback
response.status.should be 500
context "when the connector raises an AccessDenied error" do
before do
allow(Stripe::AccountConnector).to receive(:new).and_raise CanCan::AccessDenied, "some error"
end
it "redirects to unauthorized" do
spree_get :stripe_connect_callback, params
expect(response).to redirect_to spree.unauthorized_path
end
end
it "redirects to login with the query params in case of session problems" do
pending("Difficult to reproduce: sometimes get logged out of OFN during Stripe connect
redirect/callback process. After logging in again, it doesn't redirect to the callback URL.")
controller.stub spree_current_user: nil
params = {this: "that"}
spree_get :stripe_connect_callback, params
# This is the idea - but even if generated correctly not sure it actually works since the redirect
# is ultimately handled in Angular, which presumably doesn't know which controller to
# use for the action?
response.should redirect_to root_path(anchor: "login?after_login=/?action=stripe_connect&this=that")
context "when initializing the connector does not raise an error" do
let(:connector) { double(:connector) }
before do
allow(Stripe::AccountConnector).to receive(:new) { connector }
end
context "when the connector succeeds in creating a new stripe account record" do
before { allow(connector).to receive(:create_account) { true } }
it "redirects to the enterprise edit path" do
spree_get :stripe_connect_callback, params
expect(flash[:success]).to eq I18n.t('admin.controllers.enterprises.stripe_connect_success')
expect(response).to redirect_to edit_admin_enterprise_path(distributor)
end
end
context "when the connector succeeds in creating a new stripe account record" do
before { allow(connector).to receive(:create_account) { false } }
it "renders a failure message" do
spree_get :stripe_connect_callback, params
expect(response.body).to eq I18n.t('admin.controllers.enterprises.stripe_connect_fail')
expect(response.status).to be 500
end
end
end
it "redirects to unauthorized if the callback state param is invalid" do
controller.stub spree_current_user: distributor_manager
payload = {junk: "Ssfs"}
params = {state: JWT.encode(payload, Openfoodnetwork::Application.config.secret_token),
code: "code"}
spree_get :stripe_connect_callback, params
response.should redirect_to '/unauthorized'
end
# TODO: This should probably also include managers/coordinators as well as owners?
it "makes a request to cancel the Stripe connection if the user does not own the enterprise" do
controller.stub spree_current_user: distributor_manager
controller.stub(:deauthorize_request_for_stripe_id)
controller.stub_chain(:get_stripe_token, :params).and_return({stripe_user_id: "xyz123", stripe_publishable_key: "abc456"}.to_json)
payload = {enterprise_id: supplier.permalink} # Request is not for the current user's Enterprise
params = {state: JWT.encode(payload, Openfoodnetwork::Application.config.secret_token),
code: "code"}
spree_get :stripe_connect_callback, params
controller.should have_received(:deauthorize_request_for_stripe_id)
end
it "makes a new Stripe Account from the callback params" do
controller.stub spree_current_user: distributor_manager
controller.stub_chain(:get_stripe_token, :params).and_return({stripe_user_id: "xyz123", stripe_publishable_key: "abc456"}.to_json)
payload = {enterprise_id: distributor.permalink}
params = {state: JWT.encode(payload, Openfoodnetwork::Application.config.secret_token),
code: "code"}
expect{spree_get :stripe_connect_callback, params}.to change{StripeAccount.all.length}.by 1
StripeAccount.last.enterprise_id.should eq distributor.id
end
end

View File

@@ -3,40 +3,103 @@ require 'spec_helper'
describe Admin::StripeAccountsController, type: :controller do
describe "destroy_from_webhook" do
let!(:stripe_account) { create(:stripe_account, stripe_user_id: "webhook_id") }
let(:params) do
{
"format"=> "json",
"id" => "evt_123",
"object" => "event",
"data" => { "object" => { "id" => "ca_9B" } },
"type" => "account.application.deauthorized",
"user_id" => "webhook_id"
}
end
it "deletes Stripe accounts in response to a webhook" do
# https://stripe.com/docs/api#retrieve_event
allow(controller).to receive(:fetch_event_from_stripe)
.and_return(Stripe::Event.construct_from({"id"=>"evt_wrfwg4323fw",
"object"=>"event",
"api_version"=>nil,
"created"=>1484870684,
"data"=>
{"object"=>
{"id"=>"application_id",
"object"=>"application",
"name"=>"Open Food Network UK"}},
"livemode"=>false,
"pending_webhooks"=>1,
"request"=>nil,
"type"=>"account.application.deauthorized",
"user_id"=>"webhook_id"}))
account = create(:stripe_account, stripe_user_id: "webhook_id")
expect(Stripe::Event).not_to receive(:retrieve) # should not retrieve direct for a deauth event
post 'destroy_from_webhook', {"id"=>"evt_wrfwg4323fw",
"object"=>"event",
"api_version"=>nil,
"created"=>1484870684,
"data"=>
{"object"=>
{"id"=>"ca_9ByaSyyyXj5O73DWisU0KLluf0870Vro",
"object"=>"application",
"name"=>"Open Food Network UK"}},
"livemode"=>false,
"pending_webhooks"=>1,
"request"=>nil,
"type"=>"account.application.deauthorized",
"user_id"=>"webhook_id"}
expect(StripeAccount.all).not_to include account
post 'destroy_from_webhook', params
expect(response.status).to eq 200
expect(response.body).to eq "Account webhook_id deauthorized"
expect(StripeAccount.all).not_to include stripe_account
end
context "when the user_id on the event does not match any known accounts" do
before do
params["user_id"] = "webhook_id1"
end
it "does nothing" do
post 'destroy_from_webhook', params
expect(response.status).to eq 400
expect(StripeAccount.all).to include stripe_account
end
end
context "when the event is not a deauthorize event" do
before do
params["type"] = "account.application.authorized"
end
it "does nothing" do
post 'destroy_from_webhook', params
expect(response.status).to eq 400
expect(StripeAccount.all).to include stripe_account
end
end
end
describe "destroy" do
let(:enterprise) { create(:distributor_enterprise) }
let(:params) { { format: :json, id: "some_id" } }
context "when the specified stripe account doesn't exist" do
it "raises an error?" do
spree_delete :destroy, params
end
end
context "when the specified stripe account exists" do
let(:stripe_account) { create(:stripe_account, enterprise: enterprise) }
before do
# So that we can stub #deauthorize_and_destroy
allow(StripeAccount).to receive(:find) { stripe_account }
params[:id] = stripe_account.id
end
context "when I don't manage the enterprise linked to the stripe account" do
let(:some_user) { create(:user) }
before { allow(controller).to receive(:spree_current_user) { some_user } }
it "redirects to unauthorized" do
spree_delete :destroy, params
expect(response).to redirect_to spree.unauthorized_path
end
end
context "when I manage the enterprise linked to the stripe account" do
before { allow(controller).to receive(:spree_current_user) { enterprise.owner } }
context "and the attempt to deauthorize_and_destroy succeeds" do
before { allow(stripe_account).to receive(:deauthorize_and_destroy) { stripe_account } }
it "redirects to unauthorized" do
spree_delete :destroy, params
expect(response).to redirect_to edit_admin_enterprise_path(enterprise)
expect(flash[:success]).to eq "Stripe account disconnected."
end
end
context "and the attempt to deauthorize_and_destroy fails" do
before { allow(stripe_account).to receive(:deauthorize_and_destroy) { false } }
it "redirects to unauthorized" do
spree_delete :destroy, params
expect(response).to redirect_to edit_admin_enterprise_path(enterprise)
expect(flash[:error]).to eq "Failed to disconnect Stripe."
end
end
end
end
end

View File

@@ -1,52 +0,0 @@
require 'spec_helper'
describe Admin::StripeHelper do
let!(:enterprise) { create(:enterprise) }
let!(:enterprise2) { create(:enterprise) }
let!(:stripe_account) { create(:stripe_account, enterprise: enterprise) }
it "calls the Stripe API to get a token" do
expect(Admin::StripeHelper.client.auth_code).to receive(:get_token).with("abc", {})
helper.get_stripe_token("abc")
end
it "calls the Stripe API for authorization with read_write permission, passing appropriate JWT in the state param" do
expect(Admin::StripeHelper.client.auth_code).to receive(:authorize_url).with({
state: JWT.encode({enterprise_id: "enterprise-permalink"}, Openfoodnetwork::Application.config.secret_token, 'HS256'),
scope: "read_write"
})
helper.authorize_stripe("enterprise-permalink")
end
context "Disconnecting an account" do
it "doesn't destroy the database record if the Stripe API disconnect failed" do
Admin::StripeHelper.client
.deauthorize(stripe_account.stripe_user_id)
.stub(:deauthorize_request)
.and_return(nil)
deauthorize_stripe(stripe_account.id)
expect(StripeAccount.all).to include(stripe_account)
end
it "destroys the record if the Stripe API disconnect succeeds" do
Admin::StripeHelper.client
.deauthorize(stripe_account.stripe_user_id)
.stub(:deauthorize_request)
.and_return("something truthy")
deauthorize_stripe(stripe_account.id)
expect(StripeAccount.all).not_to include(stripe_account)
end
it "Doesn't make a Stripe API disconnection request if the account is also associated with another Enterprise" do
another_stripe_account = create(:stripe_account, enterprise: enterprise2, stripe_user_id: stripe_account.stripe_user_id)
expect(Admin::StripeHelper.client.deauthorize(stripe_account.stripe_user_id)).not_to receive(:deauthorize_request)
deauthorize_stripe(stripe_account.id)
end
it "encodes and decodes JWT" do
jwt_decode(jwt_encode({test: "string"})).should eq({"test" => "string"})
end
end
end

View File

@@ -0,0 +1,77 @@
require 'spec_helper'
require 'stripe/account_connector'
require 'stripe/oauth'
module Stripe
describe AccountConnector do
describe "initialization" do
let(:user) { create(:user) }
let(:enterprise) { create(:enterprise) }
let(:payload) { { "junk" => "Ssfs" } }
let(:state) { JWT.encode(payload, Openfoodnetwork::Application.config.secret_token) }
let(:params) { { "state" => state } }
context "when params have no 'code' key" do
it "raises a StripeError" do
expect{ AccountConnector.new(user, params) }.to raise_error StripeError
end
end
context "when params have a 'code' key" do
before { params["code"] = 'code' }
context "and the decoded state param doesn't contain an 'enterprise_id' key" do
it "raises an AccessDenied error" do
expect{ AccountConnector.new(user, params) }.to raise_error CanCan::AccessDenied
end
end
context "and the decoded state param contains an 'enterprise_id' key" do
let(:payload) { { enterprise_id: enterprise.permalink } }
let(:access_token) { { "stripe_user_id" => "some_user_id", "stripe_publishable_key" => "some_key" } }
before do
expect(OAuth).to receive(:request_access_token) { access_token }
end
context "but the user doesn't manage own or manage the corresponding enterprise" do
it "makes a request to cancel the Stripe connection and raises an error" do
expect(OAuth).to receive(:deauthorize).with("some_user_id")
expect{ AccountConnector.new(user, params) }.to raise_error CanCan::AccessDenied
end
end
context "and the user manages the corresponding enterprise" do
before do
user.enterprise_roles.create(enterprise: enterprise)
end
it "raises no errors" do
expect(OAuth).to_not receive(:deauthorize)
AccountConnector.new(user, params)
end
it "allows creations of a new Stripe Account from the callback params" do
connector = AccountConnector.new(user, params)
expect{connector.create_account}.to change(StripeAccount, :count).by(1)
end
end
context "and the user owns the corresponding enterprise" do
let(:user) { enterprise.owner }
it "raises no errors" do
expect(OAuth).to_not receive(:deauthorize)
AccountConnector.new(user, params)
end
it "allows creations of a new Stripe Account from the callback params" do
connector = AccountConnector.new(user, params)
expect{connector.create_account}.to change(StripeAccount, :count).by(1)
end
end
end
end
end
end
end

View File

@@ -0,0 +1,25 @@
require 'spec_helper'
require 'stripe/oauth'
module Stripe
describe OAuth do
describe "contructing an authorization url" do
let(:enterprise_id) { "ent_id" }
before do
allow(OAuth.client).to receive(:id) { 'abc' }
end
it "builds a url with all of the necessary params" do
url = OAuth.authorize_url(enterprise_id)
uri = URI.parse(url)
params = CGI::parse(uri.query)
expect(params.keys).to include 'client_id', 'response_type', 'state', 'scope'
expect(params["state"]).to eq [OAuth.jwt_encode(enterprise_id: enterprise_id)]
expect(uri.scheme).to eq 'https'
expect(uri.host).to eq 'connect.stripe.com'
expect(uri.path).to eq '/oauth/authorize'
end
end
end
end

View File

@@ -43,6 +43,7 @@ describe Spree::Gateway::StripeConnect, type: :model do
context "when the credit card provided does not have a gateway_payment_profile_id" do
before { allow(creditcard).to receive(:gateway_payment_profile_id) { nil } }
before { allow(creditcard).to receive(:gateway_customer_profile_id) { "customer_id123" } }
it "returns nil....?" do
result = subject.send(:token_from_card_profile_ids, creditcard)

View File

@@ -0,0 +1,47 @@
require 'spec_helper'
require 'stripe/oauth'
describe StripeAccount do
describe "deauthorize_and_destroy" do
let!(:enterprise) { create(:enterprise) }
let!(:enterprise2) { create(:enterprise) }
let!(:stripe_account) { create(:stripe_account, enterprise: enterprise) }
context "when the Stripe API disconnect fails" do
before do
Stripe::OAuth.client
.deauthorize(stripe_account.stripe_user_id)
.stub(:deauthorize_request)
.and_return(nil)
end
it "doesn't destroy the record" do
stripe_account.deauthorize_and_destroy
expect(StripeAccount.all).to include(stripe_account)
end
end
context "when the Stripe API disconnect succeeds" do
before do
Stripe::OAuth.client
.deauthorize(stripe_account.stripe_user_id)
.stub(:deauthorize_request)
.and_return("something truthy")
end
it "destroys the record" do
stripe_account.deauthorize_and_destroy
expect(StripeAccount.all).not_to include(stripe_account)
end
end
context "if the account is also associated with another Enterprise" do
let!(:another_stripe_account) { create(:stripe_account, enterprise: enterprise2, stripe_user_id: stripe_account.stripe_user_id) }
it "Doesn't make a Stripe API disconnection request " do
expect(Stripe::OAuth.client.deauthorize(stripe_account.stripe_user_id)).not_to receive(:deauthorize_request)
stripe_account.deauthorize_and_destroy
end
end
end
end