flexops 2.0.0 → 2.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/README.md +43 -15
- data/lib/flexops/errors.rb +5 -2
- data/lib/flexops/http.rb +2 -1
- data/lib/flexops/resources/returns.rb +17 -0
- data/lib/flexops/resources/shipping.rb +31 -0
- data/lib/flexops/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7266826af89b91d17327ba0dda3a7318081657cd7d63805be1c7a2665ffcc69c
|
|
4
|
+
data.tar.gz: ab7acc1c279b73e6c84dcf57513368d28f5b85386928260bc23b806226f2fd85
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '08dcd0f4f95c49a14ef83017a81352a4d5f3e9144ec9724b2af6d86882154d1abf7efd54eb2a9dfaa5a8509871c6686f428b90dc13b8834ef605315ff00fc6bd'
|
|
7
|
+
data.tar.gz: 6a854d05d30916be65b30cca8d6c03873bf8f5174df48721c1193c6c54d8aa5cd742767d285b9fd3d8a96c37dff72ac0ca99213d533c926f5302b8d0eaf3bf95
|
data/README.md
CHANGED
|
@@ -29,8 +29,8 @@ require "flexops"
|
|
|
29
29
|
|
|
30
30
|
# API key authentication (recommended for server-to-server)
|
|
31
31
|
client = FlexOps::Client.new(
|
|
32
|
-
api_key: "
|
|
33
|
-
workspace_id: "
|
|
32
|
+
api_key: "live_...",
|
|
33
|
+
workspace_id: "your-workspace-id"
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
# Get shipping rates from all carriers
|
|
@@ -90,7 +90,7 @@ separate contracts. The SDK never automatically confirms a preview.
|
|
|
90
90
|
### API key (recommended)
|
|
91
91
|
|
|
92
92
|
```ruby
|
|
93
|
-
client = FlexOps::Client.new(api_key: "
|
|
93
|
+
client = FlexOps::Client.new(api_key: "live_...", workspace_id: "your-workspace-id")
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
### Email / password
|
|
@@ -99,15 +99,15 @@ client = FlexOps::Client.new(api_key: "fxk_live_...", workspace_id: "ws_abc123")
|
|
|
99
99
|
client = FlexOps::Client.new(base_url: "https://gateway.flexops.io")
|
|
100
100
|
session = client.auth.login(email: "user@example.com", password: "password")
|
|
101
101
|
client.set_access_token(session["data"]["token"])
|
|
102
|
-
client.workspace_id = "
|
|
102
|
+
client.workspace_id = "your-workspace-id"
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
## Sandbox / test keys
|
|
106
106
|
|
|
107
|
-
Use `
|
|
107
|
+
Use `test_...` (instead of `live_...`) to route to the sandbox environment. Mock carriers respond, nothing hits real carrier APIs, no charges, no real labels. Perfect for CI and integration tests.
|
|
108
108
|
|
|
109
109
|
```ruby
|
|
110
|
-
client = FlexOps::Client.new(api_key: "
|
|
110
|
+
client = FlexOps::Client.new(api_key: "test_...", workspace_id: "your-workspace-id")
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
## Carrier-specific calls
|
|
@@ -209,6 +209,34 @@ client.email_templates.upsert(eventType: 3, subjectTemplate: "Delivered: {{track
|
|
|
209
209
|
client.email_templates.preview(subject_template: "Hi {{recipient_name}}", html_body_template: "<p>{{status}}</p>")
|
|
210
210
|
```
|
|
211
211
|
|
|
212
|
+
## Multi-package labels
|
|
213
|
+
|
|
214
|
+
One order shipped in several boxes with the same carrier and service; each box gets its own label and
|
|
215
|
+
a box that fails does not stop the others. Multi-package labels cannot carry HAZMAT: send
|
|
216
|
+
`FlexOps::HazmatClassification::NONE` or leave `hazmatClassification` out. `maximumPostageAmount` is required: the
|
|
217
|
+
most the whole shipment may cost in USD (positive, at most two decimals). The Gateway refuses to buy a box that would
|
|
218
|
+
take the total above it (`PriceCeilingExceeded`; once the amount is used up the remaining boxes report
|
|
219
|
+
`PriceCeilingReached`), and a request without a valid ceiling is 400 `ApprovalRequired`.
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
begin
|
|
223
|
+
shipment = client.shipping.create_multi_package(
|
|
224
|
+
{ orderId: 12345, carrierCode: "USPS", serviceCode: "PRIORITY",
|
|
225
|
+
hazmatClassification: FlexOps::HazmatClassification::NONE,
|
|
226
|
+
maximumPostageAmount: 25.00, # required: the most the whole shipment may cost, in USD
|
|
227
|
+
packages: [{ weightInOunces: 16, lengthInInches: 10, widthInInches: 8, heightInInches: 4, packageReference: "Box 1 of 2" },
|
|
228
|
+
{ weightInOunces: 8, lengthInInches: 6, widthInInches: 6, heightInInches: 6, packageReference: "Box 2 of 2" }] },
|
|
229
|
+
idempotency_key: "order-12345-boxes" # optional; reuse it when you retry
|
|
230
|
+
)
|
|
231
|
+
rescue FlexOps::Error => e
|
|
232
|
+
# No label was bought: 422 AllPackagesFailed, or 409 OutcomeUnknown when a label may exist
|
|
233
|
+
# (check the order before buying again). e.data holds the per-box results.
|
|
234
|
+
end
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`client.returns.get` reads a received return by its numeric return id; read an RMA with
|
|
238
|
+
`client.returns.get_rma("RMA-1001")` or page through them with `client.returns.list_rmas`.
|
|
239
|
+
|
|
212
240
|
## Wallet balance
|
|
213
241
|
|
|
214
242
|
```ruby
|
|
@@ -287,7 +315,7 @@ Every SDK method is a thin wrapper around the FlexOps REST API. If you want to v
|
|
|
287
315
|
```bash
|
|
288
316
|
# Shop rates across all connected carriers
|
|
289
317
|
curl -X POST https://gateway.flexops.io/api/shipping/rates \
|
|
290
|
-
-H "X-API-Key:
|
|
318
|
+
-H "X-API-Key: live_..." \
|
|
291
319
|
-H "Content-Type: application/json" \
|
|
292
320
|
-d '{
|
|
293
321
|
"origin": {"addressLine1": "123 Main St", "city": "New York", "stateProvince": "NY", "postalCode": "10001", "countryCode": "US"},
|
|
@@ -298,7 +326,7 @@ curl -X POST https://gateway.flexops.io/api/shipping/rates \
|
|
|
298
326
|
# Preview a live label; this does not purchase it.
|
|
299
327
|
# hazmatClassification 0 = not hazardous; required when there is no orderId.
|
|
300
328
|
curl -X POST https://gateway.flexops.io/api/shipping/labels \
|
|
301
|
-
-H "X-API-Key:
|
|
329
|
+
-H "X-API-Key: live_..." \
|
|
302
330
|
-H "Content-Type: application/json" \
|
|
303
331
|
-d '{
|
|
304
332
|
"carrierCode": "USPS", "serviceCode": "GROUND_ADVANTAGE",
|
|
@@ -315,14 +343,14 @@ curl -X POST https://gateway.flexops.io/api/shipping/labels \
|
|
|
315
343
|
|
|
316
344
|
# Track a shipment
|
|
317
345
|
curl https://gateway.flexops.io/api/shipping/track/9400111899223456789012 \
|
|
318
|
-
-H "X-API-Key:
|
|
346
|
+
-H "X-API-Key: live_..."
|
|
319
347
|
|
|
320
348
|
# Cancel a label
|
|
321
349
|
curl -X DELETE "https://gateway.flexops.io/api/shipping/labels/lbl_123?carrierCode=USPS" \
|
|
322
|
-
-H "X-API-Key:
|
|
350
|
+
-H "X-API-Key: live_..."
|
|
323
351
|
```
|
|
324
352
|
|
|
325
|
-
Use
|
|
353
|
+
Use a `test_...` key instead of `live_...` to hit the sandbox environment; mock carriers respond, no real charges, no real labels.
|
|
326
354
|
|
|
327
355
|
## Resources
|
|
328
356
|
|
|
@@ -330,12 +358,12 @@ Use an `fxk_test_...` key instead of `fxk_live_...` to hit the sandbox environme
|
|
|
330
358
|
|----------|-------------|
|
|
331
359
|
| `client.auth` | Login, token refresh, logout |
|
|
332
360
|
| `client.workspaces` | List and read workspaces; list, invite and remove members, change roles |
|
|
333
|
-
| `client.shipping` | Rate shopping, labels, tracking, address validation, batch, cancel |
|
|
361
|
+
| `client.shipping` | Rate shopping, labels, multi-package labels, tracking, address validation, batch, cancel |
|
|
334
362
|
| `client.carriers` | USPS, UPS, FedEx and DHL calls through the legacy proxy; writes are deprecated and need a user token |
|
|
335
363
|
| `client.webhooks` | Subscription create/update/delete, secret rotation, test events, delivery logs, signature verification |
|
|
336
364
|
| `client.wallet` | Workspace balance |
|
|
337
365
|
| `client.insurance` | Providers, quotes, policies, void, claims |
|
|
338
|
-
| `client.returns` | List and read returns; create RMAs
|
|
366
|
+
| `client.returns` | List and read received returns; create, read and list RMAs; return labels; receive |
|
|
339
367
|
| `client.analytics` | Shipments, orders, carrier performance (legacy proxy reads) |
|
|
340
368
|
| `client.orders` | Order reads (legacy proxy) |
|
|
341
369
|
| `client.inventory` | Inventory reads (legacy proxy) |
|
|
@@ -355,8 +383,8 @@ API keys, workspace settings and account management are managed in the FlexOps p
|
|
|
355
383
|
```ruby
|
|
356
384
|
client = FlexOps::Client.new(
|
|
357
385
|
base_url: "https://gateway.flexops.io", # API base URL
|
|
358
|
-
api_key: "
|
|
359
|
-
workspace_id: "
|
|
386
|
+
api_key: "live_...", # API key auth
|
|
387
|
+
workspace_id: "your-workspace-id", # Default workspace
|
|
360
388
|
timeout: 30 # Request timeout (seconds)
|
|
361
389
|
)
|
|
362
390
|
```
|
data/lib/flexops/errors.rb
CHANGED
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
|
|
9
9
|
module FlexOps
|
|
10
10
|
class Error < StandardError
|
|
11
|
-
|
|
11
|
+
# data is the envelope's "data" on an error reply, when the Gateway sends one. A
|
|
12
|
+
# multi-package 422 AllPackagesFailed or 409 OutcomeUnknown carries the per-box results there.
|
|
13
|
+
attr_reader :status, :code, :errors, :data
|
|
12
14
|
|
|
13
|
-
def initialize(message, status: 0, code: nil, errors: nil)
|
|
15
|
+
def initialize(message, status: 0, code: nil, errors: nil, data: nil)
|
|
14
16
|
super(message)
|
|
15
17
|
@status = status
|
|
16
18
|
@code = code
|
|
17
19
|
@errors = errors
|
|
20
|
+
@data = data
|
|
18
21
|
end
|
|
19
22
|
end
|
|
20
23
|
|
data/lib/flexops/http.rb
CHANGED
|
@@ -117,7 +117,8 @@ module FlexOps
|
|
|
117
117
|
error_body["message"] || "HTTP #{status}: #{response.message}",
|
|
118
118
|
status: status,
|
|
119
119
|
code: error_body["errorCode"] || error_body["code"] || (status == 403 ? "FORBIDDEN" : nil),
|
|
120
|
-
errors: error_body["errors"]
|
|
120
|
+
errors: error_body["errors"],
|
|
121
|
+
data: error_body["data"]
|
|
121
122
|
)
|
|
122
123
|
|
|
123
124
|
raise error unless retryable?(method, status, error.code)
|
|
@@ -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,15 +16,30 @@ module FlexOps
|
|
|
14
16
|
@ws_id = ws_id_proc
|
|
15
17
|
end
|
|
16
18
|
|
|
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.
|
|
17
21
|
def list(page: nil, page_size: nil, start_date: nil, end_date: nil)
|
|
18
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
|
|
|
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)
|
|
36
|
+
end
|
|
37
|
+
|
|
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)}")
|
|
41
|
+
end
|
|
42
|
+
|
|
26
43
|
# Creates an RMA and returns the enveloped RMA (201).
|
|
27
44
|
#
|
|
28
45
|
# request:: { orderId: (a number), orderNumber: (optional), reason:,
|
|
@@ -60,6 +60,37 @@ module FlexOps
|
|
|
60
60
|
@http.post("/api/addresses/validate", body: to_validation_address(address))
|
|
61
61
|
end
|
|
62
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), maximumPostageAmount: 25.00,
|
|
68
|
+
# packages: [{ weightInOunces:, lengthInInches:, widthInInches:, heightInInches:,
|
|
69
|
+
# deliveryConfirmationRequired: (optional), signatureConfirmationRequired: (optional),
|
|
70
|
+
# insuranceCoverageRequested: (optional), insuredValue: (optional),
|
|
71
|
+
# packageReference: (optional) }] }
|
|
72
|
+
#
|
|
73
|
+
# maximumPostageAmount is required: the most the whole shipment may cost, in USD (positive, at most
|
|
74
|
+
# two decimals). The Gateway refuses to buy a box that would take the total above it, so you never
|
|
75
|
+
# pay more: a box priced above what is left reports PriceCeilingExceeded, and once the amount is
|
|
76
|
+
# used up the remaining boxes report PriceCeilingReached and are not bought. Boxes already bought
|
|
77
|
+
# stay bought. A missing or invalid value is 400 ApprovalRequired.
|
|
78
|
+
#
|
|
79
|
+
# Weight is in ounces and every dimension in inches; all four are required. Multi-package
|
|
80
|
+
# labels cannot carry HAZMAT yet: send HazmatClassification::NONE or leave hazmatClassification
|
|
81
|
+
# out (any other class is refused with 400 HazmatNotSupported).
|
|
82
|
+
#
|
|
83
|
+
# Returns the envelope when at least one label was bought (check data["failedPackages"] and
|
|
84
|
+
# each box in data["labels"]). When none was, the Gateway answers 422 AllPackagesFailed, or 409
|
|
85
|
+
# OutcomeUnknown when a box's carrier outcome is unknown and a label may exist: both raise
|
|
86
|
+
# FlexOps::Error whose data holds the per-box results. After OutcomeUnknown, check the order
|
|
87
|
+
# before buying again. Pass your own idempotency_key (and reuse it) to make a retry you run
|
|
88
|
+
# yourself safe.
|
|
89
|
+
def create_multi_package(request, idempotency_key: nil)
|
|
90
|
+
@http.post("/api/shipping/multi-package", body: request,
|
|
91
|
+
headers: idempotency_key.nil? ? {} : { "Idempotency-Key" => idempotency_key })
|
|
92
|
+
end
|
|
93
|
+
|
|
63
94
|
# The payload is sent as given. Set hazmatClassification on an item only when you have a declaration.
|
|
64
95
|
def create_batch(request)
|
|
65
96
|
@http.post("#{ws_path}/labels/batch", body: request)
|
data/lib/flexops/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: flexops
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- FlexOps, LLC
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|