duffel_api 0.0.1.pre.dev → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +82 -0
- data/.github/dependabot.yml +6 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +9 -9
- data/CHANGELOG.md +1 -3
- data/CODE_OF_CONDUCT.md +1 -1
- data/Gemfile +10 -2
- data/LICENSE.txt +1 -1
- data/README.md +190 -33
- data/Rakefile +1 -3
- data/bin/console +7 -0
- data/bin/setup +2 -0
- data/duffel_api.gemspec +40 -0
- data/examples/book_and_change.rb +106 -0
- data/examples/book_with_seat.rb +91 -0
- data/examples/exploring_data.rb +43 -0
- data/examples/hold_and_pay_later.rb +82 -0
- data/examples/search_and_book.rb +91 -0
- data/lib/duffel_api/api_response.rb +17 -0
- data/lib/duffel_api/api_service.rb +37 -0
- data/lib/duffel_api/client.rb +87 -0
- data/lib/duffel_api/errors/airline_error.rb +8 -0
- data/lib/duffel_api/errors/api_error.rb +8 -0
- data/lib/duffel_api/errors/authentication_error.rb +8 -0
- data/lib/duffel_api/errors/error.rb +54 -0
- data/lib/duffel_api/errors/invalid_request_error.rb +8 -0
- data/lib/duffel_api/errors/invalid_state_error.rb +8 -0
- data/lib/duffel_api/errors/rate_limit_error.rb +8 -0
- data/lib/duffel_api/errors/validation_error.rb +8 -0
- data/lib/duffel_api/list_response.rb +27 -0
- data/lib/duffel_api/middlewares/raise_duffel_errors.rb +67 -0
- data/lib/duffel_api/paginator.rb +27 -0
- data/lib/duffel_api/request.rb +64 -0
- data/lib/duffel_api/resources/aircraft.rb +26 -0
- data/lib/duffel_api/resources/airline.rb +26 -0
- data/lib/duffel_api/resources/airport.rb +40 -0
- data/lib/duffel_api/resources/base_resource.rb +16 -0
- data/lib/duffel_api/resources/offer.rb +60 -0
- data/lib/duffel_api/resources/offer_passenger.rb +28 -0
- data/lib/duffel_api/resources/offer_request.rb +34 -0
- data/lib/duffel_api/resources/order.rb +58 -0
- data/lib/duffel_api/resources/order_cancellation.rb +34 -0
- data/lib/duffel_api/resources/order_change.rb +46 -0
- data/lib/duffel_api/resources/order_change_offer.rb +46 -0
- data/lib/duffel_api/resources/order_change_request.rb +30 -0
- data/lib/duffel_api/resources/payment.rb +26 -0
- data/lib/duffel_api/resources/payment_intent.rb +52 -0
- data/lib/duffel_api/resources/refund.rb +42 -0
- data/lib/duffel_api/resources/seat_map.rb +24 -0
- data/lib/duffel_api/resources/webhook.rb +32 -0
- data/lib/duffel_api/response.rb +45 -0
- data/lib/duffel_api/services/aircraft_service.rb +36 -0
- data/lib/duffel_api/services/airlines_service.rb +36 -0
- data/lib/duffel_api/services/airports_service.rb +36 -0
- data/lib/duffel_api/services/base_service.rb +29 -0
- data/lib/duffel_api/services/offer_passengers_service.rb +30 -0
- data/lib/duffel_api/services/offer_requests_service.rb +67 -0
- data/lib/duffel_api/services/offers_service.rb +36 -0
- data/lib/duffel_api/services/order_cancellations_service.rb +75 -0
- data/lib/duffel_api/services/order_change_offers_service.rb +36 -0
- data/lib/duffel_api/services/order_change_requests_service.rb +37 -0
- data/lib/duffel_api/services/order_changes_service.rb +56 -0
- data/lib/duffel_api/services/orders_service.rb +74 -0
- data/lib/duffel_api/services/payment_intents_service.rb +56 -0
- data/lib/duffel_api/services/payments_service.rb +26 -0
- data/lib/duffel_api/services/refunds_service.rb +36 -0
- data/lib/duffel_api/services/seat_maps_service.rb +19 -0
- data/lib/duffel_api/services/webhooks_service.rb +83 -0
- data/lib/duffel_api/version.rb +1 -1
- data/lib/duffel_api.rb +51 -4
- metadata +90 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb4caaadd883236526bb2e8bdbc78b4f96f2264ae3a09f0c6b2d645eb0e63b4f
|
4
|
+
data.tar.gz: 81a190fbc71c4b5033647393634e0759fdc18513809c1f81fa80dc2ca2453a44
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70950a1db91a40ba7fbc1fe2b0b232f8553ab16c497225641ca13d4244374efc9c8b5d80174477e2ab8c3dedd085ea99abc71bdc2159c3af4083ebeab9ac35f6
|
7
|
+
data.tar.gz: 4686727e0c4e1d005713b44e684f91fa931f73d214a778fad5b2cb83724d5592b4d4aace1810f95d9c544d92d37a62eb903af9a4d2765d18ad9ad14a1a5d2fac
|
@@ -0,0 +1,82 @@
|
|
1
|
+
version: 2.1
|
2
|
+
|
3
|
+
commands:
|
4
|
+
setup_ruby:
|
5
|
+
steps:
|
6
|
+
- run:
|
7
|
+
name: Install bundler gem
|
8
|
+
command: gem install bundler --version '~> 2.2'
|
9
|
+
- run:
|
10
|
+
name: Install Ruby dependencies
|
11
|
+
command: bundle check || bundle install
|
12
|
+
|
13
|
+
executors:
|
14
|
+
ruby:
|
15
|
+
parameters:
|
16
|
+
image:
|
17
|
+
default: "cimg/ruby:3.0"
|
18
|
+
type: string
|
19
|
+
docker:
|
20
|
+
- image: << parameters.image >>
|
21
|
+
resource_class: small
|
22
|
+
|
23
|
+
orbs:
|
24
|
+
ruby: circleci/ruby@1.2
|
25
|
+
|
26
|
+
jobs:
|
27
|
+
test:
|
28
|
+
parameters:
|
29
|
+
ruby-image:
|
30
|
+
type: string
|
31
|
+
executor:
|
32
|
+
name: ruby
|
33
|
+
image: << parameters.ruby-image >>
|
34
|
+
steps:
|
35
|
+
- checkout
|
36
|
+
- setup_ruby
|
37
|
+
- ruby/rubocop-check
|
38
|
+
- ruby/rspec-test
|
39
|
+
coverage:
|
40
|
+
executor: ruby
|
41
|
+
environment:
|
42
|
+
COVERAGE: true
|
43
|
+
steps:
|
44
|
+
- checkout
|
45
|
+
- setup_ruby
|
46
|
+
- run:
|
47
|
+
name: Run tests with Simplecov enabled
|
48
|
+
command: bundle exec rspec spec
|
49
|
+
- store_artifacts:
|
50
|
+
path: coverage
|
51
|
+
code_examples:
|
52
|
+
executor: ruby
|
53
|
+
steps:
|
54
|
+
- checkout
|
55
|
+
- setup_ruby
|
56
|
+
- run:
|
57
|
+
name: Run "search and book" example
|
58
|
+
command: bundle exec ruby examples/search_and_book.rb
|
59
|
+
- run:
|
60
|
+
name: Run "hold and pay later" example
|
61
|
+
command: bundle exec ruby examples/hold_and_pay_later.rb
|
62
|
+
- run:
|
63
|
+
name: Run "exploring data" example
|
64
|
+
command: bundle exec ruby examples/exploring_data.rb
|
65
|
+
- run:
|
66
|
+
name: Run "book with seat" example
|
67
|
+
command: bundle exec ruby examples/book_with_seat.rb
|
68
|
+
- run:
|
69
|
+
name: Run "book and change" example
|
70
|
+
command: bundle exec ruby examples/book_and_change.rb
|
71
|
+
workflows:
|
72
|
+
version: 2
|
73
|
+
build_and_test:
|
74
|
+
jobs:
|
75
|
+
- test:
|
76
|
+
matrix:
|
77
|
+
parameters:
|
78
|
+
ruby-image: ["cimg/ruby:2.6", "cimg/ruby:2.7", "cimg/ruby:3.0", "cimg/ruby:3.1"]
|
79
|
+
- coverage
|
80
|
+
- code_examples:
|
81
|
+
requires:
|
82
|
+
- test
|
data/.gitignore
ADDED
data/.rubocop.yml
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
+
require: rubocop-rake
|
2
|
+
|
3
|
+
inherit_gem:
|
4
|
+
gc_ruboconfig: rubocop.yml
|
1
5
|
AllCops:
|
2
6
|
TargetRubyVersion: 2.6
|
7
|
+
NewCops: enable
|
3
8
|
|
4
|
-
|
5
|
-
Enabled:
|
6
|
-
EnforcedStyle: double_quotes
|
7
|
-
|
8
|
-
Style/StringLiteralsInInterpolation:
|
9
|
-
Enabled: true
|
10
|
-
EnforcedStyle: double_quotes
|
9
|
+
RSpec/ExampleLength:
|
10
|
+
Enabled: false
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
RSpec/MultipleExpectations:
|
13
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
data/CODE_OF_CONDUCT.md
CHANGED
@@ -39,7 +39,7 @@ This Code of Conduct applies within all community spaces, and also applies when
|
|
39
39
|
|
40
40
|
## Enforcement
|
41
41
|
|
42
|
-
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at tim@duffel.com. All complaints will be reviewed and investigated promptly and fairly.
|
43
43
|
|
44
44
|
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
45
|
|
data/Gemfile
CHANGED
@@ -2,10 +2,18 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
+
# Specify your gem's dependencies in duffel_api.gemspec
|
5
6
|
gemspec
|
6
7
|
|
7
8
|
group :development, :test do
|
9
|
+
gem "gc_ruboconfig", "~> 2.29.0"
|
10
|
+
gem "pry", "~> 0.14.1"
|
8
11
|
gem "rake", "~> 13.0"
|
9
|
-
gem "rspec", "~> 3.0"
|
10
|
-
gem "
|
12
|
+
gem "rspec", "~> 3.10.0"
|
13
|
+
gem "rspec-its", "~> 1.3.0"
|
14
|
+
gem "rspec_junit_formatter", "~> 0.4.1"
|
15
|
+
gem "rubocop", "~> 1.24.0"
|
16
|
+
gem "rubocop-rake", "~> 0.6.0"
|
17
|
+
gem "simplecov", "~> 0.21.2"
|
18
|
+
gem "webmock", "~> 3.14.0"
|
11
19
|
end
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,55 +1,212 @@
|
|
1
|
-
#
|
1
|
+
# Duffel API Ruby client library
|
2
2
|
|
3
|
-
A Ruby client library for the [Duffel API](https://duffel.com/docs/api)
|
4
|
-
|
3
|
+
A Ruby client library for the [Duffel API](https://duffel.com/docs/api).
|
4
|
+
|
5
|
+
## Contents
|
6
|
+
|
7
|
+
- [Requirements](#requirements)
|
8
|
+
- [Installation](#installation)
|
9
|
+
- [Usage](#usage)
|
10
|
+
|
11
|
+
## Requirements
|
12
|
+
|
13
|
+
* Ruby 2.6 or later
|
14
|
+
* A Duffel API access token (get started [here](https://duffel.com/docs/guides/quick-start) ✨)
|
5
15
|
|
6
16
|
## Installation
|
7
17
|
|
8
|
-
|
18
|
+
In most cases, you'll want to add `duffel_api` to your project as a dependency by listing it in your `Gemfile`, and then running `bundle`:
|
9
19
|
|
10
20
|
```ruby
|
11
|
-
gem
|
21
|
+
gem "duffel_api", "~> 0.1.0"
|
12
22
|
```
|
13
23
|
|
14
|
-
|
24
|
+
You can install `duffel_api` outside of the context of a project by running `gem install duffel_api` - for example if you want to play with the client library in `irb`.
|
25
|
+
|
26
|
+
## Usage
|
15
27
|
|
16
|
-
|
28
|
+
You can see a complete end-to-end example of searching and booking using the client library in [`example/search_and_book.rb`](https://github.com/duffelhq/duffel-api-ruby/blob/main/examples/search_and_book.rb).
|
17
29
|
|
18
|
-
|
30
|
+
### Initialising the client
|
19
31
|
|
20
|
-
|
32
|
+
All of the library's functionality is accessed from a `DuffelAPI::Client` instance.
|
21
33
|
|
22
|
-
|
34
|
+
To initialise a `DuffelAPI::Client`, all you'll need is your API access token:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require "duffel_api"
|
38
|
+
|
39
|
+
client = DuffelAPI::Client.new(access_token: "duffel_test_000000000")
|
40
|
+
```
|
41
|
+
|
42
|
+
### Resources in the Duffel API
|
43
|
+
|
44
|
+
In this readme, we'll use the term "resources" to refer to the different Duffel concepts that you can *act on* in the API - for example airports, offers, orders and payment intents.
|
45
|
+
|
46
|
+
We'll refer to instances of each of these resources - an airport, an offer, a payment intent - as "records".
|
47
|
+
|
48
|
+
In the [Duffel API reference](https://duffel.com/docs/api/), the resources are listed in the sidebar. For each resource, you'll find:
|
49
|
+
|
50
|
+
* a schema, which describes the data attributes we expose for each record from this resource
|
51
|
+
* a list of actions you can perform related to that resource (e.g. for [Orders](https://duffel.com/docs/api/orders), you can "Get a single order", "Update a single order", "List orders" and "Create an order")
|
52
|
+
|
53
|
+
The Ruby client library is structured around these resources. Each resource has its own "service" which you use to perform actions. These services are accessible from your `Client` instance:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
client.orders
|
57
|
+
client.offers
|
58
|
+
client.payment_intents
|
59
|
+
```
|
60
|
+
|
61
|
+
__To see what actions are available for each resource, check out the definitions for the service classes [here](https://github.com/duffelhq/duffel-api-ruby/tree/main/lib/duffel_api/services).__
|
62
|
+
|
63
|
+
### Creating a record
|
64
|
+
|
65
|
+
Most resources allow you to create a record. In fact, the most important flows in the Duffel API start with creating a record. You'll do this with the `#create` method exposed on a service.
|
66
|
+
|
67
|
+
For example, you'll search for flights by creating an offer request:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
offer_request = client.offer_requests.create(params: {
|
71
|
+
cabin_class: "economy",
|
72
|
+
passengers: [{
|
73
|
+
age: 28
|
74
|
+
}],
|
75
|
+
slices: [{
|
76
|
+
origin: "LHR",
|
77
|
+
destination: "NYC",
|
78
|
+
departure_date: "2022-12-31"
|
79
|
+
}],
|
80
|
+
# This attribute is sent as a query parameter rather than in the body like the others.
|
81
|
+
# Worry not! The library handles this complexity for you.
|
82
|
+
return_offers: false
|
83
|
+
})
|
84
|
+
|
85
|
+
puts "You've created an offer request, #{offer_request.id}."
|
86
|
+
```
|
87
|
+
|
88
|
+
The `#create` method returns the created record.
|
89
|
+
|
90
|
+
### Listing records
|
91
|
+
|
92
|
+
Many resources in the Duffel API (e.g. airports, orders and offers) allow you to list their records.
|
93
|
+
|
94
|
+
For example, you can get a list of all the airports that Duffel knows about - that is, a list of airport records.
|
95
|
+
|
96
|
+
For performance reasons, we [paginate](https://duffel.com/docs/api/overview/pagination) records in the API when listing. You can only see up to 200 at a time. You'll need to page through, like moving through pages of a book.
|
97
|
+
|
98
|
+
This is quite fiddly, so the client library does it for you in the `#all` method exposed by relevant services. All you have to do is something like this:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
client.offer_requests.all.each do |offer_request|
|
102
|
+
puts "Loaded offer request #{order_request.id}"
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
Sometimes, you'll want to specify filters or sort orders when listing,like this:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
# The filters you can use for a given resource are documented in the API Reference
|
110
|
+
client.offers.
|
111
|
+
all(params: { offer_request_id: "ofr_123", sort: "total_amount" }).
|
112
|
+
each do |order|
|
113
|
+
puts "Loaded order #{order.id}"
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
A call to `#all` returns a Ruby [`Enumerator`](https://ruby-doc.org/core-2.6/Enumerator.html), which behaves a lot like an array - you can get the number of records with `#length`, loop through it with `#each`, etc.
|
118
|
+
|
119
|
+
If you prefer, you can also page through records manually using a service's `#list` method (e.g. `client.orders.list`) which returns a `DuffelAPI::ListResponse`.
|
120
|
+
|
121
|
+
The records in the page are returned by `#records` (`client.orders.list.records`) and the cursor for the next page (if there is one) can be found with `#after` (`client.orders.list.after`)
|
122
|
+
|
123
|
+
#### An exception: seat maps
|
124
|
+
|
125
|
+
Watch out! There is one kind of list in the Duffel API which isn't paginated: seat maps.
|
126
|
+
|
127
|
+
When you call `client.seat_maps.list(params: { offer_id: "off_123" })`, all of the seat maps will be returned at once in a single `ListResponse`.
|
128
|
+
|
129
|
+
### Fetching single records
|
130
|
+
|
131
|
+
Many resources in the Duffel API allow you fetch a single record (e.g. *an* airport, *a* payment intent) if you know its ID.
|
132
|
+
|
133
|
+
You do that using the `#get` method on a resource like this:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
order = client.orders.get("ord_123")
|
137
|
+
puts "Your booking reference is #{order.booking_reference}."
|
138
|
+
```
|
139
|
+
|
140
|
+
The `#get` method returns the record.
|
141
|
+
|
142
|
+
### Updating a record
|
143
|
+
|
144
|
+
Some records in the Duffel API allow you to update them after they've been created, if you know their ID.
|
145
|
+
|
146
|
+
That works like this using the `#update` method on a resource:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
client.webhooks.update("sev_0000AEdmUJKCvFK45qMFBg", params: {
|
150
|
+
active: false
|
151
|
+
})
|
152
|
+
```
|
153
|
+
|
154
|
+
The `#update` method returns the updated record.
|
155
|
+
|
156
|
+
### Performing an action on a record
|
157
|
+
|
158
|
+
Some resources allow you to perform special actions on their records - for example confirming an order cancellation or pinging a webhook.
|
159
|
+
|
160
|
+
The methods you'll use to do this aren't named consistently, because each resource has different actions. For example, you'll call `#confirm` to confirm an order cancellation but `#ping` to ping a webhook.
|
161
|
+
|
162
|
+
It'll look a bit like this:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
client.order_cancellations.confirm("ore_0000AEUvjGoJlav2j6FDlZ")
|
166
|
+
```
|
167
|
+
|
168
|
+
Sometimes, you'll need to pass extra data when performing the action. That works like this:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
client.order_changes.confirm("oce_0000AEdlOBVlABkDhgsUqW", params: {
|
172
|
+
payment: {
|
173
|
+
type: "balance",
|
174
|
+
currency: "GBP",
|
175
|
+
amount: "125.00",
|
176
|
+
}
|
177
|
+
})
|
178
|
+
```
|
179
|
+
|
180
|
+
In general, these action methods return the record you've acted on.
|
181
|
+
|
182
|
+
#### An exception: pinging a webhook
|
183
|
+
|
184
|
+
Watch out! There is one action in the API which doesn't return the record you've acted on.
|
185
|
+
|
186
|
+
When you ping a webhook with `client.webhooks.ping("sev_0000AEdmUJKCvFK45qMFBg")`, it'll return a `DuffelAPI::Services::WebhooksService::PingResult` if successful, or otherwise it'll raise an error.
|
187
|
+
|
188
|
+
### Handling errors
|
189
|
+
|
190
|
+
When the Duffel API returns an error, the library will raise an exception.
|
191
|
+
|
192
|
+
We have an exception class for each of the possible `type`s of error which the API can return, documented [here](https://duffel.com/docs/api/overview/errors) in the API reference. For example, if the API returns an error with `type` `invalid_state_error`, the library will raise a `DuffelAPI::Errors::InvalidStateError` exception.
|
23
193
|
|
24
|
-
|
194
|
+
You can find all of those error classes [here](https://github.com/duffelhq/duffel-api-ruby/tree/main/lib/duffel_api/errors).
|
25
195
|
|
26
|
-
|
196
|
+
You can rescue all of these errors and get important information with them using instances methods: `#message`, `#title`, `#code`, `#request_id`, etc.
|
27
197
|
|
28
|
-
|
29
|
-
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
30
|
-
prompt that will allow you to experiment.
|
198
|
+
If the client library is unable to connect to Duffel, an appropriate exception will be raised, for example:
|
31
199
|
|
32
|
-
|
33
|
-
|
34
|
-
`
|
35
|
-
git commits and the created tag, and push the `.gem` file to
|
36
|
-
[rubygems.org](https://rubygems.org).
|
200
|
+
* `Faraday::TimeoutError` in case of a timeout
|
201
|
+
* `Faraday::ConnectionFailed` in case of a connection issue (e.g. problems with DNS resolution)
|
202
|
+
* `DuffelAPI::Errors::Error` for `5XX` errors returned from by Duffel's infrastructure, but not by the API itself (e.g. a load balancer)
|
37
203
|
|
38
|
-
|
204
|
+
### Accessing the raw API response
|
39
205
|
|
40
|
-
|
41
|
-
https://github.com/duffelhq/duffel-api-ruby. This project is intended to be a safe,
|
42
|
-
welcoming space for collaboration, and contributors are expected to adhere to
|
43
|
-
the [code of
|
44
|
-
conduct](https://github.com/sgerrand/duffel_api/blob/main/CODE_OF_CONDUCT.md).
|
206
|
+
Sometimes, you might want to get lower-level details about the response you received from the Duffel API - for example the raw body or headers.
|
45
207
|
|
46
|
-
|
208
|
+
If an error has been raised, you can call `#api_response` on the exception, which returns a `DuffelAPI::APIResponse`. If you're looking at a `ListResponse` or any resource, you can call `#api_response` on that.
|
47
209
|
|
48
|
-
|
49
|
-
License](https://opensource.org/licenses/MIT).
|
210
|
+
From the `APIResponse`, you can call `#headers`, `#status_code`, `#raw_body`, `#parsed_body`, `#meta` or `#request_id` to get key information from the response.
|
50
211
|
|
51
|
-
## Code of Conduct
|
52
212
|
|
53
|
-
Everyone interacting in the Ruby Duffel API project's codebases, issue trackers,
|
54
|
-
chat rooms and mailing lists is expected to follow the [code of
|
55
|
-
conduct](https://github.com/duffelhq/duffel-api-ruby/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "bundler/gem_tasks"
|
4
|
+
require "rubocop/rake_task"
|
4
5
|
require "rspec/core/rake_task"
|
5
6
|
|
6
7
|
RSpec::Core::RakeTask.new(:spec)
|
7
|
-
|
8
|
-
require "rubocop/rake_task"
|
9
|
-
|
10
8
|
RuboCop::RakeTask.new
|
11
9
|
|
12
10
|
task default: %i[spec rubocop]
|
data/bin/console
CHANGED
@@ -4,5 +4,12 @@
|
|
4
4
|
require "bundler/setup"
|
5
5
|
require "duffel_api"
|
6
6
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
7
14
|
require "irb"
|
8
15
|
IRB.start(__FILE__)
|
data/bin/setup
CHANGED
data/duffel_api.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/duffel_api/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "duffel_api"
|
7
|
+
spec.version = DuffelAPI::VERSION
|
8
|
+
spec.authors = ["The Duffel team"]
|
9
|
+
spec.email = ["help@duffel.com"]
|
10
|
+
|
11
|
+
spec.summary = "A Ruby client for interacting with the Duffel API"
|
12
|
+
spec.homepage = "https://github.com/duffelhq/duffel-api-ruby"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 2.6.0"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/duffelhq/duffel-api-ruby/blob/main/CHANGELOG.md"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_dependency "faraday", [">= 0.9.2", "< 2"]
|
31
|
+
|
32
|
+
# Uncomment to register a new dependency of your gem
|
33
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
34
|
+
|
35
|
+
# For more information and examples about making a new gem, checkout our
|
36
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
37
|
+
spec.metadata = {
|
38
|
+
"rubygems_mfa_required" => "true",
|
39
|
+
}
|
40
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "duffel_api"
|
4
|
+
|
5
|
+
client = DuffelAPI::Client.new(
|
6
|
+
access_token: ENV["DUFFEL_ACCESS_TOKEN"],
|
7
|
+
)
|
8
|
+
|
9
|
+
# 365 days from now
|
10
|
+
departure_date = (Time.now + (60 * 60 * 24 * 365)).strftime("%Y-%m-%d")
|
11
|
+
|
12
|
+
offer_request = client.offer_requests.create(params: {
|
13
|
+
cabin_class: "economy",
|
14
|
+
passengers: [{
|
15
|
+
age: 28,
|
16
|
+
}],
|
17
|
+
slices: [{
|
18
|
+
# We use a non-sensical route to make sure we get speedy, reliable Duffel Airways
|
19
|
+
# results.
|
20
|
+
origin: "LHR",
|
21
|
+
destination: "STN",
|
22
|
+
departure_date: departure_date,
|
23
|
+
}],
|
24
|
+
# This attribute is sent as a query parameter rather than in the body like the others.
|
25
|
+
# Worry not! The library handles this complexity for you.
|
26
|
+
return_offers: false,
|
27
|
+
})
|
28
|
+
|
29
|
+
puts "Created offer request: #{offer_request.id}"
|
30
|
+
|
31
|
+
offers = client.offers.all(params: { offer_request_id: offer_request.id })
|
32
|
+
|
33
|
+
puts "Got #{offers.count} offers"
|
34
|
+
|
35
|
+
selected_offer = offers.first
|
36
|
+
|
37
|
+
puts "Selected offer #{selected_offer.id} to book"
|
38
|
+
|
39
|
+
priced_offer = client.offers.get(selected_offer.id)
|
40
|
+
|
41
|
+
puts "The final price for offer #{priced_offer.id} is #{priced_offer.total_amount} " \
|
42
|
+
"#{priced_offer.total_currency}"
|
43
|
+
|
44
|
+
order = client.orders.create(params: {
|
45
|
+
selected_offers: [priced_offer.id],
|
46
|
+
payments: [
|
47
|
+
{
|
48
|
+
type: "balance",
|
49
|
+
amount: priced_offer.total_amount,
|
50
|
+
currency: priced_offer.total_currency,
|
51
|
+
},
|
52
|
+
],
|
53
|
+
passengers: [
|
54
|
+
{
|
55
|
+
id: priced_offer.passengers.first["id"],
|
56
|
+
title: "mr",
|
57
|
+
gender: "m",
|
58
|
+
given_name: "Tim",
|
59
|
+
family_name: "Rogers",
|
60
|
+
born_on: "1993-04-01",
|
61
|
+
phone_number: "+441290211999",
|
62
|
+
email: "tim@duffel.com",
|
63
|
+
},
|
64
|
+
],
|
65
|
+
})
|
66
|
+
|
67
|
+
puts "Created order #{order.id} with booking reference #{order.booking_reference}"
|
68
|
+
|
69
|
+
order_change_request = client.order_change_requests.create(params: {
|
70
|
+
order_id: order.id,
|
71
|
+
slices: {
|
72
|
+
add: [{
|
73
|
+
cabin_class: "economy",
|
74
|
+
departure_date: "2022-12-25",
|
75
|
+
origin: "LHR",
|
76
|
+
destination: "STN",
|
77
|
+
}],
|
78
|
+
remove: [{
|
79
|
+
slice_id: order.slices.first["id"],
|
80
|
+
}],
|
81
|
+
},
|
82
|
+
})
|
83
|
+
|
84
|
+
order_change_offers = client.order_change_offers.
|
85
|
+
all(params: { order_change_request_id: order_change_request.id })
|
86
|
+
|
87
|
+
puts "Got #{order_change_offers.count} options for changing the order; picking first " \
|
88
|
+
"option"
|
89
|
+
|
90
|
+
order_change = client.order_changes.create(params: {
|
91
|
+
selected_order_change_offer: order_change_offers.first.id,
|
92
|
+
order_id: order.id,
|
93
|
+
})
|
94
|
+
|
95
|
+
puts "Created order change #{order_change.id}, confirming..."
|
96
|
+
|
97
|
+
client.order_changes.confirm(order_change.id, params: {
|
98
|
+
payment: {
|
99
|
+
type: "balance",
|
100
|
+
amount: order_change.change_total_amount,
|
101
|
+
currency: order_change.change_total_currency,
|
102
|
+
},
|
103
|
+
})
|
104
|
+
|
105
|
+
puts "Processed change to order #{order.id} costing " \
|
106
|
+
"#{order_change.change_total_amount} #{order_change.change_total_currency}"
|