moneytree-rails 0.1.3 β†’ 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b651472c44c921358698e5469bd1fee526bcf70987e63ca516b2bf04fa11aa8
4
- data.tar.gz: 2f2838ef7ee618326b1cf2b1e4fb1ebded13a051450f673193236da3a630a955
3
+ metadata.gz: 7ece9ae7dbc6908f3ec1fb926a9aa66aef986b24c20043b8718ec7731b1f8a8e
4
+ data.tar.gz: c76befe52a2f475589a76c77a7c7ef70a46398b71b877338e9b590e7a91f989a
5
5
  SHA512:
6
- metadata.gz: 78f099d2dbb4f5c7bafc75376befdfdf1b1d55edfc356a02a3a18116288eeddb0515cd8bf3d277cfb43ac458e9497a160c6e7286df2c9f715dc8dd62bec1f169
7
- data.tar.gz: a3324a522e1c3350a2740684a48917bd3d637850281d49198992269719151372ae9c050a8075c02b9fccc24703b22a3cde687af0e3931915eb26ddf14e44c9ac
6
+ metadata.gz: 1dec186ed75bbe234833425edf5a0cdc69eb78472292669b32c77ed06076729a39f7f78b92df22919686c1ddb6cea46924d062a5c6eec5177d3bcd161f736817
7
+ data.tar.gz: 43f11e22b038e0bdd8027a8fbf621957fb9ffb339e88502addb671ae976b0d8c8a416b19b5d3f5d47644d342e2cde5dd2f151d2417e9e769e8836c2f9d309a58
data/README.md CHANGED
@@ -1,9 +1,5 @@
1
1
  # 🚧 WORK IN PROGRESS 🚧
2
2
 
3
- Currently only supports:
4
-
5
- - Oauth flow for stripe
6
-
7
3
  # Moneytree πŸ’΅ 🌴
8
4
 
