spackle-ruby 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d27b263b4e2980ec835c98ddecf665742d566c746ac6e992b0b6c92a7c182bfa
4
- data.tar.gz: 566c2461defd89631a9c514b44284718e218065ca6bb6b6ff04a558fe750ea23
3
+ metadata.gz: e8cf2b94103517a295249a12015de47bba73211680e6414e171df2bc9ab18ceb
4
+ data.tar.gz: 694bb847e82273d30bb5f7e325bdb795e56a5d4555bcefe01783dc597d22c3d6
5
5
  SHA512:
6
- metadata.gz: a13b6955dddbe47c3c7385f657fb0d2a184c9decb18cd4ce3fdffdb9cf30cb00def4d354c1652f4e6b997467345d64d34f62d07076dc0d4505f102a61cd0e837
7
- data.tar.gz: bad22643c179c73f4b87ecab410c86f2e33264e58dc61ef677f7ca1e67ae5eb2437e1f5e8e17f0a95599ab8fc9cbb4ba3dddd6f337fce86bb1f422baffe44053
6
+ metadata.gz: e8e7b93be2dc27f5b823741bed3c29b05113811fb37ae6769e89cf323eb5f6b34c2f6aa1d402e15b064dae86e6864ac155cf6f3fb565fa3c873107020119384c
7
+ data.tar.gz: 0e4f7073e81f0bba5a572f9ff5e1ad9670cbf0f5e5ec9afc93d3de20c2c2d0cfe476208a232403aaa61046abed6f1e826c3b98fdd9e373bff61d0410067fcbb7
data/Gemfile CHANGED
@@ -4,8 +4,9 @@ source "https://rubygems.org"
4
4
 
5
5
  git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
6
6
 
7
- gem 'aws-sdk', '~> 3'
7
+ gem "aws-sdk", "~> 3"
8
8
  gem "rake", "~> 13.0"
9
- gem "test-unit", "~> 3.5"
10
-
11
9
  gem "nokogiri", "~> 1.13"
10
+ gem "minitest", "~> 5.18"
11
+
12
+ gem "stripe", "~> 8.3"
data/Gemfile.lock CHANGED
@@ -1356,23 +1356,23 @@ GEM
1356
1356
  aws-eventstream (~> 1, >= 1.0.2)
1357
1357
  jmespath (1.6.2)
1358
1358
  mini_portile2 (2.8.1)
1359
+ minitest (5.18.0)
1359
1360
  nokogiri (1.13.10)
1360
1361
  mini_portile2 (~> 2.8.0)
1361
1362
  racc (~> 1.4)
1362
- power_assert (2.0.3)
1363
1363
  racc (1.6.2)
1364
1364
  rake (13.0.6)
1365
- test-unit (3.5.7)
1366
- power_assert
1365
+ stripe (8.3.0)
1367
1366
 
1368
1367
  PLATFORMS
1369
1368
  ruby
1370
1369
 
1371
1370
  DEPENDENCIES
1372
1371
  aws-sdk (~> 3)
1372
+ minitest (~> 5.18)
1373
1373
  nokogiri (~> 1.13)
1374
1374
  rake (~> 13.0)
1375
- test-unit (~> 3.5)
1375
+ stripe (~> 8.3)
1376
1376
 
1377
1377
  BUNDLED WITH
1378
1378
  1.17.2
data/README.md CHANGED
@@ -63,10 +63,37 @@ customer.enabled("feature_key")
63
63
  customer.limit("feature_key")
