Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 63 additions & 37 deletions .rspec_status
Original file line number Diff line number Diff line change
@@ -1,58 +1,84 @@
example_id | status | run_time |
------------------------------------------------------- | ------ | --------------- |
./spec/zai_payment/auth/token_provider_spec.rb[1:1:1] | passed | 0.00066 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:1:1] | passed | 0.00321 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:1:2] | passed | 0.00047 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:2:1] | passed | 0.00023 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:2:2] | passed | 0.00021 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:2:3] | passed | 0.00019 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:3:1] | passed | 0.00021 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:3:2] | passed | 0.00022 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:3:3] | passed | 0.00019 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:4:1] | passed | 0.00015 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:4:2] | passed | 0.00016 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:3:1] | passed | 0.0003 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:3:2] | passed | 0.00025 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:1:1] | passed | 0.00034 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:1:1] | passed | 0.00244 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:1:2] | passed | 0.00046 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:2:1] | passed | 0.00025 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:2:2] | passed | 0.00026 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:2:3] | passed | 0.0002 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:3:1] | passed | 0.00022 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:3:2] | passed | 0.00021 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:3:3] | passed | 0.0002 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:4:1] | passed | 0.00018 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:2:4:2] | passed | 0.00017 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:3:1] | passed | 0.00029 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:3:2] | passed | 0.00027 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:3:3] | passed | 0.0003 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:3:4:1] | passed | 0.00088 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:3:5:1] | passed | 0.00026 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:3:6:1] | passed | 0.00024 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:3:7:1] | passed | 0.00027 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:4:1] | passed | 0.00285 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:4:2] | passed | 0.00028 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:3:4:1] | passed | 0.00394 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:3:5:1] | passed | 0.00031 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:3:6:1] | passed | 0.00027 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:3:7:1] | passed | 0.00046 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:4:1] | passed | 0.00024 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:4:2] | passed | 0.00023 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:4:3] | passed | 0.00022 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:4:4] | passed | 0.00022 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:4:5] | passed | 0.00033 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:4:4] | passed | 0.00031 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:4:5] | passed | 0.00022 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:4:6] | passed | 0.00021 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:5:1] | passed | 0.00023 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:5:2] | passed | 0.00013 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:5:3] | passed | 0.00165 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:5:4] | passed | 0.00019 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:6:1] | passed | 0.00011 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:6:2] | passed | 0.00009 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:6:3] | passed | 0.00016 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:6:4:1] | passed | 0.00011 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:7:1] | passed | 0.00044 seconds |
./spec/zai_payment/config_spec.rb[1:1:1] | passed | 0.00004 seconds |
./spec/zai_payment/config_spec.rb[1:1:2] | passed | 0.00064 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:5:3] | passed | 0.0009 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:5:4] | passed | 0.00014 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:6:1] | passed | 0.00009 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:6:2] | passed | 0.00008 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:6:3] | passed | 0.00014 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:6:4:1] | passed | 0.00009 seconds |
./spec/zai_payment/auth/token_provider_spec.rb[1:7:1] | passed | 0.00035 seconds |
./spec/zai_payment/config_spec.rb[1:1:1] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:1:2] | passed | 0.0006 seconds |
./spec/zai_payment/config_spec.rb[1:1:3] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:1:4] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:2:1:1] | passed | 0.00004 seconds |
./spec/zai_payment/config_spec.rb[1:2:2:1] | passed | 0.00004 seconds |
./spec/zai_payment/config_spec.rb[1:1:4] | passed | 0.00025 seconds |
./spec/zai_payment/config_spec.rb[1:2:1:1] | passed | 0.00007 seconds |
./spec/zai_payment/config_spec.rb[1:2:2:1] | passed | 0.00005 seconds |
./spec/zai_payment/config_spec.rb[1:2:3:1] | passed | 0.00004 seconds |
./spec/zai_payment/config_spec.rb[1:2:4:1] | passed | 0.00004 seconds |
./spec/zai_payment/config_spec.rb[1:2:4:1] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:2:5:1] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:2:6:1] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:2:7:1] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:2:7:1] | passed | 0.00004 seconds |
./spec/zai_payment/config_spec.rb[1:3:1:1] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:3:2:1] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:3:3:1] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:3:4:1] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:3:5:1] | passed | 0.00004 seconds |
./spec/zai_payment/config_spec.rb[1:4:1] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:4:2] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:4:3] | passed | 0.00003 seconds |
./spec/zai_payment/config_spec.rb[1:4:2] | passed | 0.00002 seconds |
./spec/zai_payment/config_spec.rb[1:4:3] | passed | 0.00002 seconds |
./spec/zai_payment/config_spec.rb[1:4:4] | passed | 0.00002 seconds |
./spec/zai_payment/config_spec.rb[1:4:5] | passed | 0.00002 seconds |
./spec/zai_payment/config_spec.rb[1:4:6] | passed | 0.00002 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:1:1:1] | passed | 0.00075 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:1:1:2] | passed | 0.00029 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:1:1:3] | passed | 0.00026 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:1:2:1] | passed | 0.00025 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:1:3:1] | passed | 0.00026 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:2:1:1] | passed | 0.00023 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:2:1:2] | passed | 0.00023 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:2:2:1] | passed | 0.00021 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:2:3:1] | passed | 0.00015 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:2:3:2] | passed | 0.00014 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:3:1:1] | passed | 0.00025 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:3:1:2] | passed | 0.0002 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:3:2:1] | passed | 0.00014 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:3:3:1] | passed | 0.00013 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:3:4:1] | passed | 0.00015 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:3:5:1] | passed | 0.00022 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:4:1:1] | passed | 0.00022 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:4:1:2] | passed | 0.00021 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:4:2:1] | passed | 0.00021 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:4:3:1] | passed | 0.00014 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:4:4:1] | passed | 0.00014 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:4:5:1] | passed | 0.0002 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:5:1:1] | passed | 0.00021 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:5:2:1] | passed | 0.00015 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:5:2:2] | passed | 0.00012 seconds |
./spec/zai_payment/resources/webhook_spec.rb[1:5:3:1] | passed | 0.0002 seconds |
./spec/zai_payment_spec.rb[1:1] | passed | 0.00003 seconds |
5 changes: 4 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ Metrics/MethodLength:

