paddle 2.10 → 3.0

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.
@@ -0,0 +1,65 @@
1
+ require "json"
2
+ require "openssl"
3
+
4
+ module Paddle
5
+ # Verifies the Paddle-Signature header sent with Billing webhooks.
6
+ # https://developer.paddle.com/webhooks/signature-verification
7
+ class Webhook
8
+ class SignatureVerificationError < Paddle::Error; end
9
+
10
+ # Paddle's SDKs reject webhooks with a timestamp more than 5 seconds from the current time
11
+ DEFAULT_TOLERANCE = 5
12
+
13
+ class << self
14
+ # payload must be the raw request body, before any JSON parsing. secret is the
15
+ # endpoint secret key for the notification destination. Pass tolerance: nil to
16
+ # skip the timestamp check, e.g. when replaying a stored webhook in tests.
17
+ def verify!(payload:, signature:, secret:, tolerance: DEFAULT_TOLERANCE)
18
+ raise SignatureVerificationError, "Missing Paddle-Signature header" if signature.nil? || signature.empty?
19
+ raise SignatureVerificationError, "Missing webhook secret" if secret.nil? || secret.empty?
20
+
21
+ timestamp, signatures = parse_signature(signature)
22
+
23
+ if tolerance && (Time.now.to_i - timestamp).abs > tolerance
24
+ raise SignatureVerificationError, "Timestamp is outside the tolerance of #{tolerance} seconds"
25
+ end
26
+
27
+ expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}:#{payload}")
28
+
29
+ # During secret rotation Paddle sends more than one h1, so any match is valid
30
+ unless signatures.any? { |h1| OpenSSL.secure_compare(expected, h1) }
31
+ raise SignatureVerificationError, "Signature doesn't match the payload"
32
+ end
33
+
34
+ true
35
+ end
36
+
37
+ def valid?(**args)
38
+ verify!(**args)
39
+ rescue SignatureVerificationError
40
+ false
41
+ end
42
+
43
+ # Verifies the signature, then returns the payload as a Paddle::Event
44
+ def construct_event(payload:, signature:, secret:, tolerance: DEFAULT_TOLERANCE)
45
+ verify!(payload: payload, signature: signature, secret: secret, tolerance: tolerance)
46
+ Event.new(JSON.parse(payload))
47
+ end
48
+
49
+ private
50
+
51
+ # Parses "ts=1671552777;h1=eb4d0dc8..." into the timestamp and a list of h1 signatures
52
+ def parse_signature(header)
53
+ parts = header.split(";").map { |part| part.split("=", 2) }
54
+ timestamp = parts.find { |key, _| key == "ts" }&.last
55
+ signatures = parts.select { |key, _| key == "h1" }.map(&:last).compact
56
+
57
+ unless timestamp&.match?(/\A\d+\z/) && signatures.any?
58
+ raise SignatureVerificationError, "Paddle-Signature header is malformed"
59
+ end
60
+
61
+ [ timestamp.to_i, signatures ]
62
+ end
63
+ end
64
+ end
65
+ end
data/lib/paddle.rb CHANGED
@@ -14,19 +14,41 @@ module Paddle
14
14
  autoload :ErrorFactory, "paddle/error_generator"
15
15
 
16
16
  autoload :Object, "paddle/object"
17
+ autoload :Webhook, "paddle/webhook"
17
18
 
18
19
  class << self
19
20
  attr_writer :config
20
21
  end
21
22
 
23
+ # Configures the global config, even inside a with_config block
22
24
  def self.configure
23
- yield(config) if block_given?
25
+ yield(global_config) if block_given?
24
26
  end
25
27
 
28
+ # The config for the current request: the with_config override if there is one, or the global config
26
29
  def self.config
30
+ Fiber[:paddle_config] || global_config
31
+ end
32
+
33
+ def self.global_config
27
34
  @config ||= Paddle::Configuration.new
28
35
  end
29
36
 
37
+ # Uses a different API key, environment or version for everything in the block, e.g. to make
38
+ # requests for another Paddle account. The override is stored in fiber storage, so it only
39
+ # applies to the current thread or fiber, and to threads and fibers started inside the block.
40
+ #
41
+ # Paddle.with_config(api_key: account.paddle_api_key) do
42
+ # Paddle::Subscription.list
43
+ # end
44
+ def self.with_config(**options)
45
+ previous = Fiber[:paddle_config]
46
+ Fiber[:paddle_config] = config.merge(**options)
47
+ yield
48
+ ensure
49
+ Fiber[:paddle_config] = previous
50
+ end
51
+
30
52
  # Load Billing APIs
