loyverse_api 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/.env.sample +1 -0
- data/.gitignore +54 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +516 -0
- data/Rakefile +6 -0
- data/examples/basic_usage.rb +115 -0
- data/examples/create_item.rb +71 -0
- data/examples/webhook_server.rb +120 -0
- data/lib/loyverse_api/client.rb +157 -0
- data/lib/loyverse_api/configuration.rb +13 -0
- data/lib/loyverse_api/endpoints/categories.rb +43 -0
- data/lib/loyverse_api/endpoints/customers.rb +131 -0
- data/lib/loyverse_api/endpoints/discounts.rb +91 -0
- data/lib/loyverse_api/endpoints/employees.rb +31 -0
- data/lib/loyverse_api/endpoints/inventory.rb +41 -0
- data/lib/loyverse_api/endpoints/items.rb +75 -0
- data/lib/loyverse_api/endpoints/modifiers.rb +51 -0
- data/lib/loyverse_api/endpoints/receipts.rb +115 -0
- data/lib/loyverse_api/endpoints/webhooks.rb +85 -0
- data/lib/loyverse_api/errors.rb +19 -0
- data/lib/loyverse_api/version.rb +3 -0
- data/lib/loyverse_api.rb +33 -0
- data/loyverse_api.gemspec +34 -0
- metadata +170 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'loyverse_api'
|
|
5
|
+
|
|
6
|
+
# This example demonstrates basic usage of the Loyverse API gem
|
|
7
|
+
|
|
8
|
+
# Configure the gem with your access token
|
|
9
|
+
LoyverseApi.configure do |config|
|
|
10
|
+
config.access_token = ENV['LOYVERSE_ACCESS_TOKEN'] || 'your_access_token_here'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Create a client instance
|
|
14
|
+
client = LoyverseApi.client
|
|
15
|
+
|
|
16
|
+
puts "=== Loyverse API Examples ==="
|
|
17
|
+
puts
|
|
18
|
+
|
|
19
|
+
# Example 1: List Categories
|
|
20
|
+
puts "1. Listing categories:"
|
|
21
|
+
begin
|
|
22
|
+
categories = client.categories.list(limit: 5)
|
|
23
|
+
if categories.empty?
|
|
24
|
+
puts " No categories found"
|
|
25
|
+
else
|
|
26
|
+
categories.each do |category|
|
|
27
|
+
puts " - #{category['name']} (#{category['color']})"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
rescue LoyverseApi::Error => e
|
|
31
|
+
puts " Error: #{e.message}"
|
|
32
|
+
end
|
|
33
|
+
puts
|
|
34
|
+
|
|
35
|
+
# Example 2: List Items
|
|
36
|
+
puts "2. Listing items:"
|
|
37
|
+
begin
|
|
38
|
+
items = client.items.list(limit: 5)
|
|
39
|
+
if items.empty?
|
|
40
|
+
puts " No items found"
|
|
41
|
+
else
|
|
42
|
+
items.each do |item|
|
|
43
|
+
puts " - #{item['item_name']}"
|
|
44
|
+
if item['variants']
|
|
45
|
+
item['variants'].each do |variant|
|
|
46
|
+
puts " * SKU: #{variant['sku']}, Price: $#{variant['price']}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
rescue LoyverseApi::Error => e
|
|
52
|
+
puts " Error: #{e.message}"
|
|
53
|
+
end
|
|
54
|
+
puts
|
|
55
|
+
|
|
56
|
+
# Example 3: Get Inventory Levels
|
|
57
|
+
puts "3. Checking inventory:"
|
|
58
|
+
begin
|
|
59
|
+
inventory = client.inventory.list(limit: 5)
|
|
60
|
+
if inventory.empty?
|
|
61
|
+
puts " No inventory data found"
|
|
62
|
+
else
|
|
63
|
+
inventory.each do |level|
|
|
64
|
+
puts " - Variant: #{level['variant_id']}"
|
|
65
|
+
puts " Store: #{level['store_id']}"
|
|
66
|
+
puts " In Stock: #{level['in_stock']}"
|
|
67
|
+
puts " Updated: #{level['updated_at']}"
|
|
68
|
+
puts
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
rescue LoyverseApi::Error => e
|
|
72
|
+
puts " Error: #{e.message}"
|
|
73
|
+
end
|
|
74
|
+
puts
|
|
75
|
+
|
|
76
|
+
# Example 4: List Recent Receipts
|
|
77
|
+
puts "4. Listing recent receipts:"
|
|
78
|
+
begin
|
|
79
|
+
receipts = client.receipts.list(limit: 5, order: 'DESC')
|
|
80
|
+
if receipts.empty?
|
|
81
|
+
puts " No receipts found"
|
|
82
|
+
else
|
|
83
|
+
receipts.each do |receipt|
|
|
84
|
+
puts " - Receipt ##{receipt['receipt_number']}"
|
|
85
|
+
puts " Date: #{receipt['receipt_date']}"
|
|
86
|
+
puts " Total: $#{receipt['total_money']}"
|
|
87
|
+
puts " Items: #{receipt['line_items']&.count || 0}"
|
|
88
|
+
puts
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
rescue LoyverseApi::Error => e
|
|
92
|
+
puts " Error: #{e.message}"
|
|
93
|
+
end
|
|
94
|
+
puts
|
|
95
|
+
|
|
96
|
+
# Example 5: List Webhooks
|
|
97
|
+
puts "5. Listing webhooks:"
|
|
98
|
+
begin
|
|
99
|
+
webhooks = client.webhooks.list
|
|
100
|
+
if webhooks.empty?
|
|
101
|
+
puts " No webhooks configured"
|
|
102
|
+
else
|
|
103
|
+
webhooks.each do |webhook|
|
|
104
|
+
puts " - #{webhook['url']}"
|
|
105
|
+
puts " Events: #{webhook['event_types']&.join(', ')}"
|
|
106
|
+
puts " Description: #{webhook['description']}"
|
|
107
|
+
puts
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
rescue LoyverseApi::Error => e
|
|
111
|
+
puts " Error: #{e.message}"
|
|
112
|
+
end
|
|
113
|
+
puts
|
|
114
|
+
|
|
115
|
+
puts "=== Examples Complete ==="
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'loyverse_api'
|
|
5
|
+
|
|
6
|
+
# This example demonstrates how to create a new item with variants
|
|
7
|
+
|
|
8
|
+
LoyverseApi.configure do |config|
|
|
9
|
+
config.access_token = ENV['LOYVERSE_ACCESS_TOKEN'] || 'your_access_token_here'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
client = LoyverseApi.client
|
|
13
|
+
|
|
14
|
+
puts "Creating a new item with variants..."
|
|
15
|
+
puts
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
# First, create a category for the item
|
|
19
|
+
category = client.categories.create(
|
|
20
|
+
name: 'Beverages',
|
|
21
|
+
color: 'BLUE'
|
|
22
|
+
)
|
|
23
|
+
puts "Created category: #{category['name']} (ID: #{category['id']})"
|
|
24
|
+
puts
|
|
25
|
+
|
|
26
|
+
# Create an item with multiple variants
|
|
27
|
+
item = client.items.create(
|
|
28
|
+
item_name: 'Premium Coffee',
|
|
29
|
+
category_id: category['id'],
|
|
30
|
+
track_stock: true,
|
|
31
|
+
variants: [
|
|
32
|
+
{
|
|
33
|
+
sku: 'COFFEE-S-001',
|
|
34
|
+
barcode: '1234567890123',
|
|
35
|
+
price: 3.50,
|
|
36
|
+
cost: 1.50,
|
|
37
|
+
option1_value: 'Small'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
sku: 'COFFEE-M-001',
|
|
41
|
+
barcode: '1234567890124',
|
|
42
|
+
price: 4.50,
|
|
43
|
+
cost: 2.00,
|
|
44
|
+
option1_value: 'Medium'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
sku: 'COFFEE-L-001',
|
|
48
|
+
barcode: '1234567890125',
|
|
49
|
+
price: 5.50,
|
|
50
|
+
cost: 2.50,
|
|
51
|
+
option1_value: 'Large'
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
puts "Created item: #{item['item_name']}"
|
|
57
|
+
puts "Item ID: #{item['id']}"
|
|
58
|
+
puts "Category: #{item['category_id']}"
|
|
59
|
+
puts
|
|
60
|
+
puts "Variants:"
|
|
61
|
+
item['variants'].each do |variant|
|
|
62
|
+
puts " - #{variant['option1_value']}: $#{variant['price']} (SKU: #{variant['sku']})"
|
|
63
|
+
end
|
|
64
|
+
puts
|
|
65
|
+
puts "Item created successfully!"
|
|
66
|
+
|
|
67
|
+
rescue LoyverseApi::Error => e
|
|
68
|
+
puts "Error creating item: #{e.message}"
|
|
69
|
+
puts "Error code: #{e.code}" if e.code
|
|
70
|
+
puts "Error details: #{e.details}" if e.details
|
|
71
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'loyverse_api'
|
|
5
|
+
require 'webrick'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
# This example demonstrates a simple webhook receiver server
|
|
9
|
+
|
|
10
|
+
# Configure the Loyverse API client
|
|
11
|
+
LoyverseApi.configure do |config|
|
|
12
|
+
config.access_token = ENV['LOYVERSE_ACCESS_TOKEN'] || 'your_access_token_here'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
client = LoyverseApi.client
|
|
16
|
+
|
|
17
|
+
# Webhook secret (if using OAuth 2.0 created webhooks)
|
|
18
|
+
WEBHOOK_SECRET = ENV['LOYVERSE_WEBHOOK_SECRET']
|
|
19
|
+
|
|
20
|
+
# Simple webhook handler
|
|
21
|
+
class WebhookHandler < WEBrick::HTTPServlet::AbstractServlet
|
|
22
|
+
def initialize(server, client)
|
|
23
|
+
super(server)
|
|
24
|
+
@client = client
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def do_POST(request, response)
|
|
28
|
+
begin
|
|
29
|
+
# Get the raw body
|
|
30
|
+
body = request.body
|
|
31
|
+
|
|
32
|
+
# Verify signature if using OAuth 2.0
|
|
33
|
+
if WEBHOOK_SECRET
|
|
34
|
+
signature = request.header['x-loyverse-signature']&.first
|
|
35
|
+
is_valid = @client.webhooks.verify_signature(body, signature, WEBHOOK_SECRET)
|
|
36
|
+
|
|
37
|
+
unless is_valid
|
|
38
|
+
response.status = 401
|
|
39
|
+
response.body = JSON.generate({ error: 'Invalid signature' })
|
|
40
|
+
return
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Parse the webhook payload
|
|
45
|
+
payload = JSON.parse(body)
|
|
46
|
+
|
|
47
|
+
# Handle the webhook event
|
|
48
|
+
handle_event(payload)
|
|
49
|
+
|
|
50
|
+
# Respond with 200 OK
|
|
51
|
+
response.status = 200
|
|
52
|
+
response['Content-Type'] = 'application/json'
|
|
53
|
+
response.body = JSON.generate({ received: true })
|
|
54
|
+
|
|
55
|
+
rescue JSON::ParserError => e
|
|
56
|
+
response.status = 400
|
|
57
|
+
response.body = JSON.generate({ error: 'Invalid JSON' })
|
|
58
|
+
rescue => e
|
|
59
|
+
puts "Error processing webhook: #{e.message}"
|
|
60
|
+
response.status = 500
|
|
61
|
+
response.body = JSON.generate({ error: 'Internal server error' })
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def handle_event(payload)
|
|
68
|
+
event_type = payload['event_type']
|
|
69
|
+
data = payload['data']
|
|
70
|
+
timestamp = payload['timestamp']
|
|
71
|
+
|
|
72
|
+
puts "\n=== Webhook Received ==="
|
|
73
|
+
puts "Event Type: #{event_type}"
|
|
74
|
+
puts "Timestamp: #{timestamp}"
|
|
75
|
+
puts "Data: #{JSON.pretty_generate(data)}"
|
|
76
|
+
puts "========================\n"
|
|
77
|
+
|
|
78
|
+
case event_type
|
|
79
|
+
when 'ORDER_CREATED'
|
|
80
|
+
handle_order_created(data)
|
|
81
|
+
when 'ITEM_UPDATED'
|
|
82
|
+
handle_item_updated(data)
|
|
83
|
+
when 'INVENTORY_UPDATED'
|
|
84
|
+
handle_inventory_updated(data)
|
|
85
|
+
else
|
|
86
|
+
puts "Unknown event type: #{event_type}"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def handle_order_created(data)
|
|
91
|
+
puts "New order created: Receipt ##{data['receipt_number']}"
|
|
92
|
+
# Add your custom logic here
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def handle_item_updated(data)
|
|
96
|
+
puts "Item updated: #{data['item_name']}"
|
|
97
|
+
# Add your custom logic here
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def handle_inventory_updated(data)
|
|
101
|
+
puts "Inventory updated for variant: #{data['variant_id']}"
|
|
102
|
+
puts "New stock level: #{data['in_stock']}"
|
|
103
|
+
# Add your custom logic here
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Create and start the server
|
|
108
|
+
port = ENV['PORT'] || 3000
|
|
109
|
+
server = WEBrick::HTTPServer.new(Port: port)
|
|
110
|
+
|
|
111
|
+
server.mount '/webhooks/loyverse', WebhookHandler, client
|
|
112
|
+
|
|
113
|
+
trap('INT') { server.shutdown }
|
|
114
|
+
|
|
115
|
+
puts "Webhook server listening on port #{port}"
|
|
116
|
+
puts "Webhook URL: http://localhost:#{port}/webhooks/loyverse"
|
|
117
|
+
puts "Press Ctrl+C to stop"
|
|
118
|
+
puts
|
|
119
|
+
|
|
120
|
+
server.start
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LoyverseApi
|
|
4
|
+
class Client
|
|
5
|
+
include Endpoints::Items
|
|
6
|
+
include Endpoints::Categories
|
|
7
|
+
include Endpoints::Inventory
|
|
8
|
+
include Endpoints::Receipts
|
|
9
|
+
include Endpoints::Webhooks
|
|
10
|
+
include Endpoints::Customers
|
|
11
|
+
include Endpoints::Discounts
|
|
12
|
+
include Endpoints::Employees
|
|
13
|
+
include Endpoints::Modifiers
|
|
14
|
+
|
|
15
|
+
attr_reader :configuration
|
|
16
|
+
|
|
17
|
+
def initialize(configuration = nil)
|
|
18
|
+
@configuration = configuration || LoyverseApi.configuration || Configuration.new
|
|
19
|
+
|
|
20
|
+
raise AuthenticationError, "Access token is required" if @configuration.access_token.nil?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def connection
|
|
24
|
+
@connection ||= Faraday.new(url: configuration.base_url) do |conn|
|
|
25
|
+
conn.request :json
|
|
26
|
+
conn.request :retry, {
|
|
27
|
+
max: 3,
|
|
28
|
+
interval: 0.5,
|
|
29
|
+
interval_randomness: 0.5,
|
|
30
|
+
backoff_factor: 2,
|
|
31
|
+
retry_statuses: [429, 500, 502, 503, 504],
|
|
32
|
+
methods: [:get, :post, :put, :delete]
|
|
33
|
+
}
|
|
34
|
+
conn.response :json, content_type: /\bjson$/
|
|
35
|
+
conn.headers['Authorization'] = "Bearer #{configuration.access_token}"
|
|
36
|
+
conn.headers['Content-Type'] = 'application/json'
|
|
37
|
+
conn.headers['Accept'] = 'application/json'
|
|
38
|
+
conn.adapter Faraday.default_adapter
|
|
39
|
+
conn.options.timeout = configuration.timeout
|
|
40
|
+
conn.options.open_timeout = configuration.open_timeout
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def get(path, params: {})
|
|
45
|
+
handle_response do
|
|
46
|
+
response = connection.get(path, params)
|
|
47
|
+
response
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def post(path, body: {})
|
|
52
|
+
handle_response do
|
|
53
|
+
response = connection.post(path, body)
|
|
54
|
+
response
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def put(path, body: {})
|
|
59
|
+
handle_response do
|
|
60
|
+
response = connection.put(path, body)
|
|
61
|
+
response
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def delete(path)
|
|
66
|
+
handle_response do
|
|
67
|
+
response = connection.delete(path)
|
|
68
|
+
response
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def handle_response
|
|
75
|
+
response = yield
|
|
76
|
+
|
|
77
|
+
case response.status
|
|
78
|
+
when 200, 201, 204
|
|
79
|
+
response.body
|
|
80
|
+
when 400
|
|
81
|
+
raise BadRequestError.new(
|
|
82
|
+
error_message(response) || "Bad Request: #{response.body.inspect}",
|
|
83
|
+
code: error_code(response),
|
|
84
|
+
details: error_details(response)
|
|
85
|
+
)
|
|
86
|
+
when 401
|
|
87
|
+
raise AuthenticationError.new(
|
|
88
|
+
error_message(response),
|
|
89
|
+
code: error_code(response),
|
|
90
|
+
details: error_details(response)
|
|
91
|
+
)
|
|
92
|
+
when 403
|
|
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
|
+
|
|
144
|
+
def format_time(time)
|
|
145
|
+
return nil if time.nil?
|
|
146
|
+
return time if time.is_a?(String)
|
|
147
|
+
|
|
148
|
+
if time.respond_to?(:strftime) && !time.respond_to?(:hour)
|
|
149
|
+
return "#{time.strftime('%Y-%m-%d')}T00:00:00.000Z"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
return time.utc.strftime('%Y-%m-%dT%H:%M:%S.%LZ') if time.respond_to?(:utc)
|
|
153
|
+
|
|
154
|
+
time
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module LoyverseApi
|
|
2
|
+
class Configuration
|
|
3
|
+
attr_accessor :access_token, :api_base_url, :timeout, :open_timeout
|
|
4
|
+
alias_method :base_url, :api_base_url
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@api_base_url = "https://api.loyverse.com/v1.0/"
|
|
8
|
+
@timeout = 30
|
|
9
|
+
@open_timeout = 10
|
|
10
|
+
@access_token = nil
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module LoyverseApi
|
|
2
|
+
module Endpoints
|
|
3
|
+
module Categories
|
|
4
|
+
# Get a specific category by ID
|
|
5
|
+
# @param category_id [String] UUID of the category
|
|
6
|
+
# @return [Hash] Category details
|
|
7
|
+
def get_category(category_id)
|
|
8
|
+
get("categories/#{category_id}")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# List all categories
|
|
12
|
+
# @param limit [Integer] Maximum number of results per page (default: 250)
|
|
13
|
+
# @param cursor [String] Pagination cursor for next page
|
|
14
|
+
# @return [Hash] Response with categories array
|
|
15
|
+
def list_categories(limit: 250, cursor: nil)
|
|
16
|
+
params = {
|
|
17
|
+
limit: limit,
|
|
18
|
+
cursor: cursor
|
|
19
|
+
}.compact
|
|
20
|
+
|
|
21
|
+
get("categories", params: params)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Create a new category
|
|
25
|
+
# @param name [String] Category name
|
|
26
|
+
# @param color [String] Color code (e.g., "ORANGE", "RED", "BLUE")
|
|
27
|
+
# @return [Hash] Created category details
|
|
28
|
+
def create_category(name:, color: nil)
|
|
29
|
+
body = { name: name }
|
|
30
|
+
body[:color] = color if color
|
|
31
|
+
|
|
32
|
+
post("categories", body: body)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Delete a category
|
|
36
|
+
# @param category_id [String] UUID of the category
|
|
37
|
+
# @return [Hash] Response
|
|
38
|
+
def delete_category(category_id)
|
|
39
|
+
delete("categories/#{category_id}")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
module LoyverseApi
|
|
2
|
+
module Endpoints
|
|
3
|
+
module Customers
|
|
4
|
+
# Get a specific customer by ID
|
|
5
|
+
# @param customer_id [String] UUID of the customer
|
|
6
|
+
# @return [Hash] Customer details
|
|
7
|
+
def get_customer(customer_id)
|
|
8
|
+
get("customers/#{customer_id}")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# List customers
|
|
12
|
+
# @param limit [Integer] Maximum number of results per page (default: 250)
|
|
13
|
+
# @param cursor [String] Pagination cursor for next page
|
|
14
|
+
# @param email [String] Filter by email address (optional)
|
|
15
|
+
# @param phone_number [String] Filter by phone number (optional)
|
|
16
|
+
# @param updated_at_min [String, Time] Filter by minimum update time (ISO 8601)
|
|
17
|
+
# @param updated_at_max [String, Time] Filter by maximum update time (ISO 8601)
|
|
18
|
+
# @return [Hash] Response with customers array
|
|
19
|
+
def list_customers(limit: 250, cursor: nil, email: nil, phone_number: nil, updated_at_min: nil, updated_at_max: nil)
|
|
20
|
+
params = {
|
|
21
|
+
limit: limit,
|
|
22
|
+
cursor: cursor,
|
|
23
|
+
email: email,
|
|
24
|
+
phone_number: phone_number,
|
|
25
|
+
updated_at_min: format_time(updated_at_min),
|
|
26
|
+
updated_at_max: format_time(updated_at_max)
|
|
27
|
+
}.compact
|
|
28
|
+
|
|
29
|
+
get("customers", params: params)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Create a new customer
|
|
33
|
+
# @param name [String] Customer name
|
|
34
|
+
# @param email [String] Customer email (optional)
|
|
35
|
+
# @param phone_number [String] Customer phone number (optional)
|
|
36
|
+
# @param address [String] Customer address (optional)
|
|
37
|
+
# @param city [String] Customer city (optional)
|
|
38
|
+
# @param region [String] Customer region/state (optional)
|
|
39
|
+
# @param postal_code [String] Customer postal code (optional)
|
|
40
|
+
# @param country [String] Customer country (optional)
|
|
41
|
+
# @param customer_code [String] Custom customer code (optional)
|
|
42
|
+
# @param note [String] Customer note (optional)
|
|
43
|
+
# @param first_visit [String, Time] Date of first visit (optional)
|
|
44
|
+
# @param total_visits [Integer] Total number of visits (optional)
|
|
45
|
+
# @param total_spent [Float] Total amount spent (optional)
|
|
46
|
+
# @return [Hash] Created customer details
|
|
47
|
+
def create_customer(
|
|
48
|
+
name:,
|
|
49
|
+
email: nil,
|
|
50
|
+
phone_number: nil,
|
|
51
|
+
address: nil,
|
|
52
|
+
city: nil,
|
|
53
|
+
region: nil,
|
|
54
|
+
postal_code: nil,
|
|
55
|
+
country: nil,
|
|
56
|
+
customer_code: nil,
|
|
57
|
+
note: nil,
|
|
58
|
+
first_visit: nil,
|
|
59
|
+
total_visits: nil,
|
|
60
|
+
total_spent: nil
|
|
61
|
+
)
|
|
62
|
+
body = {
|
|
63
|
+
name: name,
|
|
64
|
+
email: email,
|
|
65
|
+
phone_number: phone_number,
|
|
66
|
+
address: address,
|
|
67
|
+
city: city,
|
|
68
|
+
region: region,
|
|
69
|
+
postal_code: postal_code,
|
|
70
|
+
country: country,
|
|
71
|
+
customer_code: customer_code,
|
|
72
|
+
note: note,
|
|
73
|
+
first_visit: format_time(first_visit),
|
|
74
|
+
total_visits: total_visits,
|
|
75
|
+
total_spent: total_spent
|
|
76
|
+
}.compact
|
|
77
|
+
|
|
78
|
+
post("customers", body: body)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Update an existing customer
|
|
82
|
+
# @param customer_id [String] UUID of the customer
|
|
83
|
+
# @param name [String] Customer name (optional)
|
|
84
|
+
# @param email [String] Customer email (optional)
|
|
85
|
+
# @param phone_number [String] Customer phone number (optional)
|
|
86
|
+
# @param address [String] Customer address (optional)
|
|
87
|
+
# @param city [String] Customer city (optional)
|
|
88
|
+
# @param region [String] Customer region/state (optional)
|
|
89
|
+
# @param postal_code [String] Customer postal code (optional)
|
|
90
|
+
# @param country [String] Customer country (optional)
|
|
91
|
+
# @param customer_code [String] Custom customer code (optional)
|
|
92
|
+
# @param note [String] Customer note (optional)
|
|
93
|
+
# @return [Hash] Updated customer details
|
|
94
|
+
def update_customer(
|
|
95
|
+
customer_id,
|
|
96
|
+
name: nil,
|
|
97
|
+
email: nil,
|
|
98
|
+
phone_number: nil,
|
|
99
|
+
address: nil,
|
|
100
|
+
city: nil,
|
|
101
|
+
region: nil,
|
|
102
|
+
postal_code: nil,
|
|
103
|
+
country: nil,
|
|
104
|
+
customer_code: nil,
|
|
105
|
+
note: nil
|
|
106
|
+
)
|
|
107
|
+
body = {
|
|
108
|
+
name: name,
|
|
109
|
+
email: email,
|
|
110
|
+
phone_number: phone_number,
|
|
111
|
+
address: address,
|
|
112
|
+
city: city,
|
|
113
|
+
region: region,
|
|
114
|
+
postal_code: postal_code,
|
|
115
|
+
country: country,
|
|
116
|
+
customer_code: customer_code,
|
|
117
|
+
note: note
|
|
118
|
+
}.compact
|
|
119
|
+
|
|
120
|
+
put("customers/#{customer_id}", body: body)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Delete a customer
|
|
124
|
+
# @param customer_id [String] UUID of the customer
|
|
125
|
+
# @return [Hash] Response
|
|
126
|
+
def delete_customer(customer_id)
|
|
127
|
+
delete("customers/#{customer_id}")
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|