mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Refactoring StripeHelper into service objects
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
43
lib/stripe/account_connector.rb
Normal file
43
lib/stripe/account_connector.rb
Normal 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
51
lib/stripe/oauth.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
77
spec/lib/stripe/account_connector_spec.rb
Normal file
77
spec/lib/stripe/account_connector_spec.rb
Normal 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
|
||||
25
spec/lib/stripe/oauth_spec.rb
Normal file
25
spec/lib/stripe/oauth_spec.rb
Normal 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
|
||||
@@ -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)
|
||||
|
||||
47
spec/models/stripe_account_spec.rb
Normal file
47
spec/models/stripe_account_spec.rb
Normal 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
|
||||
Reference in New Issue
Block a user