Style/Documentation:
Description: "Document classes and non-namespace modules."
Enabled: false
Enabled: false

RSpec/MultipleExpectations:
Max: 3
24 changes: 23 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
## [Released]

## [1.1.0] - 2025-10-22
### Added
- **Webhooks API**: Full CRUD operations for managing Zai webhooks
- `ZaiPayment.webhooks.list` - List all webhooks with pagination
- `ZaiPayment.webhooks.show(id)` - Get a specific webhook
- `ZaiPayment.webhooks.create(...)` - Create a new webhook
- `ZaiPayment.webhooks.update(id, ...)` - Update an existing webhook
- `ZaiPayment.webhooks.delete(id)` - Delete a webhook
- **Base API Client**: Reusable HTTP client for all API requests
- **Response Wrapper**: Standardized response handling with error management
- **Enhanced Error Handling**: New error classes for different API scenarios
- `ValidationError` (400, 422)
- `UnauthorizedError` (401)
- `ForbiddenError` (403)
- `NotFoundError` (404)
- `RateLimitError` (429)
- `ServerError` (5xx)
- `TimeoutError` and `ConnectionError` for network issues
- Comprehensive test suite for webhook functionality
- Example code in `examples/webhooks.rb`

**Full Changelog**: https://github.com/Sentia/zai-payment/compare/v1.0.2...v1.1.0

## [1.0.2] - 2025-10-22
- Update gemspec files and readme

Expand All @@ -18,4 +41,3 @@

**Full Changelog**: https://github.com/Sentia/zai-payment/commits/v1.0.0


2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
zai_payment (1.0.2)
zai_payment (1.1.0)
faraday (~> 2.0)

GEM
Expand Down
201 changes: 201 additions & 0 deletions IMPLEMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# Implementation Summary: Zai Payment Webhooks

## ✅ What Was Implemented

### 1. Core Infrastructure (New Files)

#### `/lib/zai_payment/client.rb`
- Base HTTP client for all API requests
- Handles authentication automatically
- Supports GET, POST, PATCH, DELETE methods
- Proper error handling and connection management
- Thread-safe and reusable

#### `/lib/zai_payment/response.rb`
- Response wrapper class
- Convenience methods: `success?`, `client_error?`, `server_error?`
- Automatic error raising based on HTTP status
- Clean data extraction from response body

#### `/lib/zai_payment/resources/webhook.rb`
- Complete CRUD operations for webhooks:
- `list(limit:, offset:)` - List all webhooks with pagination
- `show(webhook_id)` - Get specific webhook details
- `create(url:, object_type:, enabled:, description:)` - Create new webhook
- `update(webhook_id, ...)` - Update existing webhook
- `delete(webhook_id)` - Delete webhook
- Full input validation
- URL format validation
- Comprehensive error messages

### 2. Enhanced Error Handling

#### `/lib/zai_payment/errors.rb` (Updated)
Added new error classes:
- `ApiError` - Base API error
- `BadRequestError` (400)
- `UnauthorizedError` (401)
- `ForbiddenError` (403)
- `NotFoundError` (404)
- `ValidationError` (422)
- `RateLimitError` (429)
- `ServerError` (5xx)
- `TimeoutError` - Network timeout
- `ConnectionError` - Connection failed

### 3. Main Module Integration

