flexops 1.1.0 → 2.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.
@@ -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(carrier:, declared_value:, provider: nil)
22
- body = { carrier: carrier, declaredValue: declared_value }
23
- body[:provider] = provider if provider
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
- def purchase(tracking_number:, carrier:, declared_value:, provider: nil)
28
- body = { trackingNumber: tracking_number, carrier: carrier, declaredValue: declared_value }
29
- body[:provider] = provider if provider
30
- @http.post("#{ws_path}/insurance/purchase", body: body)
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, description:, claim_amount:)
38
- @http.post("#{ws_path}/insurance/policies/#{policy_id}/claims", body: { description: description, claimAmount: claim_amount })
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
- def get(pickup_id)
26
- @http.get("#{ws_path}/pickups/#{pickup_id}")
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
- def cancel(pickup_id)
30
- @http.delete("#{ws_path}/pickups/#{pickup_id}")
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-03-31
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(page: nil, page_size: nil, is_active: nil)
20
- query = { page: page, pageSize: page_size, isActive: is_active }.compact
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}/recurring-shipments/#{id}")
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("#{ws_path}/recurring-shipments/", body: request)
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}/recurring-shipments/#{id}", body: request)
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}/recurring-shipments/#{id}")
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}/recurring-shipments/#{id}/pause")
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}/recurring-shipments/#{id}/resume")
57
+ @http.post("#{ws_path}/#{id}/resume")
46
58
  end
47
59
 
48
- def trigger(id)
49
- @http.post("#{ws_path}/recurring-shipments/#{id}/trigger")
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
@@ -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 Returns
@@ -14,41 +16,70 @@ module FlexOps
14
16
  @ws_id = ws_id_proc
15
17
  end
16
18
 
17
- def list(page: nil, page_size: nil, status: nil)
18
- query = { page: page, pageSize: page_size, status: status }.compact
19
+ # Lists received returns (start_date and end_date are ISO 8601; the Gateway defaults to the
20
+ # last 30 days). To list return authorizations, use list_rmas.
21
+ def list(page: nil, page_size: nil, start_date: nil, end_date: nil)
22
+ query = { page: page, pageSize: page_size, startDate: start_date, endDate: end_date }.compact
19
23
  @http.get("#{ws_path}/returns", query: query.empty? ? nil : query)
20
24
  end
21
25
 
26
+ # Reads a received return by its numeric return id ("returnId" on a listed return or an RMA).
27
+ # An RMA number such as "RMA-1001" is not a return id and matches no Gateway route; use get_rma.
22
28
  def get(return_id)
23
29
  @http.get("#{ws_path}/returns/#{return_id}")
24
30
  end
25
31
 
26
- def create(request)
27
- @http.post("#{ws_path}/returns", body: request)
28
- end
29
-
30
- def approve(return_id)
31
- @http.post("#{ws_path}/returns/#{return_id}/approve")
32
+ # Lists return authorizations (RMAs), one page at a time.
33
+ def list_rmas(page: nil, page_size: nil)
34
+ query = { page: page, pageSize: page_size }.compact
35
+ @http.get("#{ws_path}/returns/rma", query: query.empty? ? nil : query)
32
36
  end
33
37
 
34
- def reject(return_id, reason)
35
- @http.post("#{ws_path}/returns/#{return_id}/reject", body: { reason: reason })
38
+ # Reads a return authorization by its RMA number, for example "RMA-1001".
39
+ def get_rma(rma_number)
40
+ @http.get("#{ws_path}/returns/rma/#{ERB::Util.url_encode(rma_number.to_s)}")
36
41
  end
37
42
 
38
- def cancel(return_id)
39
- @http.post("#{ws_path}/returns/#{return_id}/cancel")
40
- end
41
-
42
- def generate_label(return_id)
43
- @http.post("#{ws_path}/returns/#{return_id}/label")
43
+ # Creates an RMA and returns the enveloped RMA (201).
44
+ #
45
+ # request:: { orderId: (a number), orderNumber: (optional), reason:,
46
+ # items: [{ sku:, quantity:, reason: (optional) }], notes: (optional),
47
+ # customerEmail: (optional), reasonId: (optional) }
48
+ def create(request)
49
+ @http.post("#{ws_path}/returns/rma", body: request)
44
50
  end
