parcel_api 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 796b5726fcf645a3a08bc3f3604571543204838a
4
+ data.tar.gz: 0aa5d2dec24519e44ac578819d6604a3d86b31df
5
+ SHA512:
6
+ metadata.gz: a1719f6f35ac57609e2f3e142e82a0ff3e42f43bc29e9fcb40e935e00052e752995afbc19019426c52d3f77bd4b6de3522cad0159068c428e3314ea37cd56682
7
+ data.tar.gz: 214072e0c14a216fae2947d0faaecf01c1ab4b4c272f1c96c5f8565a13c5cbb6f0f3ad4d02bd8804241e7a97a374ad0edb23f1fc80ec3b97ff438aa9e765f900
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .env
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
4
+ - 2.1.0
5
+ - 2.0.0
6
+ addons:
7
+ code_climate:
8
+ repo_token: 71a1f8fa6b8f4bd0e13542f237fd8f060d4dab088c71e5f980ed6247197d1820
data/.yardopts ADDED
@@ -0,0 +1,7 @@
1
+ --readme README.md
2
+ --markup markdown
3
+ --default-return ""
4
+ --hide-void-return
5
+ --protected
6
+ --no-private
7
+ --no-highlight
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in parcel_api.gemspec
4
+ gemspec
5
+
6
+ gem 'codeclimate-test-reporter', group: :test, require: nil
data/Gemfile.lock ADDED
@@ -0,0 +1,83 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ parcel_api (0.2.0)
5
+ faraday (~> 0.9)
6
+ faraday_middleware (~> 0.9)
7
+ oauth2 (~> 1.0)
8
+ recursive-open-struct (~> 0.6)
9
+ thor (~> 0.19)
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ addressable (2.3.8)
15
+ codeclimate-test-reporter (0.4.7)
16
+ simplecov (>= 0.7.1, < 1.0.0)
17
+ coderay (1.1.0)
18
+ crack (0.4.2)
19
+ safe_yaml (~> 1.0.0)
20
+ diff-lcs (1.2.5)
21
+ docile (1.1.5)
22
+ faraday (0.9.1)
23
+ multipart-post (>= 1.2, < 3)
24
+ faraday_middleware (0.9.1)
25
+ faraday (>= 0.7.4, < 0.10)
26
+ jwt (1.5.0)
27
+ method_source (0.8.2)
28
+ multi_json (1.11.0)
29
+ multi_xml (0.5.5)
30
+ multipart-post (2.0.0)
31
+ oauth2 (1.0.0)
32
+ faraday (>= 0.8, < 0.10)
33
+ jwt (~> 1.0)
34
+ multi_json (~> 1.3)
35
+ multi_xml (~> 0.5)
36
+ rack (~> 1.2)
37
+ pry (0.10.1)
38
+ coderay (~> 1.1.0)
39
+ method_source (~> 0.8.1)
40
+ slop (~> 3.4)
41
+ rack (1.6.4)
42
+ rake (10.4.2)
43
+ recursive-open-struct (0.6.4)
44
+ rspec (3.2.0)
45
+ rspec-core (~> 3.2.0)
46
+ rspec-expectations (~> 3.2.0)
47
+ rspec-mocks (~> 3.2.0)
48
+ rspec-core (3.2.1)
49
+ rspec-support (~> 3.2.0)
50
+ rspec-expectations (3.2.0)
51
+ diff-lcs (>= 1.2.0, < 2.0)
52
+ rspec-support (~> 3.2.0)
53
+ rspec-mocks (3.2.1)
54
+ diff-lcs (>= 1.2.0, < 2.0)
55
+ rspec-support (~> 3.2.0)
56
+ rspec-support (3.2.2)
57
+ safe_yaml (1.0.4)
58
+ simplecov (0.9.2)
59
+ docile (~> 1.1.0)
60
+ multi_json (~> 1.0)
61
+ simplecov-html (~> 0.9.0)
62
+ simplecov-html (0.9.0)
63
+ slop (3.6.0)
64
+ thor (0.19.1)
65
+ vcr (2.9.3)
66
+ webmock (1.21.0)
67
+ addressable (>= 2.3.6)
68
+ crack (>= 0.3.2)
69
+ yard (0.8.7.6)
70
+
71
+ PLATFORMS
72
+ ruby
73
+
74
+ DEPENDENCIES
75
+ bundler (~> 1.8)
76
+ codeclimate-test-reporter
77
+ parcel_api!
78
+ pry (~> 0.10)
79
+ rake (~> 10.0)
80
+ rspec (~> 3.2)
81
+ vcr (~> 2.9)
82
+ webmock (~> 1.21)
83
+ yard (~> 0.8)
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Robert Coleman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # ParcelApi
2
+
3
+ [![RubyGem](https://badge.fury.io/rb/parcel_api.svg)](https://rubygems.org/gems/parcel_api)[![Build Status](https://magnum.travis-ci.com/etailer/parcel_api.svg?token=hCq9S5vXdep6iBazZLuu)](https://magnum.travis-ci.com/etailer/parcel_api) [![Code Climate](https://codeclimate.com/repos/552dc72e69568025e8001d73/badges/d0ccddbcdb28ce0d2834/gpa.svg)](https://codeclimate.com/repos/552dc72e69568025e8001d73/feed) [![Test Coverage](https://codeclimate.com/repos/552dc72e69568025e8001d73/badges/d0ccddbcdb28ce0d2834/coverage.svg)](https://codeclimate.com/repos/552dc72e69568025e8001d73/feed)
4
+
5
+ Ruby wrapper for [NZ Post's Shipping APIs](https://www.nzpost.co.nz/developer-centre#parcel).
6
+
7
+ You must be a registered user of these APIs to use this gem.
8
+
9
+ __Features__
10
+
11
+ * ParcelAddress
12
+ * ShippingOptions
13
+ * ParcelPickUp
14
+ * ParcelLabel
15
+ * ParcelTrack
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'parcel_api'
23
+ ```
24
+
25
+ And then execute:
26
+
27
+ `$ bundle`
28
+
29
+ Or install it yourself as:
30
+
31
+ `$ gem install parcel_api`
32
+
33
+ ## Usage
34
+
35
+ ### Configuration Options
36
+
37
+ By default credentials are taken from the local environment, for this to work the following ENV variables must be set:
38
+
39
+ * `ENV['CLIENT_ID']`
40
+ * `ENV['CLIENT_SECRET']`
41
+ * `ENV['USERNAME']`
42
+ * `ENV['PASSWORD']`
43
+
44
+ You can also configure credentials via `ParcelApi::Client.new`:
45
+
46
+ ```ruby
47
+ client = ParcelApi::Client.new.tap do |config|
48
+ config.client_id = ENV['CLIENT_ID']
49
+ config.client_secret = ENV['CLIENT_SECRET']
50
+ config.username = ENV['USERNAME']
51
+ config.password = ENV['PASSWORD']
52
+ config.address = 'https://api.uat.nzpost.co.nz/' # defaults to api.nzpost.co.nz
53
+ end
54
+ ```
55
+
56
+ Client connections can be passed to each method:
57
+
58
+ `ParcelApi::Address(client.connection)`
59
+
60
+ ## Documentation
61
+
62
+ Documentation is available [here](http://www.rubydoc.info/github/etailer/parcel_api)
63
+
64
+ Some usage examples are also available [here](example/mock.rb)
65
+
66
+
67
+ ## Development
68
+
69
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
70
+
71
+
72
+ ## Contributing
73
+
74
+ 1. Fork it ( https://github.com/etailer/parcel_api/fork )
75
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
76
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
77
+ 4. Push to the branch (`git push origin my-new-feature`)
78
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
9
+ rescue LoadError
10
+ # no rspec available
11
+ end
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'parcel_api'
5
+
6
+ require 'pry'
7
+ Pry.start
data/bin/parcel ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'parcel_api/cli'
5
+
6
+ ParcelApi::CLI.start(ARGV)
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/example/mock.rb ADDED
@@ -0,0 +1,267 @@
1
+ require 'parcel_api'
2
+
3
+ # configure the client
4
+ client = ParcelApi::Client.new.tap do |config|
5
+ config.client_id = ENV['CLIENT_ID']
6
+ config.client_secret = ENV['CLIENT_SECRET']
7
+ config.username = ENV['USERNAME']
8
+ config.password = ENV['PASSWORD']
9
+ config.address = 'https://api.uat.nzpost.co.nz/'
10
+ end
11
+
12
+
13
+ # Address examples
14
+
15
+ query = '151 vic'
16
+ address = ParcelApi::Address.new
17
+ # search for the query, 3 results
18
+ results = address.search(query, 3)
19
+ puts "Address Search: #{query}"
20
+ puts "Results:"
21
+ # dump the results out
22
+ puts results.map {|r| r.full_address}
23
+
24
+ # get details for the first result
25
+ puts 'First Address Details:'
26
+ details = address.details(results.first.address_id)
27
+
28
+ # dump the detail parts out
29
+ details.to_h.each {|k, v| puts "#{k}: #{v}"}
30
+
31
+ intl_query = '38 Lap'
32
+ # search for the query, defult number of results
33
+ intl_results = address.international_search(intl_query)
34
+ puts "International Address Search: #{query}"
35
+ puts "Results:"
36
+ puts intl_results.map {|r| r.full_address}
37
+
38
+ # get details for the first result
39
+ puts 'First International Address Details:'
40
+ intl_details = address.international_details(intl_results.first.address_id)
41
+
42
+ # dump the detail parts out
43
+ intl_details.to_h.each {|k, v| puts "#{k}: #{v}"}
44
+
45
+
46
+ # Tracking Example
47
+
48
+ tracking = ParcelApi::Track.new
49
+ results = tracking.details('1818120002213401AKL003HN')
50
+
51
+ # example tracking output
52
+ last_event = results.tracking_events.last
53
+ puts results.carrier
54
+ puts results.service
55
+ puts last_event.event_datetime.to_s + ' ' + last_event.event_description
56
+
57
+
58
+ # Labelling example
59
+
60
+ labeller = ParcelApi::Label.new
61
+
62
+ # Label options
63
+ cp_label_options = {
64
+ orientation: 'landscape',
65
+ requests: [
66
+ {
67
+ carrier: 'COURIERPOST',
68
+ sender_details: {
69
+ name: 'Glenn Dodd',
70
+ phone: '0274123456',
71
+ email: 'glenn@example.co.nz',
72
+ reference: '654334',
73
+ },
74
+ pickup_address: {
75
+ company: 'Glenns Acme Company',
76
+ city: 'Auckland',
77
+ floor: 'Floor 2',
78
+ postcode: '2102',
79
+ street: '25 Buller Crescent',
80
+ suburb: 'Manurewa',
81
+ unit_type: 'Flat',
82
+ unit_value: '2',
83
+ },
84
+ receiver_details: {
85
+ name: 'Glenn Dodd',
86
+ phone: '0274123456',
87
+ },
88
+ delivery_address: {
89
+ company: 'Acme Company',
90
+ city: 'Auckland',
91
+ floor: 'Floor 2',
92
+ postcode: '2102',
93
+ street: '151 Victoria Street',
94
+ suburb: 'Manurewa',
95
+ unit_type: 'Flat',
96
+ unit_value: '2',
97
+ },
98
+ return_indicator: 'NORMAL',
99
+ delivery_instructions: 'Dont feed my dog tonight',
100
+ service_code: 'CPOLE',
101
+ add_ons: [],
102
+ dimensions: {
103
+ weight: '10',
104
+ height: '35',
105
+ width: '45',
106
+ length: '15',
107
+ }
108
+ }
109
+ ]
110
+ }
111
+
112
+ # create the label
113
+ cp = labeller.create(cp_label_options)
114
+
115
+ # get the label details
116
+ details = labeller.details(cp.label_id)
117
+
118
+ # Print the tracking references
119
+ details.tracking_reference.map {|tr| puts tr}
120
+ # download the ticket
121
+ cp_ticket = labeller.download(cp.label_id)
122
+
123
+ # Write the ticket out
124
+ File.open("#{cp.label_id}.pdf", 'w') do |f|
125
+ f.puts(cp_ticket.read)
126
+ end
127
+
128
+ # International Labelling Example
129
+
130
+ intl_labeller = ParcelApi::Label.new
131
+
132
+ # Label options
133
+ intl_label_options = {
134
+ requests: [
135
+ {
136
+ sender_details: {
137
+ name: 'The sender',
138
+ phone: '091428774',
139
+ email: 'the_sender@mail.com',
140
+ reference: 'Glqddd 008',
141
+ signatory: 'Mary Jones',
142
+ customs_code: 'HGD34373',
143
+ },
144
+ pickup_address: {
145
+ building: '',
146
+ company: 'Express Courier Limited',
147
+ street: '151 Victoria Street West',
148
+ suburb: 'Auckland Central',
149
+ city: 'Auckland',
150
+ country_code: 'NZ',
151
+ postcode: '1070',
152
+ },
153
+ receiver_details: {
154
+ name: 'Tom Smith',
155
+ phone: '051236547',
156
+ vat_number: 'GB123123123123',
157
+ },
158
+ delivery_address: {
159
+ building: '',
160
+ company: 'American Express',
161
+ street: '23 Broadway Street',
162
+ suburb: 'Queens',
163
+ city: 'New York',
164
+ postcode: '4414',
165
+ country_code: 'US',
166
+ state: 'New York',
167
+ },
168
+ delivery_instructions: 'Leave at door',
169
+ dimensions: {
170
+ length: 600,
171
+ width: 100,
172
+ height: 200
173
+ },
174
+ undeliverable_instructions: 'RETURN',
175
+ contents: [
176
+ {
177
+ unit_description: 'Light bulbs',
178
+ unit_count: 2,
179
+ unit_value: 5.1,
180
+ unit_weight: 0.5,
181
+ },
182
+ {
183
+ unit_description: 'Lego',
184
+ unit_count: 10,
185
+ unit_value: 6.1,
186
+ unit_weight: 0.6,
187
+ },
188
+ {
189
+ unit_description: 'Hot wheels',
190
+ unit_count: 13,
191
+ unit_value: 5.4,
192
+ unit_weight: 0.8,
193
+ }
194
+ ],
195
+ service_code: 'TIEX',
196
+ indicia_number: '123456',
197
+ insurance_required: true,
198
+ contains_only_documents: true,
199
+ export_type: 'Gift',
200
+ harmonised_system_tariff: '12121212'
201
+ }
202
+ ]
203
+ }
204
+
205
+ # create the label
206
+ intl = intl_labeller.international_create(intl_label_options)
207
+
208
+ # get the label details
209
+ intl_details = intl_labeller.details(intl.label_id)
210
+
211
+ # Print the tracking references
212
+ intl_details.tracking_reference.map {|tr| puts tr}
213
+ # download the ticket
214
+ intl_ticket = intl_labeller.download(intl.label_id)
215
+
216
+ # Write the ticket out
217
+ File.open("#{intl.label_id}.pdf", 'w') do |f|
218
+ f.puts(intl_ticket.read)
219
+ end
220
+
221
+
222
+ ShippingOptions Example
223
+
224
+ shipping_options = ParcelApi::ShippingOptions.new
225
+
226
+ params = {
227
+ weight: 10,
228
+ length: 10,
229
+ width: 10,
230
+ height: 10,
231
+ pickup_address_id: 990003,
232
+ delivery_dpid: 2727895,
233
+ }
234
+
235
+ domestic_options = shipping_options.get_domestic(params)
236
+ domestic_options.to_h.each {|k, v| puts "#{k}: #{v}"}
237
+
238
+ intl_params = {
239
+ value: 100,
240
+ length: 10,
241
+ height: 10,
242
+ width: 10,
243
+ weight: 13,
244
+ country_code: 'AU',
245
+ }
246
+
247
+ intl_options = shipping_options.get_international(intl_params)
248
+ intl_options.to_h.each {|k, v| puts "#{k}: #{v}"}
249
+
250
+ # Pickup Example
251
+
252
+ pickup_params = {
253
+ carrier: 'CourierPost',
254
+ message_id: 'Test Message ID',
255
+ message_date_time: '2015-05-27T14:19:50',
256
+ account_number: '91327067',
257
+ pickup_address: {
258
+ site_code: '28979',
259
+ instructions: 'TEST'
260
+ },
261
+ pickup_date_time: '2015-05-28T17:00:00',
262
+ quantity: 1
263
+ }
264
+
265
+ pickup = ParcelApi::Pickup.new(client.connection) # use a custom connection
266
+ pickup_results = pickup.create(pickup_params)
267
+ pickup_results.to_h.each {|k, v| puts "#{k}: #{v}"}
@@ -0,0 +1,66 @@
1
+ module ParcelApi
2
+
3
+ # This module provides API requests to Search Domestic(NZ) Addresses, Get Specific Domestic Address Detail,
4
+ # Search International Addresses and Get Specifc International Address Detail.
5
+
6
+ class Address
7
+ DOMESTIC_URL = '/ParcelAddress/2.0/domestic/addresses'
8
+ INTERNATIONAL_URL = '/ParcelAddress/2.0/international/addresses'
9
+
10
+ # Creates a new ParcelApi::Address instance.
11
+
12
+ def initialize(connection=nil)
13
+ @connection ||= connection || ParcelApi::Client.connection
14
+ end
15
+
16
+ # Search for a Domestic (NZ) Address
17
+ # @param [String] characters to search for
18
+ # @param [Integer] number of search results to return (max 10)
19
+ # @return [Array] array of addresses
20
+
21
+ def search(query, count=10)
22
+ return [] if query.length < 4
23
+
24
+ response = @connection.get DOMESTIC_URL, { q: query, count: count }
25
+ addresses = response.body['addresses'].each do |a|
26
+ a['address_id'] = Integer(a['address_id'])
27
+ a['dpid'] = Integer(a['dpid'])
28
+ end
29
+ addresses.map {|address| OpenStruct.new(address)}
30
+ end
31
+
32
+ # Return domestic address details for a domestic address id
33
+ # @param address_id [String]
34
+ # @return address detail object
35
+
36
+ def details(address_id)
37
+ details_url = File.join(DOMESTIC_URL, address_id.to_s)
38
+ response = @connection.get details_url
39
+ OpenStruct.new(response.body['address'])
40
+ end
41
+
42
+ # Search for an International Address
43
+ # @param [String] characters to search for
44
+ # @param [Integer] number of search results to return (max 10)
45
+ # @param [String] country code for results - listed here: https://developers.google.com/public-data/docs/canonical/countries_csv/
46
+ # @return [Array] array of international addresses
47
+
48
+ def international_search(query, count=5, country_code=nil)
49
+ return [] if query.length < 4
50
+
51
+ response = @connection.get INTERNATIONAL_URL, { q: query, count: count, country_code: country_code }
52
+ response.body['addresses'].map {|address| OpenStruct.new(address)}
53
+ end
54
+
55
+ # Return international address details for a specific international address id
56
+ # @param address_id [String]
57
+ # @return international address detail
58
+
59
+ def international_details(address_id)
60
+ details_url = File.join(INTERNATIONAL_URL, address_id.to_s)
61
+ response = @connection.get details_url
62
+ RecursiveOpenStruct.new(response.body['result'], recurse_over_arrays: true)
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,10 @@
1
+ require 'thor'
2
+
3
+ module ParcelApi
4
+ class CLI < Thor
5
+ desc 'version', 'print version'
6
+ def version
7
+ puts "Version: #{ParcelApi::VERSION}"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,59 @@
1
+ module ParcelApi
2
+ class Client
3
+
4
+ attr_accessor :client_id,
5
+ :client_secret,
6
+ :username,
7
+ :password,
8
+ :address,
9
+ :auth_address
10
+
11
+ def self.connection
12
+ @connection ||= new.connection
13
+ end
14
+
15
+ def initialize
16
+ @client_id = client_id || ENV['CLIENT_ID']
17
+ @client_secret = client_secret || ENV['CLIENT_SECRET']
18
+ @username = username || ENV['USERNAME']
19
+ @password = password || ENV['PASSWORD']
20
+ @address = address || 'https://api.nzpost.co.nz'
21
+ @auth_address = auth_address || 'https://oauth.nzpost.co.nz/as/token.oauth2'
22
+ end
23
+
24
+ def connection
25
+ Faraday.new(url: @address) do |conn|
26
+ conn.authorization 'Bearer', token
27
+ conn.headers['client_id'] = @client_id
28
+ conn.request :json
29
+ conn.response :json, :content_type => /\bjson$/
30
+ conn.use FaradayMiddleware::RaiseHttpException
31
+ conn.adapter Faraday.default_adapter
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def token
38
+ @token ||= begin
39
+ params = {
40
+ client_id: @client_id,
41
+ client_secret: @client_secret,
42
+ username: @username,
43
+ password: @password,
44
+ grant_type: 'password',
45
+ }
46
+
47
+ auth_api = Faraday.new do |conn|
48
+ conn.request :url_encoded
49
+ conn.response :json
50
+ conn.use FaradayMiddleware::RaiseHttpException
51
+ conn.adapter Faraday.default_adapter
52
+ end
53
+ response = auth_api.post @auth_address, params
54
+ response.body['access_token']
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,80 @@
1
+ # @private
2
+ module FaradayMiddleware
3
+ # @private
4
+ class RaiseHttpException < Faraday::Middleware
5
+ def call(env)
6
+ @app.call(env).on_complete do |response|
7
+ case response[:status].to_i
8
+ when 400
9
+ raise ParcelApi::BadRequest, error_message_400(response)
10
+ when 404
11
+ raise ParcelApi::NotFound, error_message_400(response)
12
+ when 500
13
+ raise ParcelApi::InternalServerError, error_message_500(response, "Something is technically wrong.")
14
+ when 502
15
+ raise ParcelApi::BadGateway, error_message_500(response, "The server returned an invalid or incomplete response.")
16
+ when 503
17
+ raise ParcelApi::ServiceUnavailable, error_message_500(response, "NZ Post is rate limiting your requests.")
18
+ when 504
19
+ raise ParcelApi::GatewayTimeout, error_message_500(response, "504 Gateway Time-out")
20
+ end
21
+ end
22
+ end
23
+
24
+ def initialize(app)
25
+ super app
26
+ @parser = nil
27
+ end
28
+
29
+ private
30
+
31
+ def error_message_400(response)
32
+ "#{response[:method].to_s.upcase} #{response[:url].to_s} status: #{response[:status]}#{error_body(response[:body])}"
33
+ end
34
+
35
+ def error_body(body)
36
+ # body gets passed as a string, not sure if it is passed as something else from other spots?
37
+ if not body.nil? and not body.empty? and body.kind_of?(String)
38
+ body = ::JSON.parse(body)
39
+ end
40
+
41
+ if body.nil? || body['errors'].nil?
42
+ nil
43
+ else
44
+ body_errors = body['errors'].flat_map {|error| error.map {|k,v| "#{k}: #{v}" }}.compact.join(', ')
45
+ " - #{body_errors}"
46
+ end
47
+ end
48
+
49
+ def error_message_500(response, body=nil)
50
+ "#{response[:method].to_s.upcase} #{response[:url].to_s} status: #{[response[:status].to_s + ':', body].compact.join(' ')}"
51
+ end
52
+ end
53
+ end
54
+
55
+ module ParcelApi
56
+ # Custom error class for rescuing from all NZ Post errors
57
+ class Error < StandardError; end
58
+
59
+ # Raised when NZ Post returns the HTTP status code 400
60
+ class BadRequest < Error; end
61
+
62
+ # Raised when NZ Post returns the HTTP status code 404
63
+ class NotFound < Error; end
64
+
65
+ # Raised when NZ Post returns the HTTP status code 429
66
+ class TooManyRequests < Error; end
67
+
68
+ # Raised when NZ Post returns the HTTP status code 500
69
+ class InternalServerError < Error; end
70
+
71
+ # Raised when NZ Post returns the HTTP status code 502
72
+ class BadGateway < Error; end
73
+
74
+ # Raised when NZ Post returns the HTTP status code 503
75
+ class ServiceUnavailable < Error; end
76
+
77
+ # Raised when NZ Post returns the HTTP status code 504
78
+ class GatewayTimeout < Error; end
79
+
80
+ end
@@ -0,0 +1,60 @@
1
+ module ParcelApi
2
+
3
+ # This module provides API requests to label parcels, get existing label details
4
+ # and download labels.
5
+
6
+ class Label
7
+ LABEL_URL = '/ParcelLabel/2.0/labels'
8
+
9
+ # Creates a new ParcelApi::Label instance.
10
+
11
+ def initialize(connection=nil)
12
+ @connection ||= connection || ParcelApi::Client.connection
13
+ end
14
+
15
+ # Create a label with the specified options
16
+ # @param label_options [Hash]
17
+ # @return Single or array of label objects
18
+
19
+ def create(label_options)
20
+ domestic_url = File.join(LABEL_URL, 'domestic')
21
+ response = @connection.post domestic_url, label_options
22
+ labels = response.body['labels'].map {|label| OpenStruct.new(label)}
23
+ labels.first if labels.count == 1
24
+ end
25
+
26
+ # Create an international label with the specified options
27
+ # @param label_options [Hash]
28
+ # @return Single or array of label objects
29
+
30
+ def international_create(label_options)
31
+ international_url = File.join(LABEL_URL, 'international')
32
+ response = @connection.post international_url, label_options
33
+ labels = response.body['labels'].map {|label| OpenStruct.new(label)}
34
+ labels.first if labels.count == 1
35
+ end
36
+
37
+ # Get label details
38
+ # @param label_id [String]
39
+ # @return Object of label details
40
+
41
+ def details(label_id)
42
+ details_url = File.join(LABEL_URL, "#{label_id}.json")
43
+ response = @connection.get details_url
44
+ details = response.body.tap {|d| d.delete('success')}
45
+ OpenStruct.new(details)
46
+ end
47
+
48
+ # Download label
49
+ # @param label_id [String]
50
+ # @return Object of label
51
+
52
+ def download(label_id)
53
+ download_url = File.join(LABEL_URL, "#{label_id}.pdf")
54
+ response = @connection.get download_url
55
+ StringIO.new(response.body)
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,26 @@
1
+ module ParcelApi
2
+
3
+ # The ParcelPickUp API that notifies PACE or CourierPost to come and pick up your parcel.
4
+ # The integrator provides the pick up location in the form of a site id or an address to create the pick up record.
5
+
6
+ class Pickup
7
+ PARCELPICKUP_URL = '/ParcelPickUp/2.0/bookings'
8
+
9
+ # Creates a new ParcelApi::Pickup instance.
10
+
11
+ def initialize(connection=nil)
12
+ @connection ||= connection || ParcelApi::Client.connection
13
+ end
14
+
15
+ # Create a new parcel booking
16
+ # @param pickup_options [Hash]
17
+ # @return Object of pickup details
18
+
19
+ def create(pickup_options)
20
+ response = @connection.post PARCELPICKUP_URL, pickup_options
21
+ RecursiveOpenStruct.new(response.body, recurse_over_arrays: true)
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,44 @@
1
+ module ParcelApi
2
+
3
+ # Returns the shipping options and rates available depending on customer,
4
+ # pick up and destination addresses and parcel dimensions and weight.
5
+ # Both domestic and international.
6
+
7
+ class ShippingOptions
8
+ DOMESTIC_URL = '/ShippingOptions/2.0/domestic'
9
+ INTERNATIONAL_URL = '/ShippingOptions/2.0/international'
10
+
11
+ # Creates a new ParcelApi::ShippingOptions instance.
12
+
13
+ def initialize(connection=nil)
14
+ @connection ||= connection || ParcelApi::Client.connection
15
+ end
16
+
17
+ # Search for Domestic (NZ) Shipping Options
18
+ # @param parcel_params [Hash] parcel parameters
19
+ # @return [Array] return array of shipping options
20
+
21
+ def get_domestic(parcel_params)
22
+ response = @connection.get DOMESTIC_URL, parcel_params
23
+ options = response.body.tap do |so|
24
+ so.delete('success')
25
+ so.delete('message_id')
26
+ end
27
+ RecursiveOpenStruct.new(options, recurse_over_arrays: true)
28
+ end
29
+
30
+ # Search for International Shipping Options
31
+ # @param parcel_params [Hash] parcel parameters
32
+ # @return [Array] return array of shipping options
33
+
34
+ def get_international(parcel_params)
35
+ response = @connection.get INTERNATIONAL_URL, parcel_params
36
+ options = response.body.tap do |so|
37
+ so.delete('success')
38
+ so.delete('message_id')
39
+ end
40
+ RecursiveOpenStruct.new(options, recurse_over_arrays: true)
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ module ParcelApi
2
+
3
+ # This module provides API requests to track the parcels and
4
+ # return tracking information for a specific tracking reference.
5
+
6
+ class Track
7
+ PARCELTRACK_URL = '/ParcelTrack/2.0/parcels'
8
+
9
+ # Creates a new ParcelApi::Track instance.
10
+
11
+ def initialize(connection=nil)
12
+ @connection ||= connection || ParcelApi::Client.connection
13
+ end
14
+
15
+ # Return details for a specific tracking reference.
16
+ # @param tracking_reference [String]
17
+ # @return Object of tracking details
18
+
19
+ def details(tracking_reference)
20
+ details_url = File.join(PARCELTRACK_URL, tracking_reference.to_s)
21
+ response = @connection.get details_url
22
+ events = response.body.tap do |d|
23
+ d.delete('success')
24
+ d['tracking_events'].map {|e| e['event_datetime'] = Time.parse(e['event_datetime'])}
25
+ d['tracking_events'].sort_by! {|k| k['event_datetime'].to_i}
26
+ end
27
+ RecursiveOpenStruct.new(events, recurse_over_arrays: true)
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,3 @@
1
+ module ParcelApi
2
+ VERSION = '0.2.0'
3
+ end
data/lib/parcel_api.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'recursive-open-struct'
4
+
5
+ require 'parcel_api/version'
6
+ require 'parcel_api/error'
7
+ require 'parcel_api/address'
8
+ require 'parcel_api/track'
9
+ require 'parcel_api/label'
10
+ require 'parcel_api/shipping_options'
11
+ require 'parcel_api/pickup'
12
+ require 'parcel_api/client'
13
+
14
+ module ParcelApi; end
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'parcel_api/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'parcel_api'
8
+ spec.version = ParcelApi::VERSION
9
+ spec.authors = ['Robert Coleman']
10
+ spec.email = ['github@robert.net.nz']
11
+
12
+ spec.summary = %q{Ruby client for NZ Post's Parcel APIs}
13
+ spec.homepage = 'https://github.com/etailer/parcel_api'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'thor', '~> 0.19'
22
+ spec.add_dependency 'faraday', '~> 0.9'
23
+ spec.add_dependency 'faraday_middleware', '~> 0.9'
24
+ spec.add_dependency 'oauth2', '~> 1.0'
25
+ spec.add_dependency 'recursive-open-struct', '~> 0.6'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.8'
28
+ spec.add_development_dependency 'rake', '~> 10.0'
29
+ spec.add_development_dependency 'rspec', '~> 3.2'
30
+ spec.add_development_dependency 'pry', '~> 0.10'
31
+ spec.add_development_dependency 'webmock', '~> 1.21'
32
+ spec.add_development_dependency 'vcr', '~> 2.9'
33
+ spec.add_development_dependency 'yard', '~> 0.8'
34
+ end
metadata ADDED
@@ -0,0 +1,237 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parcel_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Robert Coleman
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-09-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.19'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.19'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.9'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday_middleware
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.9'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: oauth2
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: recursive-open-struct
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.6'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.8'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.8'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '10.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '10.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.2'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.2'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.10'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.10'
139
+ - !ruby/object:Gem::Dependency
140
+ name: webmock
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.21'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.21'
153
+ - !ruby/object:Gem::Dependency
154
+ name: vcr
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '2.9'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '2.9'
167
+ - !ruby/object:Gem::Dependency
168
+ name: yard
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '0.8'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '0.8'
181
+ description:
182
+ email:
183
+ - github@robert.net.nz
184
+ executables: []
185
+ extensions: []
186
+ extra_rdoc_files: []
187
+ files:
188
+ - ".gitignore"
189
+ - ".rspec"
190
+ - ".travis.yml"
191
+ - ".yardopts"
192
+ - Gemfile
193
+ - Gemfile.lock
194
+ - LICENSE
195
+ - README.md
196
+ - Rakefile
197
+ - bin/console
198
+ - bin/parcel
199
+ - bin/setup
200
+ - example/mock.rb
201
+ - lib/parcel_api.rb
202
+ - lib/parcel_api/address.rb
203
+ - lib/parcel_api/cli.rb
204
+ - lib/parcel_api/client.rb
205
+ - lib/parcel_api/error.rb
206
+ - lib/parcel_api/label.rb
207
+ - lib/parcel_api/pickup.rb
208
+ - lib/parcel_api/shipping_options.rb
209
+ - lib/parcel_api/track.rb
210
+ - lib/parcel_api/version.rb
211
+ - parcel_api.gemspec
212
+ homepage: https://github.com/etailer/parcel_api
213
+ licenses:
214
+ - MIT
215
+ metadata: {}
216
+ post_install_message:
217
+ rdoc_options: []
218
+ require_paths:
219
+ - lib
220
+ required_ruby_version: !ruby/object:Gem::Requirement
221
+ requirements:
222
+ - - ">="
223
+ - !ruby/object:Gem::Version
224
+ version: '0'
225
+ required_rubygems_version: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ requirements: []
231
+ rubyforge_project:
232
+ rubygems_version: 2.4.5
233
+ signing_key:
234
+ specification_version: 4
235
+ summary: Ruby client for NZ Post's Parcel APIs
236
+ test_files: []
237
+ has_rdoc: