polar-ruby 0.1.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 +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +50 -0
- data/DEVELOPMENT.md +329 -0
- data/EXAMPLES.md +385 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +115 -0
- data/LICENSE +23 -0
- data/PROJECT_SUMMARY.md +256 -0
- data/README.md +635 -0
- data/Rakefile +24 -0
- data/examples/demo.rb +106 -0
- data/lib/polar/authentication.rb +83 -0
- data/lib/polar/client.rb +144 -0
- data/lib/polar/configuration.rb +46 -0
- data/lib/polar/customer_portal/benefit_grants.rb +41 -0
- data/lib/polar/customer_portal/customers.rb +69 -0
- data/lib/polar/customer_portal/license_keys.rb +70 -0
- data/lib/polar/customer_portal/orders.rb +82 -0
- data/lib/polar/customer_portal/subscriptions.rb +51 -0
- data/lib/polar/errors.rb +96 -0
- data/lib/polar/http_client.rb +150 -0
- data/lib/polar/pagination.rb +133 -0
- data/lib/polar/resources/base.rb +47 -0
- data/lib/polar/resources/benefits.rb +64 -0
- data/lib/polar/resources/checkouts.rb +75 -0
- data/lib/polar/resources/customers.rb +120 -0
- data/lib/polar/resources/events.rb +45 -0
- data/lib/polar/resources/files.rb +57 -0
- data/lib/polar/resources/license_keys.rb +81 -0
- data/lib/polar/resources/metrics.rb +30 -0
- data/lib/polar/resources/oauth2.rb +61 -0
- data/lib/polar/resources/orders.rb +54 -0
- data/lib/polar/resources/organizations.rb +41 -0
- data/lib/polar/resources/payments.rb +29 -0
- data/lib/polar/resources/products.rb +58 -0
- data/lib/polar/resources/subscriptions.rb +55 -0
- data/lib/polar/resources/webhooks.rb +81 -0
- data/lib/polar/version.rb +5 -0
- data/lib/polar/webhooks.rb +174 -0
- data/lib/polar.rb +65 -0
- metadata +239 -0
data/lib/polar/errors.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Polar
|
4
|
+
# Base error class for all Polar SDK errors
|
5
|
+
class Error < StandardError
|
6
|
+
attr_reader :status_code, :headers, :body
|
7
|
+
|
8
|
+
def initialize(message, status_code: nil, headers: {}, body: nil)
|
9
|
+
super(message)
|
10
|
+
@status_code = status_code
|
11
|
+
@headers = headers || {}
|
12
|
+
@body = body
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# HTTP error base class
|
17
|
+
class HTTPError < Error
|
18
|
+
def initialize(response)
|
19
|
+
@status_code = response.status
|
20
|
+
@headers = response.headers
|
21
|
+
@body = response.body
|
22
|
+
|
23
|
+
message = parse_error_message(response)
|
24
|
+
super(message, status_code: @status_code, headers: @headers, body: @body)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def parse_error_message(response)
|
30
|
+
return "HTTP #{response.status}" unless response.body
|
31
|
+
|
32
|
+
begin
|
33
|
+
# Handle both already parsed JSON (Hash) and raw JSON string
|
34
|
+
parsed = response.body.is_a?(Hash) ? response.body : JSON.parse(response.body)
|
35
|
+
parsed['detail'] || parsed['message'] || "HTTP #{response.status}"
|
36
|
+
rescue JSON::ParserError, TypeError
|
37
|
+
"HTTP #{response.status}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Specific HTTP error classes matching Polar API responses
|
43
|
+
class BadRequestError < HTTPError; end
|
44
|
+
class UnauthorizedError < HTTPError; end
|
45
|
+
class ForbiddenError < HTTPError; end
|
46
|
+
class NotFoundError < HTTPError; end
|
47
|
+
class MethodNotAllowedError < HTTPError; end
|
48
|
+
class UnprocessableEntityError < HTTPError; end
|
49
|
+
class TooManyRequestsError < HTTPError; end
|
50
|
+
class InternalServerError < HTTPError; end
|
51
|
+
class BadGatewayError < HTTPError; end
|
52
|
+
class ServiceUnavailableError < HTTPError; end
|
53
|
+
class GatewayTimeoutError < HTTPError; end
|
54
|
+
|
55
|
+
# Specific Polar API errors
|
56
|
+
class ValidationError < UnprocessableEntityError; end
|
57
|
+
class ResourceNotFoundError < NotFoundError; end
|
58
|
+
class NotPermittedError < ForbiddenError; end
|
59
|
+
class AlreadyCanceledSubscriptionError < ForbiddenError; end
|
60
|
+
class AlreadyActiveSubscriptionError < ForbiddenError; end
|
61
|
+
|
62
|
+
# Connection and timeout errors
|
63
|
+
class ConnectionError < Error; end
|
64
|
+
class TimeoutError < Error; end
|
65
|
+
class RetryExhaustedError < Error; end
|
66
|
+
|
67
|
+
# Authentication errors
|
68
|
+
class AuthenticationError < Error; end
|
69
|
+
class InvalidTokenError < AuthenticationError; end
|
70
|
+
|
71
|
+
# Webhook errors
|
72
|
+
class WebhookError < Error; end
|
73
|
+
class WebhookVerificationError < WebhookError; end
|
74
|
+
|
75
|
+
class << self
|
76
|
+
# Map HTTP status codes to error classes
|
77
|
+
STATUS_CODE_TO_ERROR = {
|
78
|
+
400 => BadRequestError,
|
79
|
+
401 => UnauthorizedError,
|
80
|
+
403 => ForbiddenError,
|
81
|
+
404 => NotFoundError,
|
82
|
+
405 => MethodNotAllowedError,
|
83
|
+
422 => UnprocessableEntityError,
|
84
|
+
429 => TooManyRequestsError,
|
85
|
+
500 => InternalServerError,
|
86
|
+
502 => BadGatewayError,
|
87
|
+
503 => ServiceUnavailableError,
|
88
|
+
504 => GatewayTimeoutError
|
89
|
+
}.freeze
|
90
|
+
|
91
|
+
def error_for_status(response)
|
92
|
+
error_class = STATUS_CODE_TO_ERROR[response.status] || HTTPError
|
93
|
+
error_class.new(response)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Polar
|
4
|
+
class HTTPClient
|
5
|
+
attr_reader :configuration, :auth
|
6
|
+
|
7
|
+
def initialize(configuration, auth = nil)
|
8
|
+
@configuration = configuration
|
9
|
+
@auth = auth
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(path, params = {}, headers = {})
|
13
|
+
request(:get, path, params: params, headers: headers)
|
14
|
+
end
|
15
|
+
|
16
|
+
def post(path, body = nil, headers = {})
|
17
|
+
request(:post, path, body: body, headers: headers)
|
18
|
+
end
|
19
|
+
|
20
|
+
def put(path, body = nil, headers = {})
|
21
|
+
request(:put, path, body: body, headers: headers)
|
22
|
+
end
|
23
|
+
|
24
|
+
def patch(path, body = nil, headers = {})
|
25
|
+
request(:patch, path, body: body, headers: headers)
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete(path, headers = {})
|
29
|
+
request(:delete, path, headers: headers)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def request(method, path, params: nil, body: nil, headers: {})
|
35
|
+
url = build_url(path)
|
36
|
+
request_headers = build_headers(headers)
|
37
|
+
|
38
|
+
log_request(method, url, body) if configuration.debug
|
39
|
+
|
40
|
+
response = connection.public_send(method) do |req|
|
41
|
+
req.url url
|
42
|
+
req.headers.update(request_headers)
|
43
|
+
params.each { |key, value| req.params[key.to_s] = value } if params && method == :get
|
44
|
+
req.body = prepare_body(body) if body && %i[post put patch].include?(method)
|
45
|
+
end
|
46
|
+
|
47
|
+
log_response(response) if configuration.debug
|
48
|
+
|
49
|
+
handle_response(response)
|
50
|
+
rescue Faraday::Error => e
|
51
|
+
handle_faraday_error(e)
|
52
|
+
end
|
53
|
+
|
54
|
+
def connection
|
55
|
+
@connection ||= Faraday.new do |conn|
|
56
|
+
conn.request :json
|
57
|
+
conn.request :multipart
|
58
|
+
conn.request :retry, retry_options
|
59
|
+
conn.response :json, content_type: /\bjson$/
|
60
|
+
conn.options.timeout = configuration.timeout
|
61
|
+
conn.adapter Faraday.default_adapter
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def retry_options
|
66
|
+
{
|
67
|
+
max: configuration.retries,
|
68
|
+
interval: 0.5,
|
69
|
+
interval_randomness: 0.5,
|
70
|
+
backoff_factor: 2,
|
71
|
+
exceptions: [
|
72
|
+
Faraday::ConnectionFailed,
|
73
|
+
Faraday::TimeoutError,
|
74
|
+
Faraday::RetriableResponse
|
75
|
+
],
|
76
|
+
retry_statuses: [429, 500, 502, 503, 504],
|
77
|
+
retry_if: ->(env, _exception) { env.method != :post || retriable_post?(env) }
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def retriable_post?(env)
|
82
|
+
# Only retry POST requests that are idempotent (like searches)
|
83
|
+
env.url.path.include?('/search') || env.url.path.include?('/validate')
|
84
|
+
end
|
85
|
+
|
86
|
+
def build_url(path)
|
87
|
+
path.start_with?('http') ? path : "#{configuration.base_url}#{path}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def build_headers(headers)
|
91
|
+
default_headers = {
|
92
|
+
'User-Agent' => "polar-ruby/#{Polar::VERSION}",
|
93
|
+
'Accept' => 'application/json',
|
94
|
+
'Content-Type' => 'application/json'
|
95
|
+
}
|
96
|
+
|
97
|
+
default_headers.merge!(auth.headers) if auth
|
98
|
+
default_headers.merge(headers)
|
99
|
+
end
|
100
|
+
|
101
|
+
def prepare_body(body)
|
102
|
+
case body
|
103
|
+
when Hash, Array
|
104
|
+
body.to_json
|
105
|
+
when String
|
106
|
+
body
|
107
|
+
else
|
108
|
+
body.to_s
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def handle_response(response)
|
113
|
+
case response.status
|
114
|
+
when 200..299
|
115
|
+
response
|
116
|
+
when 400..499, 500..599
|
117
|
+
raise Polar.error_for_status(response)
|
118
|
+
else
|
119
|
+
raise HTTPError.new(response)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def handle_faraday_error(error)
|
124
|
+
case error
|
125
|
+
when Faraday::ConnectionFailed
|
126
|
+
raise ConnectionError, "Failed to connect to Polar API: #{error.message}"
|
127
|
+
when Faraday::TimeoutError
|
128
|
+
raise TimeoutError, "Request timed out: #{error.message}"
|
129
|
+
when Faraday::RetriableResponse
|
130
|
+
raise RetryExhaustedError, "Request failed after #{configuration.retries} retries: #{error.message}"
|
131
|
+
else
|
132
|
+
raise Error, "Network error: #{error.message}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def log_request(method, url, body)
|
137
|
+
return unless configuration.logger
|
138
|
+
|
139
|
+
configuration.logger.debug("Polar API Request: #{method.upcase} #{url}")
|
140
|
+
configuration.logger.debug("Request Body: #{body}") if body
|
141
|
+
end
|
142
|
+
|
143
|
+
def log_response(response)
|
144
|
+
return unless configuration.logger
|
145
|
+
|
146
|
+
configuration.logger.debug("Polar API Response: #{response.status}")
|
147
|
+
configuration.logger.debug("Response Body: #{response.body}") if response.body
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Polar
|
4
|
+
class PaginatedResponse
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :client, :path, :params, :headers, :current_page, :total_count
|
8
|
+
|
9
|
+
def initialize(client, path, params = {}, headers = {})
|
10
|
+
@client = client
|
11
|
+
@path = path
|
12
|
+
@params = params.dup
|
13
|
+
@headers = headers
|
14
|
+
@current_page = nil
|
15
|
+
@total_count = nil
|
16
|
+
@items_cache = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def each(&block)
|
20
|
+
return enum_for(:each) unless block_given?
|
21
|
+
|
22
|
+
page = 1
|
23
|
+
loop do
|
24
|
+
response_data = fetch_page(page)
|
25
|
+
items = extract_items(response_data)
|
26
|
+
|
27
|
+
break if items.empty?
|
28
|
+
|
29
|
+
items.each(&block)
|
30
|
+
|
31
|
+
# Check if there are more pages
|
32
|
+
break unless has_next_page?(response_data, page)
|
33
|
+
|
34
|
+
page += 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def auto_paginate
|
39
|
+
all_items = []
|
40
|
+
each { |item| all_items << item }
|
41
|
+
all_items
|
42
|
+
end
|
43
|
+
|
44
|
+
def first_page
|
45
|
+
@first_page ||= fetch_page(1)
|
46
|
+
end
|
47
|
+
|
48
|
+
def page(page_number)
|
49
|
+
fetch_page(page_number)
|
50
|
+
end
|
51
|
+
|
52
|
+
def count
|
53
|
+
first_page['pagination']['total'] if first_page.dig('pagination')
|
54
|
+
end
|
55
|
+
|
56
|
+
def total_pages
|
57
|
+
pagination_info = first_page.dig('pagination')
|
58
|
+
return nil unless pagination_info
|
59
|
+
|
60
|
+
total = pagination_info['total']
|
61
|
+
per_page = pagination_info['per_page'] || pagination_info['limit'] || 20
|
62
|
+
(total.to_f / per_page).ceil
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def fetch_page(page_number)
|
68
|
+
return @items_cache[page_number] if @items_cache[page_number]
|
69
|
+
|
70
|
+
page_params = @params.merge(page: page_number)
|
71
|
+
response = @client.http_client.get(@path, page_params, @headers)
|
72
|
+
|
73
|
+
response_data = response.body
|
74
|
+
@items_cache[page_number] = response_data
|
75
|
+
@current_page = page_number
|
76
|
+
|
77
|
+
response_data
|
78
|
+
end
|
79
|
+
|
80
|
+
def extract_items(response_data)
|
81
|
+
# The response could have different structures
|
82
|
+
if response_data.is_a?(Hash)
|
83
|
+
# Check for common pagination response structures
|
84
|
+
if response_data.key?('data')
|
85
|
+
response_data['data']
|
86
|
+
elsif response_data.key?('items')
|
87
|
+
response_data['items']
|
88
|
+
elsif response_data.key?('results')
|
89
|
+
response_data['results']
|
90
|
+
else
|
91
|
+
# If it's a hash but no data key, it might be the items themselves
|
92
|
+
[]
|
93
|
+
end
|
94
|
+
elsif response_data.is_a?(Array)
|
95
|
+
response_data
|
96
|
+
else
|
97
|
+
[]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def has_next_page?(response_data, current_page)
|
102
|
+
return false unless response_data.is_a?(Hash)
|
103
|
+
|
104
|
+
pagination = response_data['pagination']
|
105
|
+
return false unless pagination
|
106
|
+
|
107
|
+
# Check various pagination indicators
|
108
|
+
if pagination.key?('has_next')
|
109
|
+
pagination['has_next']
|
110
|
+
elsif pagination.key?('next_page')
|
111
|
+
!pagination['next_page'].nil?
|
112
|
+
elsif pagination.key?('total')
|
113
|
+
total = pagination['total']
|
114
|
+
per_page = pagination['per_page'] || pagination['limit'] || 20
|
115
|
+
current_page * per_page < total
|
116
|
+
else
|
117
|
+
# If no clear pagination info, check if we got any items
|
118
|
+
items = extract_items(response_data)
|
119
|
+
!items.empty?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
module Pagination
|
125
|
+
def paginate(path, params = {}, headers = {})
|
126
|
+
PaginatedResponse.new(self, path, params, headers)
|
127
|
+
end
|
128
|
+
|
129
|
+
def auto_paginate(path, params = {}, headers = {})
|
130
|
+
paginate(path, params, headers).auto_paginate
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Polar
|
4
|
+
module Resources
|
5
|
+
class Base
|
6
|
+
attr_reader :client
|
7
|
+
|
8
|
+
def initialize(client)
|
9
|
+
@client = client
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def http_client
|
15
|
+
client.http_client
|
16
|
+
end
|
17
|
+
|
18
|
+
def get(path, params = {}, headers = {})
|
19
|
+
http_client.get(path, params, headers)
|
20
|
+
end
|
21
|
+
|
22
|
+
def post(path, body = nil, headers = {})
|
23
|
+
http_client.post(path, body, headers)
|
24
|
+
end
|
25
|
+
|
26
|
+
def put(path, body = nil, headers = {})
|
27
|
+
http_client.put(path, body, headers)
|
28
|
+
end
|
29
|
+
|
30
|
+
def patch(path, body = nil, headers = {})
|
31
|
+
http_client.patch(path, body, headers)
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(path, headers = {})
|
35
|
+
http_client.delete(path, headers)
|
36
|
+
end
|
37
|
+
|
38
|
+
def paginate(path, params = {}, headers = {})
|
39
|
+
client.paginate(path, params, headers)
|
40
|
+
end
|
41
|
+
|
42
|
+
def auto_paginate(path, params = {}, headers = {})
|
43
|
+
client.auto_paginate(path, params, headers)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Polar
|
6
|
+
module Resources
|
7
|
+
class Benefits < Base
|
8
|
+
# List benefits
|
9
|
+
# @param params [Hash] Query parameters
|
10
|
+
# @option params [String] :organization_id Filter by organization ID
|
11
|
+
# @option params [String] :type Filter by benefit type
|
12
|
+
# @option params [Integer] :page Page number
|
13
|
+
# @option params [Integer] :limit Items per page
|
14
|
+
# @return [PaginatedResponse] Paginated list of benefits
|
15
|
+
def list(params = {})
|
16
|
+
paginate('/benefits/', params)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Create a benefit
|
20
|
+
# @param attributes [Hash] Benefit attributes
|
21
|
+
# @option attributes [String] :type Benefit type
|
22
|
+
# @option attributes [String] :description Benefit description
|
23
|
+
# @option attributes [String] :organization_id Organization ID
|
24
|
+
# @return [Hash] Created benefit
|
25
|
+
def create(attributes)
|
26
|
+
response = post('/benefits/', attributes)
|
27
|
+
response.body
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get a benefit by ID
|
31
|
+
# @param id [String] Benefit ID
|
32
|
+
# @return [Hash] Benefit data
|
33
|
+
def get(id)
|
34
|
+
response = get("/benefits/#{id}")
|
35
|
+
response.body
|
36
|
+
end
|
37
|
+
|
38
|
+
# Update a benefit
|
39
|
+
# @param id [String] Benefit ID
|
40
|
+
# @param attributes [Hash] Updated attributes
|
41
|
+
# @return [Hash] Updated benefit
|
42
|
+
def update(id, attributes)
|
43
|
+
response = patch("/benefits/#{id}", attributes)
|
44
|
+
response.body
|
45
|
+
end
|
46
|
+
|
47
|
+
# Delete a benefit
|
48
|
+
# @param id [String] Benefit ID
|
49
|
+
# @return [Boolean] Success status
|
50
|
+
def delete(id)
|
51
|
+
delete("/benefits/#{id}")
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
# List benefit grants
|
56
|
+
# @param id [String] Benefit ID
|
57
|
+
# @param params [Hash] Query parameters
|
58
|
+
# @return [PaginatedResponse] Paginated list of benefit grants
|
59
|
+
def grants(id, params = {})
|
60
|
+
paginate("/benefits/#{id}/grants", params)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Polar
|
6
|
+
module Resources
|
7
|
+
class Checkouts < Base
|
8
|
+
# List checkout sessions
|
9
|
+
# @param params [Hash] Query parameters
|
10
|
+
# @option params [String] :organization_id Filter by organization ID
|
11
|
+
# @option params [String] :customer_id Filter by customer ID
|
12
|
+
# @option params [Integer] :page Page number
|
13
|
+
# @option params [Integer] :limit Items per page
|
14
|
+
# @return [PaginatedResponse] Paginated list of checkout sessions
|
15
|
+
def list(params = {})
|
16
|
+
paginate('/checkouts/', params)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Create a checkout session
|
20
|
+
# @param attributes [Hash] Checkout session attributes
|
21
|
+
# @option attributes [String] :product_price_id Product price ID
|
22
|
+
# @option attributes [String] :success_url Success redirect URL
|
23
|
+
# @option attributes [String] :cancel_url Cancel redirect URL
|
24
|
+
# @option attributes [Hash] :customer_data Customer information
|
25
|
+
# @return [Hash] Created checkout session
|
26
|
+
def create(attributes)
|
27
|
+
response = post('/checkouts/', attributes)
|
28
|
+
response.body
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get a checkout session by ID
|
32
|
+
# @param id [String] Checkout session ID
|
33
|
+
# @return [Hash] Checkout session data
|
34
|
+
def get(id)
|
35
|
+
response = get("/checkouts/#{id}")
|
36
|
+
response.body
|
37
|
+
end
|
38
|
+
|
39
|
+
# Update a checkout session
|
40
|
+
# @param id [String] Checkout session ID
|
41
|
+
# @param attributes [Hash] Updated attributes
|
42
|
+
# @return [Hash] Updated checkout session
|
43
|
+
def update(id, attributes)
|
44
|
+
response = patch("/checkouts/#{id}", attributes)
|
45
|
+
response.body
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get checkout session from client (no auth required)
|
49
|
+
# @param id [String] Checkout session ID
|
50
|
+
# @return [Hash] Checkout session data
|
51
|
+
def client_get(id)
|
52
|
+
response = get("/checkouts/#{id}/client")
|
53
|
+
response.body
|
54
|
+
end
|
55
|
+
|
56
|
+
# Update checkout session from client
|
57
|
+
# @param id [String] Checkout session ID
|
58
|
+
# @param attributes [Hash] Updated attributes
|
59
|
+
# @return [Hash] Updated checkout session
|
60
|
+
def client_update(id, attributes)
|
61
|
+
response = patch("/checkouts/#{id}/client", attributes)
|
62
|
+
response.body
|
63
|
+
end
|
64
|
+
|
65
|
+
# Confirm checkout session from client
|
66
|
+
# @param id [String] Checkout session ID
|
67
|
+
# @param confirmation_data [Hash] Confirmation data
|
68
|
+
# @return [Hash] Confirmed checkout session
|
69
|
+
def client_confirm(id, confirmation_data = {})
|
70
|
+
response = post("/checkouts/#{id}/client/confirm", confirmation_data)
|
71
|
+
response.body
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Polar
|
6
|
+
module Resources
|
7
|
+
class Customers < Base
|
8
|
+
# List customers
|
9
|
+
# @param params [Hash] Query parameters
|
10
|
+
# @option params [String] :organization_id Filter by organization ID
|
11
|
+
# @option params [String] :query Search query
|
12
|
+
# @option params [Integer] :page Page number
|
13
|
+
# @option params [Integer] :limit Items per page
|
14
|
+
# @return [PaginatedResponse] Paginated list of customers
|
15
|
+
def list(params = {})
|
16
|
+
paginate('/customers/', params)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Create a customer
|
20
|
+
# @param attributes [Hash] Customer attributes
|
21
|
+
# @option attributes [String] :email Customer email
|
22
|
+
# @option attributes [String] :name Customer name
|
23
|
+
# @option attributes [String] :organization_id Organization ID
|
24
|
+
# @return [Hash] Created customer
|
25
|
+
def create(attributes)
|
26
|
+
response = post('/customers/', attributes)
|
27
|
+
response.body
|
28
|
+
end
|
29
|
+
|
30
|
+
# Export customers
|
31
|
+
# @param params [Hash] Export parameters
|
32
|
+
# @return [Hash] Export job details
|
33
|
+
def export(params = {})
|
34
|
+
response = post('/customers/export', params)
|
35
|
+
response.body
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get a customer by ID
|
39
|
+
# @param id [String] Customer ID
|
40
|
+
# @return [Hash] Customer data
|
41
|
+
def get(id)
|
42
|
+
response = get("/customers/#{id}")
|
43
|
+
response.body
|
44
|
+
end
|
45
|
+
|
46
|
+
# Update a customer
|
47
|
+
# @param id [String] Customer ID
|
48
|
+
# @param attributes [Hash] Updated attributes
|
49
|
+
# @return [Hash] Updated customer
|
50
|
+
def update(id, attributes)
|
51
|
+
response = patch("/customers/#{id}", attributes)
|
52
|
+
response.body
|
53
|
+
end
|
54
|
+
|
55
|
+
# Delete a customer
|
56
|
+
# @param id [String] Customer ID
|
57
|
+
# @return [Boolean] Success status
|
58
|
+
def delete(id)
|
59
|
+
delete("/customers/#{id}")
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get customer by external ID
|
64
|
+
# @param external_id [String] External customer ID
|
65
|
+
# @param organization_id [String] Organization ID
|
66
|
+
# @return [Hash] Customer data
|
67
|
+
def get_external(external_id, organization_id:)
|
68
|
+
params = { organization_id: organization_id }
|
69
|
+
response = get("/customers/external/#{external_id}", params)
|
70
|
+
response.body
|
71
|
+
end
|
72
|
+
|
73
|
+
# Update customer by external ID
|
74
|
+
# @param external_id [String] External customer ID
|
75
|
+
# @param attributes [Hash] Updated attributes
|
76
|
+
# @option attributes [String] :organization_id Organization ID (required)
|
77
|
+
# @return [Hash] Updated customer
|
78
|
+
def update_external(external_id, attributes)
|
79
|
+
response = patch("/customers/external/#{external_id}", attributes)
|
80
|
+
response.body
|
81
|
+
end
|
82
|
+
|
83
|
+
# Delete customer by external ID
|
84
|
+
# @param external_id [String] External customer ID
|
85
|
+
# @param organization_id [String] Organization ID
|
86
|
+
# @return [Boolean] Success status
|
87
|
+
def delete_external(external_id, organization_id:)
|
88
|
+
params = { organization_id: organization_id }
|
89
|
+
delete("/customers/external/#{external_id}", params)
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
# Get customer state
|
94
|
+
# @param id [String] Customer ID
|
95
|
+
# @return [Hash] Customer state data
|
96
|
+
def get_state(id)
|
97
|
+
response = get("/customers/#{id}/state")
|
98
|
+
response.body
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get customer state by external ID
|
102
|
+
# @param external_id [String] External customer ID
|
103
|
+
# @param organization_id [String] Organization ID
|
104
|
+
# @return [Hash] Customer state data
|
105
|
+
def get_state_external(external_id, organization_id:)
|
106
|
+
params = { organization_id: organization_id }
|
107
|
+
response = get("/customers/external/#{external_id}/state", params)
|
108
|
+
response.body
|
109
|
+
end
|
110
|
+
|
111
|
+
# Get customer balance
|
112
|
+
# @param id [String] Customer ID
|
113
|
+
# @return [Hash] Customer balance data
|
114
|
+
def get_balance(id)
|
115
|
+
response = get("/customers/#{id}/balance")
|
116
|
+
response.body
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|