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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f3431e22b7e150074661673bb00d2f3517068bda4edb32fc934f3a564b50211
4
- data.tar.gz: 3a05a630a0c24690ec4c9203115af40ca92bbb3cd1eb498e61507b13151659fa
3
+ metadata.gz: ae96654d26047a02fed95576d33befa70e04ae3103e4f191de9f8a8a6e71bdb8
4
+ data.tar.gz: 3e434eabe578af9f29e9fd543b829a691f8afc8b2c6f4cb2fbf1f9334a75ea97
5
5
  SHA512:
6
- metadata.gz: e5f300962a282b491c414a50fd8b73698e504ad22e2873827102951f8b50a632d2524e497cc182773a04d69183b8974646664f71de27775cce9abd1a9693ddcc
7
- data.tar.gz: 40b5fb6bc07be70da8f3d8b976155233d284f5627954571737818b1a929ad67976354ed02954cf0e3fb5a8d60cfb3b92dd0f81a4491402f822324eb1b82c619b
6
+ metadata.gz: abc283ed4ca2bda5cc36ff72c576fafeba7b858ed4a87b807ead8187c276690b8f995e8f6be51be41f8ac7dab90d897e43a7aca5baea38eb227465f956ef1aa3
7
+ data.tar.gz: 53f563a9dec92f02717c6cf46a953fd31f605b8888ca04e6e1b61c488a6372ec94f1703aad21e43a9198da7c265689e4046b29613e76a5728de9d0fa2bf2b5a5
data/CHANGELOG.md ADDED
@@ -0,0 +1,60 @@
1
+ # Changelog
2
+
3
+ ## 3.0 - 2026-09-27
4
+
5
+ ### Breaking changes
6
+
7
+ - **Ruby 3.3 or later is required.** Ruby 3.2 has reached end of life.
8
+ - **Responses are no longer OpenStructs.** `Paddle::Object` is now a lightweight class, and nested objects are
9
+ `Paddle::Object` rather than `OpenStruct`. Dot access, hash access (`obj[:id]`, `obj["id"]`), setters, `nil` for
10
+ missing attributes, `each_pair` and `update` work as before. Code that checks `is_a?(OpenStruct)` or uses
11
+ OpenStruct-only methods, such as `delete_field` or `to_h` with a block, will need updating. Attributes named after
12
+ built-in methods, such as `hash` or `class`, need to be read with `obj[:hash]`.
13
+ - **`to_h` now converts nested objects to hashes too**, rather than only the top level.
14
+ - **The `ostruct` and `cgi` dependencies have been removed.** If your app uses either and relied on this gem to install
15
+ it, add it to your Gemfile.
16
+ - **Every non-2xx response now raises an error.** Previously only a fixed list of status codes raised, so responses
17
+ like 422, 502 and 504 were returned as if they'd succeeded. Non-JSON error bodies, such as an HTML page from a
18
+ gateway, also raise now rather than causing a `NoMethodError`.
19
+ - **The environment is detected from the API key.** Keys starting with `pdl_live_` or `pdl_sdbx_` set the environment
20
+ automatically, so a sandbox key with no environment set now goes to the sandbox rather than production. Setting an
21
+ environment that doesn't match the key raises an `ArgumentError`. Older keys without a prefix behave as before.
22
+ - **Config changes take effect straight away.** The connection used to keep the API key, environment, version and
23
+ connection options from the first request, so later changes were ignored.
24
+ - **Array query params are sent as comma-separated lists**, e.g. `status=active,past_due`, as Paddle expects. They
25
+ were sent as `status[]=active&status[]=past_due`, which Paddle didn't read as intended.
26
+ - `ServiceUnavailableError` (503) now says the API is temporarily unavailable, rather than describing a rate limit.
27
+ Rate limits are raised as `TooManyRequestsError` (429).
28
+
29
+ ### Added
30
+
31
+ - `Paddle.with_config` to make requests with a different API key, environment or version, such as for another
32
+ Paddle account. It's safe to use in multi-threaded servers and job runners.
33
+ - `Paddle::Webhook` for verifying webhook signatures, with `verify!`, `valid?` and `construct_event`.
34
+ - `Paddle::Metric` for the metrics API: `monthly_recurring_revenue`, `monthly_recurring_revenue_change`,
35
+ `active_subscribers`, `revenue`, `refunds`, `chargebacks` and `checkout_conversion`.
36
+ - The Explore metrics API, with `Metric.explore_entities` and `Metric.explore`.
37
+ - `Collection#has_more?`, `next_page` and `auto_paging_each` for paging through results.
38
+ - `skip_count: true` on any list method, to skip counting results for faster responses.
39
+ - `Subscription.history`, `Subscription.charge_preview` and `Transaction.revise`.
40
+ - `Customer.credit_balances` to return the credit balance for every currency.
41
+ - `Notification.replay` has been restored.
42
+ - `Adjustment.create` no longer needs `items:` when `type: "full"` is given.
43
+ - `Simulation.runs` and `SimulationRun.events` accept pagination params.
44
+ - `Paddle::Object#to_json`, `as_json`, `dig` and `key?`.
45
+
46
+ ### Changed
47
+
48
+ - Building response objects is around 20x faster and uses around 20x less memory.
49
+ - Faraday 2.14.3 or later is required, for [GHSA-98m9-hrrm-r99r](https://github.com/advisories/GHSA-98m9-hrrm-r99r).
50
+ - The repository has moved to [d34ndev/paddle](https://github.com/d34ndev/paddle).
51
+ - The Classic API docs have moved to [docs/classic.md](docs/classic.md), and now cover every Classic endpoint.
52
+
53
+ ### Fixed
54
+
55
+ - `delete_request` passed headers to Faraday as query params.
56
+ - Calling `update` on an object whose model doesn't support updating now raises a `NoMethodError`.
57
+
58
+ ## Earlier versions
59
+
60
+ See the [releases on GitHub](https://github.com/d34ndev/paddle/releases) for versions before 3.0.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021-2026 Dean Perry
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  The easiest and most complete Ruby library for the Paddle APIs, both Classic and Billing.
4
4
 
5
+ Using Paddle Classic? See the [Classic API docs](docs/classic.md).
6
+
5
7
  ## Installation
6
8
 
7
9
  Add this line to your application's Gemfile:
8
10
 
9
11
  ```ruby
10
- gem "paddle", "~> 2.10"
12
+ gem "paddle", "~> 3.0"
11
13
  ```
12
14
 
13
15
  ## Billing API
@@ -33,6 +35,41 @@ Paddle.configure do |config|
33
35
  end
34
36
  ```
35
37
 
38
+ API keys created since May 2025 start with `pdl_live_` or `pdl_sdbx_`, so the environment is detected from the key and you don't need to set it:
39
+
40
+ ```ruby
41
+ Paddle.configure do |config|
42
+ config.api_key = "pdl_sdbx_apikey_..."
43
+ end
44
+
45
+ Paddle.config.environment
46
+ #=> :sandbox
47
+ ```
48
+
49
+ If you do set an environment that doesn't match the key, such as `:production` with a `pdl_sdbx_` key,
50
+ an `ArgumentError` is raised straight away, since those requests would always fail. Older API keys don't have a prefix, so set the environment for them as before. It defaults to `:production`.
51
+
52
+ ### Using Multiple API Keys
53
+
54
+ To make requests with a different API key, environment or version, such as for another Paddle account,
55
+ wrap them in `Paddle.with_config`. Everything in the block uses the given options over the global config:
56
+
57
+ ```ruby
58
+ Paddle.with_config(api_key: account.paddle_api_key) do
59
+ Paddle::Subscription.list(status: "active")
60
+ end
61
+
62
+ # Blocks can be nested, and you can change the environment or version too
63
+ Paddle.with_config(api_key: "pdl_live_apikey_...", version: 1) do
64
+ Paddle::Product.list
65
+ end
66
+ ```
67
+
68
+ When a new API key is given without an environment, the environment is detected from the key. For older keys without a prefix, the current environment is kept.
69
+
70
+ The config is stored per thread and fiber, so it's safe to use in multi-threaded servers like Puma and job
71
+ runners like Sidekiq. Concurrent requests never see each other's keys. Threads and fibers started inside the block use the same config. `Paddle.configure` always changes the global config, even inside a block.
72
+
36
73
  ### Connection Options
37
74
 
38
75
  You can pass [options](https://lostisland.github.io/faraday/#/customization/connection-options) to the underlying [Faraday](https://lostisland.github.io/faraday/) connection using `connection_options`. This is useful for setting timeouts, proxies, or SSL configuration:
@@ -52,8 +89,21 @@ end
52
89
 
53
90
  The gem maps as closely as we can to the Paddle API so you can easily convert API examples to gem code.
54
91
 
55
- Responses are created as objects like `Paddle::Product`. Having types like `Paddle::Product` is handy for understanding what
56
- type of object you're working with. They're built using OpenStruct so you can easily access data in a Ruby-ish way.
92
+ Responses are created as objects like `Paddle::Product`. Having types like `Paddle::Product` is handy for understanding what type of object you're working with. Attributes can be read with dot notation or like a hash, and nested data is wrapped too:
93
+
94
+ ```ruby
95
+ transaction = Paddle::Transaction.retrieve(id: "txn_abc123")
96
+
97
+ transaction.status #=> "completed"
98
+ transaction[:status] #=> "completed"
99
+ transaction.details.totals.total #=> "1000"
100
+ transaction.dig(:items, 0, :price, :id) #=> "pri_abc123"
101
+ transaction.missing_attribute #=> nil
102
+
103
+ # Convert back to a hash or JSON, including nested data
104
+ transaction.to_h
105
+ transaction.to_json
106
+ ```
57
107
 
58
108
  ### Pagination
59
109
 
@@ -66,8 +116,14 @@ results = Paddle::Product.list(per_page: 10)
66
116
  #=> Paddle::Collection
67
117
 
68
118
  results.total
119
+ #=> 42
120
+
121
+ results.per_page
69
122
  #=> 10
70
123
 
124
+ results.has_more?
125
+ #=> true
126
+
71
127
  results.data
72
128
  #=> [#<Paddle::Product>, #<Paddle::Product>]
73
129
 
@@ -81,9 +137,37 @@ results.first
81
137
  results.last
82
138
  #=> #<Paddle::Product>
83
139
 
84
- # Retrieve the next page
140
+ # Retrieve the next page. Returns nil when there are no more pages
141
+ results.next_page
142
+ #=> Paddle::Collection
143
+
144
+ # Or use the after cursor directly
85
145
  Paddle::Product.list(per_page: 10, after: "abc123")
86
146
  #=> Paddle::Collection
147
+
148
+ # Iterate over every result across all pages, fetching each page as it's needed
149
+ Paddle::Product.list(per_page: 50).auto_paging_each do |product|
150
+ puts product.id
151
+ end
152
+
153
+ # Without a block, auto_paging_each returns an Enumerator. Pages are only fetched until a match is found
154
+ Paddle::Customer.list.auto_paging_each.find { |customer| customer.email == "michael@mycompany.com" }
155
+ ```
156
+
157
+ >[!NOTE]
158
+ >
159
+ > `total` is Paddle's `estimated_total`. For lists of more than 100,000 results it's capped at `100001`,
160
+ > and it's `-1` when Paddle can't count the results. Use `has_more?`, `next_page` or `auto_paging_each`
161
+ > to page through results rather than relying on `total`.
162
+
163
+ If you don't need `total`, pass `skip_count: true` to any list method. This sends the `Skip-Count` header,
164
+ so Paddle skips counting the results and responds faster. `total` will be `-1`, and `next_page` and
165
+ `auto_paging_each` keep sending the header for later pages.
166
+
167
+ ```ruby
168
+ Paddle::Transaction.list(skip_count: true).auto_paging_each do |transaction|
169
+ puts transaction.id
170
+ end
87
171
  ```
88
172
 
89
173
  ### Caveats
@@ -92,6 +176,14 @@ Paddle::Product.list(per_page: 10, after: "abc123")
92
176
  >
93
177
  > 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.
94
178
 
179
+ When filtering a list, you can pass an array or a comma-separated string to filter by more than one value. Arrays are sent as comma-separated lists, as Paddle expects:
180
+
181
+ ```ruby
182
+ Paddle::Subscription.list(status: [ "active", "past_due" ])
183
+ # is the same as
184
+ Paddle::Subscription.list(status: "active,past_due")
185
+ ```
186
+
95
187
  ### Error Handling
96
188
 
97
189
  When API requests fail, the gem provides detailed error information to help you debug issues. Errors are raised as exceptions with comprehensive details including field-level validation errors.
@@ -116,6 +208,7 @@ All errors inherit from `Paddle::ErrorGenerator` and include:
116
208
  - `Paddle::Errors::ConflictError` (409) - Request conflicts with existing data
117
209
  - `Paddle::Errors::TooManyRequestsError` (429) - Rate limit exceeded
118
210
  - `Paddle::Errors::InternalError` (500) - Server error
211
+ - `Paddle::Errors::NotImplementedError` (501) - Resource not implemented
119
212
  - `Paddle::Errors::ServiceUnavailableError` (503) - Service unavailable
120
213
 
121
214
  #### Error Example
@@ -291,8 +384,12 @@ customer.update(status: "archived")
291
384
  # or
292
385
  Paddle::Customer.update(id: "ctm_abc123", status: "archived")
293
386
 
294
- # Retrieve credit balance for a customer
387
+ # List credit balances for a customer. Customers have a balance for each currency
295
388
  # https://developer.paddle.com/api-reference/customers/list-credit-balances
389
+ Paddle::Customer.credit_balances(id: "ctm_abc123")
390
+ Paddle::Customer.credit_balances(id: "ctm_abc123", currency_code: "USD")
391
+
392
+ # Retrieve the first credit balance for a customer
296
393
  Paddle::Customer.credit(id: "ctm_abc123")
297
394
 
298
395
  # Generate an authentication token for a customer
@@ -364,21 +461,32 @@ Paddle::Transaction.retrieve(id: "txn_abc123")
364
461
  transaction = Paddle::Transaction.retrieve(id: "txn_abc123", extra: "customer")
365
462
 
366
463
  # Update a transaction
367
- # https://developer.paddle.com/api-reference/transaction/update-transaction
464
+ # https://developer.paddle.com/api-reference/transactions/update-transaction
368
465
  transaction.update(items: [ { price_id: "pri_abc123", quantity: 2 } ])
369
466
  # or
370
467
  Paddle::Transaction.update(id: "txn_abc123", items: [ { price_id: "pri_abc123", quantity: 2 } ])
371
468
 
372
469
  # Preview a transaction
373
- # https://developer.paddle.com/api-reference/transaction/preview-transaction
470
+ # https://developer.paddle.com/api-reference/transactions/preview-transaction
374
471
  Paddle::Transaction.preview(items: [ { price_id: "pri_123abc", quantity: 5 } ])
375
472
 
376
473
  # Get a PDF invoice for a transaction
377
474
  # disposition defaults to "attachment"
378
475
  # Returns a raw URL. This URL is not permanent and will expire.
379
- # https://developer.paddle.com/api-reference/transaction/get-invoice-pdf
476
+ # https://developer.paddle.com/api-reference/transactions/get-invoice-pdf
380
477
  Paddle::Transaction.invoice(id: "txn_abc123", disposition: "inline")
381
478
  #=> https://paddle-sandbox-invoice...
479
+
480
+ # Revise customer, business and address details on a billed or completed transaction
481
+ # Only address lines, city and region can be changed, and a transaction can only be revised once.
482
+ # The related customer, business and address records aren't updated
483
+ # https://developer.paddle.com/api-reference/transactions/revise-transaction
484
+ Paddle::Transaction.revise(
485
+ id: "txn_abc123",
486
+ customer: { name: "Sam Miller" },
487
+ business: { tax_identifier: "AB0123456789" },
488
+ address: { first_line: "3811 Ditmars Blvd" }
489
+ )
382
490
  ```
383
491
 
384
492
  ### Subscriptions
@@ -418,6 +526,11 @@ Paddle::Subscription.get_transaction(id: "sub_abc123")
418
526
  # https://developer.paddle.com/api-reference/subscriptions/create-one-time-charge
419
527
  Paddle::Subscription.charge(id: "sub_abc123", items: [ { price_id: "pri_123abc", quantity: 2 } ], effective_from: "immediately")
420
528
 
529
+ # Preview a one-time charge for a subscription without billing it
530
+ # Returns a Subscription with immediate_transaction and next_transaction previews
531
+ # https://developer.paddle.com/api-reference/subscriptions/preview-subscription-charge
532
+ Paddle::Subscription.charge_preview(id: "sub_abc123", items: [ { price_id: "pri_123abc", quantity: 2 } ], effective_from: "immediately")
533
+
421
534
  # Pause a subscription
422
535
  # https://developer.paddle.com/api-reference/subscriptions/pause-subscription
423
536
  Paddle::Subscription.pause(id: "sub_abc123")
@@ -437,6 +550,18 @@ Paddle::Subscription.cancel(id: "sub_abc123", effective_from: "immediately")
437
550
  # Activate a trialing subscription
438
551
  # https://developer.paddle.com/api-reference/subscriptions/activate-subscription
439
552
  Paddle::Subscription.activate(id: "sub_abc123")
553
+
554
+ # List the history of a subscription, newest first
555
+ # Returns a Paddle::Collection of Paddle::SubscriptionHistory
556
+ # https://developer.paddle.com/api-reference/subscription-history/list-subscription-history
557
+ Paddle::Subscription.history(id: "sub_abc123")
558
+ Paddle::Subscription.history(id: "sub_abc123", action: [ "subscription_created", "subscription_canceled" ])
559
+ Paddle::Subscription.history(id: "sub_abc123", source: "customer_portal", actor_type: "customer")
560
+ Paddle::Subscription.history(id: "sub_abc123", "occurred_at[GTE]": "2026-01-01T00:00:00Z")
561
+
562
+ Paddle::Subscription.history(id: "sub_abc123").auto_paging_each do |entry|
563
+ puts "#{entry.occurred_at} #{entry.detail.action} by #{entry.actor.type} via #{entry.source}"
564
+ end
440
565
  ```
441
566
 
442
567
  ### Customer Portal Sessions
@@ -466,11 +591,38 @@ Paddle::Adjustment.create(
466
591
  items: [
467
592
  {
468
593
  type: "full",
469
- item_id: "txnitm_anc123"
594
+ item_id: "txnitm_abc123"
595
+ }
596
+ ]
597
+ )
598
+
599
+ # Partially refund an item
600
+ # amount is in the lowest denomination of the currency, e.g. "500" is $5.00 for USD
601
+ Paddle::Adjustment.create(
602
+ action: "refund",
603
+ transaction_id: "txn_abc123",
604
+ reason: "Requested by customer",
605
+ items: [
606
+ {
607
+ type: "partial",
608
+ item_id: "txnitm_abc123",
609
+ amount: "500"
470
610
  }
471
611
  ]
472
612
  )
473
613
 
614
+ # Refund or credit the grand total of a transaction without specifying items
615
+ Paddle::Adjustment.create(
616
+ action: "refund",
617
+ transaction_id: "txn_abc123",
618
+ reason: "Requested by customer",
619
+ type: "full"
620
+ )
621
+
622
+ # Refunds are created with a status of "pending_approval" and are reviewed by Paddle
623
+ # before being processed. Listen for the adjustment.updated webhook to know when
624
+ # a refund has been approved or rejected.
625
+
474
626
  # Get a credit note for an adjustment
475
627
  # disposition defaults to "attachment"
476
628
  # Returns a raw URL. This URL is not permanent and will expire.
@@ -561,9 +713,11 @@ Paddle::Notification.list(status: "failed")
561
713
  Paddle::Notification.retrieve(id: "ntf_abc123")
562
714
 
563
715
  # Replay a notification
564
- # Attempts to resend a notification
565
- # (currently not working)
716
+ # Creates a new notification for the same event. Only delivered or failed notifications
717
+ # with an origin of "event" can be replayed. Returns the new notification_id
718
+ # https://developer.paddle.com/api-reference/notifications/replay-notification
566
719
  Paddle::Notification.replay(id: "ntf_abc123")
720
+ #=> #<Paddle::Notification notification_id="ntf_abc456">
567
721
 
568
722
  # List all logs for a notification
569
723
  # https://developer.paddle.com/api-reference/notifications/list-notification-logs
@@ -595,6 +749,116 @@ Paddle::Report.create(
595
749
  )
596
750
  ```
597
751
 
752
+ ### Metrics
753
+
754
+ Daily metrics for your account. `from` and `to` are dates, as a string like `"2025-09-01"` or a `Date`.
755
+ `from` is inclusive and `to` is exclusive, and you can query up to 3 years in the past.
756
+ Your API key needs the `metrics.read` permission.
757
+
758
+ ```ruby
759
+ # https://developer.paddle.com/api-reference/metrics/overview
760
+ metric = Paddle::Metric.monthly_recurring_revenue(from: "2025-09-01", to: "2025-09-05")
761
+ #=> #<Paddle::Metric interval="day", currency_code="USD", starts_at=..., ends_at=..., timeseries=[...]>
762
+
763
+ metric.timeseries.each do |point|
764
+ puts "#{point.timestamp}: #{point.amount}"
765
+ end
766
+
767
+ # Amounts are strings in the smallest currency unit, e.g. cents
768
+ Paddle::Metric.monthly_recurring_revenue_change(from: "2025-09-01", to: "2025-09-05") # amount
769
+ Paddle::Metric.revenue(from: "2025-09-01", to: "2025-09-05") # amount and count
770
+ Paddle::Metric.refunds(from: "2025-09-01", to: "2025-09-05") # amount
771
+ Paddle::Metric.active_subscribers(from: "2025-09-01", to: "2025-09-05") # count
772
+ Paddle::Metric.chargebacks(from: "2025-09-01", to: "2025-09-05") # count
773
+ Paddle::Metric.checkout_conversion(from: "2025-09-01", to: "2025-09-05") # count, completed_count and rate
774
+ ```
775
+
776
+ #### Explore
777
+
778
+ Explore lets you run your own queries against your account data. Pick an entity and one or more measures,
779
+ then optionally filter the results and break them down by dimensions. Each combination of dimension values is a
780
+ segment, returned as one entry in `series`.
781
+
782
+ ```ruby
783
+ # List the entities you can query, with their dimensions, measures and allowed intervals
784
+ # https://developer.paddle.com/api-reference/metrics/list-explore-metric-entities
785
+ Paddle::Metric.explore_entities
786
+ Paddle::Metric.explore_entities(entity: "transactions.completed")
787
+
788
+ # Run a query. from is inclusive and to is exclusive. interval can be day, week or month (the default)
789
+ # https://developer.paddle.com/api-reference/metrics/run-explore-metrics-query
790
+ result = Paddle::Metric.explore(
791
+ entity: "transactions.completed",
792
+ from: "2026-05-01",
793
+ to: "2026-08-01",
794
+ interval: "month",
795
+ measures: [ { field: "gross_revenue", agg: "sum" } ],
796
+ dimensions: [ "product" ],
797
+ order_by: [ { field: "gross_revenue", dir: "desc" } ],
798
+ filters: [ { field: "country", operator: "in", value: [ "GB", "US" ] } ]
799
+ )
800
+ #=> Paddle::ExploreResult
801
+
802
+ result.series.each do |segment|
803
+ puts segment.dimensions.product
804
+ segment.timeseries.each { |point| puts " #{point.timestamp}: #{point.measures.sum_gross_revenue}" }
805
+ end
806
+
807
+ # Results are paged by segment, 5 per page by default (max 50 using per_page)
808
+ result.has_more?
809
+ result.next_page
810
+ #=> Paddle::ExploreResult
811
+
812
+ # Iterate over every segment across all pages
813
+ result.auto_paging_each do |segment|
814
+ puts segment.dimensions.product
815
+ end
816
+ ```
817
+
818
+ ### Verifying Webhooks
819
+
820
+ Paddle signs every webhook it sends with a `Paddle-Signature` header. Verify it before trusting the payload.
821
+ You'll need the endpoint secret key for your notification destination, which is the `endpoint_secret_key` on its
822
+ `Paddle::NotificationSetting`, or in the dashboard under Developer Tools > Notifications.
823
+
824
+ The payload must be the **raw request body**. If it's parsed or reformatted first, the signature won't match.
825
+
826
+ ```ruby
827
+ # https://developer.paddle.com/webhooks/signature-verification
828
+ class PaddleWebhooksController < ApplicationController
829
+ skip_forgery_protection
830
+
831
+ def create
832
+ event = Paddle::Webhook.construct_event(
833
+ payload: request.raw_post,
834
+ signature: request.headers["Paddle-Signature"],
835
+ secret: ENV["PADDLE_WEBHOOK_SECRET"]
836
+ )
837
+ #=> Paddle::Event
838
+
839
+ case event.event_type
840
+ when "transaction.completed"
841
+ # event.data.id, event.data.customer_id, ...
842
+ when "subscription.canceled"
843
+ # ...
844
+ end
845
+
846
+ head :ok
847
+ rescue Paddle::Webhook::SignatureVerificationError
848
+ head :bad_request
849
+ end
850
+ end
851
+
852
+ # Or just verify the signature. verify! returns true or raises
853
+ # Paddle::Webhook::SignatureVerificationError, and valid? returns true or false
854
+ Paddle::Webhook.verify!(payload: payload, signature: signature, secret: secret)
855
+ Paddle::Webhook.valid?(payload: payload, signature: signature, secret: secret)
856
+ ```
857
+
858
+ Webhooks are rejected if their timestamp is more than 5 seconds from the current time, to stop old requests being
859
+ replayed. You can change this with `tolerance:` (in seconds), or pass `tolerance: nil` to skip the check, for example
860
+ when testing with a stored webhook.
861
+
598
862
  ### Webhook Simulation Types
599
863
 
600
864
  Retrieves a list of Simulation Types - <https://developer.paddle.com/api-reference/simulation-types/overview>
@@ -613,8 +877,8 @@ Paddle::Simulation.list(notification_setting_id: "nftset_abc123")
613
877
 
614
878
  # Create a simulation
615
879
  # https://developer.paddle.com/api-reference/simulations/create-simulation
616
- Paddle::Simulation.create(notification_setting_id: "ntfset_abc123", name: "Customer Create", type: "customer.completed")
617
- Paddle::Simulation.create(notification_setting_id: "ntfset_abc123", name: "Subscription Created", type: "subscription_creation")
880
+ Paddle::Simulation.create(setting_id: "ntfset_abc123", name: "Customer Create", type: "customer.completed")
881
+ Paddle::Simulation.create(setting_id: "ntfset_abc123", name: "Subscription Created", type: "subscription_creation")
618
882
 
619
883
  # Retrieve a simulation
620
884
  Paddle::Simulation.retrieve(id: "ntfsim_abc123")
@@ -626,6 +890,7 @@ Paddle::Simulation.update(id: "ntfsim_abc123", status: "archived")
626
890
 
627
891
  # List all simulation runs
628
892
  Paddle::Simulation.runs(id: "ntfsim_abc123")
893
+ Paddle::Simulation.runs(id: "ntfsim_abc123", per_page: 10, include: "events")
629
894
 
630
895
  # Create a simulation run
631
896
  # https://developer.paddle.com/api-reference/simulations/create-simulation-run
@@ -635,7 +900,11 @@ Paddle::SimulationRun.create(simulation_id: "ntfsim_abc123")
635
900
  Paddle::SimulationRun.retrieve(simulation_id: "ntfsim_abc123", id: "ntfsimrun_abc123")
636
901
 
637
902
  # List all simulation run events
638
- Paddle::SimulationRun.events(simulation_id: "ntfsim_abc123", run_id: "ntfsimrun_abc123")
903
+ Paddle::SimulationRun.events(simulation_id: "ntfsim_abc123", id: "ntfsimrun_abc123")
904
+ Paddle::SimulationRun.events(simulation_id: "ntfsim_abc123", id: "ntfsimrun_abc123", per_page: 10)
905
+
906
+ # Retrieve a simulation run event
907
+ Paddle::SimulationRunEvent.retrieve(simulation_id: "ntfsim_abc123", run_id: "ntfsimrun_abc123", id: "ntfsimevt_abc123")
639
908
 
640
909
  # Replay a simulation run event
641
910
  # https://developer.paddle.com/api-reference/simulations/replay-simulation-run-event
@@ -662,64 +931,9 @@ Paddle::ClientToken.retrieve id: "ctkn_abc123"
662
931
  Paddle::ClientToken.update id: "ctkn_abc123", status: "revoked"
663
932
  ```
664
933
 
665
- ## Classic API
666
-
667
- For accessing the Paddle Classic API
668
-
669
- ### Set Client Details
670
-
671
- Firstly you'll need to set your Vendor ID, Vendor Auth Code and if you want
672
- to use the Sandbox API or not.
673
-
674
- You can find your vendor details [here for production](https://vendors.paddle.com/authentication),
675
- or [here for sandbox](https://sandbox-vendors.paddle.com/authentication)
676
-
677
- ```ruby
678
- @client = Paddle::Classic::Client.new(
679
- vendor_id: "",
680
- vendor_auth_code: "",
681
- # Use the sandbox version of the API
682
- sandbox: true
683
- )
684
- ```
685
-
686
- ### Plans
687
-
688
- ```ruby
689
- # Retrieves a list of Plans
690
- @client.plans.list
691
- ```
692
-
693
- ### Subscription Users
694
-
695
- ```ruby
696
- # List all users subscribed to any plan
697
- @client.users.list
698
- @client.users.list(subscription_id: "abc123")
699
- @client.users.list(plan_id: "abc123")
700
- @client.users.list(state: "active")
701
- @client.users.list(state: "deleted")
702
-
703
- # Update a user's subscription
704
- # https://developer.paddle.com/api-reference/e3872343dfbba-update-user
705
- @client.users.update(subscription_id: "abc123")
706
-
707
- # Pause a user's subscription
708
- @client.users.pause(subscription_id: "abc123")
709
-
710
- # Unpause a user's subscription
711
- @client.users.unpause(subscription_id: "abc123")
712
-
713
- # Update the Postcode/ZIP Code of a user's subscription
714
- @client.users.update_postcode(subscription_id: "abc123", postcode: "123abc")
715
-
716
- # Cancel a user's subscription
717
- @client.users.cancel(subscription_id: "abc123")
718
- ```
719
-
720
934
  ## Contributing
721
935
 
722
- Bug reports and pull requests are welcome on GitHub at <https://github.com/deanpcmad/paddle>.
936
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/d34ndev/paddle>.
723
937
 
724
938
  ## License
725
939
 
@@ -71,7 +71,7 @@ module Paddle
71
71
  def connection
72
72
  @connection ||= Faraday.new(url) do |conn|
73
73
  conn.headers = {
74
- "User-Agent" => "paddle/v#{VERSION} (github.com/deanpcmad/paddle)"
74
+ "User-Agent" => "paddle/v#{VERSION} (github.com/d34ndev/paddle)"
75
75
  }
76
76
 
77
77
  conn.request :url_encoded