payola 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ebbbab65c10f71b854e1813999d82f5a4dbe5b6f
4
+ data.tar.gz: c03c2cebb1f16febc73c9970641c81f67f85120b
5
+ SHA512:
6
+ metadata.gz: d68fa419cf1ed7453364c3820395029a94af4f5de960c7d7b14fd5ecacb0d006ffcd25803cbbada94b0f8114c9a536be7452c3a373c75991b9efcdf057b10114
7
+ data.tar.gz: 1fd22f2255d8b49c864df0924724089cf9ea610dbd280e7f1ddfa5b3c9a3f8fab7102edca94ea2917e2c1ef4bfc5ac58457910f10c3f41362abcab3dd1482ae9
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Payola
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'payola'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install payola
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( http://github.com/<my-github-username>/payola/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,13 @@
1
+ module Payola
2
+ class Configuration
3
+ attr_accessor :payment_gateway_api_key
4
+ end
5
+
6
+ def self.config
7
+ @@configuration ||= Configuration.new
8
+ end
9
+
10
+ def self.configure
11
+ yield config
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ class String
2
+ def constantize
3
+ names = self.split('::')
4
+ names.shift if names.empty? || names.first.empty?
5
+
6
+ constant = Object
7
+ names.each do |name|
8
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
9
+ end
10
+ constant
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ require 'human_error/error'
2
+
3
+ module Payola
4
+ class Error < RuntimeError
5
+ extend HumanError::Error
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ require 'payola/error'
2
+
3
+ module Payola
4
+ module Errors
5
+ class PaymentGatewayRequestError < Payola::Error
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ require 'payola/registry'
2
+ require 'payola/core_ext/string'
3
+ require 'payola/factories/stripe_payment_token'
4
+
5
+ module Payola
6
+ module Factories
7
+ class PaymentToken
8
+ def self.create(card: nil)
9
+ payment_gateway_adapter_class_name = Payola.registry[:payment_gateway_adapter].name
10
+ payment_gateway_adapter_type = payment_gateway_adapter_class_name[/::(\w+)PaymentGateway\z/, 1]
11
+ factory_class_name = "Payola::Factories::#{payment_gateway_adapter_type}PaymentToken"
12
+ factory_class = factory_class_name.constantize
13
+
14
+ factory_class.create(card: card)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ module Payola
2
+ module Factories
3
+ class StripePaymentToken
4
+ def self.create(card: nil)
5
+ Stripe.api_key = Payola.config.payment_gateway_api_key
6
+
7
+ card ||= { number: '4242424242424242',
8
+ exp_month: 3,
9
+ exp_year: Time.now.strftime("%Y").to_i + 5,
10
+ cvc: '314' }
11
+
12
+ Stripe::Token.create(card: card).id
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module Payola
2
+ class PaymentGateway
3
+ def self.sync(adapter: Payola.registry[:payment_gateway_adapter],
4
+ subscription:)
5
+
6
+ gateway_subscription = adapter.apply_subscription \
7
+ subscription.payment_gateway_parameters
8
+
9
+ subscription = subscription.dup
10
+
11
+ subscription.source_subscription_id = gateway_subscription[:subscription_id]
12
+ subscription.last_four_of_credit_card = gateway_subscription[:last_four_of_credit_card]
13
+ subscription.status = gateway_subscription[:status]
14
+
15
+ subscription
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,99 @@
1
+ require 'stripe'
2
+ require 'payola/errors/payment_gateway_request_error'
3
+
4
+ module Payola
5
+ module PaymentGateways
6
+ class StripePaymentGateway
7
+ attr_accessor :customer_id,
8
+ :subscription_id,
9
+ :plan_id,
10
+ :amount,
11
+ :interval,
12
+ :interval_count,
13
+ :plan_name,
14
+ :payment_token,
15
+ :api_key
16
+
17
+ def initialize(**args)
18
+ args.each do |name, value|
19
+ public_send("#{name}=", value)
20
+ end
21
+
22
+ Stripe.api_key = api_key || Payola.config.payment_gateway_api_key
23
+
24
+ combined_id = subscription_id || ''
25
+ self.customer_id = combined_id[/\A(cus_[A-Za-z0-9]{14})\:/, 1] || 'no_customer'
26
+ self.subscription_id = combined_id[/\:(sub_[A-Za-z0-9]{14})\z/, 1] || 'no_subscription'
27
+ end
28
+
29
+ def apply_subscription
30
+ update_subscription_plan
31
+
32
+ {
33
+ status: subscription.status,
34
+ plan: subscription.plan[:id],
35
+ subscription_id: "#{subscription.customer}:#{subscription.id}",
36
+ last_four_of_credit_card: customer_default_card.last4,
37
+ }
38
+ end
39
+
40
+ def self.apply_subscription(**args)
41
+ new(**args).apply_subscription
42
+ end
43
+
44
+ private
45
+
46
+ def update_subscription_plan
47
+ if subscription.plan != plan.id
48
+ subscription.plan = plan.id
49
+ subscription.save
50
+ end
51
+ rescue Stripe::InvalidRequestError => e
52
+ raise Payola::Errors::PaymentGatewayRequestError.wrap(e)
53
+ end
54
+
55
+ def customer_default_card
56
+ customer.cards.retrieve(customer.default_card)
57
+ end
58
+
59
+ def subscription
60
+ @subscription ||= customer.subscriptions.retrieve(subscription_id)
61
+ rescue Stripe::InvalidRequestError => e
62
+ raise Payola::Errors::PaymentGatewayRequestError.wrap(e) unless e.message.match 'does not have a subscription'
63
+
64
+ @subscription ||= customer.subscriptions.create(plan: plan.id, card: payment_token)
65
+ end
66
+
67
+ def customer
68
+ Stripe::Customer.retrieve customer_id
69
+ rescue Stripe::InvalidRequestError => e
70
+ raise Payola::Errors::PaymentGatewayRequestError.wrap(e) unless e.message.match 'No such customer'
71
+
72
+ begin
73
+ Stripe::Customer.create.tap do |customer|
74
+ self.customer_id = customer.id
75
+ end
76
+ rescue StandardError => e
77
+ raise Payola::Errors::PaymentGatewayRequestError.wrap(e)
78
+ end
79
+ end
80
+
81
+ def plan
82
+ Stripe::Plan.retrieve plan_id
83
+ rescue Stripe::InvalidRequestError => e
84
+ raise Payola::Errors::PaymentGatewayRequestError.wrap(e) unless e.message.match 'No such plan'
85
+
86
+ begin
87
+ Stripe::Plan.create id: plan_id,
88
+ amount: amount.cents,
89
+ currency: amount.currency,
90
+ interval: interval,
91
+ interval_count: interval_count,
92
+ name: plan_name
93
+ rescue StandardError => e
94
+ raise Payola::Errors::PaymentGatewayRequestError.wrap(e)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,21 @@
1
+ module Payola
2
+ class Registry
3
+ @@registry = {
4
+ payment_gateway_adapter: ::Payola::PaymentGateways::StripePaymentGateway
5
+ }
6
+
7
+ def self.method_missing(name, *args)
8
+ return @@registry.public_send(name, *args) if @@registry.respond_to? name
9
+
10
+ super
11
+ end
12
+
13
+ def self.respond_to_missing?(name)
14
+ @@registry.respond_to? name || super
15
+ end
16
+ end
17
+
18
+ def self.registry
19
+ Registry
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module Payola
2
+ VERSION = "0.0.1"
3
+ end
data/lib/payola.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'payola/version'
2
+ require 'payola/payment_gateway'
3
+ require 'payola/registry'
4
+ require 'payola/configuration'
5
+
6
+ module Payola
7
+ def self.sync(**args)
8
+ PaymentGateway.sync(**args)
9
+ end
10
+ end
@@ -0,0 +1,134 @@
1
+ require 'rspectacular'
2
+ require 'money'
3
+ require 'payola/payment_gateways/stripe_payment_gateway'
4
+ require 'payola/factories/stripe_payment_token'
5
+ require 'payola/configuration'
6
+
7
+ module Payola
8
+ module PaymentGateways
9
+ describe StripePaymentGateway, :stripe, :vcr do
10
+ before(:each) do
11
+ Payola.config.payment_gateway_api_key = 'sk_test_ouP1BhLytHhz96J1VuNPmiJZ'
12
+ Stripe.api_key = 'sk_test_ouP1BhLytHhz96J1VuNPmiJZ'
13
+ end
14
+
15
+ let(:credit_card_token) do
16
+ Payola::Factories::StripePaymentToken.create( card: {
17
+ number: '4242424242424242',
18
+ exp_month: 3,
19
+ exp_year: 2015,
20
+ cvc: '314' })
21
+ end
22
+
23
+ it 'can create a subscription using the API key from the configuration' do
24
+ Stripe.api_key = nil
25
+
26
+ subscription = StripePaymentGateway.apply_subscription \
27
+ subscription_id: nil,
28
+ plan_id: 'my test plan id',
29
+ amount: Money.new(100, 'USD'),
30
+ interval: 'month',
31
+ interval_count: 1,
32
+ plan_name: 'my test plan name',
33
+ payment_token: credit_card_token
34
+
35
+ expect(subscription[:plan]).to eql 'my test plan id'
36
+ end
37
+
38
+ it 'can create a subscription when neither a customer nor plan exist' do
39
+ subscription = StripePaymentGateway.apply_subscription \
40
+ subscription_id: nil,
41
+ plan_id: 'my test plan id',
42
+ amount: Money.new(100, 'USD'),
43
+ interval: 'month',
44
+ interval_count: 1,
45
+ plan_name: 'my test plan name',
46
+ payment_token: credit_card_token
47
+
48
+ expect(subscription[:plan]).to eql 'my test plan id'
49
+ expect(subscription[:subscription_id]).to match /\Acus_[A-Za-z0-9]{14}\:sub_[A-Za-z0-9]{14}\z/
50
+ expect(subscription[:status]).to eql 'active'
51
+ expect(subscription[:last_four_of_credit_card]).to eql '4242'
52
+ end
53
+
54
+ it 'can create a subscription when a customer exists but not a plan' do
55
+ customer = Stripe::Customer.create
56
+ subscription = StripePaymentGateway.apply_subscription \
57
+ subscription_id: "#{customer.id}:",
58
+ plan_id: 'my test plan id',
59
+ amount: Money.new(100, 'USD'),
60
+ interval: 'month',
61
+ interval_count: 1,
62
+ plan_name: 'my test plan name',
63
+ payment_token: credit_card_token
64
+
65
+ expect(subscription[:subscription_id]).to match /#{customer.id}\:sub_[A-Za-z0-9]{14}/
66
+ end
67
+
68
+ it 'can create a subscription when a plan already exists but not a customer' do
69
+ Stripe::Plan.create id: 'my other existing test plan id',
70
+ amount: 200,
71
+ currency: 'EUR',
72
+ interval: 'week',
73
+ name: 'what you talkin bout'
74
+
75
+ allow(Stripe::Plan).to receive(:create)
76
+
77
+ subscription = StripePaymentGateway.apply_subscription \
78
+ subscription_id: nil,
79
+ plan_id: 'my other existing test plan id',
80
+ amount: Money.new(100, 'USD'),
81
+ interval: 'month',
82
+ interval_count: 1,
83
+ plan_name: 'my test plan name',
84
+ payment_token: credit_card_token
85
+
86
+ expect(Stripe::Plan).not_to have_received(:create)
87
+ expect(subscription[:plan]).to eql 'my other existing test plan id'
88
+ expect(subscription[:subscription_id]).to match /\Acus_[A-Za-z0-9]{14}\:sub_[A-Za-z0-9]{14}\z/
89
+ expect(subscription[:status]).to eql 'active'
90
+ expect(subscription[:last_four_of_credit_card]).to eql '4242'
91
+ end
92
+
93
+ it 'can update a subscription with a new plan' do
94
+ customer = Stripe::Customer.create
95
+ plan = Stripe::Plan.create id: 'my existing test plan id',
96
+ amount: 200,
97
+ currency: 'USD',
98
+ interval: 'week',
99
+ name: 'what you talkin bout'
100
+ existing_subscription = customer.subscriptions.create(
101
+ plan: plan.id,
102
+ card: credit_card_token)
103
+
104
+ updated_subscription = StripePaymentGateway.apply_subscription \
105
+ subscription_id: "#{customer.id}:#{existing_subscription.id}",
106
+ plan_id: 'my test plan id',
107
+ amount: Money.new(100, 'USD'),
108
+ interval: 'month',
109
+ interval_count: 1,
110
+ plan_name: 'my test plan name',
111
+ payment_token: nil
112
+
113
+ expect(updated_subscription[:plan]).to eql 'my test plan id'
114
+ expect(updated_subscription[:last_four_of_credit_card]).to eql '4242'
115
+ end
116
+
117
+ it 'wraps all exceptions in Payola errors' do
118
+ allow(Stripe::Customer).to receive(:create).
119
+ and_raise(RuntimeError.new "error creating customer")
120
+
121
+ expect { StripePaymentGateway.apply_subscription \
122
+ subscription_id: nil,
123
+ plan_id: 'my existing test plan id',
124
+ amount: Money.new(100, 'USD'),
125
+ interval: 'month',
126
+ interval_count: 1,
127
+ plan_name: 'my test plan name',
128
+ payment_token: credit_card_token }.to \
129
+ raise_error(Payola::Errors::PaymentGatewayRequestError).
130
+ with_message("RuntimeError: error creating customer")
131
+ end
132
+ end
133
+ end
134
+ end
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: payola
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - jfelchner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: stripe
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: human_error
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.0.0beta
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.0.0beta
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspectacular
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.22.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.22.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: money
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '6.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '6.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: vcr
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.8'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.8'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "<"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.16'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "<"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.16'
111
+ description: ''
112
+ email: accounts+git@thekompanee.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files:
116
+ - README.md
117
+ files:
118
+ - README.md
119
+ - Rakefile
120
+ - lib/payola.rb
121
+ - lib/payola/configuration.rb
122
+ - lib/payola/core_ext/string.rb
123
+ - lib/payola/error.rb
124
+ - lib/payola/errors/payment_gateway_request_error.rb
125
+ - lib/payola/factories/payment_token.rb
126
+ - lib/payola/factories/stripe_payment_token.rb
127
+ - lib/payola/payment_gateway.rb
128
+ - lib/payola/payment_gateways/stripe_payment_gateway.rb
129
+ - lib/payola/registry.rb
130
+ - lib/payola/version.rb
131
+ - spec/lib/payola/payment_gateways/stripe_payment_gateway_spec.rb
132
+ homepage: https://github.com/thekompanee/payola
133
+ licenses: []
134
+ metadata: {}
135
+ post_install_message:
136
+ rdoc_options:
137
+ - "--charset = UTF-8"
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project: payola
152
+ rubygems_version: 2.2.2
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Abstraction layer on top of Stripe/Braintree, etc so we can get Payola'ed
156
+ test_files:
157
+ - spec/lib/payola/payment_gateways/stripe_payment_gateway_spec.rb
158
+ has_rdoc: