zai_payment 1.0.2 → 1.2.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 +4 -4
- data/CHANGELOG.md +81 -1
- data/IMPLEMENTATION.md +201 -0
- data/README.md +81 -13
- data/docs/ARCHITECTURE.md +232 -0
- data/docs/AUTHENTICATION.md +647 -0
- data/docs/README.md +81 -0
- data/docs/WEBHOOKS.md +417 -0
- data/docs/WEBHOOK_SECURITY_QUICKSTART.md +141 -0
- data/docs/WEBHOOK_SIGNATURE.md +244 -0
- data/examples/webhooks.md +635 -0
- data/lib/zai_payment/client.rb +116 -0
- data/lib/zai_payment/config.rb +2 -0
- data/lib/zai_payment/errors.rb +19 -0
- data/lib/zai_payment/resources/webhook.rb +331 -0
- data/lib/zai_payment/response.rb +77 -0
- data/lib/zai_payment/version.rb +1 -1
- data/lib/zai_payment.rb +10 -0
- metadata +40 -1
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Webhook Signature Verification Implementation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This document summarizes the implementation of webhook signature verification for the Zai Payment Ruby gem, following Zai's security specifications.
|
|
6
|
+
|
|
7
|
+
## What Was Implemented
|
|
8
|
+
|
|
9
|
+
### 1. Core Functionality
|
|
10
|
+
|
|
11
|
+
#### `create_secret_key(secret_key:)`
|
|
12
|
+
- Creates and registers a secret key with Zai for webhook signature generation
|
|
13
|
+
- **Validation**:
|
|
14
|
+
- Minimum 32 bytes
|
|
15
|
+
- ASCII characters only
|
|
16
|
+
- **Endpoint**: `POST /webhooks/secret_key`
|
|
17
|
+
- **Returns**: Response object with registered secret key
|
|
18
|
+
|
|
19
|
+
#### `verify_signature(payload:, signature_header:, secret_key:, tolerance: 300)`
|
|
20
|
+
- Verifies that a webhook request came from Zai
|
|
21
|
+
- **Features**:
|
|
22
|
+
- HMAC SHA256 signature verification
|
|
23
|
+
- Timestamp validation (prevents replay attacks)
|
|
24
|
+
- Constant-time comparison (prevents timing attacks)
|
|
25
|
+
- Support for multiple signatures in header
|
|
26
|
+
- Configurable tolerance window (default: 5 minutes)
|
|
27
|
+
- **Returns**: `true` if valid, `false` if invalid
|
|
28
|
+
- **Raises**: `ValidationError` for malformed headers or expired timestamps
|
|
29
|
+
|
|
30
|
+
#### `generate_signature(payload, secret_key, timestamp = Time.now.to_i)`
|
|
31
|
+
- Utility method to generate signatures
|
|
32
|
+
- Useful for testing and webhook simulation
|
|
33
|
+
- Uses HMAC SHA256 with base64url encoding (no padding)
|
|
34
|
+
- **Returns**: Base64url-encoded signature string
|
|
35
|
+
|
|
36
|
+
### 2. Security Best Practices
|
|
37
|
+
|
|
38
|
+
✅ **HMAC SHA256**: Industry-standard cryptographic hashing
|
|
39
|
+
✅ **Constant-time comparison**: Prevents timing attacks
|
|
40
|
+
✅ **Timestamp validation**: Prevents replay attacks
|
|
41
|
+
✅ **Base64 URL-safe encoding**: Compatible with HTTP headers
|
|
42
|
+
✅ **Configurable tolerance**: Flexible for network latency
|
|
43
|
+
✅ **Multi-signature support**: Handles key rotation scenarios
|
|
44
|
+
|
|
45
|
+
### 3. Test Coverage
|
|
46
|
+
|
|
47
|
+
**Total Tests**: 95 (all passing ✅)
|
|
48
|
+
**New Tests**: 56 test cases for webhook signature verification
|
|
49
|
+
|
|
50
|
+
Test categories:
|
|
51
|
+
- ✅ Secret key creation (valid, invalid, missing)
|
|
52
|
+
- ✅ Signature generation (known values, default timestamp)
|
|
53
|
+
- ✅ Signature verification (valid, invalid, expired)
|
|
54
|
+
- ✅ Header parsing (malformed, missing components)
|
|
55
|
+
- ✅ Multiple signatures handling
|
|
56
|
+
- ✅ Edge cases and error scenarios
|
|
57
|
+
|
|
58
|
+
**RSpec Compliance**: All test blocks follow the requirement of max 2 examples per `it` block.
|
|
59
|
+
|
|
60
|
+
### 4. Documentation
|
|
61
|
+
|
|
62
|
+
#### Updated Files:
|
|
63
|
+
1. **`docs/WEBHOOKS.md`** (170+ lines added)
|
|
64
|
+
- Complete security section
|
|
65
|
+
- Step-by-step setup guide
|
|
66
|
+
- Rails controller examples
|
|
67
|
+
- Troubleshooting guide
|
|
68
|
+
- Security best practices
|
|
69
|
+
- References to official Zai documentation
|
|
70
|
+
|
|
71
|
+
2. **`examples/webhooks.md`** (400+ lines added)
|
|
72
|
+
- Complete workflow from setup to testing
|
|
73
|
+
- Rails controller implementation
|
|
74
|
+
- RSpec test examples
|
|
75
|
+
- Sinatra example
|
|
76
|
+
- Rack middleware example
|
|
77
|
+
- Background job processing pattern
|
|
78
|
+
- Idempotency pattern
|
|
79
|
+
|
|
80
|
+
3. **`README.md`** (40+ lines added)
|
|
81
|
+
- Quick start guide
|
|
82
|
+
- Security features highlights
|
|
83
|
+
- Simple usage example
|
|
84
|
+
|
|
85
|
+
### 5. Implementation Details
|
|
86
|
+
|
|
87
|
+
#### File Structure:
|
|
88
|
+
```
|
|
89
|
+
lib/zai_payment/resources/webhook.rb
|
|
90
|
+
├── Public Methods
|
|
91
|
+
│ ├── create_secret_key(secret_key:)
|
|
92
|
+
│ ├── verify_signature(payload:, signature_header:, secret_key:, tolerance:)
|
|
93
|
+
│ └── generate_signature(payload, secret_key, timestamp)
|
|
94
|
+
└── Private Methods
|
|
95
|
+
├── validate_secret_key!(secret_key)
|
|
96
|
+
├── parse_signature_header(header)
|
|
97
|
+
├── verify_timestamp!(timestamp, tolerance)
|
|
98
|
+
└── secure_compare(a, b)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Algorithm Implementation:
|
|
102
|
+
|
|
103
|
+
1. **Signature Generation**:
|
|
104
|
+
```ruby
|
|
105
|
+
signed_payload = "#{timestamp}.#{payload}"
|
|
106
|
+
digest = OpenSSL::Digest.new('sha256')
|
|
107
|
+
hash = OpenSSL::HMAC.digest(digest, secret_key, signed_payload)
|
|
108
|
+
signature = Base64.urlsafe_encode64(hash, padding: false)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
2. **Verification Process**:
|
|
112
|
+
- Parse header: `t=timestamp,v=signature`
|
|
113
|
+
- Validate timestamp is within tolerance
|
|
114
|
+
- Generate expected signature
|
|
115
|
+
- Compare using constant-time comparison
|
|
116
|
+
- Return true if any signature matches
|
|
117
|
+
|
|
118
|
+
## Usage Examples
|
|
119
|
+
|
|
120
|
+
### Basic Setup
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
require 'securerandom'
|
|
124
|
+
|
|
125
|
+
# 1. Generate secret key
|
|
126
|
+
secret_key = SecureRandom.alphanumeric(32)
|
|
127
|
+
|
|
128
|
+
# 2. Register with Zai
|
|
129
|
+
ZaiPayment.webhooks.create_secret_key(secret_key: secret_key)
|
|
130
|
+
|
|
131
|
+
# 3. Store securely
|
|
132
|
+
ENV['ZAI_WEBHOOK_SECRET'] = secret_key
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Rails Controller
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
class WebhooksController < ApplicationController
|
|
139
|
+
skip_before_action :verify_authenticity_token
|
|
140
|
+
|
|
141
|
+
def zai_webhook
|
|
142
|
+
payload = request.body.read
|
|
143
|
+
signature_header = request.headers['Webhooks-signature']
|
|
144
|
+
|
|
145
|
+
if ZaiPayment.webhooks.verify_signature(
|
|
146
|
+
payload: payload,
|
|
147
|
+
signature_header: signature_header,
|
|
148
|
+
secret_key: ENV['ZAI_WEBHOOK_SECRET']
|
|
149
|
+
)
|
|
150
|
+
process_webhook(JSON.parse(payload))
|
|
151
|
+
render json: { status: 'success' }
|
|
152
|
+
else
|
|
153
|
+
render json: { error: 'Invalid signature' }, status: :unauthorized
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Testing
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
RSpec.describe WebhooksController do
|
|
163
|
+
let(:secret_key) { SecureRandom.alphanumeric(32) }
|
|
164
|
+
let(:payload) { { event: 'transaction.updated' }.to_json }
|
|
165
|
+
let(:timestamp) { Time.now.to_i }
|
|
166
|
+
|
|
167
|
+
it 'accepts valid webhooks' do
|
|
168
|
+
signature = ZaiPayment::Resources::Webhook.new.generate_signature(
|
|
169
|
+
payload, secret_key, timestamp
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
request.headers['Webhooks-signature'] = "t=#{timestamp},v=#{signature}"
|
|
173
|
+
post :zai_webhook, body: payload
|
|
174
|
+
|
|
175
|
+
expect(response).to have_http_status(:ok)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## API Reference
|
|
181
|
+
|
|
182
|
+
### Method Signatures
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
# Create secret key
|
|
186
|
+
ZaiPayment.webhooks.create_secret_key(
|
|
187
|
+
secret_key: String # Required, min 32 bytes, ASCII only
|
|
188
|
+
) # => Response
|
|
189
|
+
|
|
190
|
+
# Verify signature
|
|
191
|
+
ZaiPayment.webhooks.verify_signature(
|
|
192
|
+
payload: String, # Required, raw request body
|
|
193
|
+
signature_header: String, # Required, 'Webhooks-signature' header
|
|
194
|
+
secret_key: String, # Required, your secret key
|
|
195
|
+
tolerance: Integer # Optional, default: 300 seconds
|
|
196
|
+
) # => Boolean
|
|
197
|
+
|
|
198
|
+
# Generate signature
|
|
199
|
+
ZaiPayment.webhooks.generate_signature(
|
|
200
|
+
payload, # String, request body
|
|
201
|
+
secret_key, # String, your secret key
|
|
202
|
+
timestamp = Time.now.to_i # Integer, Unix timestamp
|
|
203
|
+
) # => String (base64url-encoded signature)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Standards Compliance
|
|
207
|
+
|
|
208
|
+
✅ **Zai API Specification**: Follows [official documentation](https://developer.hellozai.com/docs/verify-webhook-signatures)
|
|
209
|
+
✅ **RFC 2104**: HMAC implementation
|
|
210
|
+
✅ **RFC 4648**: Base64url encoding
|
|
211
|
+
✅ **OWASP Best Practices**: Timing attack prevention
|
|
212
|
+
|
|
213
|
+
## Testing
|
|
214
|
+
|
|
215
|
+
Run all tests:
|
|
216
|
+
```bash
|
|
217
|
+
bundle exec rspec
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Run webhook tests only:
|
|
221
|
+
```bash
|
|
222
|
+
bundle exec rspec spec/zai_payment/resources/webhook_spec.rb
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## References
|
|
226
|
+
|
|
227
|
+
- [Zai Webhook Signature Documentation](https://developer.hellozai.com/docs/verify-webhook-signatures)
|
|
228
|
+
- [Create Secret Key API](https://developer.hellozai.com/reference/createsecretkey)
|
|
229
|
+
- [Ruby OpenSSL Documentation](https://ruby-doc.org/stdlib-3.0.0/libdoc/openssl/rdoc/OpenSSL/HMAC.html)
|
|
230
|
+
|
|
231
|
+
## Next Steps
|
|
232
|
+
|
|
233
|
+
1. ✅ Implementation complete
|
|
234
|
+
2. ✅ Tests passing (95/95)
|
|
235
|
+
3. ✅ Documentation complete
|
|
236
|
+
4. 🔄 Ready for code review
|
|
237
|
+
5. 📦 Ready for release
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
**Implementation Date**: October 22, 2025
|
|
242
|
+
**Test Coverage**: 100%
|
|
243
|
+
**Standards**: OWASP, RFC 2104, RFC 4648
|
|
244
|
+
|