45
51
 
46
- def mark_received(return_id, items)
47
- @http.post("#{ws_path}/returns/#{return_id}/receive", body: { items: items })
52
+ # Generates a return label for an RMA.
53
+ #
54
+ # The carrier needs the package weight and all three dimensions: pass weight_oz, length_in,
55
+ # width_in and height_in. Anything left out comes from the workspace's default shipping
56
+ # preset; with no preset the Gateway answers 400 PackageMeasurementsRequired naming the
57
+ # missing fields.
58
+ #
59
+ # Only what you set is sent; the Gateway uses "usps" when carrier_code is left out.
60
+ # hazmat_classification (a HazmatClassification) declares what the return holds.
61
+ def generate_label(rma_number, weight_oz: nil, length_in: nil, width_in: nil, height_in: nil,
62
+ carrier_code: nil, service_type: nil, payment_confirmed: nil, hazmat_classification: nil,
63
+ rate_indicator: nil)
64
+ # Always a JSON object: the Gateway binds a required GenerateReturnLabelRequest body and refuses an empty one with 400.
65
+ body = {
66
+ carrierCode: carrier_code, serviceType: service_type, weightOz: weight_oz, lengthIn: length_in,
67
+ widthIn: width_in, heightIn: height_in, paymentConfirmed: payment_confirmed,
68
+ hazmatClassification: hazmat_classification, rateIndicator: rate_indicator
69
+ }.compact
70
+ @http.post("#{ws_path}/returns/#{rma_number}/label", body: body)
48
71
  end
49
72
 
50
- def process_refund(return_id)
51
- @http.post("#{ws_path}/returns/#{return_id}/refund")
73
+ # Records the inspected items of a received RMA.
74
+ #
75
+ # rma_id:: the numeric RMA id (the route segment)
76
+ # rma_number:: the RMA number, for example "RMA-1001"
77
+ # inspected_items:: [{ partNumber:, partDescription: (optional), quantity:,
78
+ # disposition: 0 restock, 1 dispose or 2 forward, inspectionNotes: (optional) }]
79
+ # warehouse_id:: the receiving warehouse; the Gateway uses 1 when it is left out
80
+ def mark_received(rma_id, rma_number:, inspected_items:, warehouse_id: nil)
81
+ body = { rmaNumber: rma_number, warehouseId: warehouse_id, inspectedItems: inspected_items }.compact
82
+ @http.post("#{ws_path}/returns/#{rma_id}/receive", body: body)
52
83
  end
53
84
 
54
85
  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
- def reorder(rule_ids)
38
- @http.put("#{ws_path}/shipping-rules/reorder", body: rule_ids)
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
@@ -27,24 +29,63 @@ module FlexOps
27
29
  end
28
30
 
29
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.
30
34
  def create_label(request = {}, idempotency_key: nil, **fields)
31
35
  request = request.merge(fields)
32
- @http.post("#{ws_path}/shipping/labels", body: request,
36
+ @http.post("/api/shipping/labels", body: request,
33
37
  headers: idempotency_key.nil? ? {} : { "Idempotency-Key" => idempotency_key })
34
38
  end
35
39
 
36
- def cancel_label(label_id)
37
- @http.delete("#{ws_path}/shipping/labels/#{label_id}")
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)}")
38
46
  end
39
47
 
40
48
  def track(tracking_number)
41
- @http.get("#{ws_path}/shipping/track/#{tracking_number}")
49
+ @http.get("/api/shipping/track/#{escape(tracking_number)}")
42
50
  end
43
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.
44
59
  def validate_address(address)