9
5
  [![Actions Status](https://github.com/kieranklaassen/moneytree/workflows/build/badge.svg)](https://github.com/kieranklaassen/moneytree/actions)
@@ -16,16 +12,16 @@ functionality with almost no work on your end:
16
12
 
17
13
  - πŸ’΅πŸ’ΆπŸ’·πŸ’΄ Multi-currency
18
14
  - πŸ”‘ OAuth to link your PSP account
19
- - πŸ‘©β€πŸ’»PSP account creation, (with commission)
15
+ - πŸ‘©β€πŸ’» PSP account creation, (with commission)
20
16
  - βš™οΈ Webhooks
21
- - πŸ’³ PCI compliance with Javascript libraries
17
+ - πŸ’³ ~~PCI compliance with Javascript libraries~~ comming soon
22
18
  - 🧲 Platform fees a.k.a. Market Places
23
19
 
24
20
  Currently we support the following PSP's:
25
21
 
26
- - ~~Square~~
22
+ - ~~Square~~ comming soon
27
23
  - Stripe
28
- - ~~Braintree~~
24
+ - ~~Braintree~~ comming soon
29
25
 
30
26
  But if you want to add more PSP's, we make it easy to do so. Read our
31
27
  [Contributing](https://github.com/kieranklaassen/moneytree#contributing) section to learn more.
@@ -53,6 +49,8 @@ $ rails g db:migrate
53
49
 
54
50
  ## Configuration
55
51
 
52
+ ### Initializer
53
+
56
54
  Do you need to make some changes to how Moneytree is used? You can create an initializer
57
55
  `config/initializers/moneytree.rb`
58
56
 
@@ -64,18 +62,23 @@ Moneytree.setup do |config|
64
62
  client_id: ENV['STRIPE_CLIENT_ID']
65
63
  }
66
64
  config.oauth_redirect = '/welcome_back'
65
+ config.refund_application_fee = true # false by default
67
66
  end
68
67
  ```
69
68
 
70
- Add to your routes and authenticate if needed:
69
+ ### Routes
70
+
71
+ Add to your routes and authenticate if needed to make sure only admins can integrate with OAuth.
71
72
 
72
73
  ```ruby
73
- authenticate :user, ->(u) { u.owner? } do
74
+ authenticate :user, ->(u) { u.admin? } do
74
75
  mount Moneytree::Engine => '/moneytree'
75
76
  end
76
77
  ```
77
78
 
78
- Include account concern into your model and make sure the following attributes work:
79
+ ### Models
80
+
81
+ Include account concern into your models and make sure the following attributes work:
79
82
 
80
83
  ```ruby
81
84
  class Merchant < ApplicationRecord
@@ -92,6 +95,20 @@ class Merchant < ApplicationRecord
92
95
  def website
93
96
  'https://www.boomtown.com'
94
97
  end
98
+
99
+ # Optional, will be called by Moneytree after authenticating with the PSP
100
+ def moneytree_oauth_callback
101
+ puts "Hurray, I just got associated with a Moneytree gateway!"
102
+ end
103
+ end
104
+ ```
105
+
106
+ And add `Moneytree::Order` concern to the model that will be the parent for all the transactions. In most cases this
107
+ will be an order. This model will keep a balance and you can add multiple payments and refunds to it.
108
+
109
+ ```ruby
110
+ class Order < ApplicationRecord
111
+ include Moneytree::Order
95
112
  end
96
113
  ```
97
114
 
@@ -126,28 +143,9 @@ intended to be a safe, welcoming space for collaboration, and contributors are e
126
143
 
127
144
  ## License
128
145
 
129
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
146
+ The gem is available as open-source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
130
147
 
131
148
  ## Code of Conduct
132
149
 
133
150
  Everyone interacting in the Moneytree project's codebases, issue trackers, chat rooms and mailing lists is expected to
134
151
  follow the [code of conduct](https://github.com/kieranklaassen/moneytree/blob/master/CODE_OF_CONDUCT.md).
135
-
136
- ```
137
- rails g model payment_gateway psp_credentials:text moneytree_psp:integer account:references{polymorphic}
138
-
139
- owner t.string :name t.text :psp_credentials t.integer :moneytree_psp
140
-
141
- rails g model orders t.string :description t.string :remote_identifier t.references :customer t.references :account
142
-
143
- rails g model transactions t.decimal :amount t.decimal :app_fee_amount t.integer :status t.integer :type t.string
144
- :remote_identifier t.string :currency_code t.string :psp_error t.integer :moneytree_psp t.references :account
145
- t.references :order t.references :card
146
-
147
- rails g model customers t.string :first_name t.string :last_name t.string :email t.string :remote_identifier t.integer
148
- :moneytree_psp t.references :account
149
-
150
- rails g model cards t.string :card_brand t.string :last_4 t.integer :expiration_month t.integer :expiration_year
151
- t.string :cardholder_name t.string :fingerprint t.integer :moneytree_psp t.references :customer t.references :account
152
-
153
- ```
@@ -6,7 +6,8 @@ module Moneytree
6
6
  end
7
7
 
8
8
  def callback
9
- payment_gateway = PaymentGateway.create!(psp: 'stripe', account: current_account)
9
+ # TODO: Remove this module prefix once we figure out how to properly autoload this.
10
+ payment_gateway = Moneytree::PaymentGateway.create!(psp: 'stripe', account: current_account)
10
11
  payment_gateway.oauth_callback(payment_gateway_params)
11
12
  redirect_to Moneytree.oauth_redirect, notice: 'Connected to Stripe'
12
13
  end
@@ -27,7 +28,7 @@ module Moneytree
27
28
  response_type: :code,
28
29
  client_id: Moneytree.stripe_credentials[:client_id],
29
30
  scope: PaymentProvider::Stripe::PERMISSION,
30
- redirect_uri: oauth_stripe_callback_url, # FIXME: use rails url helper and add host
31
+ redirect_uri: oauth_stripe_callback_url,
31
32
  'stripe_user[email]': current_account.email,
32
33
  'stripe_user[url]': current_account.website,
33
34
  'stripe_user[currency]': current_account.currency_code
@@ -1,8 +1,66 @@
1
1
  module Moneytree
2
2
  module Webhooks
3
3
  class StripeController < ApplicationController
4
+ skip_before_action :verify_authenticity_token
5
+
4
6
  def create
5
- # Do some callback magic here
7
+ case webhook_params.type
8
+ when 'charge.succeeded'
9
+ process_charge!
10
+ when 'charge.refunded'
11
+ process_refund!
12
+ else
13
+ puts "Unhandled event type: #{webhook_params.type}"
14
+ end
15
+
16
+ head :ok
17
+ end
18
+
19
+ private
20
+
21
+ def webhook_params
22
+ @webhook_params ||=
23
+ ::Stripe::Event.construct_from(
24
+ JSON.parse(request.body.read, symbolize_names: true)
25
+ )
26
+ end
27
+
28
+ def process_charge!
29
+ return if transaction.completed?
30
+
31
+ transaction.process_response(
32
+ TransactionResponse.new(:success, '', { charge_id: stripe_object.id })
33
+ )
34
+
35
+ if Moneytree.order_status_trigger_method
36
+ transaction.order.send(Moneytree.order_status_trigger_method, transaction)
37
+ end
38
+ end
39
+
40
+ def process_refund!
41
+ stripe_object.refunds.data.each do |stripe_refund_object|
42
+ # TODO: Create refund transaction in db for PSP-initiated refunds
43
+ next if stripe_refund_object.metadata[:moneytree_transaction_id].blank?
44
+
45
+ refund = transaction.refunds.find(stripe_refund_object.metadata[:moneytree_transaction_id])
46
+
47
+ next if refund.completed?
48
+
49
+ refund.process_response(
50
+ TransactionResponse.new(:success, '')
51
+ )
52
+ if Moneytree.order_status_trigger_method
53
+ transaction.order.send(Moneytree.order_status_trigger_method, transaction)
54
+ end
55
+ end
56
+ end
57
+
58
+ def transaction
59
+ @transaction ||= Transaction.find(stripe_object.metadata[:moneytree_transaction_id])
60
+ end
61
+
62
+ def stripe_object
63
+ @stripe_object ||= webhook_params.data.object
6
64
  end
7
65
  end
8
66
  end
@@ -0,0 +1,23 @@
1
+ module Moneytree
2
+ class Payment < Transaction
3
+ has_many :refunds, class_name: 'Refund'
4
+
5
+ validates_absence_of :payment_id
6
+
7
+ validates_numericality_of :amount, greater_than: 0
8
+ validates_numericality_of :app_fee_amount, greater_than_or_equal_to: 0
9
+
10
+ private
11
+
12
+ def execute_transaction(metadata: {})
13
+ process_response(
14
+ payment_gateway.charge(
15
+ amount,
16
+ details,
17
+ app_fee_amount: app_fee_amount,
18
+ metadata: metadata.merge(moneytree_transaction_id: id)
19
+ )
20
+ )
21
+ end
22
+ end
23
+ end
@@ -1,22 +1,18 @@
1
1
  module Moneytree
2
2
  class PaymentGateway < ApplicationRecord
3
- include Moneytree::Account
4
-
5
3
  belongs_to :account, polymorphic: true
6
4
 
7
5
  enum psp: Moneytree::PSPS
8
6
  serialize :psp_credentials
9
7
  # encrypts :psp_credentials
10
8
  # FIXME: enable https://github.com/ankane/lockbox
11
- delegate :oauth_link, :scope_correct?, to: :payment_provider
9
+ delegate :oauth_link, :scope_correct?, :charge, :refund, to: :payment_provider
12
10
 
13
- # has_many :orders
14
- # has_many :transactions
15
- # has_many :customers
16
- # has_many :cards
11
+ has_many :transactions
17
12
 
18
13
  def oauth_callback(params)
19
14
  update! psp_credentials: payment_provider.get_access_token(params)
15
+ account.send(:moneytree_oauth_callback) if account.respond_to?(:moneytree_oauth_callback, true)
20
16
  end
21
17
 
22
18
  def psp_connected?
@@ -31,16 +27,13 @@ module Moneytree
31
27
  psp_credentials[:scope] == payment_provider.scope
32
28
  end
33
29
 
34
- def charge; end
35
-
36
- def refund; end
37
-
38
30
  private
39
31
 
40
32
  def payment_provider
41
33
  @payment_provider ||=
42
34
  case psp
43
35
  when 'stripe'
36
+ # TODO: see if we only need to pass credentials
44
37
  Moneytree::PaymentProvider::Stripe.new(self)
45
38
  # when 'square'
46
39
  # Moneytree::PaymentProvider::Square.new(self)
@@ -0,0 +1,46 @@
1
+ module Moneytree
2
+ class Refund < Transaction
3
+ belongs_to :payment, class_name: 'Moneytree::Payment'
4
+
5
+ before_validation :set_order, :set_payment_gateway
6
+
7
+ validates_presence_of :payment
8
+
9
+ validates_numericality_of :amount, less_than: 0
10
+ validates_numericality_of :app_fee_amount, less_than_or_equal_to: 0
11
+
12
+ validate :order_matches_payment, :gateway_matches_payment
13
+
14
+ private
15
+
16
+ def execute_transaction(metadata: {})
17
+ process_response(
18
+ payment_gateway.refund(
19
+ amount,
20
+ payment.details,
21
+ metadata: metadata.merge(moneytree_transaction_id: id)
22
+ )
23
+ )
24
+ end
25
+
26
+ # validates_presence_of :payment
27
+ def set_order
28
+ self.order ||= payment&.order
29
+ end
30
+
31
+ # validates_presence_of :payment
32
+ def set_payment_gateway
33
+ self.payment_gateway ||= payment&.payment_gateway
34
+ end
35
+
36
+ # validates
37
+ def order_matches_payment
38
+ errors.add(:order_id, :mismatch) if order_id != payment&.order_id
39
+ end
40
+
41
+ # validates
42
+ def gateway_matches_payment
43
+ errors.add(:payment_gateway_id, :mismatch) if payment_gateway_id != payment&.payment_gateway_id
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,30 @@
1
+ module Moneytree
2
+ class Transaction < ApplicationRecord
3
+ belongs_to :payment_gateway
4
+ belongs_to :order, polymorphic: true
5
+
6
+ validates_presence_of :psp_error, if: :failed?
7
+
8
+ enum status: %i[initialized pending completed failed]
9
+
10
+ serialize :details
11
+
12
+ after_create_commit :execute_transaction
13
+
14
+ def process_response(response)
15
+ if response.success?
16
+ update!(
17
+ status: :completed,
18
+ psp_error: response.message,
19
+ details: (details || {}).merge(response.body)
20
+ )
21
+ else
22
+ # FIXME: pending state
23
+ update!(
24
+ status: :failed,
25
+ psp_error: response.message
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,4 +1,8 @@
1
1
  Moneytree::Engine.routes.draw do
2
2
  get 'oauth/stripe/new', to: 'oauth/stripe#new'
3
3
  get 'oauth/stripe/callback', to: 'oauth/stripe#callback'
4
+
5
+ namespace :webhooks do
6
+ resources :stripe, only: :create
7
+ end
4
8
  end
@@ -0,0 +1,18 @@
1
+ class CreateMoneytreeTransactions < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :moneytree_transactions do |t|
4
+ t.decimal :amount, null: false, default: 0
5
+ t.decimal :app_fee_amount, null: false, default: 0
6
+ t.integer :status, null: false, default: 0
7
+ t.string :type, null: false, default: 'Moneytree::Payment'
8
+ t.references :order, polymorphic: true, null: false
9
+ t.references :payment_gateway, null: false
10
+ t.references :payment, foreign_key: { to_table: :moneytree_transactions }
11
+ t.text :psp_error
12
+ t.text :details
13
+ t.text :refund_reason
14
+
15
+ t.timestamps
16
+ end
17
+ end
18
+ end
@@ -5,7 +5,9 @@
5
5
  # modules
6
6
  # FIXME: autoload instead? https :/ / github.com / excid3 / noticed / blob / master / lib / noticed.rb
7
7
  require 'moneytree/version'
8
+ require 'moneytree/transaction_response'
8
9
  require 'moneytree/account'
10
+ require 'moneytree/order'
9
11
  require 'moneytree/payment_provider/base'
10
12
  require 'moneytree/payment_provider/stripe'
11
13
  require 'moneytree/engine'
@@ -17,10 +19,13 @@ module Moneytree
17
19
  mattr_accessor :stripe_credentials
18
20
  mattr_accessor :current_account
19
21
  mattr_accessor :oauth_redirect
22
+ mattr_accessor :refund_application_fee
23
+ mattr_accessor :order_status_trigger_method
20
24
 
21
25
  @@enabled_psps = PSPS
22
26
  @@current_account = :current_account
23
27
  @@oauth_redirect = '/'
28
+ @@refund_application_fee = false
24
29
 
25
30
  def self.setup
26
31
  yield self
@@ -3,9 +3,10 @@ require 'active_support/concern'
3
3
  module Moneytree
4
4
  module Account
5
5
  extend ActiveSupport::Concern
6
+ # FIXME: see if we can remove config.current_account = :current_merchant and set it from here
6
7
 
7
8
  included do
8
- has_one :moneytree_payment_gateway, class_name: 'Moneytree::PaymentGateway', foreign_key: 'account_id'
9
+ has_one :moneytree_payment_gateway, class_name: 'Moneytree::PaymentGateway', foreign_key: 'account_id', inverse_of: :account, as: :account
9
10
  end
10
11
  end
11
12
  end
@@ -0,0 +1,17 @@
1
+ require 'active_support/concern'
2
+
3
+ module Moneytree
4
+ module Order
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :moneytree_transactions, class_name: 'Moneytree::Transaction', foreign_key: 'order_id', inverse_of: :order, as: :order
9
+ end
10
+
11
+ def new_payment(*args)
12
+ payment = Payment.new(*args)
13
+ moneytree_transactions << payment
14
+ payment
15
+ end
16
+ end
17
+ end
@@ -14,16 +14,62 @@ module Moneytree
14
14
 
15
15
  def get_access_token(params)
16
16
  # FIXME: add error handling
17
- ::Stripe::OAuth.token({
18
- grant_type: 'authorization_code',
19
- code: params[:code]
20
- }).to_hash
17
+ ::Stripe::OAuth.token(
18
+ {
19
+ grant_type: 'authorization_code',
20
+ code: params[:code]
21
+ }
22
+ ).to_hash
21
23
  end
22
24
 
23
25
  def scope
24
26
  PERMISSION.to_s
25
27
  end
26
28
 
29
+ def charge(amount, details, app_fee_amount: 0, description: "Charge for #{account.name}", metadata:)
30
+ # `source` is obtained with Stripe.js; see https://stripe.com/docs/payments/accept-a-payment-charges#web-create-token
31
+ response = ::Stripe::Charge.create(
32
+ {
33
+ amount: (amount * 100).to_i,
34
+ currency: account.currency_code,
35
+ source: details[:card_token],
36
+ description: description,
37
+ metadata: metadata,
38
+ application_fee_amount: (app_fee_amount * 100).to_i
39
+ },
40
+ stripe_account: payment_gateway.psp_credentials[:stripe_user_id]
41
+ )
42
+ # succeeded, pending, or failed
43
+ TransactionResponse.new(
44
+ { succeeded: :success, pending: :pending, failed: :failed }[response[:status].to_sym],
45
+ response[:failure_message],
46
+ { charge_id: response[:id] }
47
+ )
48
+ rescue ::Stripe::StripeError => e
49
+ TransactionResponse.new(:failed, e.message)
50
+ end
51
+
52
+ def refund(amount, details, metadata:)
53
+ response = ::Stripe::Refund.create(
54
+ {
55
+ charge: details[:charge_id],
56
+ amount: (-amount * 100).to_i,
57
+ metadata: metadata,
58
+ refund_application_fee: Moneytree.refund_application_fee
59
+ },
60
+ stripe_account: payment_gateway.psp_credentials[:stripe_user_id]
61
+ )
62
+
63
+ # succeeded, pending, or failed
64
+ TransactionResponse.new(
65
+ { succeeded: :success, pending: :pending, failed: :failed }[response[:status].to_sym],
66
+ response[:failure_message],
67
+ { refund_id: response[:id] }
68
+ )
69
+ rescue ::Stripe::StripeError => e
70
+ TransactionResponse.new(:failed, e.message)
71
+ end
72
+
27
73
  private
28
74
 
29
75
  def credentitals
@@ -0,0 +1,15 @@
1
+ module Moneytree
2
+ class TransactionResponse
3
+ attr_reader :message, :status, :body
4
+
5
+ def initialize(status, message, body = {})
6
+ @status = status
7
+ @message = message
8
+ @body = body
9
+ end
10
+
11
+ def success?
12
+ @status == :success
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module Moneytree
2
- VERSION = '0.1.3'.freeze
2
+ VERSION = '0.1.8'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moneytree-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kieran Klaassen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-08 00:00:00.000000000 Z
11
+ date: 2020-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -56,22 +56,26 @@ files:
56
56
  - app/controllers/moneytree/oauth/stripe_controller.rb
57
57
  - app/controllers/moneytree/webhooks/square_controller.rb
58
58
  - app/controllers/moneytree/webhooks/stripe_controller.rb
59
- - app/helpers/moneytree/application_helper.rb
60
- - app/helpers/moneytree/oauth/stripe_helper.rb
61
59
  - app/jobs/moneytree/application_job.rb
62
60
  - app/mailers/moneytree/application_mailer.rb
63
61
  - app/models/moneytree/application_record.rb
62
+ - app/models/moneytree/payment.rb
64
63
  - app/models/moneytree/payment_gateway.rb
64
+ - app/models/moneytree/refund.rb
65
+ - app/models/moneytree/transaction.rb
65
66
  - app/views/layouts/moneytree/application.html.erb
66
67
  - config/routes.rb
67
68
  - db/migrate/20200914151648_create_moneytree_payment_gateways.rb
69
+ - db/migrate/20201008161617_create_moneytree_transactions.rb
68
70
  - lib/moneytree-rails.rb
69
71
  - lib/moneytree.rb
70
72
  - lib/moneytree/account.rb
71
73
  - lib/moneytree/engine.rb
74
+ - lib/moneytree/order.rb
72
75
  - lib/moneytree/payment_provider/base.rb
73
76
  - lib/moneytree/payment_provider/square.rb
74
77
  - lib/moneytree/payment_provider/stripe.rb
78
+ - lib/moneytree/transaction_response.rb
75
79
  - lib/moneytree/version.rb
76
80
  - lib/tasks/moneytree_tasks.rake
77
81
  homepage: https://github.com/kieranklaassen/moneytree
@@ -93,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
97
  - !ruby/object:Gem::Version
94
98
  version: '0'
95
99
  requirements: []
96
- rubygems_version: 3.1.2
100
+ rubygems_version: 3.1.4
97
101
  signing_key:
98
102
  specification_version: 4
99
103
  summary: A payments engine for rails centered aorund transactional payments and orders.
@@ -1,4 +0,0 @@
1
- module Moneytree
2
- module ApplicationHelper
3
- end
4
- end
@@ -1,6 +0,0 @@
1
- module Moneytree
2
- module Oauth
3
- module StripeHelper
4
- end
5
- end
6
- end