64
64
  ```
65
65
 
66
+ ### Examine a customer's subscriptions
67
+
68
+ A customer's current subscriptions are available on the `subscriptions` property. These are valid `Stripe::Subscription` objects as defined in the [Stripe Ruby library](https://stripe.com/docs/api/subscriptions/object?lang=ruby).
69
+
70
+ ```ruby
71
+ customer.subscriptions
72
+ ```
73
+
74
+ ## Waiters
75
+
76
+ There is a brief delay between when an action takes place in Stripe and when it is reflected in Spackle. To account for this, Spackle provides a `Waiters` module that can be used to wait for a Stripe object to be updated and replicated.
77
+
78
+ 1. Wait for a customer to be created
79
+ ```ruby
80
+ Spackle::Waiters.wait_for_customer("cus_00000000")
81
+ ```
82
+ 2. Wait for a subscription to be created
83
+ ```ruby
84
+ Spackle::Waiters.wait_for_subscription("cus_000000000", "sub_00000000")
85
+ ```
86
+ 3. Wait for a subscription to be updated
87
+ ```ruby
88
+ Spackle::Waiters.wait_for_subscription("cus_000000000", "sub_00000000", status: "active")
89
+ ```
90
+
91
+ These will block until Spackle is updated with the latest information from Stripe or until a timeout occurs.
92
+
66
93
  ## Logging
67
94
  The Spackle Ruby library emits logs as it performs various internal tasks. You can control the verbosity of Spackle's logging a few different ways:
68
95
 
69
- 1. Set the environment variable SPACKLE_LOG to the value `debug`, `info`, or `error`
96
+ 1. Set the environment variable SPACKLE_LOG to the value `debug`, `info`, `warn` or `error`
70
97
 
71
98
  ```sh
72
99
  $ export SPACKLE_LOG=debug
@@ -77,3 +104,67 @@ The Spackle Ruby library emits logs as it performs various internal tasks. You c
77
104
  ```ruby
78
105
  Spackle.log_level = 'debug'
79
106
  ```
107
+
108
+ ## Usage in development environments
109
+ In production, Spackle requires a valid Stripe customer. However, that is not development environments where state needs to be controlled. As an alternative, you can use a file store to test your application with seed data.
110
+
111
+ ```json
112
+ /app/spackle.json
113
+
114
+ {
115
+ "cus_000000000": {
116
+ "features": [
117
+ {
118
+ "key": "flag_feature",
119
+ "value_flag": true
120
+ },
121
+ {
122
+ "key": "limit_feature",
123
+ "value_limit": 100
124
+ }
125
+ ],
126
+ "subscriptions": [
127
+ {
128
+ "id": "sub_000000000",
129
+ "status": "trialing",
130
+ "quantity": 1
131
+ }
132
+ ]
133
+ }
134
+ }
135
+ ```
136
+
137
+ Then configure the file store in your application:
138
+
139
+ ```ruby
140
+ Spackle.store = Spackle::FileStore.new('/app/spackle.json')
141
+ ```
142
+
143
+ ## Usage in test environments
144
+
145
+ In production, Spackle requires a valid Stripe customer. However, that is not ideal in testing or some development environments. As an alternative, you can use an in-memory store to test your application with seed data.
146
+
147
+ ```ruby
148
+ Spackle.store = Spackle::MemoryStore.new()
149
+ Spackle.store.set_customer_data("cus_000000000", {
150
+ "features": [
151
+ {
152
+ "key": "flag_feature",
153
+ "value_flag": True,
154
+ },
155
+ {
156
+ "key": "limit_feature",
157
+ "value_limit": 100,
158
+ },
159
+ ],
160
+ "subscriptions": [
161
+ {
162
+ "id": "sub_000000000",
163
+ "status": "trialing",
164
+ "quantity": 1,
165
+ }
166
+ ]
167
+ })
168
+ ```
169
+
170
+ **Note:** The in-memory store is not thread-safe and state will reset on each application restart.
data/Rakefile CHANGED
@@ -1,7 +1,14 @@
1
- require "rake/testtask"
1
+ require "minitest/test_task"
2
2
 
3
- task default: %i[test]
3
+ Minitest::TestTask.create # named test, sensible defaults
4
4
 
5
- Rake::TestTask.new do |t|
6
- t.pattern = "./test/**/*_test.rb"
5
+ # or more explicitly:
6
+
7
+ Minitest::TestTask.create(:test) do |t|
8
+ t.libs << "test"
9
+ t.libs << "lib"
10
+ t.warning = false
11
+ t.test_globs = ["test/**/*_test.rb"]
7
12
  end
13
+
14
+ task :default => :test
@@ -1,28 +1,19 @@
1
+ require 'stripe'
2
+
1
3
  module Spackle
2
4
  class Customer
3
- @data = nil
5
+ attr_accessor :id
6
+ attr_accessor :data
4
7
 
5
8
  def self.retrieve(id)
6
9
  Util.log_debug("Retrieving customer data for #{id}")