45
- @http.post("#{ws_path}/shipping/addresses/validate", body: address)
60
+ @http.post("/api/addresses/validate", body: to_validation_address(address))
61
+ end
62
+
63
+ # Buys labels for one order shipped in 1 to 99 boxes with the same carrier and service
64
+ # (POST /api/shipping/multi-package, the Gateway MultiPackageShipmentRequest):
65
+ #
66
+ # { orderId: 12345, carrierCode: "USPS", serviceCode: (optional), labelFormat: (optional),
67
+ # shipDate: (optional), hazmatClassification: (optional),
68
+ # packages: [{ weightInOunces:, lengthInInches:, widthInInches:, heightInInches:,
69
+ # deliveryConfirmationRequired: (optional), signatureConfirmationRequired: (optional),
70
+ # insuranceCoverageRequested: (optional), insuredValue: (optional),
71
+ # packageReference: (optional) }] }
72
+ #
73
+ # Weight is in ounces and every dimension in inches; all four are required. Multi-package
74
+ # labels cannot carry HAZMAT yet: send HazmatClassification::NONE or leave hazmatClassification
75
+ # out (any other class is refused with 400 HazmatNotSupported).
76
+ #
77
+ # Returns the envelope when at least one label was bought (check data["failedPackages"] and
78
+ # each box in data["labels"]). When none was, the Gateway answers 422 AllPackagesFailed, or 409
79
+ # OutcomeUnknown when a box's carrier outcome is unknown and a label may exist: both raise
80
+ # FlexOps::Error whose data holds the per-box results. After OutcomeUnknown, check the order
81
+ # before buying again. Pass your own idempotency_key (and reuse it) to make a retry you run
82
+ # yourself safe.
83
+ def create_multi_package(request, idempotency_key: nil)
84
+ @http.post("/api/shipping/multi-package", body: request,
85
+ headers: idempotency_key.nil? ? {} : { "Idempotency-Key" => idempotency_key })
46
86
  end
47
87
 
88
+ # The payload is sent as given. Set hazmatClassification on an item only when you have a declaration.
48
89
  def create_batch(request)
49
90
  @http.post("#{ws_path}/labels/batch", body: request)
50
91
  end
@@ -62,19 +103,19 @@ module FlexOps
62
103
  end
63
104
 
64
105
  def get_carriers
65
- @http.get("#{ws_path}/shipping/carriers")
106
+ @http.get("/api/shipping/carriers")
66
107
  end
67
108
 
68
109
  def get_recommendations(request)
69
- @http.post("#{ws_path}/shipping/recommendations", body: request)
110
+ @http.post("/api/shipping/recommendations", body: request)
70
111
  end
71
112
 
72
113
  def predict_delivery(request)
73
- @http.post("#{ws_path}/shipping/predictions/delivery", body: request)
114
+ @http.post("/api/shipping/predictions/delivery", body: request)
74
115
  end
75
116
 
76
117
  def get_savings
77
- @http.get("#{ws_path}/shipping/savings")
118
+ @http.get("/api/shipping/savings")
78
119
  end
79
120
 
80
121
  private
@@ -82,6 +123,28 @@ module FlexOps
82
123
  def ws_path
83
124
  "/api/workspaces/#{@ws_id.call}"
84
125
  end
126
+
127
+ def escape(segment)
128
+ ERB::Util.url_encode(segment.to_s)
129
+ end
130
+
131
+ # Gateway AddressValidationRequest field for each accepted address field name.
132
+ VALIDATION_ADDRESS_FIELDS = {
133
+ "name" => "name",
134
+ "line1" => "line1", "addressLine1" => "line1", "street1" => "line1",
135
+ "line2" => "line2", "addressLine2" => "line2", "street2" => "line2",
136
+ "city" => "city",
137
+ "state" => "state", "stateProvince" => "state",
138
+ "postalCode" => "postalCode", "zip" => "postalCode",
139
+ "country" => "country", "countryCode" => "country"
140
+ }.freeze
141
+
142
+ def to_validation_address(address)
143
+ address.each_with_object({}) do |(key, value), mapped|
144
+ field = VALIDATION_ADDRESS_FIELDS[key.to_s]
145
+ mapped[field] = value unless field.nil? || value.nil?
146
+ end
147
+ end
85
148
  end
86
149
  end
87
150
  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
- @http.get("#{ws_path}/wallet/balance")
19
- end
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