31
53
  autoload :Product, "paddle/models/product"
32
54
  autoload :Price, "paddle/models/price"
@@ -51,10 +73,14 @@ module Paddle
51
73
  autoload :PortalSession, "paddle/models/portal_session"
52
74
  autoload :PaymentMethod, "paddle/models/payment_method"
53
75
  autoload :ClientToken, "paddle/models/client_token"
76
+ autoload :Metric, "paddle/models/metric"
77
+ autoload :ExploreEntity, "paddle/models/explore_entity"
78
+ autoload :ExploreResult, "paddle/models/explore_result"
54
79
 
55
80
  autoload :NotificationLog, "paddle/models/notification_log"
56
81
  autoload :CreditBalance, "paddle/models/credit_balance"
57
82
  autoload :CustomerAuthToken, "paddle/models/customer_auth_token"
83
+ autoload :SubscriptionHistory, "paddle/models/subscription_history"
58
84
 
59
85
  # Load Classic APIs
60
86
  module Classic
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paddle
3
3
  version: !ruby/object:Gem::Version
4
- version: '2.10'
4
+ version: '3.0'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dean Perry
8
- bindir: exe
8
+ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
@@ -15,7 +15,7 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 2.14.2
18
+ version: 2.14.3
19
19
  - - "<"
20
20
  - !ruby/object:Gem::Version
21
21
  version: '3'
@@ -25,52 +25,19 @@ dependencies:
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: 2.14.2
28
+ version: 2.14.3
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
31
  version: '3'
32
- - !ruby/object:Gem::Dependency
33
- name: ostruct
34
- requirement: !ruby/object:Gem::Requirement
35
- requirements:
36
- - - "~>"
37
- - !ruby/object:Gem::Version
38
- version: 0.6.0
39
- type: :runtime
40
- prerelease: false
41
- version_requirements: !ruby/object:Gem::Requirement
42
- requirements:
43
- - - "~>"
44
- - !ruby/object:Gem::Version
45
- version: 0.6.0
46
- - !ruby/object:Gem::Dependency
47
- name: cgi
48
- requirement: !ruby/object:Gem::Requirement
49
- requirements:
50
- - - ">="
51
- - !ruby/object:Gem::Version
52
- version: '0'
53
- type: :runtime
54
- prerelease: false
55
- version_requirements: !ruby/object:Gem::Requirement
56
- requirements:
57
- - - ">="
58
- - !ruby/object:Gem::Version
59
- version: '0'
60
32
  email:
61
- - dean@deanpcmad.com
33
+ - dean@voupe.com
62
34
  executables: []
63
35
  extensions: []
64
36
  extra_rdoc_files: []
65
37
  files:
66
- - ".env.example"
67
- - ".rubocop.yml"
68
- - Gemfile
69
- - Gemfile.lock
38
+ - CHANGELOG.md
39
+ - LICENSE.txt
70
40
  - README.md
71
- - Rakefile
72
- - bin/console
73
- - bin/setup
74
41
  - lib/paddle.rb
75
42
  - lib/paddle/classic/client.rb
76
43
  - lib/paddle/classic/collection.rb
@@ -115,6 +82,9 @@ files:
115
82
  - lib/paddle/models/discount_group.rb
116
83
  - lib/paddle/models/event.rb
117
84
  - lib/paddle/models/event_type.rb
85
+ - lib/paddle/models/explore_entity.rb
86
+ - lib/paddle/models/explore_result.rb
87
+ - lib/paddle/models/metric.rb
118
88
  - lib/paddle/models/notification.rb
119
89
  - lib/paddle/models/notification_log.rb
120
90
  - lib/paddle/models/notification_setting.rb
@@ -129,16 +99,18 @@ files:
129
99
  - lib/paddle/models/simulation_run_event.rb
130
100
  - lib/paddle/models/simulation_type.rb
131
101
  - lib/paddle/models/subscription.rb
102
+ - lib/paddle/models/subscription_history.rb
132
103
  - lib/paddle/models/transaction.rb
133
104
  - lib/paddle/object.rb
134
105
  - lib/paddle/version.rb
135
- - mise.toml
136
- - paddle.gemspec
137
- homepage: https://github.com/deanpcmad/paddle
138
- licenses: []
106
+ - lib/paddle/webhook.rb
107
+ homepage: https://github.com/d34ndev/paddle
108
+ licenses:
109
+ - MIT
139
110
  metadata:
140
- homepage_uri: https://github.com/deanpcmad/paddle
141
- source_code_uri: https://github.com/deanpcmad/paddle
111
+ homepage_uri: https://github.com/d34ndev/paddle
112
+ source_code_uri: https://github.com/d34ndev/paddle
113
+ changelog_uri: https://github.com/d34ndev/paddle/blob/main/CHANGELOG.md
142
114
  rdoc_options: []
143
115
  require_paths:
144
116
  - lib
@@ -146,14 +118,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
146
118
  requirements:
147
119
  - - ">="
148
120
  - !ruby/object:Gem::Version
149
- version: 3.2.0
121
+ version: 3.3.0
150
122
  required_rubygems_version: !ruby/object:Gem::Requirement
151
123
  requirements:
152
124
  - - ">="
153
125
  - !ruby/object:Gem::Version
154
126
  version: '0'
155
127
  requirements: []
156
- rubygems_version: 4.0.10
128
+ rubygems_version: 4.0.20
157
129
  specification_version: 4
158
130
  summary: Ruby library for the Paddle Billing & Classic APIs
159
131
  test_files: []
