creem 0.0.1

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.
data/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # Creem Ruby SDK
2
+
3
+ Ruby SDK for the [Creem](https://creem.io) payment platform API — a Merchant of Record for SaaS and digital businesses.
4
+
5
+ ## Installation
6
+
7
+ Add to your Gemfile:
8
+
9
+ ```ruby
10
+ gem "creem"
11
+ ```
12
+
13
+ Or install directly:
14
+
15
+ ```bash
16
+ gem install creem
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```ruby
22
+ require "creem"
23
+
24
+ # Global configuration
25
+ Creem.configure do |config|
26
+ config.api_key = "creem_YOUR_API_KEY"
27
+ config.test_mode = true # Use sandbox environment
28
+ end
29
+
30
+ client = Creem::Client.new
31
+
32
+ # Or pass options directly
33
+ client = Creem::Client.new(api_key: "creem_YOUR_API_KEY", test_mode: true)
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ### Products
39
+
40
+ ```ruby
41
+ # List products
42
+ products = client.products.list(page_number: 1, page_size: 10)
43
+
44
+ # Get a product
45
+ product = client.products.retrieve("prod_123")
46
+ ```
47
+
48
+ ### Checkouts
49
+
50
+ ```ruby
51
+ # Create a checkout session
52
+ checkout = client.checkouts.create(
53
+ product_id: "prod_123",
54
+ success_url: "https://yoursite.com/success"
55
+ )
56
+
57
+ puts checkout["checkout_url"] # Redirect customer here
58
+ ```
59
+
60
+ ### Subscriptions
61
+
62
+ ```ruby
63
+ # List subscriptions
64
+ subs = client.subscriptions.list
65
+
66
+ # Get a subscription
67
+ sub = client.subscriptions.retrieve("sub_123")
68
+
69
+ # Update a subscription
70
+ client.subscriptions.update("sub_123",
71
+ items: [{ product_id: "prod_456", units: 3 }],
72
+ update_behavior: "proration-charge"
73
+ )
74
+
75
+ # Cancel a subscription
76
+ client.subscriptions.cancel("sub_123", mode: "immediate")
77
+ ```
78
+
79
+ ### Customers
80
+
81
+ ```ruby
82
+ # List customers
83
+ customers = client.customers.list
84
+
85
+ # Get by ID or email
86
+ customer = client.customers.retrieve(customer_id: "cust_123")
87
+ customer = client.customers.retrieve(email: "user@example.com")
88
+
89
+ # Generate billing portal link
90
+ portal = client.customers.billing_portal("cust_123")
91
+ puts portal["customer_portal_link"]
92
+ ```
93
+
94
+ ### Licenses
95
+
96
+ ```ruby
97
+ # Activate a license
98
+ license = client.licenses.activate(key: "ABC-123", instance_name: "My Device")
99
+
100
+ # Validate a license
101
+ license = client.licenses.validate(key: "ABC-123", instance_id: "inst_456")
102
+
103
+ # Deactivate a license
104
+ client.licenses.deactivate(key: "ABC-123", instance_id: "inst_456")
105
+ ```
106
+
107
+ ### Transactions
108
+
109
+ ```ruby
110
+ transactions = client.transactions.list(page_number: 1, page_size: 10)
111
+ ```
112
+
113
+ ### Discounts
114
+
115
+ ```ruby
116
+ discounts = client.discounts.list(page_number: 1, page_size: 10)
117
+ ```
118
+
119
+ ## Webhook Verification
120
+
121
+ ```ruby
122
+ # Verify webhook signature
123
+ payload = request.body.read
124
+ signature = request.headers["creem-signature"]
125
+ secret = "whsec_your_webhook_secret"
126
+
127
+ # Returns parsed event or raises Creem::WebhookSignatureError
128
+ event = Creem::Webhook.construct_event(
129
+ payload: payload,
130
+ secret: secret,
131
+ signature: signature
132
+ )
133
+
134
+ case event["event"]
135
+ when "checkout.completed"
136
+ # Handle checkout completion
137
+ when "subscription.active"
138
+ # Handle new subscription
139
+ end
140
+ ```
141
+
142
+ ## Error Handling
143
+
144
+ ```ruby
145
+ begin
146
+ client.products.retrieve("prod_invalid")
147
+ rescue Creem::AuthenticationError => e
148
+ puts "Invalid API key: #{e.message}"
149
+ rescue Creem::NotFoundError => e
150
+ puts "Resource not found: #{e.message}"
151
+ rescue Creem::BadRequestError => e
152
+ puts "Bad request: #{e.message}"
153
+ rescue Creem::RateLimitError => e
154
+ puts "Rate limited: #{e.message}"
155
+ rescue Creem::ServerError => e
156
+ puts "Server error: #{e.message}"
157
+ rescue Creem::ApiError => e
158
+ puts "API error (#{e.status}): #{e.message}"
159
+ end
160
+ ```
161
+
162
+ ## Test Mode
163
+
164
+ Toggle between production and sandbox environments:
165
+
166
+ ```ruby
167
+ # Via configuration
168
+ Creem.configure do |config|
169
+ config.test_mode = ENV["RAILS_ENV"] != "production"
170
+ end
171
+
172
+ # Or per-client
173
+ client = Creem::Client.new(api_key: "key", test_mode: true)
174
+ ```
175
+
176
+ | | Production | Test Mode |
177
+ |---|---|---|
178
+ | Base URL | `https://api.creem.io/v1` | `https://test-api.creem.io/v1` |
179
+ | Payments | Real charges | Simulated |
180
+
181
+ ## Development
182
+
183
+ ```bash
184
+ make install # Install dependencies
185
+ make test # Run tests
186
+ make lint # Run linters
187
+ make format # Auto-fix formatting
188
+ make console # Interactive console
189
+ ```
190
+
191
+ ## License
192
+
193
+ MIT License. See [LICENSE](LICENSE) for details.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require_relative "lib/creem/version"
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task default: :spec
data/creem.gemspec ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/creem/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "creem"
7
+ spec.version = Creem::VERSION
8
+ spec.authors = ["Creem Contributors"]
9
+ spec.email = ["richard.sun@ai-firstly.com"]
10
+ spec.summary = "Ruby SDK for the Creem payment platform API"
11
+ spec.description = "Official Ruby SDK for Creem - a Merchant of Record platform for SaaS and digital businesses. " \
12
+ "Manage products, checkouts, subscriptions, customers, transactions, licenses, and discounts."
13
+ spec.homepage = "https://github.com/ai-firstly/creem-ruby"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = [">= 3.3.0", "< 5.0"]
16
+
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = "https://github.com/ai-firstly/creem-ruby"
20
+ spec.metadata["changelog_uri"] = "https://github.com/ai-firstly/creem-ruby/blob/main/CHANGELOG.md"
21
+ spec.metadata["documentation_uri"] = "https://rubydoc.info/gems/creem"
22
+ spec.metadata["bug_tracker_uri"] = "https://github.com/ai-firstly/creem-ruby/issues"
23
+ spec.metadata["rubygems_mfa_required"] = "true"
24
+
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ if File.exist?(".git")
27
+ `git ls-files -z`.split("\x0").reject do |f|
28
+ f.match(%r{\A(?:test|spec|features)/})
29
+ end
30
+ else
31
+ Dir.glob("**/*").reject do |f|
32
+ File.directory?(f) ||
33
+ f.match(%r{\A(?:test|spec|features)/}) ||
34
+ f.match(/\A\./) ||
35
+ f.match(/\.gem$/)
36
+ end
37
+ end
38
+ end
39
+ spec.bindir = "exe"
40
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
41
+ spec.require_paths = ["lib"]
42
+
43
+ spec.add_dependency "net-http", ">= 0"
44
+
45
+ spec.add_development_dependency "rake", "~> 13.0"
46
+ spec.add_development_dependency "rspec", "~> 3.12"
47
+ spec.add_development_dependency "rubocop", "~> 1.50"
48
+ spec.add_development_dependency "webmock", "~> 3.18"
49
+ end
@@ -0,0 +1,335 @@
1
+ # API 参考
2
+
3
+ Creem Ruby SDK 提供以下资源模块,均通过 `Creem::Client` 实例访问。所有方法返回解析后的 `Hash`(JSON 响应体)。
4
+
5
+ ---
6
+
7
+ ## Products(产品)
8
+
9
+ ```ruby
10
+ client.products
11
+ ```
12
+
13
+ ### `list(page_number: 1, page_size: 10)`
14
+
15
+ 获取产品列表。
16
+
17
+ | 参数 | 类型 | 默认值 | 说明 |
18
+ |------|------|--------|------|
19
+ | `page_number` | Integer | 1 | 页码 |
20
+ | `page_size` | Integer | 10 | 每页数量 |
21
+
22
+ ```ruby
23
+ products = client.products.list(page_number: 1, page_size: 20)
24
+ ```
25
+
26
+ **API 端点**:`GET /products/search`
27
+
28
+ ### `retrieve(product_id)`
29
+
30
+ 获取单个产品。
31
+
32
+ | 参数 | 类型 | 说明 |
33
+ |------|------|------|
34
+ | `product_id` | String | 产品 ID |
35
+
36
+ ```ruby
37
+ product = client.products.retrieve("prod_123")
38
+ ```
39
+
40
+ **API 端点**:`GET /products?product_id=...`
41
+
42
+ ---
43
+
44
+ ## Checkouts(结账)
45
+
46
+ ```ruby
47
+ client.checkouts
48
+ ```
49
+
50
+ ### `create(product_id:, success_url: nil, request_id: nil, units: nil, discount_code: nil, customer: nil, custom_fields: nil, metadata: nil)`
51
+
52
+ 创建结账会话。
53
+
54
+ | 参数 | 类型 | 必填 | 说明 |
55
+ |------|------|------|------|
56
+ | `product_id` | String | ✅ | 产品 ID |
57
+ | `success_url` | String | - | 支付成功后的回调 URL |
58
+ | `request_id` | String | - | 幂等请求 ID |
59
+ | `units` | Integer | - | 购买数量 |
60
+ | `discount_code` | String | - | 折扣码 |
61
+ | `customer` | Hash | - | 客户信息 |
62
+ | `custom_fields` | Hash | - | 自定义字段 |
63
+ | `metadata` | Hash | - | 元数据 |
64
+
65
+ ```ruby
66
+ checkout = client.checkouts.create(
67
+ product_id: "prod_123",
68
+ success_url: "https://yoursite.com/success",
69
+ units: 2,
70
+ metadata: { order_ref: "ORD-001" }
71
+ )
72
+
73
+ puts checkout["checkout_url"] # 重定向客户到此 URL
74
+ ```
75
+
76
+ **API 端点**:`POST /checkouts`
77
+
78
+ ---
79
+
80
+ ## Subscriptions(订阅)
81
+
82
+ ```ruby
83
+ client.subscriptions
84
+ ```
85
+
86
+ ### `list(page_number: 1, page_size: 10)`
87
+
88
+ 获取订阅列表。
89
+
90
+ | 参数 | 类型 | 默认值 | 说明 |
91
+ |------|------|--------|------|
92
+ | `page_number` | Integer | 1 | 页码 |
93
+ | `page_size` | Integer | 10 | 每页数量 |
94
+
95
+ ```ruby
96
+ subs = client.subscriptions.list(page_number: 1, page_size: 20)
97
+ ```
98
+
99
+ **API 端点**:`GET /subscriptions/search`
100
+
101
+ ### `retrieve(subscription_id)`
102
+
103
+ 获取单个订阅。
104
+
105
+ | 参数 | 类型 | 说明 |
106
+ |------|------|------|
107
+ | `subscription_id` | String | 订阅 ID |
108
+
109
+ ```ruby
110
+ sub = client.subscriptions.retrieve("sub_123")
111
+ ```
112
+
113
+ **API 端点**:`GET /subscriptions?subscription_id=...`
114
+
115
+ ### `update(id, items: nil, update_behavior: nil)`
116
+
117
+ 更新订阅。
118
+
119
+ | 参数 | 类型 | 说明 |
120
+ |------|------|------|
121
+ | `id` | String | 订阅 ID(路径参数)|
122
+ | `items` | Array\<Hash\> | 订阅项,如 `[{ product_id: "prod_456", units: 3 }]` |
123
+ | `update_behavior` | String | 更新行为,如 `"proration-charge"` |
124
+
125
+ ```ruby
126
+ client.subscriptions.update("sub_123",
127
+ items: [{ product_id: "prod_456", units: 3 }],
128
+ update_behavior: "proration-charge"
129
+ )
130
+ ```
131
+
132
+ **API 端点**:`POST /subscriptions/:id`
133
+
134
+ ### `cancel(id, mode: nil, on_execute: nil)`
135
+
136
+ 取消订阅。
137
+
138
+ | 参数 | 类型 | 说明 |
139
+ |------|------|------|
140
+ | `id` | String | 订阅 ID(路径参数)|
141
+ | `mode` | String | 取消模式,如 `"immediate"`, `"at_period_end"` |
142
+ | `on_execute` | String | 执行时的回调行为 |
143
+
144
+ > **注意**:`on_execute` 在请求体中以 camelCase(`onExecute`)发送,这是 Creem API 要求的格式。
145
+
146
+ ```ruby
147
+ client.subscriptions.cancel("sub_123", mode: "immediate")
148
+ ```
149
+
150
+ **API 端点**:`POST /subscriptions/:id/cancel`
151
+
152
+ ---
153
+
154
+ ## Customers(客户)
155
+
156
+ ```ruby
157
+ client.customers
158
+ ```
159
+
160
+ ### `list(page_number: 1, page_size: 50)`
161
+
162
+ 获取客户列表。
163
+
164
+ | 参数 | 类型 | 默认值 | 说明 |
165
+ |------|------|--------|------|
166
+ | `page_number` | Integer | 1 | 页码 |
167
+ | `page_size` | Integer | 50 | 每页数量 |
168
+
169
+ ```ruby
170
+ customers = client.customers.list(page_number: 1, page_size: 20)
171
+ ```
172
+
173
+ **API 端点**:`GET /customers/list`
174
+
175
+ ### `retrieve(customer_id: nil, email: nil)`
176
+
177
+ 通过 ID 或邮箱获取客户。至少传入一个参数。
178
+
179
+ | 参数 | 类型 | 说明 |
180
+ |------|------|------|
181
+ | `customer_id` | String | 客户 ID |
182
+ | `email` | String | 客户邮箱 |
183
+
184
+ ```ruby
185
+ # 按 ID 查询
186
+ customer = client.customers.retrieve(customer_id: "cust_123")
187
+
188
+ # 按邮箱查询
189
+ customer = client.customers.retrieve(email: "user@example.com")
190
+ ```
191
+
192
+ **API 端点**:`GET /customers`
193
+
194
+ ### `billing_portal(customer_id)`
195
+
196
+ 生成客户的账单门户链接。
197
+
198
+ | 参数 | 类型 | 说明 |
199
+ |------|------|------|
200
+ | `customer_id` | String | 客户 ID |
201
+
202
+ ```ruby
203
+ portal = client.customers.billing_portal("cust_123")
204
+ puts portal["customer_portal_link"]
205
+ ```
206
+
207
+ **API 端点**:`POST /customers/billing-portal`
208
+
209
+ ---
210
+
211
+ ## Transactions(交易)
212
+
213
+ ```ruby
214
+ client.transactions
215
+ ```
216
+
217
+ ### `list(page_number: 1, page_size: 10)`
218
+
219
+ 获取交易列表。
220
+
221
+ | 参数 | 类型 | 默认值 | 说明 |
222
+ |------|------|--------|------|
223
+ | `page_number` | Integer | 1 | 页码 |
224
+ | `page_size` | Integer | 10 | 每页数量 |
225
+
226
+ ```ruby
227
+ transactions = client.transactions.list(page_number: 1, page_size: 20)
228
+ ```
229
+
230
+ **API 端点**:`GET /transactions/search`
231
+
232
+ ---
233
+
234
+ ## Licenses(许可证)
235
+
236
+ ```ruby
237
+ client.licenses
238
+ ```
239
+
240
+ ### `list(page_number: 1, page_size: 10)`
241
+
242
+ 获取许可证列表。
243
+
244
+ | 参数 | 类型 | 默认值 | 说明 |
245
+ |------|------|--------|------|
246
+ | `page_number` | Integer | 1 | 页码 |
247
+ | `page_size` | Integer | 10 | 每页数量 |
248
+
249
+ ```ruby
250
+ licenses = client.licenses.list
251
+ ```
252
+
253
+ **API 端点**:`GET /licenses/search`
254
+
255
+ ### `activate(key:, instance_name:)`
256
+
257
+ 激活许可证。
258
+
259
+ | 参数 | 类型 | 必填 | 说明 |
260
+ |------|------|------|------|
261
+ | `key` | String | ✅ | 许可证密钥 |
262
+ | `instance_name` | String | ✅ | 实例名称 |
263
+
264
+ ```ruby
265
+ result = client.licenses.activate(key: "ABC-123", instance_name: "My Device")
266
+ puts result["instance_id"] # 保存此 ID 用于后续验证/停用
267
+ ```
268
+
269
+ **API 端点**:`POST /licenses/activate`
270
+
271
+ ### `validate(key:, instance_id:)`
272
+
273
+ 验证许可证是否有效。
274
+
275
+ | 参数 | 类型 | 必填 | 说明 |
276
+ |------|------|------|------|
277
+ | `key` | String | ✅ | 许可证密钥 |
278
+ | `instance_id` | String | ✅ | 实例 ID |
279
+
280
+ ```ruby
281
+ result = client.licenses.validate(key: "ABC-123", instance_id: "inst_456")
282
+ ```
283
+
284
+ **API 端点**:`POST /licenses/validate`
285
+
286
+ ### `deactivate(key:, instance_id:)`
287
+
288
+ 停用许可证。
289
+
290
+ | 参数 | 类型 | 必填 | 说明 |
291
+ |------|------|------|------|
292
+ | `key` | String | ✅ | 许可证密钥 |
293
+ | `instance_id` | String | ✅ | 实例 ID |
294
+
295
+ ```ruby
296
+ client.licenses.deactivate(key: "ABC-123", instance_id: "inst_456")
297
+ ```
298
+
299
+ **API 端点**:`POST /licenses/deactivate`
300
+
301
+ ---
302
+
303
+ ## Discounts(折扣)
304
+
305
+ ```ruby
306
+ client.discounts
307
+ ```
308
+
309
+ ### `list(page_number: 1, page_size: 10)`
310
+
311
+ 获取折扣列表。
312
+
313
+ | 参数 | 类型 | 默认值 | 说明 |
314
+ |------|------|--------|------|
315
+ | `page_number` | Integer | 1 | 页码 |
316
+ | `page_size` | Integer | 10 | 每页数量 |
317
+
318
+ ```ruby
319
+ discounts = client.discounts.list(page_number: 1, page_size: 20)
320
+ ```
321
+
322
+ **API 端点**:`GET /discounts/search`
323
+
324
+ ---
325
+
326
+ ## 分页
327
+
328
+ 所有 `list` 方法都支持分页,通过 `page_number` 和 `page_size` 参数控制:
329
+
330
+ ```ruby
331
+ # 获取第 2 页,每页 25 条
332
+ products = client.products.list(page_number: 2, page_size: 25)
333
+ ```
334
+
335
+ 响应中通常包含分页元数据,可用于构建翻页逻辑。