paddle 2.2.1 → 2.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6224fc2a3ec9423990985241a118780185a090617407f95e97bb542120eb9323
4
- data.tar.gz: 03403341d8dce73c881ed3fc2c722cf738b625803c6352dd2cca15e5d4cd65a7
3
+ metadata.gz: 8f5aba3cef837ddde090eb1759279427dcf213115c5950b18c167cbea59f4de3
4
+ data.tar.gz: 8bd53cd25b7d29113a5f5e942bac4457afd976cca2995ba232ce888051dc5367
5
5
  SHA512:
6
- metadata.gz: 6453da01350a80adf7f8723817b7d45edfac636d5ae72ebeb92b281ad93d85d59866631eb60a17c3a8225f5a8f51780b857e1aeab940c0264daaa53446a70637
7
- data.tar.gz: 716ef3061039df26a5cfc0dd0f8931cb1e9edcf964947047acf37ed8b95cc63a3cc36595288f6aaccc45ee2b1e3f8fe0b74ae39969123f2c99eeeeea9da41381
6
+ metadata.gz: e844d8f2c15d7c7d0ad0de2d6dadc566fee298b5a1e1930529b709f9532cf673efc7258e02fe9b636338ed9e7730f90a1a293e71faa7d71293e1c1996c479ac5
7
+ data.tar.gz: 895d3532f3a0ddf935427e186d5a06e28fd7e5af610594d88540b009887023683c0ae0ad4cf06bacbef7c7c7ab9f09e29d3931e65f898b779ccd0ee888623a1c
data/.rubocop.yml CHANGED
@@ -6,3 +6,6 @@ inherit_gem: { rubocop-rails-omakase: rubocop.yml }
6
6
  # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
7
7
  # Layout/SpaceInsideArrayLiteralBrackets:
8
8
  # Enabled: false
9
+
10
+ Rails/RefuteMethods:
11
+ Enabled: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- paddle (2.2.1)
4
+ paddle (2.4.0)
5
5
  faraday (~> 2.0)
6
6
 
7
7
  GEM
@@ -40,9 +40,9 @@ GEM
40
40
  ast (~> 2.4.1)
41
41
  racc
42
42
  racc (1.8.0)
43
- rack (3.1.3)
43
+ rack (3.1.7)
44
44
  rainbow (3.1.1)
45
- rake (13.0.6)
45
+ rake (13.2.1)
46
46
  regexp_parser (2.9.2)
47
47
  rexml (3.3.0)
48
48
  strscan
data/README.md CHANGED
@@ -7,7 +7,7 @@ The easiest and most complete Ruby library for the Paddle APIs, both Classic and
7
7
  Add this line to your application's Gemfile:
8
8
 
9
9
  ```ruby
10
- gem "paddle", "~> 2.2"
10
+ gem "paddle", "~> 2.3"
11
11
  ```
12
12
 
13
13
  ## Billing API
@@ -77,6 +77,14 @@ Paddle::Product.list(per_page: 10, after: "abc123")
77
77
  >
78
78
  > The Paddle API doesn't take `nil` values for optional parameters. If you want to remove a value, you'll need to pass `"null"` instead.
79
79
 
80
+ ### Updating records
81
+
82
+ For API endpoints that support it, you can use the `update` method to update a record, like so:
83
+
84
+ ```ruby
85
+ Paddle::Product.retrieve(id: "pro_abc123").update(name: "My New Name")
86
+ ```
87
+
80
88
  ### Products
81
89
 
82
90
  ```ruby
@@ -93,10 +101,12 @@ Paddle::Product.create(name: "My SAAS Plan", tax_category: "saas")
93
101
  Paddle::Product.create(name: "My Standard Product", tax_category: "standard")
94
102
 
95
103
  # Retrieve a product
96
- Paddle::Product.retrieve(id: "pro_abc123")
104
+ product = Paddle::Product.retrieve(id: "pro_abc123")
97
105
 
98
106
  # Update a product
99
107
  # https://developer.paddle.com/api-reference/products/update-product
108
+ product.update(description: "This is a plan")
109
+ # or
100
110
  Paddle::Product.update(id: "pro_abc123", description: "This is a plan")
101
111
  ```
102
112
 
@@ -116,10 +126,12 @@ Paddle::Price.list(product_id: "pro_abc123")
116
126
  Paddle::Price.create(product_id: "pro_abc123", description: "A one off price", amount: "1000", currency: "GBP")
117
127
 
118
128
  # Retrieve a price
