Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Create Stripe connection for organizations #254

Merged
merged 14 commits into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ jobs:
DATABASE_URL: "postgres://lago:lago@localhost:5432/lago"
RAILS_MASTER_KEY: ${{ secrets.RAILS_TEST_KEY }}
SECRET_KEY_BASE: ${{ secrets.SECRET_KEY_BASE }}
ENCRYPTION_PRIMARY_KEY: ${{ secrets.ENCRYPTION_PRIMARY_KEY }}
ENCRYPTION_DETERMINISTIC_KEY: ${{ secrets.ENCRYPTION_DETERMINISTIC_KEY }}
ENCRYPTION_KEY_DERIVATION_SALT: ${{ secrets.ENCRYPTION_KEY_DERIVATION_SALT }}
steps:
- name: Checkout code
uses: actions/checkout@v2
Expand All @@ -31,4 +34,4 @@ jobs:
- name: Set up database schema
run: bin/rails db:schema:load
- name: Run tests
run: bundle exec rspec
run: bundle exec rspec
24 changes: 24 additions & 0 deletions app/graphql/mutations/payment_providers/destroy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module Mutations
module PaymentProviders
class Destroy < BaseMutation
include AuthenticableApiUser

graphql_name 'DestroyPaymentProvider'
description 'Destroy a payment provider'

argument :id, ID, required: true

field :id, ID, null: true

def resolve(id:)
result = ::PaymentProviders::DestroyService
.new(context[:current_user])
.destroy(id: id)

result.success? ? result.payment_provider : result_error(result)
end
end
end
end
30 changes: 30 additions & 0 deletions app/graphql/mutations/payment_providers/stripe.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

module Mutations
module PaymentProviders
class Stripe < BaseMutation
include AuthenticableApiUser
include RequiredOrganization

graphql_name 'AddStripePaymentProvider'
description 'Add or update Stripe API keys to the organization'

argument :secret_key, String, required: true

argument :create_customers, Boolean, required: true
argument :send_zero_amount_invoice, Boolean, required: true

type Types::PaymentProviders::Stripe

def resolve(**args)
validate_organization!

result = ::PaymentProviders::StripeService
.new(context[:current_user])
.create_or_update(**args.merge(organization_id: current_organization.id))

result.success? ? result.stripe_provider : result_error(result)
end
end
end
end
3 changes: 3 additions & 0 deletions app/graphql/types/mutation_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,8 @@ class MutationType < Types::BaseObject
field :destroy_add_on, mutation: Mutations::AddOns::Destroy

field :create_applied_add_on, mutation: Mutations::AppliedAddOns::Create

field :destroy_payment_provider, mutation: Mutations::PaymentProviders::Destroy
field :add_stripe_payment_provider, mutation: Mutations::PaymentProviders::Stripe
end
end
2 changes: 2 additions & 0 deletions app/graphql/types/organization_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ class OrganizationType < Types::BaseObject
field :webhook_url, String
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false

field :stripe_provider, Types::PaymentProviders::Stripe, null: true
end
end
21 changes: 21 additions & 0 deletions app/graphql/types/payment_providers/stripe.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module Types
module PaymentProviders
class Stripe < Types::BaseObject
graphql_name 'StripeProvider'

field :id, ID, null: false
field :secret_key, String, null: false

field :create_customers, Boolean, null: false
field :send_zero_amount_invoice, Boolean, null: false

# NOTE: Secret key is a sensitive information. It should not be sent back to the
# front end application. Instead we send an obfuscated value
def secret_key
jdenquin marked this conversation as resolved.
Show resolved Hide resolved
object.secret_key[0..2] + ('*' * 12)
end
end
end
end
3 changes: 3 additions & 0 deletions app/models/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class Organization < ApplicationRecord
has_many :events
has_many :coupons
has_many :add_ons
has_many :payment_providers

has_one :stripe_payment_provider, class_name: 'PaymentProviders::StripeProvider'

before_create :generate_api_key

Expand Down
32 changes: 32 additions & 0 deletions app/models/payment_providers/base_provider.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

module PaymentProviders
class BaseProvider < ApplicationRecord
self.table_name = 'payment_providers'

belongs_to :organization

encrypts :secrets

def secrets_json
JSON.parse(secrets || '{}')
end

def push_to_secrets(key:, value:)
self.secrets = secrets_json.merge(key => value).to_json
end

def get_from_secrets(key)
secrets_json[key.to_s]
end

def push_to_settings(key:, value:)
self.settings ||= {}
settings[key] = value
end

def get_from_settings(key)
(settings || {})[key]
end
end
end
34 changes: 34 additions & 0 deletions app/models/payment_providers/stripe_provider.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module PaymentProviders
class StripeProvider < BaseProvider
jdenquin marked this conversation as resolved.
Show resolved Hide resolved
validates :secret_key, presence: true

validates :create_customers, inclusion: { in: [true, false] }
validates :send_zero_amount_invoice, inclusion: { in: [true, false] }

def secret_key=(secret_key)
push_to_secrets(key: 'secret_key', value: secret_key)
end

def secret_key
get_from_secrets('secret_key')
end

def create_customers=(value)
push_to_settings(key: 'create_customers', value: value)
end