7
- data = Spackle.client.query({
8
- key_condition_expression: 'CustomerId = :customer_id',
9
- filter_expression: 'Version = :version',
10
- expression_attribute_values: {
11
- ':customer_id' => id,
12
- ':version' => Spackle.version
13
- },
14
- limit: 1
15
- })
16
-
17
- if not data.items.any?
18
- raise SpackleError.new "Customer #{id} not found"
19
- end
20
-
10
+ data = Spackle.store.get_customer_data(id)
21
11
  Util.log_debug("Retrieved customer data for #{id}: #{data}")
22
- Customer.new(JSON.parse(data.items[0]['State']))
12
+ Customer.new(id, data)
23
13
  end
24
14
 
25
- def initialize(data)
15
+ def initialize(id, data)
16
+ @id = id
26
17
  @data = data
27
18
  end
28
19
 
@@ -30,6 +21,18 @@ module Spackle
30
21
  return @data['features']
31
22
  end
32
23
 
24
+ def subscriptions
25
+ subscriptions = []
26
+
27
+ @data['subscriptions'].each do |s|
28
+ subscription = Stripe::Subscription.new(s['id'])
29
+ subscription.update_attributes(s)
30
+ subscriptions.push(subscription)
31
+ end
32
+
33
+ subscriptions
34
+ end
35
+
33
36
  def flag_features
34
37
  features.select { |f| f['type'] == 0 }
35
38
  end
@@ -4,20 +4,35 @@ module Spackle
4
4
  class SpackleConfiguration
5
5
  attr_accessor :api_key
6
6
  attr_accessor :api_base
7
- attr_accessor :log_level
8
7
  attr_reader :logger
9
8
  attr_reader :version
10
9
 
11
10
  def initialize
12
- @api_base = 'https://api.spackle.so'
13
- @log_level = Logger::INFO
11
+ @api_base = 'https://api.spackle.so/v1'
12
+ @log_level = Logger::WARN
14
13
  @logger = Logger.new(STDOUT, level: @log_level)
15
14
  @version = 1
15
+ @store = nil
16
+ end
17
+
18
+ def log_level()
19
+ @log_level
16
20
  end
17
21
 
18
22
  def log_level=(level)
19
23
  @log_level = level
20
24
  @logger.level = level
21
25
  end
26
+
27
+ def store()
28
+ if @store == nil
29
+ @store = DynamoDBStore.new
30
+ end
31
+ @store
32
+ end
33
+
34
+ def store=(store)
35
+ @store = store
36
+ end
22
37
  end
23
38
  end
@@ -0,0 +1,11 @@
1
+ module Spackle
2
+ class BaseStore
3
+ def get_customer_data(id)
4
+ raise NoMethodError, 'get_customer_data not implemented'
5
+ end
6
+
7
+ def set_customer_data(id, data)
8
+ raise NoMethodError, 'set_customer_data not implemented'
9
+ end
10
+ end
11
+ end
@@ -1,20 +1,41 @@
1
1
  require 'aws-sdk'
2
2
  require 'net/http'
3
3
  require 'json'
4
+ require 'logger'
4
5
 
5
6
  module Spackle
6
- class DynamoDB
7
+ class DynamoDBStore < BaseStore
7
8
  @client = nil
8
- @identity_id = nil
9
- @table_name = nil
9
+ @store_config = {}
10
10
 
11
- def initialize
12
- @client = bootstrap_client
11
+ def initialize(client = nil, store_config = nil)
12
+ @store_config = store_config || {}
13
+ @client = client || bootstrap_client
13
14
  end
14
15
 
16
+ def get_customer_data(id)
17
+ data = query({
18
+ key_condition_expression: 'CustomerId = :customer_id',
19
+ filter_expression: 'Version = :version',
20
+ expression_attribute_values: {
21
+ ':customer_id' => id,
22
+ ':version' => Spackle.version
23
+ },
24
+ limit: 1
25
+ })
26
+
27
+ if not data.items.any?
28
+ raise SpackleError.new "Customer #{id} not found"
29
+ end
30
+
31
+ JSON.parse(data.items[0]['State'])
32
+ end
33
+
34
+ private
35
+
15
36
  def get_item(key)