#### `/lib/zai_payment.rb` (Updated)
- Added `require` statements for new components
- Added `webhooks` method that returns a singleton instance
- Usage: `ZaiPayment.webhooks.list`

### 4. Testing

#### `/spec/zai_payment/resources/webhook_spec.rb` (New)
Comprehensive test suite covering:
- List webhooks (success, pagination, unauthorized)
- Show webhook (success, not found, validation)
- Create webhook (success, validation errors, API errors)
- Update webhook (success, not found, validation)
- Delete webhook (success, not found, validation)
- Edge cases and error scenarios

### 5. Documentation

#### `/examples/webhooks.rb` (New)
- Complete usage examples
- All CRUD operations
- Error handling patterns
- Pagination examples
- Custom client instances

#### `/docs/WEBHOOKS.md` (New)
- Architecture overview
- API method documentation
- Error handling guide
- Best practices
- Testing instructions
- Future enhancements

#### `/README.md` (Updated)
- Added webhook usage section
- Error handling examples
- Updated roadmap (Webhooks: Done ✅)

#### `/CHANGELOG.md` (Updated)
- Added v1.1.0 release notes
- Documented all new features
- Listed all new error classes

### 6. Version

#### `/lib/zai_payment/version.rb` (Updated)
- Bumped version to 1.1.0

## 📁 File Structure

```
lib/
├── zai_payment/
│ ├── auth/ # Authentication (existing)
│ ├── client.rb # ✨ NEW: Base HTTP client
│ ├── response.rb # ✨ NEW: Response wrapper
│ ├── resources/
│ │ └── webhook.rb # ✨ NEW: Webhook CRUD operations
│ ├── config.rb # (existing)
│ ├── errors.rb # ✅ UPDATED: Added API error classes
│ └── version.rb # ✅ UPDATED: v1.1.0
└── zai_payment.rb # ✅ UPDATED: Added webhooks accessor

spec/
└── zai_payment/
└── resources/
└── webhook_spec.rb # ✨ NEW: Comprehensive tests

examples/
└── webhooks.rb # ✨ NEW: Usage examples

docs/
└── WEBHOOKS.md # ✨ NEW: Complete documentation
```

## 🎯 Key Features

1. **Clean API**: `ZaiPayment.webhooks.list`, `.show`, `.create`, `.update`, `.delete`
2. **Automatic Authentication**: Uses existing TokenProvider
3. **Comprehensive Validation**: URL format, required fields, etc.
4. **Rich Error Handling**: Specific errors for each scenario
5. **Pagination Support**: Built-in pagination for list operations
6. **Thread-Safe**: Reuses existing thread-safe authentication
7. **Well-Tested**: Full RSpec test coverage
8. **Documented**: Inline docs, examples, and guides

## 🚀 Usage

```ruby
# Configure once
ZaiPayment.configure do |config|
config.environment = :prelive
config.client_id = ENV['ZAI_CLIENT_ID']
config.client_secret = ENV['ZAI_CLIENT_SECRET']
config.scope = ENV['ZAI_SCOPE']
end

# Use webhooks
response = ZaiPayment.webhooks.list
webhooks = response.data

response = ZaiPayment.webhooks.create(
url: 'https://example.com/webhook',
object_type: 'transactions',
enabled: true
)
```

## ✨ Best Practices Applied

1. **Single Responsibility Principle**: Each class has one clear purpose
2. **DRY**: Reusable Client and Response classes
3. **Open/Closed**: Easy to extend for new resources (Users, Items, etc.)
4. **Dependency Injection**: Client accepts custom config and token provider
5. **Fail Fast**: Validation before API calls
6. **Clear Error Messages**: Descriptive validation errors
7. **RESTful Design**: Standard HTTP methods and status codes
8. **Comprehensive Testing**: Unit tests for all scenarios
9. **Documentation**: Examples, inline docs, and guides
10. **Version Control**: Semantic versioning with changelog

## 🔄 Ready for Extension

The infrastructure is now in place to easily add more resources:

```ruby
# Future resources can follow the same pattern:
lib/zai_payment/resources/
├── webhook.rb # ✅ Done
├── user.rb # Coming soon
├── item.rb # Coming soon
├── transaction.rb # Coming soon
└── wallet.rb # Coming soon
```

Each resource can reuse:
- `ZaiPayment::Client` for HTTP requests
- `ZaiPayment::Response` for response handling
- Error classes for consistent error handling
- Same authentication mechanism
- Same configuration
- Same testing patterns

## 🎉 Summary

Successfully implemented a complete, production-ready webhook management system for the Zai Payment gem with:
- ✅ Full CRUD operations
- ✅ Comprehensive testing
- ✅ Rich error handling
- ✅ Complete documentation
- ✅ Clean, maintainable code
- ✅ Following Ruby and Rails best practices
- ✅ Ready for production use

Loading