def create_customers
get_from_settings('create_customers')
end

def send_zero_amount_invoice=(value)
push_to_settings(key: 'send_zero_amount_invoice', value: value)
end

def send_zero_amount_invoice
get_from_settings('send_zero_amount_invoice')
end
end
end
18 changes: 18 additions & 0 deletions app/services/payment_providers/destroy_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module PaymentProviders
class DestroyService < BaseService
def destroy(id:)
payment_provider = PaymentProviders::BaseProvider.find_by(
id: id,
organization_id: result.user.organization_ids,
)
return result.fail!('not_found') unless payment_provider

payment_provider.destroy!

result.payment_provider = payment_provider
result
end
end
end
21 changes: 21 additions & 0 deletions app/services/payment_providers/stripe_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module PaymentProviders
class StripeService < BaseService
def create_or_update(**args)
stripe_provider = PaymentProviders::StripeProvider.find_or_initialize_by(
organization_id: args[:organization_id],
)

stripe_provider.secret_key = args[:secret_key] if args.key?(:secret_key)
stripe_provider.create_customers = args[:create_customers]
stripe_provider.send_zero_amount_invoice = args[:send_zero_amount_invoice]
stripe_provider.save!

result.stripe_provider = stripe_provider
result
rescue ActiveRecord::RecordInvalid => e
result.fail_with_validations!(e.record)
end
end
end
5 changes: 5 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,10 @@ class Application < Rails::Application
config.eager_load_paths << Rails.root.join('lib/lago_http_client')
config.api_only = true
config.active_job.queue_adapter = :sidekiq

# Configuration for active record encryption
config.active_record.encryption.primary_key = ENV['ENCRYPTION_PRIMARY_KEY']
config.active_record.encryption.deterministic_key = ENV['ENCRYPTION_DETERMINISTIC_KEY']
config.active_record.encryption.key_derivation_salt = ENV['ENCRYPTION_KEY_DERIVATION_SALT']
end
end
2 changes: 1 addition & 1 deletion config/credentials/development.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
wZkVx9kjfpmL8VeMBQ0tO8F42+oeCKEju5b9CVCeLOlYqsvup9JEs+HmIB1agoRet4lYCwe6yP9ILQ8k8g5wIp/hcuSPPLpCHVL3lVwTGyqDJN4WQCboci86VzqBMvkqcHCPmM7iU+oCFs1K/Wc4ZhU6U/HBR2F4N4aahGMqwNGuqKB99+MbO+Ay9OfI1NoDFmZiv0LT1A==--Q542iSjIFofUQ3xJ--sEvFapMG1Vb0L6xJMtu/rw==
KpWl+e0Jm266w0d309kzRAzDdn9b2W/YZtMyl9xmqT3tyVZajCmFUerj+kNOrBHDUrRtGJsxjLukTdJhVxOrGJbM/qQlKERkJwL9HjnqdcuNGAfrSJYox6WfoJk7JEol2HFJf7HsAI7RQv8mN1a1Q7dzECxrCSKrNijdC2c+Y0aUp4o3vq7kpDfYEhs0kTKwkOaMI5ulaQ==--fkv0N16n9jub/CEr--mFVk/TUIbBFEIMWKQTkUBw==
2 changes: 1 addition & 1 deletion config/credentials/test.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ktHPQO9AsHQek2bmKTUT1SU7dBog3OrMt/Qe1hlg8RXAqbxbefqAPZB67dtOHdbyw4S2xyGuFWBfm43NFP9S4gNm1Hvj04ubzOldzh7cTBEduZfLa8dSBQHzPmn4Cv8fpgUkS3HrgBMbW7IeOEK2g6XX3g6DOGnizMJLIwmvZhLHeXUUiXg3/yPz21Xj5BBZbTyHTfpgKw==--yp4nBFqhVoILYzpv--nT5/ZLJcdax+p+cMRc3Iaw==
YH0Pw/ANrTLOONBBTcEKbYHxR+whmf6wob8v3dIJ1RhJUS9UXdpGdVb+e/iV+uGJZaKqslNxOKbeLJ1M6xRJV5dcsTp3JWBGVrIeTOkNZvs5lyYjaDWVKIrzeAu0aHNVjdNIUyeJafUS2lIepDl1JOOdOopXzBC9r2Ao3AcPPT8j+rtMvC/45/MNT0R0tdPLpG2cFRTeAQ==--YGfidkTBZid9INT/--vl7dImdkuQG+mwM+k3RoIQ==
2 changes: 1 addition & 1 deletion config/initializers/filter_parameter_logging.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
# sensitive information. See the ActiveSupport::ParameterFilter documentation for supported
# notations and behaviors.
Rails.application.config.filter_parameters += [
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :secret_key
]
14 changes: 14 additions & 0 deletions db/migrate/20220609080806_create_payment_providers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class CreatePaymentProviders < ActiveRecord::Migration[7.0]
def change
create_table :payment_providers, id: :uuid do |t|
t.references :organization, type: :uuid, foreign_key: true, null: false, index: true
t.string :type, null: false
t.string :secrets
t.jsonb :settings, null: false, default: {}

t.timestamps
end
end
end
11 changes: 11 additions & 0 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading