paddle 2.9 → 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.9'
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:
@@ -13,58 +13,31 @@ dependencies:
13
13
  name: faraday
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: '2.11'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
16
+ - - ">="
24
17
  - !ruby/object:Gem::Version
25
- version: '2.11'
26
- - !ruby/object:Gem::Dependency
27
- name: ostruct
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
18
+ version: 2.14.3
19
+ - - "<"
31
20
  - !ruby/object:Gem::Version
32
- version: 0.6.0
21
+ version: '3'
33
22
  type: :runtime
34
23
  prerelease: false
35
24
  version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: 0.6.0
40
- - !ruby/object:Gem::Dependency
41
- name: cgi
42
- requirement: !ruby/object:Gem::Requirement
43
25
  requirements:
44
26
  - - ">="
45
27
  - !ruby/object:Gem::Version
46
- version: '0'
47
- type: :runtime
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
28
+ version: 2.14.3
29
+ - - "<"
52
30
  - !ruby/object:Gem::Version
53
- version: '0'
31
+ version: '3'
54
32
  email:
55
- - dean@deanpcmad.com
33
+ - dean@voupe.com
56
34
  executables: []
57
35
  extensions: []
58
36
  extra_rdoc_files: []
59
37
  files:
60
- - ".env.example"
61
- - ".rubocop.yml"
62
- - Gemfile
63
- - Gemfile.lock
38
+ - CHANGELOG.md
39
+ - LICENSE.txt
64
40
  - README.md
65
- - Rakefile
66
- - bin/console
67
- - bin/setup
68
41
  - lib/paddle.rb
69
42
  - lib/paddle/classic/client.rb
70
43
  - lib/paddle/classic/collection.rb
@@ -109,6 +82,9 @@ files:
109
82
  - lib/paddle/models/discount_group.rb
110
83
  - lib/paddle/models/event.rb
111
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
112
88
  - lib/paddle/models/notification.rb
113
89
  - lib/paddle/models/notification_log.rb
114
90
  - lib/paddle/models/notification_setting.rb
@@ -123,16 +99,18 @@ files:
123
99
  - lib/paddle/models/simulation_run_event.rb
124
100
  - lib/paddle/models/simulation_type.rb
125
101
  - lib/paddle/models/subscription.rb
102
+ - lib/paddle/models/subscription_history.rb
126
103
  - lib/paddle/models/transaction.rb
127
104
  - lib/paddle/object.rb
128
105
  - lib/paddle/version.rb
129
- - mise.toml
130
- - paddle.gemspec
131
- homepage: https://github.com/deanpcmad/paddle
132
- licenses: []
106
+ - lib/paddle/webhook.rb
107
+ homepage: https://github.com/d34ndev/paddle
108
+ licenses:
109
+ - MIT
133
110
  metadata:
134
- homepage_uri: https://github.com/deanpcmad/paddle
135
- 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
136
114
  rdoc_options: []
137
115
  require_paths:
138
116
  - lib
@@ -140,14 +118,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
140
118
  requirements:
141
119
  - - ">="
142
120
  - !ruby/object:Gem::Version
143
- version: 2.6.0
121
+ version: 3.3.0
144
122
  required_rubygems_version: !ruby/object:Gem::Requirement
145
123
  requirements:
146
124
  - - ">="
147
125
  - !ruby/object:Gem::Version
148
126
  version: '0'
149
127
  requirements: []
150
- rubygems_version: 4.0.3
128
+ rubygems_version: 4.0.20
151
129
  specification_version: 4
152
130
  summary: Ruby library for the Paddle Billing & Classic APIs
153
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.9)
5
- cgi
6
- faraday (~> 2.11)
7
- ostruct (~> 0.6.0)
8
-
9
- GEM
10
- remote: https://rubygems.org/
11
- specs:
12
- activesupport (8.1.1)
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 (3.3.1)
28
- cgi (0.5.1)
29
- concurrent-ruby (1.3.5)
30
- connection_pool (3.0.2)
31
- dotenv (3.2.0)
32
- drb (2.2.3)
33
- faraday (2.14.0)
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.7)
40
- concurrent-ruby (~> 1.0)
41
- json (2.18.0)
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.4)
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 = ">= 2.6.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.11"
30
- spec.add_dependency "ostruct", "~> 0.6.0"
31
- spec.add_dependency "cgi"
32
- end