119
- Paddle::Price.retrieve(id: "pri_123abc")
129
+ price = Paddle::Price.retrieve(id: "pri_123abc")
120
130
 
121
131
  # Update a price
122
132
  # https://developer.paddle.com/api-reference/prices/update-price
133
+ price.update(description: "An updated description")
134
+ # or
123
135
  Paddle::Price.update(id: "pri_123abc", description: "An updated description")
124
136
  ```
125
137
 
@@ -149,10 +161,12 @@ Paddle::Discount.create(description: "$5 off", type: "flat", amount: "500", curr
149
161
  Paddle::Discount.create(description: "10% Off", type: "percentage", amount: "10", code: "10OFF")
150
162
 
151
163
  # Retrieve a discount
152
- Paddle::Discount.retrieve(id: "dsc_abc123")
164
+ discount = Paddle::Discount.retrieve(id: "dsc_abc123")
153
165
 
154
166
  # Update a discount
155
167
  # https://developer.paddle.com/api-reference/discounts/update-discount
168
+ discount.update(description: "An updated description")
169
+ # or
156
170
  Paddle::Discount.update(id: "dsc_abc123", description: "An updated description")
157
171
  ```
158
172
 
@@ -167,13 +181,16 @@ Paddle::Customer.list(email: "me@mydomain.com")
167
181
 
168
182
  # Create a customer
169
183
  # https://developer.paddle.com/api-reference/customers/create-customer
184
+ # Returns a Paddle::Errors::ConflictError if the email is already used on Paddle
170
185
  Paddle::Customer.create(email: "myemail@mydomain.com", name: "Customer Name")
171
186
 
172
187
  # Retrieve a customer
173
- Paddle::Customer.retrieve(id: "ctm_abc123")
188
+ customer = Paddle::Customer.retrieve(id: "ctm_abc123")
174
189
 
175
190
  # Update a customer
176
191
  # https://developer.paddle.com/api-reference/customers/update-customer
192
+ customer.update(status: "archived")
193
+ # or
177
194
  Paddle::Customer.update(id: "ctm_abc123", status: "archived")
178
195
 
179
196
  # Retrieve credit balance for a customer
@@ -193,10 +210,12 @@ Paddle::Address.list(customer: "ctm_abc123")
193
210
  Paddle::Address.create(customer: "ctm_abc123", country_code: "GB", postal_code: "SW1A 2AA")
194
211
 
195
212
  # Retrieve an address
196
- Paddle::Address.retrieve(customer: "ctm_abc123", id: "add_abc123")
213
+ address = Paddle::Address.retrieve(customer: "ctm_abc123", id: "add_abc123")
197
214
 
198
215
  # Update an address
199
216
  # https://developer.paddle.com/api-reference/addresses/update-address
217
+ address.update(status: "archived")
218
+ # or
200
219
  Paddle::Address.update(customer: "ctm_abc123", id: "add_abc123", status: "archived")
201
220
  ```
202
221
 
@@ -212,10 +231,12 @@ Paddle::Business.list(customer: "ctm_abc123")
212
231
  Paddle::Business.create(customer: "ctm_abc123", name: "My Ltd Company")
213
232
 
214
233
  # Retrieve a business
215
- Paddle::Business.retrieve(customer: "ctm_abc123", id: "biz_abc123")
234
+ business = Paddle::Business.retrieve(customer: "ctm_abc123", id: "biz_abc123")
216
235
 
217
236
  # Update a business
218
237
  # https://developer.paddle.com/api-reference/businesses/update-business
238
+ business.update(status: "archived")
239
+ # or
219
240
  Paddle::Business.update(customer: "ctm_abc123", id: "biz_abc123", status: "archived")
220
241
  ```
221
242
 
@@ -237,10 +258,12 @@ Paddle::Transaction.retrieve(id: "txn_abc123")
237
258
 
238
259
  # Retrieve a transaction with extra information
239
260
  # extra can be either "address", "adjustment", "adjustments_totals", "business", "customer", "discount"
240
- Paddle::Transaction.retrieve(id: "txn_abc123", extra: "customer")
261
+ transaction = Paddle::Transaction.retrieve(id: "txn_abc123", extra: "customer")
241
262
 
242
263
  # Update a transaction
243
264
  # https://developer.paddle.com/api-reference/transaction/update-transaction
265
+ transaction.update(items: [ { price_id: "pri_abc123", quantity: 2 } ])
266
+ # or
244
267
  Paddle::Transaction.update(id: "txn_abc123", items: [ { price_id: "pri_abc123", quantity: 2 } ])
