zai_payment 1.0.1 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 719c0663d33f0ced4d8bcc6627dd58ce98b8c14f436d9ce02210f01fc04b99d3
4
- data.tar.gz: 85e8ae991cec90cd59e11f8234cdff9a7f684d4c5632bd9fd4659dc1af4ccedf
3
+ metadata.gz: e3dde823881d82243bdd2f57882c57cfe06ba21d20ab5402b58a876c7c752e8c
4
+ data.tar.gz: 37ce743767afba2f834354a81102cc445c544151c26601e59a0f82eb6b967a92
5
5
  SHA512:
6
- metadata.gz: 1536177552b6ba2e10bb61734f574e697275cba87c74af530bbf8c125f37927f1cb2bf5de7dc4b94dffaaccd563f4d9f49ab96d1d40bf6fc5511f1ddf51adf11
7
- data.tar.gz: 843f7c8d8650fc974af5a701d33e139cc1170a84cf064d765bc31729a0c3cd95487e4e51d4c06595a2e70bcc3f104a908d39c00343acc89d9054d146c7f31d9a
6
+ metadata.gz: 0b96c6511bc25c2dcbfe44af58957b160c3bd7ac1fb6aa94fb5708ced6a6e2ff6a209f188f664eb6c857ebe4177f5260191eb62aa1e64924adc57d695815ed38
7
+ data.tar.gz: 6e2410984fe931c17097f145de9a23e7c6048af308c068e7f590efab9991a21c838aed6158bb1be56863fa9e86090f46599eaffec1cf415e2610c46861de06e1
data/CHANGELOG.md CHANGED
@@ -1,7 +1,43 @@
1
- ## [Unreleased]
1
+ ## [Released]
2
+
3
+ ## [1.1.0] - 2025-10-22
4
+ ### Added
5
+ - **Webhooks API**: Full CRUD operations for managing Zai webhooks
6
+ - `ZaiPayment.webhooks.list` - List all webhooks with pagination
7
+ - `ZaiPayment.webhooks.show(id)` - Get a specific webhook
8
+ - `ZaiPayment.webhooks.create(...)` - Create a new webhook
9
+ - `ZaiPayment.webhooks.update(id, ...)` - Update an existing webhook
10
+ - `ZaiPayment.webhooks.delete(id)` - Delete a webhook
11
+ - **Base API Client**: Reusable HTTP client for all API requests
12
+ - **Response Wrapper**: Standardized response handling with error management
13
+ - **Enhanced Error Handling**: New error classes for different API scenarios
14
+ - `ValidationError` (400, 422)
15
+ - `UnauthorizedError` (401)
16
+ - `ForbiddenError` (403)
17
+ - `NotFoundError` (404)
18
+ - `RateLimitError` (429)
19
+ - `ServerError` (5xx)
20
+ - `TimeoutError` and `ConnectionError` for network issues
21
+ - Comprehensive test suite for webhook functionality
22
+ - Example code in `examples/webhooks.rb`
23
+
24
+ **Full Changelog**: https://github.com/Sentia/zai-payment/compare/v1.0.2...v1.1.0
25
+
26
+ ## [1.0.2] - 2025-10-22
27
+ - Update gemspec files and readme
28
+
29
+ **Full Changelog**: https://github.com/Sentia/zai-payment/releases/tag/v1.0.2
30
+
31
+
32
+ ##[1.0.1] - 2025-10-21
33
+ - Update readme and versions
34
+
35
+ **Full Changelog**: https://github.com/Sentia/zai-payment/releases/tag/v1.0.1
36
+
2
37
 
3
38
  ## [1.0.0] - 2025-10-21
4
39
 
5
40
  - Initial release: token auth client with in-memory caching (`ZaiPayment.token`, `refresh_token!`, `clear_token!`, `token_type`, `token_expiry`)
6
41
 
7
42
  **Full Changelog**: https://github.com/Sentia/zai-payment/commits/v1.0.0
43
+
data/IMPLEMENTATION.md ADDED
@@ -0,0 +1,201 @@
1
+ # Implementation Summary: Zai Payment Webhooks
2
+
3
+ ## ✅ What Was Implemented
4
+
5
+ ### 1. Core Infrastructure (New Files)
6
+
7
+ #### `/lib/zai_payment/client.rb`
8
+ - Base HTTP client for all API requests
9
+ - Handles authentication automatically
10
+ - Supports GET, POST, PATCH, DELETE methods
11
+ - Proper error handling and connection management
12
+ - Thread-safe and reusable
13
+
14
+ #### `/lib/zai_payment/response.rb`
15
+ - Response wrapper class
16
+ - Convenience methods: `success?`, `client_error?`, `server_error?`
17
+ - Automatic error raising based on HTTP status
18
+ - Clean data extraction from response body
19
+
20
+ #### `/lib/zai_payment/resources/webhook.rb`
21
+ - Complete CRUD operations for webhooks:
22
+ - `list(limit:, offset:)` - List all webhooks with pagination
23
+ - `show(webhook_id)` - Get specific webhook details
24
+ - `create(url:, object_type:, enabled:, description:)` - Create new webhook
25
+ - `update(webhook_id, ...)` - Update existing webhook
26
+ - `delete(webhook_id)` - Delete webhook
27
+ - Full input validation
28
+ - URL format validation
29
+ - Comprehensive error messages
30
+
31
+ ### 2. Enhanced Error Handling
32
+
33
+ #### `/lib/zai_payment/errors.rb` (Updated)
34
+ Added new error classes:
35
+ - `ApiError` - Base API error
36
+ - `BadRequestError` (400)
37
+ - `UnauthorizedError` (401)
38
+ - `ForbiddenError` (403)
39
+ - `NotFoundError` (404)
40
+ - `ValidationError` (422)
41
+ - `RateLimitError` (429)
42
+ - `ServerError` (5xx)
43
+ - `TimeoutError` - Network timeout
44
+ - `ConnectionError` - Connection failed
45
+
46
+ ### 3. Main Module Integration
47
+
48
+ #### `/lib/zai_payment.rb` (Updated)
49
+ - Added `require` statements for new components
50
+ - Added `webhooks` method that returns a singleton instance
51
+ - Usage: `ZaiPayment.webhooks.list`
52
+
53
+ ### 4. Testing
54
+
55
+ #### `/spec/zai_payment/resources/webhook_spec.rb` (New)
56
+ Comprehensive test suite covering:
57
+ - List webhooks (success, pagination, unauthorized)
58
+ - Show webhook (success, not found, validation)
59
+ - Create webhook (success, validation errors, API errors)
60
+ - Update webhook (success, not found, validation)
61
+ - Delete webhook (success, not found, validation)
62
+ - Edge cases and error scenarios
63
+
64
+ ### 5. Documentation
65
+
66
+ #### `/examples/webhooks.rb` (New)
67
+ - Complete usage examples
68
+ - All CRUD operations
69
+ - Error handling patterns
70
+ - Pagination examples
71
+ - Custom client instances
72
+
73
+ #### `/docs/WEBHOOKS.md` (New)
74
+ - Architecture overview
75
+ - API method documentation
76
+ - Error handling guide
77
+ - Best practices
78
+ - Testing instructions
79
+ - Future enhancements
80
+
81
+ #### `/README.md` (Updated)
82
+ - Added webhook usage section
83
+ - Error handling examples
84
+ - Updated roadmap (Webhooks: Done ✅)
85
+
86
+ #### `/CHANGELOG.md` (Updated)
87
+ - Added v1.1.0 release notes
88
+ - Documented all new features
89
+ - Listed all new error classes
90
+
91
+ ### 6. Version
92
+
93
+ #### `/lib/zai_payment/version.rb` (Updated)
94
+ - Bumped version to 1.1.0
95
+
96
+ ## 📁 File Structure
97
+
98
+ ```
99
+ lib/
100
+ ├── zai_payment/
101
+ │ ├── auth/ # Authentication (existing)
102
+ │ ├── client.rb # ✨ NEW: Base HTTP client
103
+ │ ├── response.rb # ✨ NEW: Response wrapper
104
+ │ ├── resources/
105
+ │ │ └── webhook.rb # ✨ NEW: Webhook CRUD operations
106
+ │ ├── config.rb # (existing)
107
+ │ ├── errors.rb # ✅ UPDATED: Added API error classes
108
+ │ └── version.rb # ✅ UPDATED: v1.1.0
109
+ └── zai_payment.rb # ✅ UPDATED: Added webhooks accessor
110
+
111
+ spec/
112
+ └── zai_payment/
113
+ └── resources/
114
+ └── webhook_spec.rb # ✨ NEW: Comprehensive tests
115
+
116
+ examples/
117
+ └── webhooks.rb # ✨ NEW: Usage examples
118
+
119
+ docs/
120
+ └── WEBHOOKS.md # ✨ NEW: Complete documentation
121
+ ```
122
+
123
+ ## 🎯 Key Features
124
+
125
+ 1. **Clean API**: `ZaiPayment.webhooks.list`, `.show`, `.create`, `.update`, `.delete`
126
+ 2. **Automatic Authentication**: Uses existing TokenProvider
127
+ 3. **Comprehensive Validation**: URL format, required fields, etc.
128
+ 4. **Rich Error Handling**: Specific errors for each scenario
129
+ 5. **Pagination Support**: Built-in pagination for list operations
130
+ 6. **Thread-Safe**: Reuses existing thread-safe authentication
131
+ 7. **Well-Tested**: Full RSpec test coverage
132
+ 8. **Documented**: Inline docs, examples, and guides
133
+
134
+ ## 🚀 Usage
135
+
136
+ ```ruby
137
+ # Configure once
138
+ ZaiPayment.configure do |config|
139
+ config.environment = :prelive
140
+ config.client_id = ENV['ZAI_CLIENT_ID']
141
+ config.client_secret = ENV['ZAI_CLIENT_SECRET']
142
+ config.scope = ENV['ZAI_SCOPE']
143
+ end
144
+
145
+ # Use webhooks
146
+ response = ZaiPayment.webhooks.list
147
+ webhooks = response.data
148
+
149
+ response = ZaiPayment.webhooks.create(
150
+ url: 'https://example.com/webhook',
151
+ object_type: 'transactions',
152
+ enabled: true
153
+ )
154
+ ```
155
+
156
+ ## ✨ Best Practices Applied
157
+
158
+ 1. **Single Responsibility Principle**: Each class has one clear purpose
159
+ 2. **DRY**: Reusable Client and Response classes
160
+ 3. **Open/Closed**: Easy to extend for new resources (Users, Items, etc.)
161
+ 4. **Dependency Injection**: Client accepts custom config and token provider
162
+ 5. **Fail Fast**: Validation before API calls
163
+ 6. **Clear Error Messages**: Descriptive validation errors
164
+ 7. **RESTful Design**: Standard HTTP methods and status codes
165
+ 8. **Comprehensive Testing**: Unit tests for all scenarios
166
+ 9. **Documentation**: Examples, inline docs, and guides
167
+ 10. **Version Control**: Semantic versioning with changelog
168
+
169
+ ## 🔄 Ready for Extension
170
+
171
+ The infrastructure is now in place to easily add more resources:
172
+
173
+ ```ruby
174
+ # Future resources can follow the same pattern:
175
+ lib/zai_payment/resources/
176
+ ├── webhook.rb # ✅ Done
177
+ ├── user.rb # Coming soon
178
+ ├── item.rb # Coming soon
179
+ ├── transaction.rb # Coming soon
180
+ └── wallet.rb # Coming soon
181
+ ```
182
+
183
+ Each resource can reuse:
184
+ - `ZaiPayment::Client` for HTTP requests
185
+ - `ZaiPayment::Response` for response handling
186
+ - Error classes for consistent error handling
187
+ - Same authentication mechanism
188
+ - Same configuration
189
+ - Same testing patterns
190
+
191
+ ## 🎉 Summary
192
+
193
+ Successfully implemented a complete, production-ready webhook management system for the Zai Payment gem with:
194
+ - ✅ Full CRUD operations
195
+ - ✅ Comprehensive testing
196
+ - ✅ Rich error handling
197
+ - ✅ Complete documentation
198
+ - ✅ Clean, maintainable code
199
+ - ✅ Following Ruby and Rails best practices
200
+ - ✅ Ready for production use
201
+
data/README.md CHANGED
@@ -23,9 +23,7 @@ A lightweight and extensible Ruby client for the **Zai (AssemblyPay)** API — s
23
23
  Add this line to your Gemfile:
24
24
 
25
25
  ```ruby
26
- gem "zai_payment",
27
- git: "https://github.com/Sentia/zai-payment.git",
28
- branch: "main"
26
+ gem 'zai_payment', '~> 1.0', '>= 1.0.2'
29
27
  ```
30
28
 
31
29
  Then install
@@ -69,14 +67,78 @@ Or, more easily, you can get a token with the convenience one-liner:
69
67
  ZaiPayment.token
70
68
  ```
71
69
 
70
+ ## 🚀 Usage
71
+
72
+ ### Webhooks
73
+
74
+ The gem provides a comprehensive interface for managing Zai webhooks:
75
+
76
+ ```ruby
77
+ # List all webhooks
78
+ response = ZaiPayment.webhooks.list
79
+ webhooks = response.data
80
+
81
+ # List with pagination
82
+ response = ZaiPayment.webhooks.list(limit: 20, offset: 10)
83
+
84
+ # Get a specific webhook
85
+ response = ZaiPayment.webhooks.show('webhook_id')
86
+ webhook = response.data
87
+
88
+ # Create a webhook
89
+ response = ZaiPayment.webhooks.create(
90
+ url: 'https://example.com/webhooks/zai',
91
+ object_type: 'transactions',
92
+ enabled: true,
93
+ description: 'Production webhook for transactions'
94
+ )
95
+
96
+ # Update a webhook
97
+ response = ZaiPayment.webhooks.update(
98
+ 'webhook_id',
99
+ enabled: false,
100
+ description: 'Temporarily disabled'
101
+ )
102
+
103
+ # Delete a webhook
104
+ response = ZaiPayment.webhooks.delete('webhook_id')
105
+ ```
106
+
107
+ For more examples, see [examples/webhooks.md](examples/webhooks.md).
108
+
109
+ ### Error Handling
110
+
111
+ The gem provides specific error classes for different scenarios:
112
+
113
+ ```ruby
114
+ begin
115
+ response = ZaiPayment.webhooks.create(
116
+ url: 'https://example.com/webhook',
117
+ object_type: 'transactions'
118
+ )
119
+ rescue ZaiPayment::Errors::ValidationError => e
120
+ # Handle validation errors (400, 422)
121
+ puts "Validation error: #{e.message}"
122
+ rescue ZaiPayment::Errors::UnauthorizedError => e
123
+ # Handle authentication errors (401)
124
+ puts "Authentication failed: #{e.message}"
125
+ rescue ZaiPayment::Errors::NotFoundError => e
126
+ # Handle not found errors (404)
127
+ puts "Resource not found: #{e.message}"
128
+ rescue ZaiPayment::Errors::ApiError => e
129
+ # Handle other API errors
130
+ puts "API error: #{e.message}"
131
+ end
132
+ ```
133
+
72
134
  ## 🧩 Roadmap
73
135
 
74
136
  | Area | Description | Status |
75
137
  | ------------------------------- | --------------------------------- | -------------- |
76
138
  | ✅ Authentication | OAuth2 Client Credentials flow | Done |
139
+ | ✅ Webhooks | CRUD for webhook endpoints | Done |
77
140
  | 💳 Payments | Single and recurring payments | 🚧 In progress |
78
141
  | 🏦 Virtual Accounts (VA / PIPU) | Manage virtual accounts & PayTo | ⏳ Planned |
79
- | 🧾 Webhooks | CRUD for webhook endpoints | ⏳ Planned |
80
142
  | 👤 Users | Manage PayIn / PayOut users | ⏳ Planned |
81
143
  | 💼 Wallets | Create and manage wallet accounts | ⏳ Planned |
82
144
 
@@ -0,0 +1,232 @@
1
+ # Zai Payment Webhook Architecture
2
+
3
+ ```
4
+ ┌─────────────────────────────────────────────────────────────────┐
5
+ │ Client Application │
6
+ └────────────────────────┬────────────────────────────────────────┘
7
+
8
+ │ ZaiPayment.webhooks.list()
9
+ │ ZaiPayment.webhooks.create(...)
10
+ │ ZaiPayment.webhooks.update(...)
11
+ │ ZaiPayment.webhooks.delete(...)
12
+
13
+
14
+ ┌─────────────────────────────────────────────────────────────────┐
15
+ │ ZaiPayment (Module) │
16
+ │ ┌───────────────────────────────────────────────────────────┐ │
17
+ │ │ config() - Configuration singleton │ │
18
+ │ │ auth() - TokenProvider singleton │ │
19
+ │ │ webhooks() - Webhook resource singleton │ │
20
+ │ └───────────────────────────────────────────────────────────┘ │
21
+ └────────────────────────┬────────────────────────────────────────┘
22
+
23
+ ┌───────────────┴───────────────┐
24
+ │ │
25
+ ▼ ▼
26
+ ┌──────────────────┐ ┌──────────────────────┐
27
+ │ Config │ │ Auth::TokenProvider │
28
+ │ ───────────── │ │ ────────────────── │
29
+ │ - environment │◄─────────│ Uses config │
30
+ │ - client_id │ │ - bearer_token() │
31
+ │ - client_secret │ │ - refresh_token() │
32
+ │ - scope │ │ - clear_token() │
33
+ │ - endpoints() │ │ │
34
+ └──────────────────┘ └──────────────────────┘
35
+
36
+
37
+
38
+ ┌──────────────────────┐
39
+ │ TokenStore │
40
+ │ ────────────────── │
41
+ │ (MemoryStore) │
42
+ │ - fetch() │
43
+ │ - write() │
44
+ │ - clear() │
45
+ └──────────────────────┘
46
+
47
+
48
+ ┌─────────────────────────────────────────────────────────────────┐
49
+ │ Resources::Webhook (Resource Layer) │
50
+ │ ┌───────────────────────────────────────────────────────────┐ │
51
+ │ │ list(limit:, offset:) │ │
52
+ │ │ show(webhook_id) │ │
53
+ │ │ create(url:, object_type:, enabled:, description:) │ │
54
+ │ │ update(webhook_id, ...) │ │
55
+ │ │ delete(webhook_id) │ │
56
+ │ │ │ │
57
+ │ │ Private validation methods: │ │
58
+ │ │ - validate_id!() │ │
59
+ │ │ - validate_presence!() │ │
60
+ │ │ - validate_url!() │ │
61
+ │ └───────────────────────────────────────────────────────────┘ │
62
+ └────────────────────────┬────────────────────────────────────────┘
63
+
64
+
65
+ ┌─────────────────────────────────────────────────────────────────┐
66
+ │ Client (HTTP Layer) │
67
+ │ ┌───────────────────────────────────────────────────────────┐ │
68
+ │ │ get(path, params:) │ │
69
+ │ │ post(path, body:) │ │
70
+ │ │ patch(path, body:) │ │
71
+ │ │ delete(path) │ │
72
+ │ │ │ │
73
+ │ │ Private: │ │
74
+ │ │ - connection() - Faraday with auth headers │ │
75
+ │ │ - handle_faraday_error() │ │
76
+ │ └───────────────────────────────────────────────────────────┘ │
77
+ └────────────────────────┬────────────────────────────────────────┘
78
+
79
+
80
+ ┌─────────────────────────────────────────────────────────────────┐
81
+ │ Faraday (HTTP Client) │
82
+ │ ┌───────────────────────────────────────────────────────────┐ │
83
+ │ │ Authorization: Bearer <token> │ │
84
+ │ │ Content-Type: application/json │ │
85
+ │ │ Accept: application/json │ │
86
+ │ └───────────────────────────────────────────────────────────┘ │
87
+ └────────────────────────┬────────────────────────────────────────┘
88
+
89
+
90
+ ┌─────────────────────────────────────────────────────────────────┐
91
+ │ Zai API │
92
+ │ sandbox.au-0000.api.assemblypay.com/webhooks │
93
+ └────────────────────────┬────────────────────────────────────────┘
94
+
95
+ │ HTTP Response
96
+
97
+ ┌─────────────────────────────────────────────────────────────────┐
98
+ │ Response (Wrapper) │
99
+ │ ┌───────────────────────────────────────────────────────────┐ │
100
+ │ │ status - HTTP status code │ │
101
+ │ │ body - Raw response body │ │
102
+ │ │ headers - Response headers │ │
103
+ │ │ data() - Extracted data │ │
104
+ │ │ meta() - Pagination metadata │ │
105
+ │ │ success?() - 2xx status check │ │
106
+ │ │ client_error?()- 4xx status check │ │
107
+ │ │ server_error?()- 5xx status check │ │
108
+ │ │ │ │
109
+ │ │ Private: │ │
110
+ │ │ - check_for_errors!() - Raises specific errors │ │
111
+ │ └───────────────────────────────────────────────────────────┘ │
112
+ └────────────────────────┬────────────────────────────────────────┘
113
+
114
+
115
+ ┌─────────────────────────────────────────────────────────────────┐
116
+ │ Error Hierarchy │
117
+ │ ┌───────────────────────────────────────────────────────────┐ │
118
+ │ │ Error (Base) │ │
119
+ │ │ ├── AuthError │ │
120
+ │ │ ├── ConfigurationError │ │
121
+ │ │ ├── ApiError │ │
122
+ │ │ │ ├── BadRequestError (400) │ │
123
+ │ │ │ ├── UnauthorizedError (401) │ │
124
+ │ │ │ ├── ForbiddenError (403) │ │
125
+ │ │ │ ├── NotFoundError (404) │ │
126
+ │ │ │ ├── ValidationError (422) │ │
127
+ │ │ │ ├── RateLimitError (429) │ │
128
+ │ │ │ └── ServerError (5xx) │ │
129
+ │ │ ├── TimeoutError │ │
130
+ │ │ └── ConnectionError │ │
131
+ │ └───────────────────────────────────────────────────────────┘ │
132
+ └─────────────────────────────────────────────────────────────────┘
133
+ ```
134
+
135
+ ## Request Flow
136
+
137
+ 1. **Client calls** `ZaiPayment.webhooks.list()`
138
+ 2. **Module** returns singleton `Resources::Webhook` instance
139
+ 3. **Webhook resource** validates input and calls `client.get('/webhooks', params: {...})`
140
+ 4. **Client** prepares HTTP request with authentication
141
+ 5. **TokenProvider** provides valid bearer token (auto-refresh if expired)
142
+ 6. **Faraday** makes HTTP request to Zai API
143
+ 7. **Response** wraps Faraday response
144
+ 8. **Response** checks status and raises error if needed
145
+ 9. **Response** returns to client with `data()` and `meta()` methods
146
+ 10. **Client application** receives response and processes data
147
+
148
+ ## Key Design Decisions
149
+
150
+ ### 1. Singleton Pattern for Resources
151
+ ```ruby
152
+ ZaiPayment.webhooks # Always returns same instance
153
+ ```
154
+ - Reduces object creation overhead
155
+ - Consistent configuration across application
156
+ - Easy to use in any context
157
+
158
+ ### 2. Dependency Injection
159
+ ```ruby
160
+ Webhook.new(client: custom_client)
161
+ ```
162
+ - Testable (can inject mock client)
163
+ - Flexible (can use different configs)
164
+ - Follows SOLID principles
165
+
166
+ ### 3. Response Wrapper
167
+ ```ruby
168
+ response = webhooks.list
169
+ response.success? # Boolean check
170
+ response.data # Extracted data
171
+ response.meta # Pagination info
172
+ ```
173
+ - Consistent interface across all resources
174
+ - Rich API for checking status
175
+ - Automatic error handling
176
+
177
+ ### 4. Fail Fast Validation
178
+ ```ruby
179
+ validate_url!(url) # Before API call
180
+ ```
181
+ - Catches errors early
182
+ - Better error messages
183
+ - Reduces unnecessary API calls
184
+
185
+ ### 5. Resource-Based Organization
186
+ ```ruby
187
+ lib/zai_payment/resources/
188
+ ├── webhook.rb
189
+ ├── user.rb # Future
190
+ └── item.rb # Future
191
+ ```
192
+ - Easy to extend
193
+ - Clear separation of concerns
194
+ - Follows REST principles
195
+
196
+ ## Thread Safety
197
+
198
+ - ✅ **TokenProvider**: Uses Mutex for thread-safe token refresh
199
+ - ✅ **MemoryStore**: Thread-safe token storage
200
+ - ✅ **Client**: Creates new Faraday connection per instance
201
+ - ✅ **Webhook**: Stateless, no shared mutable state
202
+
203
+ ## Extension Points
204
+
205
+ Add new resources by following the same pattern:
206
+
207
+ ```ruby
208
+ # lib/zai_payment/resources/user.rb
209
+ module ZaiPayment
210
+ module Resources
211
+ class User
212
+ def initialize(client: nil)
213
+ @client = client || Client.new
214
+ end
215
+
216
+ def list
217
+ client.get('/users')
218
+ end
219
+
220
+ def show(user_id)
221
+ client.get("/users/#{user_id}")
222
+ end
223
+ end
224
+ end
225
+ end
226
+
227
+ # lib/zai_payment.rb
228
+ def users
229
+ @users ||= Resources::User.new
230
+ end
231
+ ```
232
+