16
37
  key = key.merge({
17
- 'AccountId' => @identity_id,
38
+ 'AccountId' => @store_config['identity_id'],
18
39
  })
19
40
 
20
41
  response = @client.get_item({
@@ -26,19 +47,17 @@ module Spackle
26
47
  end
27
48
 
28
49
  def query(query)
29
- query[:table_name] = @table_name
50
+ query[:table_name] = @store_config['table_name']
30
51
  query[:key_condition_expression] = 'AccountId = :account_id AND ' + query[:key_condition_expression]
31
52
  query[:expression_attribute_values] = query[:expression_attribute_values].merge({
32
- ':account_id' => @identity_id
53
+ ':account_id' => @store_config['identity_id']
33
54
  })
34
- response = @client.query(query)
55
+ @client.query(query)
35
56
  end
36
57
 
37
- private
38
-
39
58
  def bootstrap_client
40
59
  Util.log_debug('Bootstrapping DynamoDB client...')
41
- uri = URI(Spackle.api_base + '/auth/session')
60
+ uri = URI(Spackle.api_base + '/sessions')
42
61
  https = Net::HTTP.new(uri.host, uri.port)
43
62
  https.use_ssl = true
44
63
 
@@ -49,12 +68,10 @@ module Spackle
49
68
  data = JSON.parse(response.body)
50
69
  Util.log_debug("Created session: #{data}")
51
70
 
52
- @identity_id = data['identity_id']
53
- @table_name = data['table_name']
54
-
71
+ @store_config = data['adapter']
55
72
  credentials = SpackleCredentials.new(
56
- data['role_arn'],
57
- data['token']
73
+ @store_config['role_arn'],
74
+ @store_config['token']
58
75
  )
59
76
 
60
77
  Aws::DynamoDB::Client.new(
@@ -0,0 +1,28 @@
1
+ module Spackle
2
+ class FileStore < BaseStore
3
+ def initialize(path)
4
+ @path = path
5
+ end
6
+
7
+ def get_customer_data(id)
8
+ content = File.read(@path)
9
+ data = JSON.parse(content)
10
+ data[id]
11
+ end
12
+
13
+ def set_customer_data(id, customer_data)
14
+ data = {}
15
+ if File.exist?(@path)
16
+ content = File.read(@path)
17
+ data = JSON.parse(content)
18
+ end
19
+
20
+ if !data.has_key?(id)
21
+ raise SpackleError.new "Customer #{id} not found"
22
+ end
23
+
24
+ data[id] = customer_data
25
+ File.write(@path, JSON.pretty_generate(data))
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ module Spackle
2
+ class MemoryStore < BaseStore
3
+ @data = {}
4
+
5
+ def initialize()
6
+ @data = {}
7
+ end
8
+
9
+ def get_customer_data(id)
10
+ if !@data.has_key?(id)
11
+ raise SpackleError.new "Customer #{id} not found"
12
+ end
13
+
14
+ @data[id]
15
+ end
16
+
17
+ def set_customer_data(id, customer_data)
18
+ @data[id] = customer_data
19
+ end
20
+ end
21
+ end
data/lib/spackle/util.rb CHANGED
@@ -1,15 +1,21 @@
1
+ require 'logger'
2
+
1
3
  module Spackle
2
4
  module Util
3
- def self.log_error(message)
4
- Spackle.logger.error(message)
5
+ def self.log_debug(message)
6
+ Spackle.logger.debug(message)
5
7
  end
6
8
 
7
9
  def self.log_info(message)
8
10
  Spackle.logger.info(message)
9
11
  end
10
12
 
11
- def self.log_debug(message)
12
- Spackle.logger.debug(message)
13
+ def self.log_warn(message)
14
+ Spackle.logger.warn(message)
15
+ end
16
+
17
+ def self.log_error(message)
18
+ Spackle.logger.error(message)
13
19
  end
14
20
  end
15
21
  end
@@ -0,0 +1,37 @@
1
+ require 'logger'
2
+
3
+ module Spackle
4
+ module Waiters
5
+ def self.wait_for_customer(customer_id, timeout = 15)
6
+ start = Time.now.to_i
7
+ while Time.now.to_i - start < timeout do
8
+ begin
9
+ return Spackle::Customer.retrieve(customer_id)
10
+ rescue SpackleError
11
+ sleep 1
12
+ end
13
+ end
14
+
15
+ raise SpackleError.new "Timeout waiting for customer #{customer_id}"
16
+ end
17
+
18
+ def self.wait_for_subscription(customer_id, subscription_id, timeout=15, **filters)
19
+ start = Time.now.to_i
20
+ while Time.now.to_i - start < timeout do
21
+ begin
22
+ customer = Spackle::Customer.retrieve(customer_id)
23
+ customer.subscriptions.each do |s|
24
+ if s.id == subscription_id and filters.all? { |k, v| s.send(k) == v }
25
+ return s
26
+ end
27
+ end
28
+ sleep 1
29
+ rescue SpackleError
30
+ sleep 1
31
+ end
32
+ end
33
+
34
+ raise SpackleError.new "Timeout waiting for subscription #{subscription_id}"
35
+ end
36
+ end
37
+ end
data/lib/spackle.rb CHANGED
@@ -2,17 +2,23 @@ require 'logger'
2
2
  require 'forwardable'
3
3
 
4
4
  require 'spackle/customer'
5
- require 'spackle/dynamodb'
6
5
  require 'spackle/spackle_configuration'
7
6
  require 'spackle/util'
7
+ require 'spackle/waiters'
8
+
9
+ require 'spackle/stores/base'
10
+ require 'spackle/stores/dynamodb'
11
+ require 'spackle/stores/file'
12
+ require 'spackle/stores/memory'
8
13
 
9
14
  module Spackle
10
15
  @config = Spackle::SpackleConfiguration.new
11
16
  @client = nil
12
17
 
13
18
  LEVEL_DEBUG = Logger::DEBUG
14
- LEVEL_ERROR = Logger::ERROR
15
19
  LEVEL_INFO = Logger::INFO
20
+ LEVEL_WARN = Logger::WARN
21
+ LEVEL_ERROR = Logger::ERROR
16
22
 
17
23
  class << self
18
24
  extend Forwardable
@@ -22,21 +28,11 @@ module Spackle
22
28
  def_delegators :@config, :api_key, :api_key=
23
29
  def_delegators :@config, :api_base, :api_base=
24
30
  def_delegators :@config, :log_level, :log_level=
31
+ def_delegators :@config, :store, :store=
25
32
  def_delegators :@config, :logger, :logger=
26
33
  def_delegators :@config, :version, :version=
27
34
  end
28
35
 
29
- def self.client
30
- unless Spackle.api_key.nil?
31
- @client ||= Spackle::DynamoDB.new()
32
- end
33
- end
34
-
35
- def self.bootstrap
36
- self.client
37
- nil
38
- end
39
-
40
36
  class SpackleError < StandardError
41
37
  end
42
38
  end
data/spackle.gemspec CHANGED
@@ -2,7 +2,7 @@ $LOAD_PATH.unshift(::File.join(::File.dirname(__FILE__), "lib"))
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "spackle-ruby"
5
- s.version = "0.0.2"
5
+ s.version = "0.0.3"
6
6
  s.summary = "Spackle Ruby gem"
7
7
  s.description = "Spackle is the easiest way to integrate your Ruby app with Stripe Billing. " \
8
8
  "See https://www.spackle.so for details."
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spackle-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Spackle
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-21 00:00:00.000000000 Z
11
+ date: 2023-03-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Spackle is the easiest way to integrate your Ruby app with Stripe Billing.
14
14
  See https://www.spackle.so for details.
@@ -26,9 +26,13 @@ files:
26
26
  - bin/spackle-console
27
27
  - lib/spackle.rb
28
28
  - lib/spackle/customer.rb
29
- - lib/spackle/dynamodb.rb
30
29
  - lib/spackle/spackle_configuration.rb
30
+ - lib/spackle/stores/base.rb
31
+ - lib/spackle/stores/dynamodb.rb
32
+ - lib/spackle/stores/file.rb
33
+ - lib/spackle/stores/memory.rb
31
34
  - lib/spackle/util.rb
35
+ - lib/spackle/waiters.rb
32
36
  - spackle.gemspec
33
37
  homepage: https://docs.spackle.so/ruby
34
38
  licenses: