loyverse_api 0.1.0 → 0.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 +9 -1
- data/README.md +15 -48
- data/examples/README.md +111 -0
- data/examples/receipt_response_sample.json +236 -0
- data/lib/loyverse_api/client.rb +21 -78
- data/lib/loyverse_api/endpoints/receipts.rb +22 -36
- data/lib/loyverse_api/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3dea99a28debaaafb892b7a49c1dd892dfa5d517e0c8282999353b0fc036c9cb
|
|
4
|
+
data.tar.gz: c87af19df702a36ce73e5741ec6f3e2ba8d4dcf7ef967b49f460e42f8098c8e4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b33e3f022d0591c8416dd693484b30228eb22c024c64742b7c5f0570b500f2cfc224257bac2fa61a17a9f346033e007aa5757745b29f7a32d9aa3fe2d199987
|
|
7
|
+
data.tar.gz: 694975bdc4671d51b57880acc766fefa32e4a7afb69527815a67bb03cb4dabd04c5dfbaaafbac5a994fbb738a5ad962df56292effee354c3d54fa6e4b7d56a9d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [0.
|
|
3
|
+
## [0.2.0] - 2026-02-16
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Receipts resource
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- Simplified response handling to let the user customize it
|
|
10
|
+
|
|
11
|
+
## [0.1.0] - 2026-02-16
|
|
4
12
|
|
|
5
13
|
### Added
|
|
6
14
|
- Initial release of the Loyverse API Ruby gem
|
data/README.md
CHANGED
|
@@ -338,55 +338,8 @@ client.delete_modifier('modifier-uuid')
|
|
|
338
338
|
|
|
339
339
|
### Webhooks
|
|
340
340
|
|
|
341
|
-
Webhooks allow you to receive real-time notifications when events occur in your Loyverse account.
|
|
342
|
-
|
|
343
|
-
**Setting up a webhook:**
|
|
344
|
-
|
|
345
|
-
1. **Create the webhook** pointing to your server endpoint:
|
|
346
|
-
```ruby
|
|
347
|
-
webhook = client.create_webhook(
|
|
348
|
-
url: 'https://your-server.com/webhooks/loyverse',
|
|
349
|
-
event_types: ['ORDER_CREATED', 'ITEM_UPDATED', 'INVENTORY_UPDATED'],
|
|
350
|
-
description: 'Production webhook'
|
|
351
|
-
)
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
2. **Handle webhook requests** in your application (Rails example):
|
|
355
|
-
```ruby
|
|
356
|
-
# POST /webhooks/loyverse
|
|
357
|
-
def create
|
|
358
|
-
payload = request.raw_post
|
|
359
|
-
signature = request.headers['X-Loyverse-Signature']
|
|
360
|
-
|
|
361
|
-
# Verify signature
|
|
362
|
-
unless client.verify_webhook_signature(payload, signature, ENV['LOYVERSE_WEBHOOK_SECRET'])
|
|
363
|
-
return head :unauthorized
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
# Process the webhook
|
|
367
|
-
data = JSON.parse(payload)
|
|
368
|
-
case data['event_type']
|
|
369
|
-
when 'ORDER_CREATED'
|
|
370
|
-
# Handle new order
|
|
371
|
-
when 'ITEM_UPDATED'
|
|
372
|
-
# Handle item update
|
|
373
|
-
when 'INVENTORY_UPDATED'
|
|
374
|
-
# Handle inventory update
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
head :ok
|
|
378
|
-
end
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
**Available event types:**
|
|
382
|
-
- `ORDER_CREATED` - New receipts/sales
|
|
383
|
-
- `ITEM_UPDATED` - Product changes
|
|
384
|
-
- `INVENTORY_UPDATED` - Stock level changes
|
|
385
|
-
|
|
386
|
-
📖 **Full webhook documentation:** [Loyverse Webhooks Overview](https://developer.loyverse.com/docs/#section/Webhooks-overview/Adding-webhook)
|
|
387
|
-
|
|
388
341
|
<details>
|
|
389
|
-
<summary>Click to see
|
|
342
|
+
<summary>Click to see Webhooks examples</summary>
|
|
390
343
|
|
|
391
344
|
```ruby
|
|
392
345
|
# List webhooks
|
|
@@ -395,8 +348,22 @@ webhooks = client.list_webhooks
|
|
|
395
348
|
# Get a specific webhook
|
|
396
349
|
webhook = client.get_webhook('webhook-uuid')
|
|
397
350
|
|
|
351
|
+
# Create a webhook
|
|
352
|
+
new_webhook = client.create_webhook(
|
|
353
|
+
url: 'https://your-server.com/webhooks/loyverse',
|
|
354
|
+
event_types: ['ORDER_CREATED', 'ITEM_UPDATED'],
|
|
355
|
+
description: 'Production webhook'
|
|
356
|
+
)
|
|
357
|
+
|
|
398
358
|
# Delete a webhook
|
|
399
359
|
client.delete_webhook('webhook-uuid')
|
|
360
|
+
|
|
361
|
+
# Verify webhook signature
|
|
362
|
+
is_valid = client.verify_webhook_signature(
|
|
363
|
+
request.raw_post,
|
|
364
|
+
request.headers['X-Loyverse-Signature'],
|
|
365
|
+
'your_webhook_secret'
|
|
366
|
+
)
|
|
400
367
|
```
|
|
401
368
|
|
|
402
369
|
</details>
|
data/examples/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Loyverse API Examples
|
|
2
|
+
|
|
3
|
+
This directory contains example code and sample API responses for the Loyverse API Ruby gem.
|
|
4
|
+
|
|
5
|
+
## Sample API Responses
|
|
6
|
+
|
|
7
|
+
### Receipts
|
|
8
|
+
|
|
9
|
+
See [`receipt_response_sample.json`](receipt_response_sample.json) for a complete example of a receipt object returned by the Loyverse API.
|
|
10
|
+
|
|
11
|
+
This sample demonstrates:
|
|
12
|
+
- **Receipt metadata**: receipt number, type, dates, source
|
|
13
|
+
- **Financial data**: totals, taxes, discounts, tips
|
|
14
|
+
- **Line items**: products with quantities, prices, costs
|
|
15
|
+
- **Modifiers**: customizations applied to items (e.g., milk type, flavor syrups, extra shots)
|
|
16
|
+
- **Payments**: payment methods and amounts
|
|
17
|
+
- **Pagination**: cursor for fetching additional results
|
|
18
|
+
|
|
19
|
+
#### Key Fields
|
|
20
|
+
|
|
21
|
+
**Receipt Level:**
|
|
22
|
+
- `receipt_number`: Unique receipt identifier (e.g., "1-10131")
|
|
23
|
+
- `receipt_type`: Type of transaction ("SALE", "REFUND", etc.)
|
|
24
|
+
- `total_money`: Total amount including tax
|
|
25
|
+
- `total_tax`: Total tax amount
|
|
26
|
+
- `employee_id`: ID of the employee who processed the sale
|
|
27
|
+
- `store_id`: ID of the store where the sale occurred
|
|
28
|
+
- `dining_option`: Customer's dining preference
|
|
29
|
+
|
|
30
|
+
**Line Items:**
|
|
31
|
+
- `item_name`: Product name
|
|
32
|
+
- `sku`: Stock keeping unit
|
|
33
|
+
- `quantity`: Number of items sold
|
|
34
|
+
- `price`: Base price per item
|
|
35
|
+
- `total_money`: Total for this line item including modifiers
|
|
36
|
+
- `cost`: Cost of goods sold
|
|
37
|
+
- `line_modifiers`: Array of customizations applied
|
|
38
|
+
|
|
39
|
+
**Modifiers:**
|
|
40
|
+
- `name`: Modifier category (e.g., "Flavor Syrup", "Espresso Shot")
|
|
41
|
+
- `option`: Selected option (e.g., "Chocolate", "Double Shot Extra")
|
|
42
|
+
- `price`: Additional charge for the modifier
|
|
43
|
+
- `money_amount`: Total amount for this modifier
|
|
44
|
+
|
|
45
|
+
**Payments:**
|
|
46
|
+
- `type`: Payment method ("CASH", "CARD", etc.)
|
|
47
|
+
- `money_amount`: Amount paid
|
|
48
|
+
- `paid_at`: Timestamp of payment
|
|
49
|
+
|
|
50
|
+
## Usage Examples
|
|
51
|
+
|
|
52
|
+
### Fetching Receipts
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
require 'loyverse_api'
|
|
56
|
+
|
|
57
|
+
# Configure the client
|
|
58
|
+
LoyverseApi.configure do |config|
|
|
59
|
+
config.access_token = ENV['LOYVERSE_ACCESS_TOKEN']
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
client = LoyverseApi.client
|
|
63
|
+
|
|
64
|
+
# List recent receipts
|
|
65
|
+
receipts = client.list_receipts(limit: 100)
|
|
66
|
+
|
|
67
|
+
# List receipts in a date range
|
|
68
|
+
receipts = client.list_receipts(
|
|
69
|
+
limit: 100,
|
|
70
|
+
created_at_min: '2025-07-01T00:00:00.000Z',
|
|
71
|
+
created_at_max: '2025-07-31T23:59:59.999Z'
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# List receipts between specific receipt numbers
|
|
75
|
+
receipts = client.list_receipts(
|
|
76
|
+
limit: 250,
|
|
77
|
+
since_receipt_number: '1-10129',
|
|
78
|
+
before_receipt_number: '1-10132'
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Paginate through results
|
|
82
|
+
page1 = client.list_receipts(limit: 100)
|
|
83
|
+
page2 = client.list_receipts(limit: 100, cursor: page1['cursor'])
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Processing Receipt Data
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
receipts['receipts'].each do |receipt|
|
|
90
|
+
puts "Receipt: #{receipt['receipt_number']}"
|
|
91
|
+
puts "Total: $#{receipt['total_money']}"
|
|
92
|
+
puts "Date: #{receipt['receipt_date']}"
|
|
93
|
+
|
|
94
|
+
# Process line items
|
|
95
|
+
receipt['line_items'].each do |item|
|
|
96
|
+
puts " - #{item['item_name']}: #{item['quantity']} x $#{item['price']}"
|
|
97
|
+
|
|
98
|
+
# Process modifiers
|
|
99
|
+
item['line_modifiers']&.each do |modifier|
|
|
100
|
+
puts " + #{modifier['name']}: #{modifier['option']} (+$#{modifier['price']})"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
puts "---"
|
|
105
|
+
end
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Additional Resources
|
|
109
|
+
|
|
110
|
+
- [Loyverse API Documentation](https://developer.loyverse.com/docs/)
|
|
111
|
+
- [Main README](../README.md)
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
{
|
|
2
|
+
"receipts": [
|
|
3
|
+
{
|
|
4
|
+
"receipt_number": "1-10131",
|
|
5
|
+
"note": null,
|
|
6
|
+
"receipt_type": "SALE",
|
|
7
|
+
"refund_for": null,
|
|
8
|
+
"order": null,
|
|
9
|
+
"created_at": "2025-07-19T07:00:02.000Z",
|
|
10
|
+
"updated_at": "2025-07-19T07:00:02.000Z",
|
|
11
|
+
"source": "point of sale",
|
|
12
|
+
"receipt_date": "2025-07-19T06:57:31.000Z",
|
|
13
|
+
"cancelled_at": null,
|
|
14
|
+
"total_money": 335.00,
|
|
15
|
+
"total_tax": 46.21,
|
|
16
|
+
"points_earned": 0.00,
|
|
17
|
+
"points_deducted": 0.00,
|
|
18
|
+
"points_balance": 0.00,
|
|
19
|
+
"customer_id": null,
|
|
20
|
+
"total_discount": 0.00,
|
|
21
|
+
"employee_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
|
|
22
|
+
"store_id": "f1e2d3c4-b5a6-4978-8c9d-0e1f2a3b4c5d",
|
|
23
|
+
"pos_device_id": "d1c2b3a4-e5f6-4978-8c9d-0e1f2a3b4c5d",
|
|
24
|
+
"dining_option": "Dine In",
|
|
25
|
+
"total_discounts": [],
|
|
26
|
+
"total_taxes": [
|
|
27
|
+
{
|
|
28
|
+
"id": "c2afb81c-d75f-4f70-8acc-dd0112b37aae",
|
|
29
|
+
"type": "INCLUDED",
|
|
30
|
+
"name": "VAT",
|
|
31
|
+
"rate": 16.0,
|
|
32
|
+
"money_amount": 46.21
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"tip": 0.00,
|
|
36
|
+
"surcharge": 0.00,
|
|
37
|
+
"line_items": [
|
|
38
|
+
{
|
|
39
|
+
"id": "9553152a-9d9d-f0cd-f6a3-2ab6fcbd516c",
|
|
40
|
+
"item_id": "edb828f0-23a8-4755-a171-b6b8c7d13808",
|
|
41
|
+
"variant_id": "96ceec2c-5f58-4170-84fd-96c84fb6482c",
|
|
42
|
+
"item_name": "Coffee Americano",
|
|
43
|
+
"variant_name": null,
|
|
44
|
+
"sku": "10006",
|
|
45
|
+
"quantity": 1,
|
|
46
|
+
"price": 50.00,
|
|
47
|
+
"gross_total_money": 95.00,
|
|
48
|
+
"total_money": 95.00,
|
|
49
|
+
"cost": 13.05,
|
|
50
|
+
"cost_total": 13.05,
|
|
51
|
+
"line_note": null,
|
|
52
|
+
"line_taxes": [
|
|
53
|
+
{
|
|
54
|
+
"money_amount": 13.10,
|
|
55
|
+
"id": "c2afb81c-d75f-4f70-8acc-dd0112b37aae",
|
|
56
|
+
"type": "INCLUDED",
|
|
57
|
+
"name": "VAT",
|
|
58
|
+
"rate": 16.0
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"total_discount": 0.00,
|
|
62
|
+
"line_discounts": [],
|
|
63
|
+
"line_modifiers": [
|
|
64
|
+
{
|
|
65
|
+
"id": "22c541f2-f224-4f26-bdbe-0eecdd77aa6c",
|
|
66
|
+
"modifier_option_id": "fc5ed733-1756-48c3-b7c1-529288e869af",
|
|
67
|
+
"name": "Flavor Syrup",
|
|
68
|
+
"option": "Chocolate",
|
|
69
|
+
"price": 15.00,
|
|
70
|
+
"money_amount": 15.00
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "01e734f1-9348-46b6-a3ba-d841f2042fe9",
|
|
74
|
+
"modifier_option_id": "571d5613-d909-427e-954e-f3b9e7227101",
|
|
75
|
+
"name": "Espresso Shot",
|
|
76
|
+
"option": "Double Shot Extra",
|
|
77
|
+
"price": 15.00,
|
|
78
|
+
"money_amount": 15.00
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "d0346233-92ec-4c58-9455-cd6c55d124f7",
|
|
82
|
+
"modifier_option_id": "f044d08d-f5c7-4a5c-adf8-cfdd8bf4edfc",
|
|
83
|
+
"name": "With Milk",
|
|
84
|
+
"option": "Light",
|
|
85
|
+
"price": 15.00,
|
|
86
|
+
"money_amount": 15.00
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "9553152a-9d9d-f0cd-f6a3-2ab6fcbd516d",
|
|
92
|
+
"item_id": "016e03e8-ec36-4ec2-81be-dfcf30820fd2",
|
|
93
|
+
"variant_id": "4267d877-ba3c-44af-ac3a-0d15692a9e16",
|
|
94
|
+
"item_name": "Espresso",
|
|
95
|
+
"variant_name": null,
|
|
96
|
+
"sku": "10009",
|
|
97
|
+
"quantity": 1,
|
|
98
|
+
"price": 40.00,
|
|
99
|
+
"gross_total_money": 55.00,
|
|
100
|
+
"total_money": 55.00,
|
|
101
|
+
"cost": 9.73,
|
|
102
|
+
"cost_total": 9.73,
|
|
103
|
+
"line_note": null,
|
|
104
|
+
"line_taxes": [
|
|
105
|
+
{
|
|
106
|
+
"money_amount": 7.59,
|
|
107
|
+
"id": "c2afb81c-d75f-4f70-8acc-dd0112b37aae",
|
|
108
|
+
"type": "INCLUDED",
|
|
109
|
+
"name": "VAT",
|
|
110
|
+
"rate": 16.0
|
|
111
|
+
}
|
|
112
|
+
],
|
|
113
|
+
"total_discount": 0.00,
|
|
114
|
+
"line_discounts": [],
|
|
115
|
+
"line_modifiers": [
|
|
116
|
+
{
|
|
117
|
+
"id": "01e734f1-9348-46b6-a3ba-d841f2042fe9",
|
|
118
|
+
"modifier_option_id": "571d5613-d909-427e-954e-f3b9e7227101",
|
|
119
|
+
"name": "Espresso Shot",
|
|
120
|
+
"option": "Double Shot Extra",
|
|
121
|
+
"price": 15.00,
|
|
122
|
+
"money_amount": 15.00
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"id": "9553152a-9d9d-f0cd-f6a3-2ab6fcbd516e",
|
|
128
|
+
"item_id": "3282814e-0638-49dd-9566-7f7a485e38fc",
|
|
129
|
+
"variant_id": "7966819c-f78b-4b81-909f-f9af478cee42",
|
|
130
|
+
"item_name": "Flat White",
|
|
131
|
+
"variant_name": null,
|
|
132
|
+
"sku": "10004",
|
|
133
|
+
"quantity": 1,
|
|
134
|
+
"price": 60.00,
|
|
135
|
+
"gross_total_money": 90.00,
|
|
136
|
+
"total_money": 90.00,
|
|
137
|
+
"cost": 17.28,
|
|
138
|
+
"cost_total": 17.28,
|
|
139
|
+
"line_note": null,
|
|
140
|
+
"line_taxes": [
|
|
141
|
+
{
|
|
142
|
+
"money_amount": 12.41,
|
|
143
|
+
"id": "c2afb81c-d75f-4f70-8acc-dd0112b37aae",
|
|
144
|
+
"type": "INCLUDED",
|
|
145
|
+
"name": "VAT",
|
|
146
|
+
"rate": 16.0
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
"total_discount": 0.00,
|
|
150
|
+
"line_discounts": [],
|
|
151
|
+
"line_modifiers": [
|
|
152
|
+
{
|
|
153
|
+
"id": "69b59738-4e7f-447d-ab1c-74aa5b67b7b1",
|
|
154
|
+
"modifier_option_id": "6d64fb54-f667-452e-bfce-c43bebdfcc9e",
|
|
155
|
+
"name": "Oat Milk",
|
|
156
|
+
"option": "Barista Oat",
|
|
157
|
+
"price": 15.00,
|
|
158
|
+
"money_amount": 15.00
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "01e734f1-9348-46b6-a3ba-d841f2042fe9",
|
|
162
|
+
"modifier_option_id": "571d5613-d909-427e-954e-f3b9e7227101",
|
|
163
|
+
"name": "Espresso Shot",
|
|
164
|
+
"option": "Double Shot Extra",
|
|
165
|
+
"price": 15.00,
|
|
166
|
+
"money_amount": 15.00
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": "9553152a-9d9d-f0cd-f6a3-2ab6fcbd516f",
|
|
172
|
+
"item_id": "b8afa0b1-0851-4225-91e2-53c670d41374",
|
|
173
|
+
"variant_id": "2caa728f-0024-4f29-ae7a-d53420c4ef4d",
|
|
174
|
+
"item_name": "Latte",
|
|
175
|
+
"variant_name": null,
|
|
176
|
+
"sku": "10000",
|
|
177
|
+
"quantity": 1,
|
|
178
|
+
"price": 65.00,
|
|
179
|
+
"gross_total_money": 95.00,
|
|
180
|
+
"total_money": 95.00,
|
|
181
|
+
"cost": 23.36,
|
|
182
|
+
"cost_total": 23.36,
|
|
183
|
+
"line_note": null,
|
|
184
|
+
"line_taxes": [
|
|
185
|
+
{
|
|
186
|
+
"money_amount": 13.11,
|
|
187
|
+
"id": "c2afb81c-d75f-4f70-8acc-dd0112b37aae",
|
|
188
|
+
"type": "INCLUDED",
|
|
189
|
+
"name": "VAT",
|
|
190
|
+
"rate": 16.0
|
|
191
|
+
}
|
|
192
|
+
],
|
|
193
|
+
"total_discount": 0.00,
|
|
194
|
+
"line_discounts": [],
|
|
195
|
+
"line_modifiers": [
|
|
196
|
+
{
|
|
197
|
+
"id": "0c75ed60-8713-413e-bd4d-8cbc2aed78ca",
|
|
198
|
+
"modifier_option_id": "3690e2a3-6062-4cb9-bdf6-ca58aa856c21",
|
|
199
|
+
"name": "Milk Type (Included)",
|
|
200
|
+
"option": "Lactose-Free",
|
|
201
|
+
"price": 0.00,
|
|
202
|
+
"money_amount": 0.00
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"id": "22c541f2-f224-4f26-bdbe-0eecdd77aa6c",
|
|
206
|
+
"modifier_option_id": "a60ad701-ddbe-49da-9fee-fa3d59da4e3b",
|
|
207
|
+
"name": "Flavor Syrup",
|
|
208
|
+
"option": "Marzipan",
|
|
209
|
+
"price": 15.00,
|
|
210
|
+
"money_amount": 15.00
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"id": "01e734f1-9348-46b6-a3ba-d841f2042fe9",
|
|
214
|
+
"modifier_option_id": "571d5613-d909-427e-954e-f3b9e7227101",
|
|
215
|
+
"name": "Espresso Shot",
|
|
216
|
+
"option": "Double Shot Extra",
|
|
217
|
+
"price": 15.00,
|
|
218
|
+
"money_amount": 15.00
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
],
|
|
223
|
+
"payments": [
|
|
224
|
+
{
|
|
225
|
+
"payment_type_id": "a9dd8a31-1ff2-4418-b538-8f66cd5a5878",
|
|
226
|
+
"name": "Cash",
|
|
227
|
+
"type": "CASH",
|
|
228
|
+
"money_amount": 335.00,
|
|
229
|
+
"paid_at": "2025-07-19T06:57:31.000Z",
|
|
230
|
+
"payment_details": null
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
],
|
|
235
|
+
"cursor": "CPnzvouCMxDn3P-LgjNCA-H8ekjn4sjhG2ABcAc="
|
|
236
|
+
}
|
data/lib/loyverse_api/client.rb
CHANGED
|
@@ -42,113 +42,56 @@ module LoyverseApi
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def get(path, params: {})
|
|
45
|
-
handle_response do
|
|
46
45
|
response = connection.get(path, params)
|
|
47
46
|
response
|
|
48
|
-
end
|
|
49
47
|
end
|
|
50
48
|
|
|
51
49
|
def post(path, body: {})
|
|
52
|
-
handle_response do
|
|
53
50
|
response = connection.post(path, body)
|
|
54
51
|
response
|
|
55
|
-
end
|
|
56
52
|
end
|
|
57
53
|
|
|
58
54
|
def put(path, body: {})
|
|
59
|
-
handle_response do
|
|
60
55
|
response = connection.put(path, body)
|
|
61
56
|
response
|
|
62
|
-
end
|
|
63
57
|
end
|
|
64
58
|
|
|
65
59
|
def delete(path)
|
|
66
|
-
handle_response do
|
|
67
60
|
response = connection.delete(path)
|
|
68
61
|
response
|
|
69
|
-
end
|
|
70
62
|
end
|
|
71
63
|
|
|
72
64
|
private
|
|
73
65
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
raise AuthorizationError.new(
|
|
94
|
-
error_message(response),
|
|
95
|
-
code: error_code(response),
|
|
96
|
-
details: error_details(response)
|
|
97
|
-
)
|
|
98
|
-
when 404
|
|
99
|
-
raise NotFoundError.new(
|
|
100
|
-
error_message(response),
|
|
101
|
-
code: error_code(response),
|
|
102
|
-
details: error_details(response)
|
|
103
|
-
)
|
|
104
|
-
when 429
|
|
105
|
-
raise RateLimitError.new(
|
|
106
|
-
error_message(response) || "Rate limit exceeded",
|
|
107
|
-
code: error_code(response),
|
|
108
|
-
details: error_details(response)
|
|
109
|
-
)
|
|
110
|
-
when 500, 502, 503, 504
|
|
111
|
-
raise ServerError.new(
|
|
112
|
-
error_message(response) || "Server error occurred",
|
|
113
|
-
code: error_code(response),
|
|
114
|
-
details: error_details(response)
|
|
115
|
-
)
|
|
116
|
-
else
|
|
117
|
-
raise ApiError.new(
|
|
118
|
-
error_message(response) || "Unknown error occurred",
|
|
119
|
-
code: error_code(response),
|
|
120
|
-
details: error_details(response)
|
|
121
|
-
)
|
|
122
|
-
end
|
|
123
|
-
rescue Faraday::TimeoutError
|
|
124
|
-
raise Error, "Request timeout"
|
|
125
|
-
rescue Faraday::ConnectionFailed
|
|
126
|
-
raise Error, "Connection failed"
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def error_message(response)
|
|
130
|
-
return nil unless response.body.is_a?(Hash)
|
|
131
|
-
response.body.dig("error", "message") || response.body["message"]
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def error_code(response)
|
|
135
|
-
return nil unless response.body.is_a?(Hash)
|
|
136
|
-
response.body.dig("error", "code")
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def error_details(response)
|
|
140
|
-
return nil unless response.body.is_a?(Hash)
|
|
141
|
-
response.body.dig("error", "details")
|
|
142
|
-
end
|
|
143
|
-
|
|
66
|
+
# Formats time values to ISO 8601 format for Loyverse API
|
|
67
|
+
#
|
|
68
|
+
# The Loyverse API requires timestamps in ISO 8601 format (e.g., "2024-01-15T14:30:00.000Z")
|
|
69
|
+
# This method handles different input types and converts them appropriately:
|
|
70
|
+
#
|
|
71
|
+
# @param time [String, Time, Date, nil] The time value to format
|
|
72
|
+
# @return [String, nil] ISO 8601 formatted timestamp or nil
|
|
73
|
+
#
|
|
74
|
+
# @example String input (already formatted)
|
|
75
|
+
# format_time("2024-01-15T14:30:00.000Z") #=> "2024-01-15T14:30:00.000Z"
|
|
76
|
+
#
|
|
77
|
+
# @example Time object (with time component)
|
|
78
|
+
# format_time(Time.now) #=> "2024-01-15T14:30:00.123Z"
|
|
79
|
+
#
|
|
80
|
+
# @example Date object (no time component - sets to midnight UTC)
|
|
81
|
+
# format_time(Date.today) #=> "2024-01-15T00:00:00.000Z"
|
|
82
|
+
#
|
|
83
|
+
# @example Nil input
|
|
84
|
+
# format_time(nil) #=> nil
|
|
144
85
|
def format_time(time)
|
|
145
86
|
return nil if time.nil?
|
|
146
87
|
return time if time.is_a?(String)
|
|
147
88
|
|
|
89
|
+
# Date objects don't have hour method, so set to midnight UTC
|
|
148
90
|
if time.respond_to?(:strftime) && !time.respond_to?(:hour)
|
|
149
91
|
return "#{time.strftime('%Y-%m-%d')}T00:00:00.000Z"
|
|
150
92
|
end
|
|
151
93
|
|
|
94
|
+
# Time objects - convert to UTC and format with milliseconds
|
|
152
95
|
return time.utc.strftime('%Y-%m-%dT%H:%M:%S.%LZ') if time.respond_to?(:utc)
|
|
153
96
|
|
|
154
97
|
time
|
|
@@ -9,46 +9,32 @@ module LoyverseApi
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
# List receipts
|
|
12
|
-
# @param receipt_numbers [Array<String, Integer>] Array of specific receipt numbers (optional)
|
|
13
|
-
# @param since_receipt_number [String, Integer] Return receipts after this number (optional)
|
|
14
|
-
# @param before_receipt_number [String, Integer] Return receipts before this number (optional)
|
|
15
|
-
# @param store_id [String] Filter by store UUID (optional)
|
|
16
|
-
# @param order [String] Sort order: "ASC" or "DESC" (default: "DESC")
|
|
17
|
-
# @param source [String] Filter by source (e.g., "POS", "API") (optional)
|
|
18
|
-
# @param updated_at_min [String, Time] Filter by minimum update time (optional)
|
|
19
|
-
# @param updated_at_max [String, Time] Filter by maximum update time (optional)
|
|
20
|
-
# @param created_at_min [String, Time] Filter by minimum creation time (optional)
|
|
21
|
-
# @param created_at_max [String, Time] Filter by maximum creation time (optional)
|
|
22
12
|
# @param limit [Integer] Maximum number of results per page (default: 250)
|
|
23
|
-
# @param
|
|
13
|
+
# @param options [Hash] Optional filters
|
|
14
|
+
# @option options [Array<String, Integer>] :receipt_numbers Array of specific receipt numbers
|
|
15
|
+
# @option options [String, Integer] :since_receipt_number Return receipts after this number
|
|
16
|
+
# @option options [String, Integer] :before_receipt_number Return receipts before this number
|
|
17
|
+
# @option options [String] :store_id Filter by store UUID
|
|
18
|
+
# @option options [String] :source Filter by source (e.g., "POS", "API")
|
|
19
|
+
# @option options [String, Time] :updated_at_min Filter by minimum update time
|
|
20
|
+
# @option options [String, Time] :updated_at_max Filter by maximum update time
|
|
21
|
+
# @option options [String, Time] :created_at_min Filter by minimum creation time
|
|
22
|
+
# @option options [String, Time] :created_at_max Filter by maximum creation time
|
|
23
|
+
# @option options [String] :cursor Pagination cursor for next page
|
|
24
24
|
# @return [Hash] Response with receipts array
|
|
25
|
-
def list_receipts(
|
|
26
|
-
receipt_numbers: nil,
|
|
27
|
-
since_receipt_number: nil,
|
|
28
|
-
before_receipt_number: nil,
|
|
29
|
-
store_id: nil,
|
|
30
|
-
order: "DESC",
|
|
31
|
-
source: nil,
|
|
32
|
-
updated_at_min: nil,
|
|
33
|
-
updated_at_max: nil,
|
|
34
|
-
created_at_min: nil,
|
|
35
|
-
created_at_max: nil,
|
|
36
|
-
limit: 250,
|
|
37
|
-
cursor: nil
|
|
38
|
-
)
|
|
25
|
+
def list_receipts(limit: 100, **options)
|
|
39
26
|
params = {
|
|
40
27
|
limit: limit,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
created_at_max: format_time(created_at_max)
|
|
28
|
+
receipt_numbers: options[:receipt_numbers] ? Array(options[:receipt_numbers]).join(",") : nil,
|
|
29
|
+
since_receipt_number: options[:since_receipt_number],
|
|
30
|
+
before_receipt_number: options[:before_receipt_number],
|
|
31
|
+
store_id: options[:store_id],
|
|
32
|
+
source: options[:source],
|
|
33
|
+
cursor: options[:cursor],
|
|
34
|
+
updated_at_min: format_time(options[:updated_at_min]),
|
|
35
|
+
updated_at_max: format_time(options[:updated_at_max]),
|
|
36
|
+
created_at_min: format_time(options[:created_at_min]),
|
|
37
|
+
created_at_max: format_time(options[:created_at_max])
|
|
52
38
|
}.compact
|
|
53
39
|
|
|
54
40
|
get("receipts", params: params)
|
data/lib/loyverse_api/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: loyverse_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Loyverse API Wrapper
|
|
@@ -125,8 +125,10 @@ files:
|
|
|
125
125
|
- LICENSE.txt
|
|
126
126
|
- README.md
|
|
127
127
|
- Rakefile
|
|
128
|
+
- examples/README.md
|
|
128
129
|
- examples/basic_usage.rb
|
|
129
130
|
- examples/create_item.rb
|
|
131
|
+
- examples/receipt_response_sample.json
|
|
130
132
|
- examples/webhook_server.rb
|
|
131
133
|
- lib/loyverse_api.rb
|
|
132
134
|
- lib/loyverse_api/client.rb
|