245
268
 
246
269
  # Preview a transaction
@@ -271,7 +294,7 @@ Paddle::Subscription.retrieve(id: "sub_abc123")
271
294
 
272
295
  # Retrieve a subscription with extra information
273
296
  # extra can be either "next_transaction" or "recurring_transaction_details"
274
- Paddle::Subscription.retrieve(id: "sub_abc123", extra: "next_transaction")
297
+ subscription = Paddle::Subscription.retrieve(id: "sub_abc123", extra: "next_transaction")
275
298
 
276
299
  # Preview an update to a subscription
277
300
  # https://developer.paddle.com/api-reference/subscriptions/preview-subscription
@@ -279,6 +302,8 @@ Paddle::Subscription.preview(id: "sub_abc123", items: [ { price_id: "pri_123abc"
279
302
 
280
303
  # Update a subscription
281
304
  # https://developer.paddle.com/api-reference/subscriptions/update-subscription
305
+ subscription.update(billing_details: {purchase_order_number: "PO-1234"})
306
+ # or
282
307
  Paddle::Subscription.update(id: "sub_abc123", billing_details: {purchase_order_number: "PO-1234"})
283
308
 
284
309
  # Get a transaction to update payment method
@@ -358,7 +383,7 @@ Used for creating webhook and email notifications
358
383
  Paddle::NotificationSetting.list
359
384
 
360
385
  # Retrieve a notification setting
361
- Paddle::NotificationSetting.retrieve(id: "ntfset_abc123")
386
+ setting = Paddle::NotificationSetting.retrieve(id: "ntfset_abc123")
362
387
 
363
388
  # Create a notification setting
364
389
  # https://developer.paddle.com/api-reference/notification-settings/create-notification-setting
@@ -374,6 +399,8 @@ Paddle::NotificationSetting.create(
374
399
 
375
400
  # Update a notification setting
376
401
  # https://developer.paddle.com/api-reference/notification-settings/update-notification-setting
402
+ setting.update(subscribed_events: %w[subscription.activated transaction.completed transaction.billed])
403
+ # or
377
404
  Paddle::NotificationSetting.update(id: "ntfset_abc123",
378
405
  subscribed_events: [
379
406
  "subscription.activated",
@@ -0,0 +1,6 @@
1
+ module Paddle
2
+ module Classic
3
+ class Error < StandardError
4
+ end
5
+ end
6
+ end
data/lib/paddle/client.rb CHANGED
@@ -1,66 +1,61 @@
1
+ require "faraday"
2
+
1
3
  module Paddle
2
4
  class Client
3
5
  class << self
4
6
  def connection
5
- @connection ||= Faraday.new(Paddle.config.url) do |conn|
6
- conn.request :authorization, :Bearer, Paddle.config.api_key
7
-
8
- conn.headers = {
9
- "User-Agent" => "paddle/v#{VERSION} (github.com/deanpcmad/paddle)",
10
- "Paddle-Version" => Paddle.config.version.to_s
11
- }
12
-
13
- conn.request :json
14
- conn.response :json
15
- end
7
+ @connection ||= create_connection
16
8
  end
17
9
 
18
-
19
10
  def get_request(url, params: {}, headers: {})
20
- handle_response connection.get(url, params, headers)
11
+ handle_response(connection.get(url, params, headers))
21
12
  end
22
13
 
23
14
  def post_request(url, body: {}, headers: {})
24
- handle_response connection.post(url, body, headers)
15
+ handle_response(connection.post(url, body, headers))
25
16
  end
26
17
 
27
18
  def patch_request(url, body:, headers: {})
28
- handle_response connection.patch(url, body, headers)
19
+ handle_response(connection.patch(url, body, headers))
29
20
  end
30
21
 
31
22
  def delete_request(url, headers: {})
32
- handle_response connection.delete(url, headers)
23
+ handle_response(connection.delete(url, headers))
33
24
  end
34
25
 
35
- def handle_response(response)
36
- case response.status
37
- when 400
38
- raise Error, "Error 400: Your request was malformed. '#{response.body["error"]["code"]}'"
39
- when 401
40
- raise Error, "Error 401: You did not supply valid authentication credentials. '#{response.body["error"]}'"
41
- when 403
42
- raise Error, "Error 403: You are not allowed to perform that action. '#{response.body["error"]["code"]}'"
43
- when 404
44
- raise Error, "Error 404: No results were found for your request. '#{response.body["error"]["code"]}'"
45
- when 409
46
- raise Error, "Error 409: Your request was a conflict. '#{response.body["error"]["code"]}'"
47
- when 429
48
- raise Error, "Error 429: Your request exceeded the API rate limit. '#{response.body["error"]["code"]}'"
49
- when 500
50
- raise Error, "Error 500: We were unable to perform the request due to server-side problems. '#{response.body["error"]["code"]}'"
51
- when 503
52
- raise Error, "Error 503: You have been rate limited for sending more than 20 requests per second. '#{response.body["error"]["code"]}'"
53
- when 501
54
- raise Error, "Error 501: This resource has not been implemented. '#{response.body["error"]["code"]}'"
55
- when 204
56
- return true
57
- end
26
+ private
58
27
 
59
- if response.body && response.body["error"]
60
- raise Error, "Error #{response.body["error"]["code"]} - #{response.body["errors"]["message"]}"
28
+ def create_connection
29
+ Faraday.new(Paddle.config.url) do |conn|
30
+ conn.request :authorization, :Bearer, Paddle.config.api_key
31
+ conn.headers = default_headers
32
+ conn.request :json
33
+ conn.response :json
61
34
  end
35
+ end
36
+
37
+ def default_headers
38
+ {
39
+ "User-Agent" => "paddle/v#{VERSION} (github.com/deanpcmad/paddle)",
40
+ "Paddle-Version" => Paddle.config.version.to_s
41
+ }
42
+ end
43
+
44
+ def handle_response(response)
45
+ return true if response.status == 204
46
+ return response unless error?(response)
47
+
48
+ raise_error(response)
49
+ end
50
+
51
+ def error?(response)
52
+ [ 400, 401, 403, 404, 409, 429, 500, 501, 503 ].include?(response.status) ||
53
+ response.body&.key?("error")
54
+ end
62
55
 
63
- response
56
+ def raise_error(response)
57
+ error = Paddle::ErrorFactory.create(response.body, response.status)
58
+ raise error if error
64
59
  end
65
60
  end
66
61
  end
@@ -0,0 +1,128 @@
1
+ module Paddle
2
+ class ErrorGenerator < StandardError
3
+ attr_reader :http_status_code
4
+ attr_reader :paddle_error_code
5
+ attr_reader :paddle_error_message
6
+
7
+ def initialize(response_body, http_status_code)
8
+ @response_body = response_body
9
+ @http_status_code = http_status_code
10
+ set_paddle_error_values
11
+ super(build_message)
12
+ end
13
+
14
+ private
15
+
16
+ def set_paddle_error_values
17
+ @paddle_error_code = @response_body.dig("error", "code")
18
+ @paddle_error_message = @response_body.dig("error", "detail")
19
+ end
20
+
21
+ def error_message
22
+ @paddle_error_message || @response_body.dig("error", "code")
23
+ rescue NoMethodError
24
+ "An unknown error occurred."
25
+ end
26
+
27
+ def build_message
28
+ if paddle_error_code.nil?
29
+ return "Error #{@http_status_code}: #{error_message}"
30
+ end
31
+ "Error #{@http_status_code}: #{error_message} '#{paddle_error_code}'"
32
+ end
33
+ end
34
+
35
+ module Errors
36
+ class BadRequestError < ErrorGenerator
37
+ private
38
+
39
+ def error_message
40
+ "Your request was malformed."
41
+ end
42
+ end
43
+
44
+ class AuthenticationMissingError < ErrorGenerator
45
+ private
46
+
47
+ def error_message
48
+ "You did not supply valid authentication credentials."
49
+ end
50
+ end
51
+
52
+ class ForbiddenError < ErrorGenerator
53
+ private
54
+
55
+ def error_message
56
+ "You are not allowed to perform that action."
57
+ end
58
+ end
59
+
60
+ class EntityNotFoundError < ErrorGenerator
61
+ private
62
+
63
+ def error_message
64
+ "No results were found for your request."
65
+ end
66
+ end
67
+
68
+ class ConflictError < ErrorGenerator
69
+ private
70
+
71
+ def error_message
72
+ "Your request was a conflict."
73
+ end
74
+ end
75
+
76
+ class TooManyRequestsError < ErrorGenerator
77
+ private
78
+
79
+ def error_message
80
+ "Your request exceeded the API rate limit."
81
+ end
82
+ end
83
+
84
+ class InternalError < ErrorGenerator
85
+ private
86
+
87
+ def error_message
88
+ "We were unable to perform the request due to server-side problems."
89
+ end
90
+ end
91
+
92
+ class ServiceUnavailableError < ErrorGenerator
93
+ private
94
+
95
+ def error_message
96
+ "You have been rate limited for sending more than 20 requests per second."
97
+ end
98
+ end
99
+
100
+ class NotImplementedError < ErrorGenerator
101
+ private
102
+
103
+ def error_message
104
+ "This resource has not been implemented."
105
+ end
106
+ end
107
+ end
108
+
109
+ class ErrorFactory
110
+ HTTP_ERROR_MAP = {
111
+ 400 => Errors::BadRequestError,
112
+ 401 => Errors::AuthenticationMissingError,
113
+ 403 => Errors::ForbiddenError,
114
+ 404 => Errors::EntityNotFoundError,
115
+ 409 => Errors::ConflictError,
116
+ 429 => Errors::TooManyRequestsError,
117
+ 500 => Errors::InternalError,
118
+ 503 => Errors::ServiceUnavailableError,
119
+ 501 => Errors::NotImplementedError
120
+ }.freeze
121
+
122
+ def self.create(response_body, http_status_code)
123
+ status = http_status_code
124
+ error_class = HTTP_ERROR_MAP[status] || ErrorGenerator
125
+ error_class.new(response_body, http_status_code) if error_class
126
+ end
127
+ end
128
+ end
data/lib/paddle/object.rb CHANGED
@@ -15,5 +15,26 @@ module Paddle
15
15
  obj
16
16
  end
17
17
  end
18
+
19
+ def update(**params)
20
+ method_missing :update unless klass.respond_to? :update
21
+
22
+ primary_attributes = klass.method(:update).parameters.select { |(type, name)| type == :keyreq }.map(&:last) # Identified by whatever is a required named parameter.
23
+
24
+ primary_attributes.each do |attrib|
25
+ # ? Should we need to handle blank strings here?
26
+ params[attrib] = self[attrib] || self["#{attrib}_id"] # Handling for customer (customer_id) and id.
27
+ end
28
+
29
+ klass.public_send(:update, **params).each_pair { |key, val| self[key] = val } # Updating self
30
+
31
+ self
32
+ end
33
+
34
+ private
35
+
36
+ def klass
37
+ self.class
38
+ end
18
39
  end
19
40
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Paddle
4
- VERSION = "2.2.1"
4
+ VERSION = "2.4.0"
5
5
  end
data/lib/paddle.rb CHANGED
@@ -9,6 +9,9 @@ module Paddle
9
9
  autoload :Client, "paddle/client"
10
10
  autoload :Collection, "paddle/collection"
11
11
  autoload :Error, "paddle/error"
12
+ autoload :ErrorGenerator, "paddle/error_generator"
13
+ autoload :ErrorFactory, "paddle/error_generator"
14
+
12
15
  autoload :Object, "paddle/object"
13
16
 
14
17
  class << self
@@ -48,6 +51,7 @@ module Paddle
48
51
  autoload :Client, "paddle/classic/client"
49
52
  autoload :Collection, "paddle/classic/collection"
50
53
  autoload :Resource, "paddle/classic/resource"
54
+ autoload :Error, "paddle/classic/error"
51
55
 
52
56
  autoload :PlansResource, "paddle/classic/resources/plans"
53
57
  autoload :CouponsResource, "paddle/classic/resources/coupons"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paddle
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dean Perry
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-06-28 00:00:00.000000000 Z
11
+ date: 2024-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -42,6 +42,7 @@ files:
42
42
  - lib/paddle.rb
43
43
  - lib/paddle/classic/client.rb
44
44
  - lib/paddle/classic/collection.rb
45
+ - lib/paddle/classic/error.rb
45
46
  - lib/paddle/classic/objects/charge.rb
46
47
  - lib/paddle/classic/objects/coupon.rb
47
48
  - lib/paddle/classic/objects/license.rb
@@ -70,6 +71,7 @@ files:
70
71
  - lib/paddle/collection.rb
71
72
  - lib/paddle/configuration.rb
72
73
  - lib/paddle/error.rb
74
+ - lib/paddle/error_generator.rb
73
75
  - lib/paddle/models/address.rb
74
76
  - lib/paddle/models/adjustment.rb
75
77
  - lib/paddle/models/business.rb
@@ -110,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
112
  - !ruby/object:Gem::Version
111
113
  version: '0'
112
114
  requirements: []
113
- rubygems_version: 3.5.9
115
+ rubygems_version: 3.5.11
114
116
  signing_key:
115
117
  specification_version: 4
116
118
  summary: Ruby library for the Paddle Billing & Classic APIs