data/.env.example DELETED
@@ -1,3 +0,0 @@
1
- PADDLE_VENDOR_ID=
2
- PADDLE_VENDOR_AUTH_CODE=
3
- PADDLE_API_KEY=
data/.rubocop.yml DELETED
@@ -1,11 +0,0 @@
1
- # Omakase Ruby styling for Rails
2
- inherit_gem: { rubocop-rails-omakase: rubocop.yml }
3
-
4
- # Overwrite or add rules to create your own house style
5
- #
6
- # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
7
- # Layout/SpaceInsideArrayLiteralBrackets:
8
- # Enabled: false
9
-
10
- Rails/RefuteMethods:
11
- Enabled: false
data/Gemfile DELETED
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- # Specify your gem's dependencies in paddle.gemspec
6
- gemspec
7
-
8
- gem "rake", "~> 13.0"
9
-
10
- gem "minitest", "~> 5.0"
11
- gem "dotenv"
12
- gem "vcr"
13
- gem "rubocop-rails-omakase", require: false
data/Gemfile.lock DELETED
@@ -1,110 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- paddle (2.10)
5
- cgi
6
- faraday (>= 2.14.2, < 3)
7
- ostruct (~> 0.6.0)
8
-
9
- GEM
10
- remote: https://rubygems.org/
11
- specs:
12
- activesupport (8.1.3)
13
- base64
14
- bigdecimal
15
- concurrent-ruby (~> 1.0, >= 1.3.1)
16
- connection_pool (>= 2.2.5)
17
- drb
18
- i18n (>= 1.6, < 2)
19
- json
20
- logger (>= 1.4.2)
21
- minitest (>= 5.1)
22
- securerandom (>= 0.3)
23
- tzinfo (~> 2.0, >= 2.0.5)
24
- uri (>= 0.13.1)
25
- ast (2.4.3)
26
- base64 (0.3.0)
27
- bigdecimal (4.1.2)
28
- cgi (0.5.1)
29
- concurrent-ruby (1.3.6)
30
- connection_pool (3.0.2)
31
- dotenv (3.2.0)
32
- drb (2.2.3)
33
- faraday (2.14.2)
34
- faraday-net_http (>= 2.0, < 3.5)
35
- json
36
- logger
37
- faraday-net_http (3.4.2)
38
- net-http (~> 0.5)
39
- i18n (1.14.8)
40
- concurrent-ruby (~> 1.0)
41
- json (2.19.7)
42
- language_server-protocol (3.17.0.5)
43
- lint_roller (1.1.0)
44
- logger (1.7.0)
45
- minitest (5.27.0)
46
- net-http (0.8.0)
47
- uri (>= 0.11.1)
48
- ostruct (0.6.3)
49
- parallel (1.27.0)
50
- parser (3.3.10.0)
51
- ast (~> 2.4.1)
52
- racc
53
- prism (1.6.0)
54
- racc (1.8.1)
55
- rack (3.2.6)
56
- rainbow (3.1.1)
57
- rake (13.3.1)
58
- regexp_parser (2.11.3)
59
- rubocop (1.82.1)
60
- json (~> 2.3)
61
- language_server-protocol (~> 3.17.0.2)
62
- lint_roller (~> 1.1.0)
63
- parallel (~> 1.10)
64
- parser (>= 3.3.0.2)
65
- rainbow (>= 2.2.2, < 4.0)
66
- regexp_parser (>= 2.9.3, < 3.0)
67
- rubocop-ast (>= 1.48.0, < 2.0)
68
- ruby-progressbar (~> 1.7)
69
- unicode-display_width (>= 2.4.0, < 4.0)
70
- rubocop-ast (1.48.0)
71
- parser (>= 3.3.7.2)
72
- prism (~> 1.4)
73
- rubocop-performance (1.26.1)
74
- lint_roller (~> 1.1)
75
- rubocop (>= 1.75.0, < 2.0)
76
- rubocop-ast (>= 1.47.1, < 2.0)
77
- rubocop-rails (2.34.2)
78
- activesupport (>= 4.2.0)
79
- lint_roller (~> 1.1)
80
- rack (>= 1.1)
81
- rubocop (>= 1.75.0, < 2.0)
82
- rubocop-ast (>= 1.44.0, < 2.0)
83
- rubocop-rails-omakase (1.1.0)
84
- rubocop (>= 1.72)
85
- rubocop-performance (>= 1.24)
86
- rubocop-rails (>= 2.30)
87
- ruby-progressbar (1.13.0)
88
- securerandom (0.4.1)
89
- tzinfo (2.0.6)
90
- concurrent-ruby (~> 1.0)
91
- unicode-display_width (3.2.0)
92
- unicode-emoji (~> 4.1)
93
- unicode-emoji (4.2.0)
94
- uri (1.1.1)
95
- vcr (6.3.1)
96
- base64
97
-
98
- PLATFORMS
99
- x86_64-linux
100
-
101
- DEPENDENCIES
102
- dotenv
103
- minitest (~> 5.0)
104
- paddle!
105
- rake (~> 13.0)
106
- rubocop-rails-omakase
107
- vcr
108
-
109
- BUNDLED WITH
110
- 4.0.3
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rake/testtask"
5
-
6
- Rake::TestTask.new(:test) do |t|
7
- t.libs << "test"
8
- t.libs << "lib"
9
- t.test_files = FileList["test/**/*_test.rb"]
10
- end
11
-
12
- task default: :test
data/bin/console DELETED
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "paddle"
6
-
7
- # Load environment variables from .env file
8
- require 'dotenv/load'
9
-
10
- # You can add fixtures and/or initialization code here to make experimenting
11
- # with your gem easier. You can also use a different console, if you like.
12
-
13
- # (If you use this, don't forget to add pry to your Gemfile!)
14
- # require "pry"
15
- # Pry.start
16
-
17
- @client_classic = Paddle::Classic::Client.new(vendor_id: ENV["PADDLE_VENDOR_ID"], vendor_auth_code: ENV["PADDLE_VENDOR_AUTH_CODE"], sandbox: true)
18
-
19
- Paddle.configure do |config|
20
- config.environment = :sandbox
21
- config.api_key = ENV["PADDLE_API_KEY"]
22
- end
23
-
24
- require "irb"
25
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/mise.toml DELETED
@@ -1,2 +0,0 @@
1
- [tools]
2
- ruby = "4.0"
data/paddle.gemspec DELETED
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/paddle/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "paddle"
7
- spec.version = Paddle::VERSION
8
- spec.authors = [ "Dean Perry" ]
9
- spec.email = [ "dean@deanpcmad.com" ]
10
-
11
- spec.summary = "Ruby library for the Paddle Billing & Classic APIs"
12
- spec.homepage = "https://github.com/deanpcmad/paddle"
13
- spec.required_ruby_version = ">= 3.2.0"
14
-
15
- spec.metadata["homepage_uri"] = spec.homepage
16
- spec.metadata["source_code_uri"] = "https://github.com/deanpcmad/paddle"
17
-
18
- # Specify which files should be added to the gem when it is released.
19
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
- `git ls-files -z`.split("\x0").reject do |f|
22
- (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
23
- end
24
- end
25
- spec.bindir = "exe"
26
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
27
- spec.require_paths = [ "lib" ]
28
-
29
- spec.add_dependency "faraday", ">= 2.14.2", "< 3"
30
- spec.add_dependency "ostruct", "~> 0.6.0"
31
- spec.add_dependency "cgi"
32
- end