flexops 1.0.2 → 2.0.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/README.md +235 -61
- data/lib/flexops/client.rb +6 -7
- data/lib/flexops/errors.rb +9 -0
- data/lib/flexops/hazmat_classification.rb +34 -0
- data/lib/flexops/http.rb +78 -45
- data/lib/flexops/resources/auth.rb +8 -29
- data/lib/flexops/resources/carbon_offsets.rb +60 -0
- data/lib/flexops/resources/carriers.rb +95 -8
- data/lib/flexops/resources/email_templates.rb +27 -17
- data/lib/flexops/resources/hs_codes.rb +10 -11
- data/lib/flexops/resources/insurance.rb +19 -9
- data/lib/flexops/resources/inventory.rb +0 -8
- data/lib/flexops/resources/orders.rb +0 -8
- data/lib/flexops/resources/pickups.rb +14 -5
- data/lib/flexops/resources/recurring_shipments.rb +30 -15
- data/lib/flexops/resources/report_schedules.rb +63 -0
- data/lib/flexops/resources/returns.rb +37 -23
- data/lib/flexops/resources/rules.rb +18 -2
- data/lib/flexops/resources/shipping.rb +51 -10
- data/lib/flexops/resources/wallet.rb +4 -20
- data/lib/flexops/resources/webhooks.rb +132 -9
- data/lib/flexops/resources/workspaces.rb +8 -13
- data/lib/flexops/version.rb +1 -1
- data/lib/flexops/webhook_event_type.rb +25 -0
- data/lib/flexops/workspace_role.rb +32 -0
- data/lib/flexops.rb +5 -3
- metadata +7 -5
- data/lib/flexops/resources/api_keys.rb +0 -40
- data/lib/flexops/resources/offsets.rb +0 -38
- data/lib/flexops/resources/reports.rb +0 -46
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
module FlexOps
|
|
10
10
|
module Resources
|
|
11
|
+
# Shipping insurance. Answers use the { success, data, message, errorCode } envelope.
|
|
11
12
|
class Insurance
|
|
12
13
|
def initialize(http, ws_id_proc)
|
|
13
14
|
@http = http
|
|
@@ -18,24 +19,33 @@ module FlexOps
|
|
|
18
19
|
@http.get("#{ws_path}/insurance/providers")
|
|
19
20
|
end
|
|
20
21
|
|
|
21
|
-
def get_quote(
|
|
22
|
-
body = {
|
|
23
|
-
|
|
22
|
+
def get_quote(declared_value:, carrier_code:, tracking_number: nil, provider: nil)
|
|
23
|
+
body = { declaredValue: declared_value, carrierCode: carrier_code,
|
|
24
|
+
trackingNumber: tracking_number, provider: provider }.compact
|
|
24
25
|
@http.post("#{ws_path}/insurance/quote", body: body)
|
|
25
26
|
end
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
# Buys a policy and charges the premium to the workspace wallet. A shipment that already has a
|
|
29
|
+
# policy in force is refused with 409 ALREADY_EXISTS.
|
|
30
|
+
# Pass an idempotency_key you store with the purchase to make your own retry safe: after an
|
|
31
|
+
# error or a timeout, retry with the same key and the Gateway returns the first result instead
|
|
32
|
+
# of buying again. Without one, the SDK's own retries still share a key, but a new call gets a new key.
|
|
33
|
+
def purchase(tracking_number:, carrier_code:, declared_value:, coverage_tier: nil, provider: nil,
|
|
34
|
+
idempotency_key: nil)
|
|
35
|
+
body = { trackingNumber: tracking_number, carrierCode: carrier_code, declaredValue: declared_value,
|
|
36
|
+
coverageTier: coverage_tier, provider: provider }.compact
|
|
37
|
+
@http.post("#{ws_path}/insurance/policies", body: body,
|
|
38
|
+
headers: idempotency_key.nil? ? {} : { "Idempotency-Key" => idempotency_key })
|
|
31
39
|
end
|
|
32
40
|
|
|
33
41
|
def void(policy_id)
|
|
34
42
|
@http.delete("#{ws_path}/insurance/policies/#{policy_id}")
|
|
35
43
|
end
|
|
36
44
|
|
|
37
|
-
def file_claim(policy_id
|
|
38
|
-
|
|
45
|
+
def file_claim(policy_id:, claim_reason:, claim_amount:, evidence_json: nil)
|
|
46
|
+
body = { policyId: policy_id, claimReason: claim_reason, claimAmount: claim_amount,
|
|
47
|
+
evidenceJson: evidence_json }.compact
|
|
48
|
+
@http.post("#{ws_path}/insurance/claims", body: body)
|
|
39
49
|
end
|
|
40
50
|
|
|
41
51
|
private
|
|
@@ -15,10 +15,6 @@ module FlexOps
|
|
|
15
15
|
@http = http
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def post_asn_receipt(receipt)
|
|
19
|
-
@http.post("#{PROXY}/api/v1/Inventory/postNewAsnReceipt", body: receipt)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
18
|
def get_warehouse_snapshot(params = nil)
|
|
23
19
|
@http.get("#{PROXY}/api/v1/Inventory/getWarehouseInventorySnapshot", query: params)
|
|
24
20
|
end
|
|
@@ -30,10 +26,6 @@ module FlexOps
|
|
|
30
26
|
def get_part_numbers(params = nil)
|
|
31
27
|
@http.get("#{PROXY}/api/v1/Inventory/getPartNumberList", query: params)
|
|
32
28
|
end
|
|
33
|
-
|
|
34
|
-
def update_inventory(data)
|
|
35
|
-
@http.post("#{PROXY}/api/v2/Inventory/postCustomerInventoryUpdate", body: data)
|
|
36
|
-
end
|
|
37
29
|
end
|
|
38
30
|
end
|
|
39
31
|
end
|
|
@@ -15,10 +15,6 @@ module FlexOps
|
|
|
15
15
|
@http = http
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def create(order)
|
|
19
|
-
@http.post("#{BASE}/postNewOrder", body: order)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
18
|
def get_new_orders(params = nil)
|
|
23
19
|
@http.get("#{BASE}/getNewOrderList", query: params)
|
|
24
20
|
end
|
|
@@ -39,10 +35,6 @@ module FlexOps
|
|
|
39
35
|
@http.get("#{BASE}/getIndividualOrderStatusByOrderNumber", query: { orderNumber: order_number })
|
|
40
36
|
end
|
|
41
37
|
|
|
42
|
-
def cancel(order_number)
|
|
43
|
-
@http.post("#{BASE}/cancelOrderByOrderNumber", body: { orderNumber: order_number })
|
|
44
|
-
end
|
|
45
|
-
|
|
46
38
|
def get_items(order_number)
|
|
47
39
|
@http.get("#{BASE}/getAllOrderItemsByOrderNumber", query: { orderNumber: order_number })
|
|
48
40
|
end
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
# Copyright (c) 2021-2026 by FlexOps, LLC. All rights reserved.
|
|
7
7
|
# ***********************************************************************
|
|
8
8
|
|
|
9
|
+
require "uri"
|
|
10
|
+
|
|
9
11
|
module FlexOps
|
|
10
12
|
module Resources
|
|
11
13
|
class Pickups
|
|
@@ -14,20 +16,27 @@ module FlexOps
|
|
|
14
16
|
@ws_id = ws_id_proc
|
|
15
17
|
end
|
|
16
18
|
|
|
19
|
+
# request:: { carrierCode:, pickupDate:, street:, city:, state:, postalCode:,
|
|
20
|
+
# country: (optional, "US"), packageCount:, totalWeightOz: (optional),
|
|
21
|
+
# specialInstructions:, readyTime:, closeTime:, contactName:, contactPhone: (all optional) }
|
|
17
22
|
def schedule(request)
|
|
18
23
|
@http.post("#{ws_path}/pickups", body: request)
|
|
19
24
|
end
|
|
20
25
|
|
|
26
|
+
# Lists the workspace's scheduled pickups.
|
|
21
27
|
def list
|
|
22
|
-
@http.get("#{ws_path}/pickups")
|
|
28
|
+
@http.get("#{ws_path}/pickups/scheduled")
|
|
23
29
|
end
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
# The Gateway uses "usps" when carrier_code is left out.
|
|
32
|
+
def get(pickup_id, carrier_code: nil)
|
|
33
|
+
@http.get("#{ws_path}/pickups/#{pickup_id}", query: carrier_code.nil? ? nil : { carrierCode: carrier_code })
|
|
27
34
|
end
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
# The Gateway uses "usps" when carrier_code is left out.
|
|
37
|
+
def cancel(pickup_id, carrier_code: nil)
|
|
38
|
+
query = carrier_code.nil? ? "" : "?#{URI.encode_www_form(carrierCode: carrier_code)}"
|
|
39
|
+
@http.delete("#{ws_path}/pickups/#{pickup_id}#{query}")
|
|
31
40
|
end
|
|
32
41
|
|
|
33
42
|
private
|
|
@@ -1,58 +1,73 @@
|
|
|
1
1
|
# ***********************************************************************
|
|
2
2
|
# Package : flexops
|
|
3
3
|
# Author : FlexOps, LLC
|
|
4
|
-
# Created : 2026-
|
|
4
|
+
# Created : 2026-10-19
|
|
5
5
|
#
|
|
6
6
|
# Copyright (c) 2021-2026 by FlexOps, LLC. All rights reserved.
|
|
7
7
|
# ***********************************************************************
|
|
8
8
|
|
|
9
|
-
# frozen_string_literal: true
|
|
10
|
-
|
|
11
9
|
module FlexOps
|
|
12
10
|
module Resources
|
|
11
|
+
# Recurring shipment schedules. Answers are bare JSON (no envelope); ids are GUID strings.
|
|
12
|
+
#
|
|
13
|
+
# Reads work with any API key or a user token. Writes (create, update, delete, pause, resume,
|
|
14
|
+
# trigger) need a user token (auth.login) or an API key granted the opt-in
|
|
15
|
+
# "recurring-shipments:write" scope; a key without it gets 403 API_KEY_SCOPE_REQUIRED.
|
|
16
|
+
#
|
|
17
|
+
# +interval+ is the Gateway RecurrenceInterval number: Daily 0, Weekly 1, Biweekly 2,
|
|
18
|
+
# Monthly 3, Custom 4 (Custom runs on +cronExpression+).
|
|
13
19
|
class RecurringShipments
|
|
14
20
|
def initialize(http, ws_id_proc)
|
|
15
21
|
@http = http
|
|
16
22
|
@ws_id = ws_id_proc
|
|
17
23
|
end
|
|
18
24
|
|
|
19
|
-
def list
|
|
20
|
-
|
|
21
|
-
@http.get("#{ws_path}/recurring-shipments/", query: query.empty? ? nil : query)
|
|
25
|
+
def list
|
|
26
|
+
@http.get(ws_path)
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
def get(id)
|
|
25
|
-
@http.get("#{ws_path}
|
|
30
|
+
@http.get("#{ws_path}/#{id}")
|
|
26
31
|
end
|
|
27
32
|
|
|
33
|
+
# request:: { name:, firstRunUtc: (ISO 8601), interval: (0-4), presetId:, addressId:,
|
|
34
|
+
# cronExpression:, maxFailures: (1-100, default 3) }. Each run needs a destination
|
|
35
|
+
# +addressId+; a +presetId+ supplies the package and service. Returns the schedule.
|
|
28
36
|
def create(request)
|
|
29
|
-
@http.post(
|
|
37
|
+
@http.post(ws_path, body: request)
|
|
30
38
|
end
|
|
31
39
|
|
|
40
|
+
# request:: any of name:, presetId:, addressId:, interval:, cronExpression:, maxFailures:.
|
|
32
41
|
def update(id, request)
|
|
33
|
-
@http.put("#{ws_path}
|
|
42
|
+
@http.put("#{ws_path}/#{id}", body: request)
|
|
34
43
|
end
|
|
35
44
|
|
|
45
|
+
# Returns nil (204).
|
|
36
46
|
def delete(id)
|
|
37
|
-
@http.delete("#{ws_path}
|
|
47
|
+
@http.delete("#{ws_path}/#{id}")
|
|
38
48
|
end
|
|
39
49
|
|
|
50
|
+
# Returns nil (204).
|
|
40
51
|
def pause(id)
|
|
41
|
-
@http.post("#{ws_path}
|
|
52
|
+
@http.post("#{ws_path}/#{id}/pause")
|
|
42
53
|
end
|
|
43
54
|
|
|
55
|
+
# Returns nil (204).
|
|
44
56
|
def resume(id)
|
|
45
|
-
@http.post("#{ws_path}
|
|
57
|
+
@http.post("#{ws_path}/#{id}/resume")
|
|
46
58
|
end
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
# Runs the schedule now, which creates an order. Pass the same +idempotency_key+ when you
|
|
61
|
+
# retry a trigger yourself so the Gateway does not create a second order. Returns nil (204).
|
|
62
|
+
def trigger(id, idempotency_key: nil)
|
|
63
|
+
@http.post("#{ws_path}/#{id}/trigger",
|
|
64
|
+
headers: idempotency_key.nil? ? {} : { "Idempotency-Key" => idempotency_key })
|
|
50
65
|
end
|
|
51
66
|
|
|
52
67
|
private
|
|
53
68
|
|
|
54
69
|
def ws_path
|
|
55
|
-
"/api/workspaces/#{@ws_id.call}"
|
|
70
|
+
"/api/workspaces/#{@ws_id.call}/recurring-shipments"
|
|
56
71
|
end
|
|
57
72
|
end
|
|
58
73
|
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# ***********************************************************************
|
|
2
|
+
# Package : flexops
|
|
3
|
+
# Author : FlexOps, LLC
|
|
4
|
+
# Created : 2026-10-19
|
|
5
|
+
#
|
|
6
|
+
# Copyright (c) 2021-2026 by FlexOps, LLC. All rights reserved.
|
|
7
|
+
# ***********************************************************************
|
|
8
|
+
|
|
9
|
+
module FlexOps
|
|
10
|
+
module Resources
|
|
11
|
+
# Scheduled report emails. Answers use the { success, data, message } envelope, except
|
|
12
|
+
# delete, which returns nil (204). Ids are GUID strings.
|
|
13
|
+
#
|
|
14
|
+
# Reads work with any API key or a user token. Writes (create, update, delete, toggle) need a
|
|
15
|
+
# user token (auth.login) or an API key granted the opt-in "report-schedules:write" scope; a
|
|
16
|
+
# key without it gets 403 API_KEY_SCOPE_REQUIRED.
|
|
17
|
+
#
|
|
18
|
+
# Numbers on the wire: +format+ Csv 0, Json 1; +frequencyType+ Daily 0, Weekly 1, Monthly 2;
|
|
19
|
+
# +dayOfWeek+ 0-6 (Sunday 0, for Weekly); +dayOfMonth+ 1-28 (for Monthly).
|
|
20
|
+
class ReportSchedules
|
|
21
|
+
def initialize(http, ws_id_proc)
|
|
22
|
+
@http = http
|
|
23
|
+
@ws_id = ws_id_proc
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def list
|
|
27
|
+
@http.get(ws_path)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def get(id)
|
|
31
|
+
@http.get("#{ws_path}/#{id}")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# request:: { reportType: (e.g. "shipments"), format:, frequencyType:, timeOfDayUtc: ("HH:mm:ss"),
|
|
35
|
+
# recipientEmails: (comma-separated), dayOfWeek:, dayOfMonth:, filterParametersJson:,
|
|
36
|
+
# maxFailures: (1-100, default 3) }.
|
|
37
|
+
def create(request)
|
|
38
|
+
@http.post(ws_path, body: request)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# request:: any of the create fields.
|
|
42
|
+
def update(id, request)
|
|
43
|
+
@http.put("#{ws_path}/#{id}", body: request)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns nil (204).
|
|
47
|
+
def delete(id)
|
|
48
|
+
@http.delete("#{ws_path}/#{id}")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Flips isEnabled. The envelope's data is nil; read the schedule to see the new state.
|
|
52
|
+
def toggle(id)
|
|
53
|
+
@http.post("#{ws_path}/#{id}/toggle")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def ws_path
|
|
59
|
+
"/api/workspaces/#{@ws_id.call}/report-schedules"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -14,8 +14,8 @@ module FlexOps
|
|
|
14
14
|
@ws_id = ws_id_proc
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def list(page: nil, page_size: nil,
|
|
18
|
-
query = { page: page, pageSize: page_size,
|
|
17
|
+
def list(page: nil, page_size: nil, start_date: nil, end_date: nil)
|
|
18
|
+
query = { page: page, pageSize: page_size, startDate: start_date, endDate: end_date }.compact
|
|
19
19
|
@http.get("#{ws_path}/returns", query: query.empty? ? nil : query)
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -23,32 +23,46 @@ module FlexOps
|
|
|
23
23
|
@http.get("#{ws_path}/returns/#{return_id}")
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
# Creates an RMA and returns the enveloped RMA (201).
|
|
27
|
+
#
|
|
28
|
+
# request:: { orderId: (a number), orderNumber: (optional), reason:,
|
|
29
|
+
# items: [{ sku:, quantity:, reason: (optional) }], notes: (optional),
|
|
30
|
+
# customerEmail: (optional), reasonId: (optional) }
|
|
26
31
|
def create(request)
|
|
27
|
-
@http.post("#{ws_path}/returns", body: request)
|
|
32
|
+
@http.post("#{ws_path}/returns/rma", body: request)
|
|
28
33
|
end
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
# Generates a return label for an RMA.
|
|
36
|
+
#
|
|
37
|
+
# The carrier needs the package weight and all three dimensions: pass weight_oz, length_in,
|
|
38
|
+
# width_in and height_in. Anything left out comes from the workspace's default shipping
|
|
39
|
+
# preset; with no preset the Gateway answers 400 PackageMeasurementsRequired naming the
|
|
40
|
+
# missing fields.
|
|
41
|
+
#
|
|
42
|
+
# Only what you set is sent; the Gateway uses "usps" when carrier_code is left out.
|
|
43
|
+
# hazmat_classification (a HazmatClassification) declares what the return holds.
|
|
44
|
+
def generate_label(rma_number, weight_oz: nil, length_in: nil, width_in: nil, height_in: nil,
|
|
45
|
+
carrier_code: nil, service_type: nil, payment_confirmed: nil, hazmat_classification: nil,
|
|
46
|
+
rate_indicator: nil)
|
|
47
|
+
# Always a JSON object: the Gateway binds a required GenerateReturnLabelRequest body and refuses an empty one with 400.
|
|
48
|
+
body = {
|
|
49
|
+
carrierCode: carrier_code, serviceType: service_type, weightOz: weight_oz, lengthIn: length_in,
|
|
50
|
+
widthIn: width_in, heightIn: height_in, paymentConfirmed: payment_confirmed,
|
|
51
|
+
hazmatClassification: hazmat_classification, rateIndicator: rate_indicator
|
|
52
|
+
}.compact
|
|
53
|
+
@http.post("#{ws_path}/returns/#{rma_number}/label", body: body)
|
|
32
54
|
end
|
|
33
55
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@http.post("#{ws_path}/returns/#{
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def mark_received(return_id, items)
|
|
47
|
-
@http.post("#{ws_path}/returns/#{return_id}/receive", body: { items: items })
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def process_refund(return_id)
|
|
51
|
-
@http.post("#{ws_path}/returns/#{return_id}/refund")
|
|
56
|
+
# Records the inspected items of a received RMA.
|
|
57
|
+
#
|
|
58
|
+
# rma_id:: the numeric RMA id (the route segment)
|
|
59
|
+
# rma_number:: the RMA number, for example "RMA-1001"
|
|
60
|
+
# inspected_items:: [{ partNumber:, partDescription: (optional), quantity:,
|
|
61
|
+
# disposition: 0 restock, 1 dispose or 2 forward, inspectionNotes: (optional) }]
|
|
62
|
+
# warehouse_id:: the receiving warehouse; the Gateway uses 1 when it is left out
|
|
63
|
+
def mark_received(rma_id, rma_number:, inspected_items:, warehouse_id: nil)
|
|
64
|
+
body = { rmaNumber: rma_number, warehouseId: warehouse_id, inspectedItems: inspected_items }.compact
|
|
65
|
+
@http.post("#{ws_path}/returns/#{rma_id}/receive", body: body)
|
|
52
66
|
end
|
|
53
67
|
|
|
54
68
|
private
|
|
@@ -22,10 +22,13 @@ module FlexOps
|
|
|
22
22
|
@http.get("#{ws_path}/shipping-rules/#{rule_id}")
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# rule:: { name:, priority:, isEnabled:, warehouseId: (optional),
|
|
26
|
+
# conditions: [{ field:, operator:, value:, group: (optional) }], actions: [{ type:, value: }] }
|
|
25
27
|
def create(rule)
|
|
26
28
|
@http.post("#{ws_path}/shipping-rules", body: rule)
|
|
27
29
|
end
|
|
28
30
|
|
|
31
|
+
# rule:: the fields to change, named as for #create.
|
|
29
32
|
def update(rule_id, rule)
|
|
30
33
|
@http.put("#{ws_path}/shipping-rules/#{rule_id}", body: rule)
|
|
31
34
|
end
|
|
@@ -34,8 +37,21 @@ module FlexOps
|
|
|
34
37
|
@http.delete("#{ws_path}/shipping-rules/#{rule_id}")
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
# Sets rule priorities. +rules+ is an array of { ruleId:, priority: } hashes, or of rule ids
|
|
41
|
+
# in the order to apply (the first gets priority 0).
|
|
42
|
+
def reorder(rules)
|
|
43
|
+
items = rules.each_with_index.map do |rule, index|
|
|
44
|
+
rule.is_a?(Hash) ? rule : { ruleId: rule, priority: index }
|
|
45
|
+
end
|
|
46
|
+
@http.post("#{ws_path}/shipping-rules/reorder", body: { rules: items })
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Evaluates the workspace's rules against a sample shipment without changing anything.
|
|
50
|
+
#
|
|
51
|
+
# request:: { weightOz:, declaredValue:, destinationCountry:, destinationState:,
|
|
52
|
+
# destinationPostalCode:, orderSource:, itemCount:, shipMethod:, destinationZone:, tags: }
|
|
53
|
+
def test(request)
|
|
54
|
+
@http.post("#{ws_path}/shipping-rules/test", body: request)
|
|
39
55
|
end
|
|
40
56
|
|
|
41
57
|
private
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
# Copyright (c) 2021-2026 by FlexOps, LLC. All rights reserved.
|
|
7
7
|
# ***********************************************************************
|
|
8
8
|
|
|
9
|
+
require "erb"
|
|
10
|
+
|
|
9
11
|
module FlexOps
|
|
10
12
|
module Resources
|
|
11
13
|
class Shipping
|
|
@@ -26,22 +28,39 @@ module FlexOps
|
|
|
26
28
|
@http.post("/api/shipping/rates/fastest", body: request)
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
# Returns a raw preview or label; confirmation always belongs to the caller.
|
|
32
|
+
# A label without an orderId must declare hazmatClassification (a HazmatClassification;
|
|
33
|
+
# HazmatClassification::NONE for an ordinary parcel). The SDK never fills it in.
|
|
34
|
+
def create_label(request = {}, idempotency_key: nil, **fields)
|
|
35
|
+
request = request.merge(fields)
|
|
36
|
+
@http.post("/api/shipping/labels", body: request,
|
|
37
|
+
headers: idempotency_key.nil? ? {} : { "Idempotency-Key" => idempotency_key })
|
|
31
38
|
end
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
# The Gateway requires the carrier code (for example "USPS"). It is a keyword with no
|
|
41
|
+
# default value so older call sites fail with a clear ArgumentError before any request.
|
|
42
|
+
def cancel_label(label_id, carrier_code: nil)
|
|
43
|
+
raise ArgumentError, "carrier_code is required to cancel a label (for example \"USPS\")" if carrier_code.to_s.empty?
|
|
44
|
+
|
|
45
|
+
@http.delete("/api/shipping/labels/#{escape(label_id)}?carrierCode=#{escape(carrier_code)}")
|
|
35
46
|
end
|
|
36
47
|
|
|
37
48
|
def track(tracking_number)
|
|
38
|
-
@http.get("
|
|
49
|
+
@http.get("/api/shipping/track/#{escape(tracking_number)}")
|
|
39
50
|
end
|
|
40
51
|
|
|
52
|
+
# Validates an address with POST /api/addresses/validate and returns the bare result
|
|
53
|
+
# ("isValid", "validationUnavailable", "suggestedAddress", "isResidential", "warnings",
|
|
54
|
+
# "validatedBy", "checkedAt").
|
|
55
|
+
#
|
|
56
|
+
# The address may use the rate and label field names (addressLine1, stateProvince,
|
|
57
|
+
# countryCode, ...), the legacy street1/state/zip names, or the Gateway's own
|
|
58
|
+
# (line1, line2, city, state, postalCode, country). Only those fields and name are sent.
|
|
41
59
|
def validate_address(address)
|
|
42
|
-
@http.post("
|
|
60
|
+
@http.post("/api/addresses/validate", body: to_validation_address(address))
|
|
43
61
|
end
|
|
44
62
|
|
|
63
|
+
# The payload is sent as given. Set hazmatClassification on an item only when you have a declaration.
|
|
45
64
|
def create_batch(request)
|
|
46
65
|
@http.post("#{ws_path}/labels/batch", body: request)
|
|
47
66
|
end
|
|
@@ -59,19 +78,19 @@ module FlexOps
|
|
|
59
78
|
end
|
|
60
79
|
|
|
61
80
|
def get_carriers
|
|
62
|
-
@http.get("
|
|
81
|
+
@http.get("/api/shipping/carriers")
|
|
63
82
|
end
|
|
64
83
|
|
|
65
84
|
def get_recommendations(request)
|
|
66
|
-
@http.post("
|
|
85
|
+
@http.post("/api/shipping/recommendations", body: request)
|
|
67
86
|
end
|
|
68
87
|
|
|
69
88
|
def predict_delivery(request)
|
|
70
|
-
@http.post("
|
|
89
|
+
@http.post("/api/shipping/predictions/delivery", body: request)
|
|
71
90
|
end
|
|
72
91
|
|
|
73
92
|
def get_savings
|
|
74
|
-
@http.get("
|
|
93
|
+
@http.get("/api/shipping/savings")
|
|
75
94
|
end
|
|
76
95
|
|
|
77
96
|
private
|
|
@@ -79,6 +98,28 @@ module FlexOps
|
|
|
79
98
|
def ws_path
|
|
80
99
|
"/api/workspaces/#{@ws_id.call}"
|
|
81
100
|
end
|
|
101
|
+
|
|
102
|
+
def escape(segment)
|
|
103
|
+
ERB::Util.url_encode(segment.to_s)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Gateway AddressValidationRequest field for each accepted address field name.
|
|
107
|
+
VALIDATION_ADDRESS_FIELDS = {
|
|
108
|
+
"name" => "name",
|
|
109
|
+
"line1" => "line1", "addressLine1" => "line1", "street1" => "line1",
|
|
110
|
+
"line2" => "line2", "addressLine2" => "line2", "street2" => "line2",
|
|
111
|
+
"city" => "city",
|
|
112
|
+
"state" => "state", "stateProvince" => "state",
|
|
113
|
+
"postalCode" => "postalCode", "zip" => "postalCode",
|
|
114
|
+
"country" => "country", "countryCode" => "country"
|
|
115
|
+
}.freeze
|
|
116
|
+
|
|
117
|
+
def to_validation_address(address)
|
|
118
|
+
address.each_with_object({}) do |(key, value), mapped|
|
|
119
|
+
field = VALIDATION_ADDRESS_FIELDS[key.to_s]
|
|
120
|
+
mapped[field] = value unless field.nil? || value.nil?
|
|
121
|
+
end
|
|
122
|
+
end
|
|
82
123
|
end
|
|
83
124
|
end
|
|
84
125
|
end
|
|
@@ -14,27 +14,11 @@ module FlexOps
|
|
|
14
14
|
@ws_id = ws_id_proc
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
# Returns the configured workspace's balance, bare: { "hasWallet", "balanceCents", "balance" }.
|
|
18
|
+
# Funding and transaction history are managed in the FlexOps portal.
|
|
17
19
|
def get_balance
|
|
18
|
-
@
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def add_funds(amount)
|
|
22
|
-
@http.post("#{ws_path}/wallet/add-funds", body: { amount: amount })
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def list_transactions(page: nil, page_size: nil, start_date: nil, end_date: nil)
|
|
26
|
-
query = { page: page, pageSize: page_size, startDate: start_date, endDate: end_date }.compact
|
|
27
|
-
@http.get("#{ws_path}/wallet/transactions", query: query.empty? ? nil : query)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def configure_auto_reload(enabled:, threshold: nil, amount: nil)
|
|
31
|
-
@http.put("#{ws_path}/wallet/auto-reload", body: { enabled: enabled, threshold: threshold, amount: amount }.compact)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
def ws_path
|
|
37
|
-
"/api/workspaces/#{@ws_id.call}"
|
|
20
|
+
ws_id = @ws_id.call
|
|
21
|
+
@http.get("/api/wallet/my-balance", headers: ws_id.to_s.empty? ? {} : { "X-Workspace-Id" => ws_id.to_s })
|
|
38
22
|
end
|
|
39
23
|
end
|
|
40
24
|
end
|