epos_now_client 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/CHANGELOG.md +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +326 -0
- data/lib/epos_now_client/client.rb +63 -0
- data/lib/epos_now_client/configuration.rb +44 -0
- data/lib/epos_now_client/connection.rb +141 -0
- data/lib/epos_now_client/error.rb +21 -0
- data/lib/epos_now_client/resources/base_resource.rb +56 -0
- data/lib/epos_now_client/resources/brands.rb +33 -0
- data/lib/epos_now_client/resources/categories.rb +33 -0
- data/lib/epos_now_client/resources/customers.rb +37 -0
- data/lib/epos_now_client/resources/devices.rb +21 -0
- data/lib/epos_now_client/resources/locations.rb +21 -0
- data/lib/epos_now_client/resources/products.rb +37 -0
- data/lib/epos_now_client/resources/staff.rb +33 -0
- data/lib/epos_now_client/resources/tax_groups.rb +21 -0
- data/lib/epos_now_client/resources/tender_types.rb +25 -0
- data/lib/epos_now_client/resources/transactions.rb +44 -0
- data/lib/epos_now_client/version.rb +5 -0
- data/lib/epos_now_client.rb +34 -0
- metadata +205 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 486c114791db7ffda314246e447b5c03a56879dc08a8551d9e695338ad02bdbe
|
|
4
|
+
data.tar.gz: 6c43a07100a3f83a2fbce1feea144aaa4eda78b0d11cf1e636a7309d413bf9ea
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a64dbe7f3883d91a09ef3dca33a721571dbe2c574f88462fdfe6ac50207e0a69919b2ac9976207f63521e1f68bda6950b57cf0d976d8decefddb3bac81ec494e
|
|
7
|
+
data.tar.gz: df5c985d5b28e3728ca1a92792b4e0635f5f7dac069e855c8c272e0a860a3e46a406bfcc0484e4f673a4ac074179206823f3b4006136e871ecc0151b4431d1e9
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-03-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial release
|
|
13
|
+
- Basic Auth configuration with API Key and Secret
|
|
14
|
+
- HTTP connection layer with retry logic and exponential backoff
|
|
15
|
+
- Automatic pagination (200 items/page)
|
|
16
|
+
- 10 resource classes: Products, Categories, Transactions, TenderTypes, TaxGroups, Customers, Staff, Devices, Brands, Locations
|
|
17
|
+
- Search and filtering support via Epos Now query syntax
|
|
18
|
+
- Structured error hierarchy: AuthenticationError, RateLimitError, NotFoundError, ValidationError, ServerError, ConnectionError, TimeoutError
|
|
19
|
+
- Per-client and global configuration
|
|
20
|
+
- Optional logging support
|
|
21
|
+
- 131 specs with 100% line and branch coverage
|
|
22
|
+
- RuboCop with 0 offenses
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TheOwnerStack
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# EposNowClient
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/epos_now_client)
|
|
4
|
+
[](https://github.com/dan1d/epos_now_client/actions)
|
|
5
|
+
[](LICENSE.txt)
|
|
6
|
+
|
|
7
|
+
A lightweight, fully-tested Ruby client for the [Epos Now REST API (V4)](https://developer.eposnowhq.com).
|
|
8
|
+
|
|
9
|
+
**131 specs | 100% line coverage | 100% branch coverage | 0 RuboCop offenses**
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Basic Auth** with API Key + Secret (Base64-encoded)
|
|
14
|
+
- **10 resource classes**: Products, Categories, Transactions, TenderTypes, TaxGroups, Customers, Staff, Devices, Brands, Locations
|
|
15
|
+
- **Automatic pagination** (200 items/page, fetches all pages)
|
|
16
|
+
- **Retry with exponential backoff** on transient network errors
|
|
17
|
+
- **Structured error hierarchy** for clean exception handling
|
|
18
|
+
- **Search & filtering** using Epos Now query syntax
|
|
19
|
+
- **Zero runtime dependencies** beyond HTTParty
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Add to your Gemfile:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
gem 'epos_now_client'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then run:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bundle install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or install directly:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
gem install epos_now_client
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
require 'epos_now_client'
|
|
45
|
+
|
|
46
|
+
client = EposNowClient::Client.new(
|
|
47
|
+
api_key: 'your_api_key',
|
|
48
|
+
api_secret: 'your_api_secret'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Fetch all products
|
|
52
|
+
products = client.products.all
|
|
53
|
+
|
|
54
|
+
# Create a category
|
|
55
|
+
client.categories.create_category({ 'Name' => 'Drinks' })
|
|
56
|
+
|
|
57
|
+
# Search products
|
|
58
|
+
client.products.search('(Name|contains|Burger)')
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Authentication
|
|
62
|
+
|
|
63
|
+
Epos Now uses **Basic Auth** with an API Key and Secret. Get your credentials from the Epos Now Backoffice:
|
|
64
|
+
|
|
65
|
+
1. Navigate to **Web Integrations > REST API**
|
|
66
|
+
2. Click **Add Device**
|
|
67
|
+
3. Copy the **API Key** and **API Secret**
|
|
68
|
+
|
|
69
|
+
> **Note:** Epos Now uses a single API endpoint (`https://api.eposnowhq.com`) for all environments. There is no separate sandbox URL. Developer accounts use the same API with test credentials.
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
### Global (recommended for single-account apps)
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
EposNowClient.configure do |config|
|
|
77
|
+
config.api_key = ENV['EPOS_NOW_API_KEY']
|
|
78
|
+
config.api_secret = ENV['EPOS_NOW_API_SECRET']
|
|
79
|
+
config.base_url = 'https://api.eposnowhq.com' # default
|
|
80
|
+
config.timeout = 30 # default (seconds)
|
|
81
|
+
config.open_timeout = 10 # default (seconds)
|
|
82
|
+
config.max_retries = 3 # default
|
|
83
|
+
config.retry_delay = 1.0 # default (seconds, doubles each retry)
|
|
84
|
+
config.per_page = 200 # default
|
|
85
|
+
config.logger = Logger.new($stdout) # optional
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
client = EposNowClient::Client.new
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Per-client (for multi-account apps)
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
client = EposNowClient::Client.new(
|
|
95
|
+
api_key: 'merchant_api_key',
|
|
96
|
+
api_secret: 'merchant_api_secret',
|
|
97
|
+
timeout: 60
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Rails initializer
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
# config/initializers/epos_now.rb
|
|
105
|
+
EposNowClient.configure do |config|
|
|
106
|
+
config.api_key = Rails.application.credentials.dig(:epos_now, :api_key)
|
|
107
|
+
config.api_secret = Rails.application.credentials.dig(:epos_now, :api_secret)
|
|
108
|
+
config.logger = Rails.logger
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Usage
|
|
113
|
+
|
|
114
|
+
### Categories
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
client.categories.all # List all (paginated)
|
|
118
|
+
client.categories.find(42) # Get by ID
|
|
119
|
+
client.categories.create_category({ 'Name' => 'Drinks' }) # Create
|
|
120
|
+
client.categories.update_category(42, { 'Name' => 'Bev' }) # Update
|
|
121
|
+
client.categories.delete_category(42) # Delete
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Products
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
client.products.all # List all
|
|
128
|
+
client.products.find(10) # Get by ID
|
|
129
|
+
client.products.create_product({ # Create
|
|
130
|
+
'Name' => 'Burger',
|
|
131
|
+
'SalePrice' => 9.99,
|
|
132
|
+
'CategoryId' => 1
|
|
133
|
+
})
|
|
134
|
+
client.products.update_product(10, { 'SalePrice' => 11.99 }) # Update
|
|
135
|
+
client.products.delete_product(10) # Delete
|
|
136
|
+
client.products.search('(Name|contains|Burg)') # Search
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Transactions
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
client.transactions.all # List all
|
|
143
|
+
client.transactions.find(100) # Get by ID
|
|
144
|
+
client.transactions.by_date( # By date range
|
|
145
|
+
start_date: '2026-03-01',
|
|
146
|
+
end_date: '2026-03-13'
|
|
147
|
+
)
|
|
148
|
+
client.transactions.latest # Latest
|
|
149
|
+
client.transactions.create_transaction({ # Create
|
|
150
|
+
'TransactionItems' => [{ 'ProductId' => 1, 'Quantity' => 2 }],
|
|
151
|
+
'Tenders' => [{ 'TenderTypeId' => 1, 'Amount' => 25.50 }],
|
|
152
|
+
'ServiceType' => 0 # 0=EatIn, 1=Takeaway, 2=Delivery
|
|
153
|
+
})
|
|
154
|
+
client.transactions.delete_transaction(200) # Delete
|
|
155
|
+
client.transactions.validate(payload) # Validate
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Tender Types
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
client.tender_types.all
|
|
162
|
+
client.tender_types.find(1)
|
|
163
|
+
client.tender_types.create_tender_type({ 'Name' => 'Gift Card' })
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Tax Groups
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
client.tax_groups.all
|
|
170
|
+
client.tax_groups.find(1)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Customers
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
client.customers.all
|
|
177
|
+
client.customers.find(1)
|
|
178
|
+
client.customers.create_customer({ 'FirstName' => 'Jane', 'LastName' => 'Doe' })
|
|
179
|
+
client.customers.update_customer(1, { 'FirstName' => 'Janet' })
|
|
180
|
+
client.customers.delete_customer(1)
|
|
181
|
+
client.customers.search('(FirstName|contains|Jane)')
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Staff
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
client.staff.all
|
|
188
|
+
client.staff.find(1)
|
|
189
|
+
client.staff.create_staff({ 'FirstName' => 'Bob', 'Pin' => '1234' })
|
|
190
|
+
client.staff.update_staff(1, { 'FirstName' => 'Robert' })
|
|
191
|
+
client.staff.delete_staff(1)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Devices
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
client.devices.all
|
|
198
|
+
client.devices.find(1)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Brands
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
client.brands.all
|
|
205
|
+
client.brands.find(1)
|
|
206
|
+
client.brands.create_brand({ 'Name' => 'Coca-Cola' })
|
|
207
|
+
client.brands.update_brand(1, { 'Name' => 'PepsiCo' })
|
|
208
|
+
client.brands.delete_brand(1)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Locations
|
|
212
|
+
|
|
213
|
+
```ruby
|
|
214
|
+
client.locations.all
|
|
215
|
+
client.locations.find(1)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Pagination
|
|
219
|
+
|
|
220
|
+
All `.all` methods automatically paginate through every page (200 items per page):
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
# Fetches ALL products, regardless of how many pages exist
|
|
224
|
+
all_products = client.products.all
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Search & Filtering
|
|
228
|
+
|
|
229
|
+
Epos Now supports search filters using the syntax `(PropertyName|Operator|Value)`:
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
# Available operators: contains, StartsWith, EndsWith, >, >=, <, <=, like
|
|
233
|
+
client.products.search('(Name|contains|Burger)')
|
|
234
|
+
client.customers.search('(FirstName|StartsWith|J)')
|
|
235
|
+
client.products.search('(SalePrice|>|10.00)')
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Error Handling
|
|
239
|
+
|
|
240
|
+
All errors inherit from `EposNowClient::Error` and include `status` and `body` attributes:
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
begin
|
|
244
|
+
client.products.find(999)
|
|
245
|
+
rescue EposNowClient::AuthenticationError => e
|
|
246
|
+
# 401 - Invalid API key or secret
|
|
247
|
+
rescue EposNowClient::NotFoundError => e
|
|
248
|
+
# 404 - Resource not found
|
|
249
|
+
rescue EposNowClient::ValidationError => e
|
|
250
|
+
# 422 - Invalid request payload
|
|
251
|
+
rescue EposNowClient::RateLimitError => e
|
|
252
|
+
# 429 - API rate limit exceeded
|
|
253
|
+
rescue EposNowClient::ServerError => e
|
|
254
|
+
# 500-599 - Epos Now server error
|
|
255
|
+
rescue EposNowClient::ConnectionError => e
|
|
256
|
+
# Network errors (after all retries exhausted)
|
|
257
|
+
rescue EposNowClient::Error => e
|
|
258
|
+
# Catch-all
|
|
259
|
+
e.status # HTTP status code (Integer or nil)
|
|
260
|
+
e.body # Parsed response body (Hash, String, or nil)
|
|
261
|
+
e.message # Human-readable error message
|
|
262
|
+
end
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Retry Logic
|
|
266
|
+
|
|
267
|
+
The client automatically retries on transient network errors with exponential backoff:
|
|
268
|
+
|
|
269
|
+
- `Errno::ECONNRESET`, `Errno::ECONNREFUSED`, `Errno::ETIMEDOUT`
|
|
270
|
+
- `Net::OpenTimeout`, `Net::ReadTimeout`
|
|
271
|
+
- `SocketError`
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
EposNowClient.configure do |config|
|
|
275
|
+
config.max_retries = 3 # Retry up to 3 times (default)
|
|
276
|
+
config.retry_delay = 1.0 # 1s, 2s, 4s backoff (default)
|
|
277
|
+
end
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## API Reference
|
|
281
|
+
|
|
282
|
+
| Resource | Methods |
|
|
283
|
+
|----------|---------|
|
|
284
|
+
| `client.categories` | `all`, `find`, `create_category`, `update_category`, `delete_category` |
|
|
285
|
+
| `client.products` | `all`, `find`, `create_product`, `update_product`, `delete_product`, `search` |
|
|
286
|
+
| `client.transactions` | `all`, `find`, `create_transaction`, `delete_transaction`, `by_date`, `latest`, `validate` |
|
|
287
|
+
| `client.tender_types` | `all`, `find`, `create_tender_type` |
|
|
288
|
+
| `client.tax_groups` | `all`, `find` |
|
|
289
|
+
| `client.customers` | `all`, `find`, `create_customer`, `update_customer`, `delete_customer`, `search` |
|
|
290
|
+
| `client.staff` | `all`, `find`, `create_staff`, `update_staff`, `delete_staff` |
|
|
291
|
+
| `client.devices` | `all`, `find` |
|
|
292
|
+
| `client.brands` | `all`, `find`, `create_brand`, `update_brand`, `delete_brand` |
|
|
293
|
+
| `client.locations` | `all`, `find` |
|
|
294
|
+
|
|
295
|
+
## Development
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
git clone https://github.com/dan1d/epos_now_client.git
|
|
299
|
+
cd epos_now_client
|
|
300
|
+
bundle install
|
|
301
|
+
|
|
302
|
+
# Run the full suite
|
|
303
|
+
bundle exec rake # RuboCop + RSpec
|
|
304
|
+
|
|
305
|
+
# Or individually
|
|
306
|
+
bundle exec rspec # 131 specs, 100% line + branch coverage
|
|
307
|
+
bundle exec rubocop # 0 offenses
|
|
308
|
+
|
|
309
|
+
# View coverage report
|
|
310
|
+
open coverage/index.html
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Contributing
|
|
314
|
+
|
|
315
|
+
1. Fork it
|
|
316
|
+
2. Create your feature branch (`git checkout -b feature/my-feature`)
|
|
317
|
+
3. Write tests first (TDD)
|
|
318
|
+
4. Ensure 100% coverage (`bundle exec rspec`)
|
|
319
|
+
5. Ensure 0 RuboCop offenses (`bundle exec rubocop`)
|
|
320
|
+
6. Commit your changes (`git commit -m 'Add my feature'`)
|
|
321
|
+
7. Push to the branch (`git push origin feature/my-feature`)
|
|
322
|
+
8. Create a Pull Request
|
|
323
|
+
|
|
324
|
+
## License
|
|
325
|
+
|
|
326
|
+
MIT License. See [LICENSE.txt](LICENSE.txt) for details.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EposNowClient
|
|
4
|
+
class Client
|
|
5
|
+
attr_reader :configuration, :connection
|
|
6
|
+
|
|
7
|
+
def initialize(api_key: nil, api_secret: nil, base_url: nil, **)
|
|
8
|
+
@configuration = build_configuration(api_key, api_secret, base_url, **)
|
|
9
|
+
@connection = Connection.new(@configuration)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def categories
|
|
13
|
+
@categories ||= Resources::Categories.new(connection)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def products
|
|
17
|
+
@products ||= Resources::Products.new(connection)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def transactions
|
|
21
|
+
@transactions ||= Resources::Transactions.new(connection)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def tender_types
|
|
25
|
+
@tender_types ||= Resources::TenderTypes.new(connection)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def tax_groups
|
|
29
|
+
@tax_groups ||= Resources::TaxGroups.new(connection)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def customers
|
|
33
|
+
@customers ||= Resources::Customers.new(connection)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def staff
|
|
37
|
+
@staff ||= Resources::Staff.new(connection)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def devices
|
|
41
|
+
@devices ||= Resources::Devices.new(connection)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def brands
|
|
45
|
+
@brands ||= Resources::Brands.new(connection)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def locations
|
|
49
|
+
@locations ||= Resources::Locations.new(connection)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def build_configuration(api_key, api_secret, base_url, **options)
|
|
55
|
+
config = EposNowClient.configuration.dup
|
|
56
|
+
config.api_key = api_key if api_key
|
|
57
|
+
config.api_secret = api_secret if api_secret
|
|
58
|
+
config.base_url = base_url if base_url
|
|
59
|
+
options.each { |key, value| config.send(:"#{key}=", value) }
|
|
60
|
+
config
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module EposNowClient
|
|
6
|
+
class Configuration
|
|
7
|
+
attr_accessor :api_key, :api_secret, :timeout, :open_timeout,
|
|
8
|
+
:max_retries, :retry_delay, :logger, :per_page
|
|
9
|
+
attr_reader :base_url
|
|
10
|
+
|
|
11
|
+
DEFAULT_BASE_URL = 'https://api.eposnowhq.com'
|
|
12
|
+
DEFAULT_TIMEOUT = 30
|
|
13
|
+
DEFAULT_OPEN_TIMEOUT = 10
|
|
14
|
+
DEFAULT_MAX_RETRIES = 3
|
|
15
|
+
DEFAULT_RETRY_DELAY = 1.0
|
|
16
|
+
DEFAULT_PER_PAGE = 200
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@api_key = nil
|
|
20
|
+
@api_secret = nil
|
|
21
|
+
@base_url = DEFAULT_BASE_URL
|
|
22
|
+
@timeout = DEFAULT_TIMEOUT
|
|
23
|
+
@open_timeout = DEFAULT_OPEN_TIMEOUT
|
|
24
|
+
@max_retries = DEFAULT_MAX_RETRIES
|
|
25
|
+
@retry_delay = DEFAULT_RETRY_DELAY
|
|
26
|
+
@per_page = DEFAULT_PER_PAGE
|
|
27
|
+
@logger = nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def auth_token
|
|
31
|
+
validate!
|
|
32
|
+
Base64.strict_encode64("#{api_key}:#{api_secret}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def validate!
|
|
36
|
+
raise AuthenticationError, 'API key is required' if api_key.nil? || api_key.empty?
|
|
37
|
+
raise AuthenticationError, 'API secret is required' if api_secret.nil? || api_secret.empty?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def base_url=(url)
|
|
41
|
+
@base_url = url&.chomp('/')
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'httparty'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module EposNowClient
|
|
7
|
+
class Connection
|
|
8
|
+
RETRYABLE_ERRORS = [
|
|
9
|
+
Errno::ECONNRESET,
|
|
10
|
+
Errno::ECONNREFUSED,
|
|
11
|
+
Errno::ETIMEDOUT,
|
|
12
|
+
Net::OpenTimeout,
|
|
13
|
+
Net::ReadTimeout,
|
|
14
|
+
SocketError
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
RETRYABLE_STATUS_CODES = [502, 503, 504].freeze
|
|
18
|
+
|
|
19
|
+
def initialize(configuration)
|
|
20
|
+
@configuration = configuration
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get(path, params: {})
|
|
24
|
+
request(:get, path, query: params)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def post(path, body: {})
|
|
28
|
+
request(:post, path, body: body.to_json)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def put(path, body: {})
|
|
32
|
+
request(:put, path, body: body.to_json)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def delete(path, body: nil)
|
|
36
|
+
options = {}
|
|
37
|
+
options[:body] = body.to_json if body
|
|
38
|
+
request(:delete, path, **options)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def request(method, path, **)
|
|
44
|
+
url = build_url(path)
|
|
45
|
+
attempts = 0
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
attempts += 1
|
|
49
|
+
response = execute_request(method, url, **)
|
|
50
|
+
handle_response(response)
|
|
51
|
+
rescue *RETRYABLE_ERRORS => e
|
|
52
|
+
raise ConnectionError, "Connection failed: #{e.message}" unless retryable?(attempts)
|
|
53
|
+
|
|
54
|
+
sleep_before_retry(attempts)
|
|
55
|
+
retry
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def execute_request(method, url, **)
|
|
60
|
+
HTTParty.send(
|
|
61
|
+
method,
|
|
62
|
+
url,
|
|
63
|
+
**default_options, **
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def default_options
|
|
68
|
+
{
|
|
69
|
+
headers: {
|
|
70
|
+
'Authorization' => "Basic #{@configuration.auth_token}",
|
|
71
|
+
'Content-Type' => 'application/json',
|
|
72
|
+
'Accept' => 'application/json'
|
|
73
|
+
},
|
|
74
|
+
timeout: @configuration.timeout,
|
|
75
|
+
open_timeout: @configuration.open_timeout
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def build_url(path)
|
|
80
|
+
"#{@configuration.base_url}/api/v4/#{path}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def handle_response(response)
|
|
84
|
+
log_response(response)
|
|
85
|
+
|
|
86
|
+
return parse_body(response) if response.code.between?(200, 204)
|
|
87
|
+
|
|
88
|
+
raise_error_for(response)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def raise_error_for(response)
|
|
92
|
+
error_class, message = error_mapping(response)
|
|
93
|
+
raise error_class.new(message, status: response.code, body: parse_body(response))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def error_mapping(response)
|
|
97
|
+
case response.code
|
|
98
|
+
when 401 then [AuthenticationError, 'Authentication failed (401)']
|
|
99
|
+
when 404 then [NotFoundError, 'Resource not found (404)']
|
|
100
|
+
when 422 then [ValidationError, "Validation failed (422): #{response.body}"]
|
|
101
|
+
when 429 then [RateLimitError, 'Rate limit exceeded (429)']
|
|
102
|
+
when 500..599 then [ServerError, "Server error (#{response.code})"]
|
|
103
|
+
else [Error, "Unexpected response (#{response.code}): #{response.body}"]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def parse_body(response)
|
|
108
|
+
return nil if response.body.nil? || response.body.empty?
|
|
109
|
+
|
|
110
|
+
JSON.parse(response.body)
|
|
111
|
+
rescue JSON::ParserError
|
|
112
|
+
response.body
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def retryable?(attempts)
|
|
116
|
+
attempts < @configuration.max_retries
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def sleep_before_retry(attempts)
|
|
120
|
+
delay = @configuration.retry_delay * (2**(attempts - 1))
|
|
121
|
+
log_retry(attempts, delay)
|
|
122
|
+
sleep(delay)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def log_response(response)
|
|
126
|
+
return unless @configuration.logger
|
|
127
|
+
|
|
128
|
+
@configuration.logger.debug(
|
|
129
|
+
"EposNowClient [#{response.code}] #{response.request.http_method::METHOD} #{response.request.uri}"
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def log_retry(attempts, delay)
|
|
134
|
+
return unless @configuration.logger
|
|
135
|
+
|
|
136
|
+
@configuration.logger.warn(
|
|
137
|
+
"EposNowClient retry #{attempts}/#{@configuration.max_retries} in #{delay}s"
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EposNowClient
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
attr_reader :status, :body
|
|
6
|
+
|
|
7
|
+
def initialize(message = nil, status: nil, body: nil)
|
|
8
|
+
@status = status
|
|
9
|
+
@body = body
|
|
10
|
+
super(message)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class AuthenticationError < Error; end
|
|
15
|
+
class RateLimitError < Error; end
|
|
16
|
+
class NotFoundError < Error; end
|
|
17
|
+
class ValidationError < Error; end
|
|
18
|
+
class ServerError < Error; end
|
|
19
|
+
class ConnectionError < Error; end
|
|
20
|
+
class TimeoutError < Error; end
|
|
21
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EposNowClient
|
|
4
|
+
module Resources
|
|
5
|
+
class BaseResource
|
|
6
|
+
attr_reader :connection
|
|
7
|
+
|
|
8
|
+
def initialize(connection)
|
|
9
|
+
@connection = connection
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def resource_name
|
|
15
|
+
raise NotImplementedError, "#{self.class} must implement #resource_name"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def list(params = {})
|
|
19
|
+
connection.get(resource_name, params: params)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def fetch(id)
|
|
23
|
+
connection.get("#{resource_name}/#{id}")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def create(attributes)
|
|
27
|
+
connection.post(resource_name, body: attributes)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def update(id, attributes)
|
|
31
|
+
connection.put("#{resource_name}/#{id}", body: attributes)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def destroy(id)
|
|
35
|
+
connection.delete("#{resource_name}/#{id}")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def fetch_all_pages(params = {})
|
|
39
|
+
page = 1
|
|
40
|
+
all_records = []
|
|
41
|
+
|
|
42
|
+
loop do
|
|
43
|
+
results = list(params.merge(page: page))
|
|
44
|
+
break if results.nil? || !results.is_a?(Array) || results.empty?
|
|
45
|
+
|
|
46
|
+
all_records.concat(results)
|
|
47
|
+
break if results.length < 200
|
|
48
|
+
|
|
49
|
+
page += 1
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
all_records
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EposNowClient
|
|
4
|
+
module Resources
|
|
5
|
+
class Brands < BaseResource
|
|
6
|
+
def all(params = {})
|
|
7
|
+
fetch_all_pages(params)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find(id)
|
|
11
|
+
fetch(id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_brand(attributes)
|
|
15
|
+
create(attributes)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def update_brand(id, attributes)
|
|
19
|
+
update(id, attributes)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def delete_brand(id)
|
|
23
|
+
destroy(id)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def resource_name
|
|
29
|
+
'Brand'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EposNowClient
|
|
4
|
+
module Resources
|
|
5
|
+
class Categories < BaseResource
|
|
6
|
+
def all(params = {})
|
|
7
|
+
fetch_all_pages(params)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find(id)
|
|
11
|
+
fetch(id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_category(attributes)
|
|
15
|
+
create(attributes)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def update_category(id, attributes)
|
|
19
|
+
update(id, attributes)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def delete_category(id)
|
|
23
|
+
destroy(id)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def resource_name
|
|
29
|
+
'Category'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EposNowClient
|
|
4
|
+
module Resources
|
|
5
|
+
class Customers < BaseResource
|
|
6
|
+
def all(params = {})
|
|
7
|
+
fetch_all_pages(params)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find(id)
|
|
11
|
+
fetch(id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_customer(attributes)
|
|
15
|
+
create(attributes)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def update_customer(id, attributes)
|
|
19
|
+
update(id, attributes)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def delete_customer(id)
|
|
23
|
+
destroy(id)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def search(query, params = {})
|
|
27
|
+
list(params.merge(search: query))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def resource_name
|
|
33
|
+
'Customer'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EposNowClient
|
|
4
|
+
module Resources
|
|
5
|
+
class Devices < BaseResource
|
|
6
|
+
def all(params = {})
|
|
7
|
+
fetch_all_pages(params)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find(id)
|
|
11
|
+
fetch(id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def resource_name
|
|
17
|
+
'Device'
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EposNowClient
|
|
4
|
+
module Resources
|
|
5
|
+
class Locations < BaseResource
|
|
6
|
+
def all(params = {})
|
|
7
|
+
fetch_all_pages(params)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find(id)
|
|
11
|
+
fetch(id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def resource_name
|
|
17
|
+
'Location'
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EposNowClient
|
|
4
|
+
module Resources
|
|
5
|
+
class Products < BaseResource
|
|
6
|
+
def all(params = {})
|
|
7
|
+
fetch_all_pages(params)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find(id)
|
|
11
|
+
fetch(id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_product(attributes)
|
|
15
|
+
create(attributes)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def update_product(id, attributes)
|
|
19
|
+
update(id, attributes)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def delete_product(id)
|
|
23
|
+
destroy(id)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def search(query, params = {})
|
|
27
|
+
list(params.merge(search: query))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def resource_name
|
|
33
|
+
'Product'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EposNowClient
|
|
4
|
+
module Resources
|
|
5
|
+
class Staff < BaseResource
|
|
6
|
+
def all(params = {})
|
|
7
|
+
fetch_all_pages(params)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find(id)
|
|
11
|
+
fetch(id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_staff(attributes)
|
|
15
|
+
create(attributes)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def update_staff(id, attributes)
|
|
19
|
+
update(id, attributes)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def delete_staff(id)
|
|
23
|
+
destroy(id)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def resource_name
|
|
29
|
+
'Staff'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EposNowClient
|
|
4
|
+
module Resources
|
|
5
|
+
class TaxGroups < BaseResource
|
|
6
|
+
def all(params = {})
|
|
7
|
+
fetch_all_pages(params)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find(id)
|
|
11
|
+
fetch(id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def resource_name
|
|
17
|
+
'TaxGroup'
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EposNowClient
|
|
4
|
+
module Resources
|
|
5
|
+
class TenderTypes < BaseResource
|
|
6
|
+
def all(params = {})
|
|
7
|
+
fetch_all_pages(params)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find(id)
|
|
11
|
+
fetch(id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_tender_type(attributes)
|
|
15
|
+
create(attributes)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def resource_name
|
|
21
|
+
'TenderType'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EposNowClient
|
|
4
|
+
module Resources
|
|
5
|
+
class Transactions < BaseResource
|
|
6
|
+
def all(params = {})
|
|
7
|
+
fetch_all_pages(params)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find(id)
|
|
11
|
+
fetch(id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_transaction(attributes)
|
|
15
|
+
create(attributes)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def delete_transaction(id)
|
|
19
|
+
destroy(id)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def by_date(start_date:, end_date:, params: {})
|
|
23
|
+
connection.get(
|
|
24
|
+
"#{resource_name}/GetByDate",
|
|
25
|
+
params: params.merge(startDate: start_date, endDate: end_date)
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def latest(params = {})
|
|
30
|
+
connection.get("#{resource_name}/GetLatest", params: params)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def validate(payload)
|
|
34
|
+
connection.post("#{resource_name}/Validate", body: payload)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def resource_name
|
|
40
|
+
'Transaction'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'epos_now_client/version'
|
|
4
|
+
require_relative 'epos_now_client/error'
|
|
5
|
+
require_relative 'epos_now_client/configuration'
|
|
6
|
+
require_relative 'epos_now_client/connection'
|
|
7
|
+
require_relative 'epos_now_client/resources/base_resource'
|
|
8
|
+
require_relative 'epos_now_client/resources/categories'
|
|
9
|
+
require_relative 'epos_now_client/resources/products'
|
|
10
|
+
require_relative 'epos_now_client/resources/transactions'
|
|
11
|
+
require_relative 'epos_now_client/resources/tender_types'
|
|
12
|
+
require_relative 'epos_now_client/resources/tax_groups'
|
|
13
|
+
require_relative 'epos_now_client/resources/customers'
|
|
14
|
+
require_relative 'epos_now_client/resources/staff'
|
|
15
|
+
require_relative 'epos_now_client/resources/devices'
|
|
16
|
+
require_relative 'epos_now_client/resources/brands'
|
|
17
|
+
require_relative 'epos_now_client/resources/locations'
|
|
18
|
+
require_relative 'epos_now_client/client'
|
|
19
|
+
|
|
20
|
+
module EposNowClient
|
|
21
|
+
class << self
|
|
22
|
+
def configuration
|
|
23
|
+
@configuration ||= Configuration.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def configure
|
|
27
|
+
yield(configuration)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def reset_configuration!
|
|
31
|
+
@configuration = Configuration.new
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: epos_now_client
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Daniel Dominguez
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: base64
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.2'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.2'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: httparty
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.22'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.22'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: bundler
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: logger
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.6'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.6'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rake
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '13.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '13.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rspec
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '3.13'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '3.13'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: rubocop
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '1.75'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '1.75'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: rubocop-rspec
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '3.6'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '3.6'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: simplecov
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0.22'
|
|
131
|
+
type: :development
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0.22'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: webmock
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '3.24'
|
|
145
|
+
type: :development
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '3.24'
|
|
152
|
+
description: A lightweight, fully-tested Ruby client for the Epos Now REST API. Supports
|
|
153
|
+
Basic Auth, pagination, and all V4 resources including products, categories, transactions,
|
|
154
|
+
customers, staff, and more.
|
|
155
|
+
email:
|
|
156
|
+
- danielfromarg@gmail.com
|
|
157
|
+
executables: []
|
|
158
|
+
extensions: []
|
|
159
|
+
extra_rdoc_files: []
|
|
160
|
+
files:
|
|
161
|
+
- CHANGELOG.md
|
|
162
|
+
- LICENSE.txt
|
|
163
|
+
- README.md
|
|
164
|
+
- lib/epos_now_client.rb
|
|
165
|
+
- lib/epos_now_client/client.rb
|
|
166
|
+
- lib/epos_now_client/configuration.rb
|
|
167
|
+
- lib/epos_now_client/connection.rb
|
|
168
|
+
- lib/epos_now_client/error.rb
|
|
169
|
+
- lib/epos_now_client/resources/base_resource.rb
|
|
170
|
+
- lib/epos_now_client/resources/brands.rb
|
|
171
|
+
- lib/epos_now_client/resources/categories.rb
|
|
172
|
+
- lib/epos_now_client/resources/customers.rb
|
|
173
|
+
- lib/epos_now_client/resources/devices.rb
|
|
174
|
+
- lib/epos_now_client/resources/locations.rb
|
|
175
|
+
- lib/epos_now_client/resources/products.rb
|
|
176
|
+
- lib/epos_now_client/resources/staff.rb
|
|
177
|
+
- lib/epos_now_client/resources/tax_groups.rb
|
|
178
|
+
- lib/epos_now_client/resources/tender_types.rb
|
|
179
|
+
- lib/epos_now_client/resources/transactions.rb
|
|
180
|
+
- lib/epos_now_client/version.rb
|
|
181
|
+
homepage: https://github.com/dan1d/epos_now_client
|
|
182
|
+
licenses:
|
|
183
|
+
- MIT
|
|
184
|
+
metadata:
|
|
185
|
+
source_code_uri: https://github.com/dan1d/epos_now_client
|
|
186
|
+
changelog_uri: https://github.com/dan1d/epos_now_client/blob/main/CHANGELOG.md
|
|
187
|
+
rubygems_mfa_required: 'true'
|
|
188
|
+
rdoc_options: []
|
|
189
|
+
require_paths:
|
|
190
|
+
- lib
|
|
191
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
192
|
+
requirements:
|
|
193
|
+
- - ">="
|
|
194
|
+
- !ruby/object:Gem::Version
|
|
195
|
+
version: 3.2.0
|
|
196
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
197
|
+
requirements:
|
|
198
|
+
- - ">="
|
|
199
|
+
- !ruby/object:Gem::Version
|
|
200
|
+
version: '0'
|
|
201
|
+
requirements: []
|
|
202
|
+
rubygems_version: 3.6.9
|
|
203
|
+
specification_version: 4
|
|
204
|
+
summary: Ruby client for the Epos Now POS API (V4)
|
|
205
|
+
test_files: []
|