easypost 4.1.2 → 4.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b2de1c12852ffd59083d6ee132f2071577e7ff774f77cb515b444f75eaf86fc
4
- data.tar.gz: 98b1d4bce4a86d9cdf3480f91e818dc362af2963e38f9bccb434b557aebf6bcc
3
+ metadata.gz: '02404008f0fc1078e49e1e418c5a208fbca5d646f31886c3c7fab55d932def70'
4
+ data.tar.gz: b8a2f7d514f9f22fa5b7ec3daf010adbfcc793c689954102b6ccbaafd7742e6a
5
5
  SHA512:
6
- metadata.gz: 97fceef78d0d5706cd0ddb553f6da95ad5a27fe2491069f2eb416dafcb296a158d6e03bab6232ef0efe63f7503a256830635847288e99f17129920adade55eae
7
- data.tar.gz: 2d3a0058d85435a3b743ef9f1b3f21e42bd77bf5cf830f5522794369f27315a9ca673f65fb9f3900df718668d2207ed63c295b7f36f415979cd53dae8cfe0110
6
+ metadata.gz: f6c9af835a6ff8ccb7574fd5a14ef96f850784e6600249a8441d23a521763ad3fb40d826cdce0f758cd4b87213a65c6ee733476d34824dabbb7fb09db2af0de0
7
+ data.tar.gz: c492ad0635133598e8535a2a0c72949b0acd6f0095b511bb91011c50cec67e6d8999b5358da8aa7ae4543e38a30bc029367d8c0cfd791e4944f46fac3cdf75d9
@@ -0,0 +1,81 @@
1
+ name: Bug Report
2
+ description: File a bug report
3
+ title: "[Bug]: "
4
+ labels: [ "triage" ]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Thank you for taking the time to report an issue in this repository. Please fill out the form below.
10
+ - type: input
11
+ id: software-version
12
+ attributes:
13
+ label: Software Version
14
+ # change this description for the specific repo
15
+ description: |
16
+ What version of our software are you running?
17
+ TIP: [Available versions](https://github.com/EasyPost/easypost-ruby/releases)
18
+ validations:
19
+ required: true
20
+ - type: input
21
+ id: language-version
22
+ attributes:
23
+ label: Language Version
24
+ # change this description for the specific language of the repo
25
+ description: |
26
+ What language version and/or framework are you using?
27
+ TIP: [How to find your Ruby version](https://blog.arkency.com/which-ruby-version-am-i-using-how-to-check/)
28
+ validations:
29
+ required: true
30
+ - type: input
31
+ id: os
32
+ attributes:
33
+ label: Operating System
34
+ description: What operating system are you running the software on?
35
+ validations:
36
+ required: true
37
+ - type: textarea
38
+ id: behavior
39
+ attributes:
40
+ label: What happened?
41
+ description: |
42
+ Please describe what happened in reproducible steps.
43
+ Include how often you see this issue, and any relevant links (i.e. GitHub issue, Stack Overflow, etc.).
44
+ value: |
45
+ 1.
46
+ 2.
47
+ 3.
48
+ ...
49
+ validations:
50
+ required: true
51
+ - type: textarea
52
+ id: expected-behavior
53
+ attributes:
54
+ label: What was expected?
55
+ description: Please describe what was expected to happen instead.
56
+ validations:
57
+ required: true
58
+ - type: textarea
59
+ id: sample-code
60
+ attributes:
61
+ label: Sample Code
62
+ description: |
63
+ Please provide any sample code that demonstrates the behavior.
64
+ This will be automatically formatted into the appropriate language, so no need for backticks.
65
+ **Do not include any private information such as API keys or passwords.**
66
+ # change this render to the appropriate language: https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
67
+ render: rb
68
+ validations:
69
+ required: false
70
+ - type: textarea
71
+ id: logs
72
+ attributes:
73
+ label: Relevant logs
74
+ description: |
75
+ Please copy and paste any relevant log output.
76
+ This will be automatically formatted into shell output, so no need for backticks.
77
+ If you have screenshots instead, please paste them below.
78
+ **Do not include any private information such as API keys or passwords.**
79
+ render: sh
80
+ validations:
81
+ required: false
@@ -0,0 +1,37 @@
1
+ name: Feature Request
2
+ description: Request a new feature
3
+ title: "[Feat]: "
4
+ labels: [ "triage" ]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Thank you for taking the time to request a new feature.
10
+ Please note, all feature requests are subject to review and approval.
11
+ We welcome all suggestions and ideas, but we cannot guarantee when or if we will implement them.
12
+
13
+ We also welcome pull requests, if you would like to implement the feature yourself.
14
+ Doing so will likely accelerate the process of implementing the requested feature.
15
+ - type: checkboxes
16
+ id: searched
17
+ attributes:
18
+ label: Feature Request Is New
19
+ # change issue link below for the specific repo
20
+ description: |
21
+ Before we begin, please confirm that the requested feature does not already exist or has not [already been requested](https://github.com/EasyPost/easypost-ruby/issues).
22
+ options:
23
+ - label: I have verified that the requested feature does not already exist or has not already been requested.
24
+ required: true
25
+ - type: textarea
26
+ id: description
27
+ attributes:
28
+ label: Description of the feature
29
+ description: |
30
+ Please provide a detailed description of the feature, including:
31
+ - What the feature is
32
+ - What value it adds to the application
33
+ - How it should be implemented (i.e. pseudo-code, or a high-level description of the user experience)
34
+ - Any other relevant information
35
+ validations:
36
+ required: true
37
+
@@ -0,0 +1,22 @@
1
+ # Description
2
+
3
+ <!-- Please provide a general summary of your PR changes and link any related issues or other pull requests. -->
4
+
5
+ # Testing
6
+
7
+ <!--
8
+ Please provide details on how you tested this code. See below.
9
+
10
+ - All pull requests must be tested (unit tests where possible with accompanying cassettes, or provide a screenshot of end-to-end testing when unit tests are not possible)
11
+ - New features must get a new unit test
12
+ - Bug fixes/refactors must re-record existing cassettes
13
+ -->
14
+
15
+ # Pull Request Type
16
+
17
+ Please select the option(s) that are relevant to this PR.
18
+
19
+ - [ ] Bug fix (non-breaking change which fixes an issue)
20
+ - [ ] New feature (non-breaking change which adds functionality)
21
+ - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
22
+ - [ ] Improvement (fixing a typo, updating readme, renaming a variable name, etc)
@@ -13,23 +13,23 @@ jobs:
13
13
  rubyversion: ["2.5", "2.6", "2.7", "3.0", "3.1"]
14
14
  steps:
15
15
  - name: Checkout Repository
16
- uses: actions/checkout@v2
16
+ uses: actions/checkout@v3
17
17
  - name: Set up Ruby
18
18
  uses: ruby/setup-ruby@v1
19
19
  with:
20
20
  ruby-version: ${{ matrix.rubyversion }}
21
21
  bundler-cache: true
22
22
  - name: run tests
23
- run: bundle exec rspec
23
+ run: EASYPOST_TEST_API_KEY=123 EASYPOST_PROD_API_KEY=123 bundle exec rspec
24
24
  lint:
25
25
  runs-on: ubuntu-latest
26
26
  steps:
27
27
  - name: Checkout Repository
28
- uses: actions/checkout@v2
28
+ uses: actions/checkout@v3
29
29
  - name: Set up Ruby
30
30
  uses: ruby/setup-ruby@v1
31
31
  with:
32
- ruby-version: "3.0"
32
+ ruby-version: "3.1"
33
33
  - name: Install Dependencies
34
34
  run: bundle install
35
35
  - name: Lint Project
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v4.3.0 (2022-05-19)
4
+
5
+ - Adds the `EndShipper` Beta class with `create`, `retrieve`, `all`, and `save` functions
6
+ - Requests will now fail fast with an error if an API key is not provided instead of making a live API call with no key
7
+ - Fixes a bug where the library could not properly parse the response of deleting a child user
8
+ - Fixes a bug where you could not update a webhook due to a `wrong number of arguments` error
9
+
10
+ ## v4.2.1 (2022-05-11)
11
+
12
+ - Corrects the `Beta` namespace for the new Referral class
13
+
14
+ ## v4.2.0 (2022-05-09)
15
+
16
+ - Adds a `lowest_rate()` function to Orders and Pickups
17
+ - Adds a `Shipment.get_lowest_smartrate()` function and a `shipment.lowest_smartrate()` function
18
+ - Removes the unusable `carrier` param from `Address.create_and_verify` along with the dead `message` conditional check
19
+ - Add beta Referral class for White Label API with these new functions: `create()`, `update_email()`, `all()`, and `add_credit_card()`
20
+
3
21
  ## v4.1.2 (2022-03-16)
4
22
 
5
23
  - Rolls back the original connection behavior of establishing a new connection for every request (restores previous expectations for multithreaded implementations)
@@ -0,0 +1,16 @@
1
+ # Code of Conduct
2
+
3
+ > Remember the golden rule? Treat others as you'd like to be treated
4
+
5
+ ## Standards
6
+
7
+ - Create a welcoming and safe community for `everyone`
8
+ - Demonstrate empathy towards others, realize that everyone has a different `level of experience`
9
+ - Be respectful of others opinions, viewpoints, and experiences
10
+ - Give and receive respectful constructive feedback
11
+ - Accept responsibility and apologize when necessary
12
+ - Focus on what is best for the overall community
13
+
14
+ ## Failure to Follow Standards
15
+
16
+ If there is a failure to follow the standards laid out above, the user will first be warned. If the offending behavior continues, the user may be blocked entirely.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,47 @@
1
+ # Contributing Guide
2
+
3
+ Please read this document in its entirety.
4
+
5
+ ## General
6
+
7
+ - Be nice and respectful of maintainers’ and contributors’ time and viewpoints
8
+ - Be as descriptive as possible! More info is always better than not enough
9
+ - Be patient, there may be a lot of in-flight work, some of it across multiple projects not related to the repo being contributed to
10
+ - Have fun!
11
+
12
+ ## Contributing
13
+
14
+ Contributing comes in many forms! We are incredibly grateful to anyone who can do any of the following:
15
+
16
+ - Add new features
17
+ - Fix bugs
18
+ - Fix typos
19
+ - Improve docs
20
+ - Improve tests
21
+ - Triage issues
22
+ - Review pull requests
23
+ - Share opinions and viewpoints on issues
24
+
25
+ ## Issues
26
+
27
+ - If your issue is security related, please follow the [SECURITY guide](https://github.com/easypost/.github/SECURITY.md)
28
+ - Before opening a new issue, check for existing issues that are related, including closed ones
29
+ - Provide as much information as possible about the issue, including how to reproduce the problem and the expected behavior
30
+ - Don't needlessly bump issues (eg: if there hasn’t been progress for more than a few weeks, feel free to reach back out)
31
+
32
+ ## Pull Requests
33
+
34
+ - All Pull Requests should be accompanied first by an issue describing the reason why the Pull Request is needed
35
+ - Be as descriptive as possible in your PR description about why the changes are being made and what the changes contain
36
+ - Pull Requests should be as small as possible
37
+ - Don't make unrelated changes in your Pull Request
38
+ - Don't open a Pull Request if you don't plan to see it through. PRs submitted by individuals that cannot complete additional work to get a PR merged may be closed without completion
39
+ - Adhere to the existing code style of the repo, even if it differs from your personal preference
40
+ - When applicable, add tests that provide ample coverage of the logic added or changed
41
+ - Pull Requests should come from branches and never the default `master` branch
42
+ - Pull Requests must pass CI, including linting and testing
43
+ - Pull Requests must go through code review before they can be merged to the main branch
44
+ - Do not include "version bump" changes in the same PR as your code changes; these will be handled as a separate PR and releasing process
45
+ - Make sure the `Allow edits from maintainers` checkbox is checked. That way EasyPost engineers can make certain minor changes as needed, allowing your pull request to be merged sooner.
46
+
47
+ You agree and acknowledge that you have necessary intellectual property rights to provide your Contribution and hereby assign to EasyPost all right, title and interest in such Contribution.
data/README.md CHANGED
@@ -3,112 +3,65 @@
3
3
  [![Build Status](https://github.com/EasyPost/easypost-ruby/workflows/CI/badge.svg)](https://github.com/EasyPost/easypost-ruby/actions?query=workflow%3ACI)
4
4
  [![Gem Version](https://badge.fury.io/rb/easypost.svg)](https://badge.fury.io/rb/easypost)
5
5
 
6
+ EasyPost, the simple shipping solution. You can sign up for an account at <https://easypost.com>.
6
7
 
7
- EasyPost is a simple shipping API. You can sign up for an account at https://easypost.com.
8
-
9
- ## Installation
10
-
11
- Install the gem:
8
+ ## Install
12
9
 
13
10
  ```bash
14
11
  gem install easypost
15
12
  ```
16
13
 
17
- Import the EasyPost client in your application:
18
-
19
14
  ```ruby
15
+ # Require the library in your project:
20
16
  require 'easypost'
21
17
  ```
22
18
 
23
- ## Example
19
+ ## Usage
20
+
21
+ A simple create & buy shipment example:
24
22
 
25
23
  ```ruby
26
24
  require 'easypost'
27
- EasyPost.api_key = 'API_KEY'
28
-
29
- to_address = EasyPost::Address.create(
30
- :name => 'Dr. Steve Brule',
31
- :street1 => '179 N Harbor Dr',
32
- :city => 'Redondo Beach',
33
- :state => 'CA',
34
- :zip => '90277',
35
- :country => 'US',
36
- :phone => '310-808-5243'
37
- )
38
-
39
- from_address = EasyPost::Address.create(
40
- :company => 'EasyPost',
41
- :street1 => '118 2nd Street',
42
- :street2 => '4th Floor',
43
- :city => 'San Francisco',
44
- :state => 'CA',
45
- :zip => '94105',
46
- :phone => '415-456-7890'
47
- )
48
-
49
- parcel = EasyPost::Parcel.create(
50
- :width => 15.2,
51
- :length => 18,
52
- :height => 9.5,
53
- :weight => 35.1
54
- )
55
25
 
56
- customs_item = EasyPost::CustomsItem.create(
57
- :description => 'EasyPost T-shirts',
58
- :quantity => 2,
59
- :value => 23.56,
60
- :weight => 33,
61
- :origin_country => 'us',
62
- :hs_tariff_number => 123456
63
- )
64
-
65
- customs_info = EasyPost::CustomsInfo.create(
66
- :integrated_form_type => 'form_2976',
67
- :customs_certify => true,
68
- :customs_signer => 'Dr. Pepper',
69
- :contents_type => 'gift',
70
- :contents_explanation => '', # only required when contents_type => 'other'
71
- :eel_pfc => 'NOEEI 30.37(a)',
72
- :non_delivery_option => 'abandon',
73
- :restriction_type => 'none',
74
- :restriction_comments => '',
75
- :customs_items => [customs_item]
76
- )
26
+ EasyPost.api_key = ENV['EASYPOST_API_KEY']
77
27
 
78
28
  shipment = EasyPost::Shipment.create(
79
- :to_address => to_address,
80
- :from_address => from_address,
81
- :parcel => parcel,
82
- :customs_info => customs_info
29
+ from_address: {
30
+ company: 'EasyPost',
31
+ street1: '118 2nd Street',
32
+ street2: '4th Floor',
33
+ city: 'San Francisco',
34
+ state: 'CA',
35
+ zip: '94105',
36
+ phone: '415-456-7890',
37
+ },
38
+ to_address: {
39
+ name: 'Dr. Steve Brule',
40
+ street1: '179 N Harbor Dr',
41
+ city: 'Redondo Beach',
42
+ state: 'CA',
43
+ zip: '90277',
44
+ country: 'US',
45
+ phone: '310-808-5243',
46
+ },
47
+ parcel: {
48
+ width: 15.2,
49
+ length: 18,
50
+ height: 9.5,
51
+ weight: 35.1,
52
+ },
83
53
  )
84
54
 
85
- shipment.buy(
86
- :rate => shipment.lowest_rate
87
- )
88
-
89
- shipment.insure(amount: 100)
90
-
91
- puts shipment.insurance
55
+ shipment.buy(rate: shipment.lowest_rate)
92
56
 
93
- puts shipment.postage_label.label_url
57
+ puts shipment
94
58
  ```
95
59
 
96
- ## Documentation
97
-
98
- Up-to-date documentation at: https://easypost.com/docs
99
-
100
- ## Development
101
-
102
- ```bash
103
- # Run tests (coverage is generated on a successful test suite run)
104
- EASYPOST_TEST_API_KEY=123... EASYPOST_PROD_API_KEY=123... bundle exec rspec
105
- ```
106
-
107
- ## Custom connections
60
+ ### Custom Connections
108
61
 
109
62
  Set `EasyPost.default_connection` to an object that responds to `call(method, path, api_key = nil, body = nil)`
110
63
 
111
- ### Faraday
64
+ #### Faraday
112
65
 
113
66
  ```ruby
114
67
  require 'faraday'
@@ -134,7 +87,7 @@ EasyPost.default_connection = lambda do |method, path, api_key = nil, body = nil
134
87
  end
135
88
  ```
136
89
 
137
- ### Typhoeus
90
+ #### Typhoeus
138
91
 
139
92
  ```ruby
140
93
  require 'typhoeus'
@@ -154,3 +107,41 @@ EasyPost.default_connection = lambda do |method, path, api_key = nil, body = nil
154
107
  }
155
108
  end
156
109
  ```
110
+
111
+ ## Documentation
112
+
113
+ API Documentation can be found at: <https://easypost.com/docs/api>.
114
+
115
+ ## Development
116
+
117
+ ```bash
118
+ # Install dependencies
119
+ bundle install
120
+
121
+ # Run tests (coverage is generated on a successful test suite run)
122
+ EASYPOST_TEST_API_KEY=123... EASYPOST_PROD_API_KEY=123... bundle exec rspec
123
+ ```
124
+
125
+ ### Testing
126
+
127
+ The test suite in this project was specifically built to produce consistent results on every run, regardless of when they run or who is running them. This project uses [VCR](https://github.com/vcr/vcr) to record and replay HTTP requests and responses via "cassettes". When the suite is run, the HTTP requests and responses for each test function will be saved to a cassette if they do not exist already and replayed from this saved file if they do, which saves the need to make live API calls on every test run.
128
+
129
+ **Sensitive Data:** We've made every attempt to include scrubbers for sensitive data when recording cassettes so that PII or sensitive info does not persist in version control; however, please ensure when recording or re-recording cassettes that prior to committing your changes, no PII or sensitive information gets persisted by inspecting the cassette.
130
+
131
+ **Making Changes:** If you make an addition to this project, the request/response will get recorded automatically for you. When making changes to this project, you'll need to re-record the associated cassette to force a new live API call for that test which will then record the request/response used on the next run.
132
+
133
+ **Test Data:** The test suite has been populated with various helpful fixtures that are available for use, each completely independent from a particular user **with the exception of the USPS carrier account ID** (see [Unit Test API Keys](#unit-test-api-keys) for more information) which has a fallback value of our internal testing user's ID. Some fixtures use hard-coded dates that may need to be incremented if cassettes get re-recorded (such as reports or pickups).
134
+
135
+ #### Unit Test API Keys
136
+
137
+ The following are required on every test run:
138
+
139
+ - `EASYPOST_TEST_API_KEY`
140
+ - `EASYPOST_PROD_API_KEY`
141
+
142
+ The following are required when you need to re-record cassettes for applicable tests (fallback values are used otherwise):
143
+
144
+ - `USPS_CARRIER_ACCOUNT_ID` (eg: one-call buying a shipment for non-EasyPost employees)
145
+ - `REFERRAL_USER_PROD_API_KEY` (eg: adding a credit card to a referral user)
146
+
147
+ Some tests may require a user with a particular set of enabled features such as a `Partner` user when creating referrals. We have attempted to call out these functions in their respective docstrings.
data/SECURITY.md ADDED
@@ -0,0 +1,7 @@
1
+ # Security Guide
2
+
3
+ We take security seriously at EasyPost. If you find a security issue or vulnerability in our open source projects, please abide by the following guidelines:
4
+
5
+ - Please read our [Responsible Disclosure Policy](https://www.easypost.com/privacy#disclosure-policy).
6
+ - Do not open an issue on GitHub about the security vulnerability. Doing so draws attention to the issue and exposes it to the public.
7
+ - Send an email to `security-abuse@easypost.com` including as many details as possible.
data/SUPPORT.md ADDED
@@ -0,0 +1,3 @@
1
+ # Support Guide
2
+
3
+ Looking for support for one of our projects? If your question is related to our API, please contact our support team at `support@easypost.com`. If you need support regarding this project, create an issue on GitHub with as many details as possible and we’ll take a look.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.1.2
1
+ 4.3.0
data/easycop.yml CHANGED
@@ -41,7 +41,7 @@ Layout/FirstMethodArgumentLineBreak:
41
41
  Enabled: true
42
42
  Layout/LineLength:
43
43
  Max: 120
44
- IgnoredPatterns:
44
+ IgnoredPatterns: # deprecated in 1.28
45
45
  - "(\\A|\\s)#"
46
46
  Layout/LineEndStringConcatenationIndentation: # new in 1.18
47
47
  Enabled: true
data/easypost.gemspec CHANGED
@@ -24,10 +24,10 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency 'pry', '~> 0.14'
25
25
  spec.add_development_dependency 'rake', '~> 13.0'
26
26
  spec.add_development_dependency 'rspec', '~> 3.10'
27
- spec.add_development_dependency 'rubocop', '~> 1.24'
27
+ spec.add_development_dependency 'rubocop', '= 1.27' # rubocop 1.28 requires Ruby 2.6+
28
28
  spec.add_development_dependency 'rubocop-rspec', '~> 2.7'
29
29
  spec.add_development_dependency 'simplecov', '~> 0.21'
30
30
  spec.add_development_dependency 'simplecov-lcov', '~> 0.8'
31
- spec.add_development_dependency 'vcr', '~> 6.0'
31
+ spec.add_development_dependency 'vcr', '= 6.0' # VCR 6.1 requires Ruby 2.6+
32
32
  spec.add_development_dependency 'webmock', '~> 3.14'
33
33
  end
@@ -6,40 +6,30 @@ class EasyPost::Address < EasyPost::Resource
6
6
 
7
7
  # Create an Address.
8
8
  def self.create(params = {}, api_key = nil)
9
- url = self.url
10
-
11
9
  address = params.reject { |k, _| [:verify, :verify_strict].include?(k) }
12
10
 
13
- if params[:verify] || params[:verify_strict]
14
- verify = params[:verify] || []
15
- verify_strict = params[:verify_strict] || []
16
-
17
- url += '?'
18
- verify.each do |verification|
19
- url += "verify[]=#{verification}&"
20
- end
21
- verify_strict.each do |verification|
22
- url += "verify_strict[]=#{verification}&"
23
- end
11
+ wrapped_params = { address: address }
12
+
13
+ if params[:verify]
14
+ wrapped_params[:verify] = params[:verify]
15
+ end
16
+
17
+ if params[:verify_strict]
18
+ wrapped_params[:verify_strict] = params[:verify_strict]
24
19
  end
25
20
 
26
- response = EasyPost.make_request(:post, url, api_key, { address: address })
21
+ response = EasyPost.make_request(:post, url, api_key, wrapped_params)
27
22
  EasyPost::Util.convert_to_easypost_object(response, api_key)
28
23
  end
29
24
 
30
25
  # Create and verify an Address in one call.
31
- def self.create_and_verify(params = {}, carrier = nil, api_key = nil)
26
+ def self.create_and_verify(params = {}, api_key = nil)
32
27
  wrapped_params = {}
33
28
  wrapped_params[class_name.to_sym] = params
34
- wrapped_params[:carrier] = carrier
35
29
  response = EasyPost.make_request(:post, "#{url}/create_and_verify", api_key, wrapped_params)
36
30
 
37
31
  raise EasyPost::Error.new('Unable to verify address.') unless response.key?('address')
38
32
 
39
- if response.key?('message')
40
- response['address']['message'] = response['message']
41
- end
42
-
43
33
  EasyPost::Util.convert_to_easypost_object(response['address'], api_key)
44
34
  end
45
35
 
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # EndShipper objects are fully-qualified Address objects that require all parameters and get verified upon creation.
4
+ class EasyPost::Beta::EndShipper < EasyPost::Resource
5
+ # Create an EndShipper object.
6
+ def self.create(params = {}, api_key = nil)
7
+ response = EasyPost.make_request(:post, '/beta/end_shippers', api_key, { address: params })
8
+ EasyPost::Util.convert_to_easypost_object(response, api_key)
9
+ end
10
+
11
+ # Retrieves an EndShipper object.
12
+ def self.retrieve(id, params = {}, api_key = nil)
13
+ response = EasyPost.make_request(:get, "/beta/end_shippers/#{id}", api_key, params)
14
+ EasyPost::Util.convert_to_easypost_object(response, api_key)
15
+ end
16
+
17
+ # Retrieves a list of EndShipper objects.
18
+ def self.all(params = {}, api_key = nil)
19
+ response = EasyPost.make_request(:get, '/beta/end_shippers', api_key, params)
20
+ EasyPost::Util.convert_to_easypost_object(response, api_key)
21
+ end
22
+
23
+ # Updates (saves) an EndShipper object. This requires all parameters to be set.
24
+ def save
25
+ if @unsaved_values.length.positive?
26
+ values = {}
27
+ @unsaved_values.each { |k| values[k] = @values[k] }
28
+
29
+ wrapped_params = { address: values }
30
+
31
+ response = EasyPost.make_request(:put, "/beta/end_shippers/#{id}", @api_key, wrapped_params)
32
+ refresh_from(response, api_key)
33
+ end
34
+ self
35
+ end
36
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Referral objects are User objects created from a Partner user.
4
+ class EasyPost::Beta::Referral < EasyPost::Resource
5
+ # Create a referral user. This function requires the Partner User's API key.
6
+ def self.create(params = {}, api_key = nil)
7
+ response = EasyPost.make_request(:post, '/beta/referral_customers', api_key, { user: params })
8
+ EasyPost::Util.convert_to_easypost_object(response, api_key)
9
+ end
10
+
11
+ # Update a referral user. This function requires the Partner User's API key.
12
+ def self.update_email(email, user_id, api_key = nil)
13
+ wrapped_params = {
14
+ user: {
15
+ email: email,
16
+ },
17
+ }
18
+ EasyPost.make_request(:put, "/beta/referral_customers/#{user_id}", api_key, wrapped_params)
19
+
20
+ # return true if API succeeds, else an error is throw if it fails.
21
+ true
22
+ end
23
+
24
+ # Retrieve a list of referral users. This function requires the Partner User's API key.
25
+ def self.all(params = {}, api_key = nil)
26
+ response = EasyPost.make_request(:get, '/beta/referral_customers', api_key, params)
27
+ EasyPost::Util.convert_to_easypost_object(response, api_key)
28
+ end
29
+
30
+ # Add credit card to a referral user. This function requires the Referral User's API key.
31
+ def self.add_credit_card(referral_api_key, number, expiration_month, expiration_year, cvc, priority = 'primary')
32
+ easypost_stripe_api_key = retrieve_easypost_stripe_api_key
33
+
34
+ begin
35
+ stripe_credit_card_token = create_stripe_token(
36
+ number,
37
+ expiration_month,
38
+ expiration_year,
39
+ cvc,
40
+ easypost_stripe_api_key,
41
+ )
42
+ rescue StandardError
43
+ raise EasyPost::Error.new('Could not send card details to Stripe, please try again later.')
44
+ end
45
+
46
+ response = create_easypost_credit_card(referral_api_key, stripe_credit_card_token, priority)
47
+ EasyPost::Util.convert_to_easypost_object(response, referral_api_key)
48
+ end
49
+
50
+ # Retrieve EasyPost's Stripe public API key.
51
+ def self.retrieve_easypost_stripe_api_key
52
+ response = EasyPost.make_request(:get, '/beta/partners/stripe_public_key', @api_key)
53
+ response['public_key']
54
+ end
55
+
56
+ # Get credit card token from Stripe.
57
+ def self.create_stripe_token(number, expiration_month, expiration_year, cvc, easypost_stripe_token)
58
+ headers = {
59
+ # This Stripe endpoint only accepts URL form encoded bodies.
60
+ Authorization: "Bearer #{easypost_stripe_token}",
61
+ 'Content-type': 'application/x-www-form-urlencoded',
62
+ }
63
+
64
+ credit_card_hash = {
65
+ card: {
66
+ number: number,
67
+ exp_month: expiration_month,
68
+ exp_year: expiration_year,
69
+ cvc: cvc,
70
+ },
71
+ }
72
+
73
+ form_encoded_params = EasyPost::Util.form_encode_params(credit_card_hash)
74
+
75
+ uri = URI.parse('https://api.stripe.com/v1/tokens')
76
+ http = Net::HTTP.new(uri.host, uri.port)
77
+ http.use_ssl = true
78
+ request = Net::HTTP::Post.new(uri.request_uri, headers)
79
+ query = URI.encode_www_form(form_encoded_params)
80
+
81
+ response = http.request(request, query)
82
+ response_json = JSON.parse(response.body)
83
+ response_json['id']
84
+ end
85
+
86
+ # Submit Stripe credit card token to EasyPost.
87
+ def self.create_easypost_credit_card(referral_api_key, stripe_object_id, priority = 'primary')
88
+ wrapped_params = {
89
+ credit_card: {
90
+ stripe_object_id: stripe_object_id,
91
+ priority: priority,
92
+ },
93
+ }
94
+ response = EasyPost.make_request(:post, '/beta/credit_cards', referral_api_key, wrapped_params)
95
+ EasyPost::Util.convert_to_easypost_object(response, referral_api_key)
96
+ end
97
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyPost::Beta
4
+ end
5
+
6
+ require_relative 'beta/end_shipper'
7
+ require_relative 'beta/referral'
@@ -10,6 +10,10 @@ EasyPost::Connection = Struct.new(:uri, :config, keyword_init: true) do
10
10
  # @raise [EasyPost::Error] if the response has a non-2xx status code
11
11
  # @return [Hash] JSON object parsed from the response body
12
12
  def call(method, path, api_key = nil, body = nil)
13
+ if api_key.nil?
14
+ raise EasyPost::Error, 'No API key provided.'
15
+ end
16
+
13
17
  connection =
14
18
  if config[:proxy]
15
19
  proxy_uri = URI(config[:proxy])
@@ -51,11 +55,12 @@ EasyPost::Connection = Struct.new(:uri, :config, keyword_init: true) do
51
55
  request['Authorization'] = EasyPost.authorization(api_key) if api_key
52
56
 
53
57
  response = connection.request(request)
58
+ response_is_json = response['Content-Type'] ? response['Content-Type'].start_with?('application/json') : false
54
59
 
55
60
  EasyPost.parse_response(
56
61
  status: response.code.to_i,
57
62
  body: response.body,
58
- json: response['Content-Type'].start_with?('application/json'),
63
+ json: response_is_json,
59
64
  )
60
65
  end
61
66
  end
@@ -29,4 +29,9 @@ class EasyPost::Order < EasyPost::Resource
29
29
  def self.all
30
30
  raise NotImplementedError.new('Order.all not implemented.')
31
31
  end
32
+
33
+ # Get the lowest rate of an Order (can exclude by having `'!'` as the first element of your optional filter lists).
34
+ def lowest_rate(carriers = [], services = [])
35
+ EasyPost::Util.get_lowest_object_rate(self, carriers, services)
36
+ end
32
37
  end
@@ -29,4 +29,9 @@ class EasyPost::Pickup < EasyPost::Resource
29
29
  def self.all
30
30
  raise NotImplementedError.new('Pickup.all not implemented.')
31
31
  end
32
+
33
+ # Get the lowest rate of a Pickup (can exclude by having `'!'` as the first element of your optional filter lists).
34
+ def lowest_rate(carriers = [], services = [])
35
+ EasyPost::Util.get_lowest_object_rate(self, carriers, services, 'pickup_rates')
36
+ end
32
37
  end
@@ -5,6 +5,7 @@ class EasyPost::Report < EasyPost::Resource
5
5
  # Create a Report.
6
6
  def self.create(params = {}, api_key = nil)
7
7
  url = "#{self.url}/#{params[:type]}"
8
+
8
9
  wrapped_params = {}
9
10
  wrapped_params[class_name.to_sym] = params
10
11
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  # The workhorse of the EasyPost API, a Shipment is made up of a "to" and "from" Address, the Parcel
4
6
  # being shipped, and any customs forms required for international deliveries.
5
7
  class EasyPost::Shipment < EasyPost::Resource
@@ -68,58 +70,46 @@ class EasyPost::Shipment < EasyPost::Resource
68
70
  self
69
71
  end
70
72
 
71
- # Get the lowest rate of a Shipment.
73
+ # Get the lowest rate of a Shipment (can exclude by having `'!'` as the first element of your optional filter lists).
72
74
  def lowest_rate(carriers = [], services = [])
73
- lowest = nil
74
-
75
- get_rates unless rates
75
+ EasyPost::Util.get_lowest_object_rate(self, carriers, services)
76
+ end
76
77
 
77
- carriers = EasyPost::Util.normalize_string_list(carriers)
78
+ # Get the lowest smartrate of a Shipment.
79
+ def lowest_smartrate(delivery_days, delivery_accuracy)
80
+ smartrates = get_smartrates
81
+ EasyPost::Shipment.get_lowest_smartrate(smartrates, delivery_days, delivery_accuracy)
82
+ end
78
83
 
79
- negative_carriers = []
80
- carriers_copy = carriers.clone
81
- carriers_copy.each do |carrier|
82
- if carrier[0, 1] == '!'
83
- negative_carriers << carrier[1..-1]
84
- carriers.delete(carrier)
85
- end
84
+ # Get the lowest smartrate from a list of smartrates.
85
+ def self.get_lowest_smartrate(smartrates, delivery_days, delivery_accuracy)
86
+ valid_delivery_accuracy_values = Set[
87
+ 'percentile_50',
88
+ 'percentile_75',
89
+ 'percentile_85',
90
+ 'percentile_90',
91
+ 'percentile_95',
92
+ 'percentile_97',
93
+ 'percentile_99',
94
+ ]
95
+ lowest_smartrate = nil
96
+
97
+ unless valid_delivery_accuracy_values.include?(delivery_accuracy.downcase)
98
+ raise EasyPost::Error.new("Invalid delivery accuracy value, must be one of: #{valid_delivery_accuracy_values}")
86
99
  end
87
100
 
88
- services = EasyPost::Util.normalize_string_list(services)
101
+ smartrates.each do |rate|
102
+ next if rate['time_in_transit'][delivery_accuracy] > delivery_days.to_i
89
103
 
90
- negative_services = []
91
- services_copy = services.clone
92
- services_copy.each do |service|
93
- if service[0, 1] == '!'
94
- negative_services << service[1..-1]
95
- services.delete(service)
104
+ if lowest_smartrate.nil? || rate['rate'].to_f < lowest_smartrate['rate'].to_f
105
+ lowest_smartrate = rate
96
106
  end
97
107
  end
98
108
 
99
- rates.each do |k|
100
- rate_carrier = k.carrier.downcase
101
- if carriers.size.positive? && !carriers.include?(rate_carrier)
102
- next
103
- end
104
- if negative_carriers.size.positive? && negative_carriers.include?(rate_carrier)
105
- next
106
- end
107
-
108
- rate_service = k.service.downcase
109
- if services.size.positive? && !services.include?(rate_service)
110
- next
111
- end
112
- if negative_services.size.positive? && negative_services.include?(rate_service)
113
- next
114
- end
115
-
116
- if lowest.nil? || k.rate.to_f < lowest.rate.to_f
117
- lowest = k
118
- end
109
+ if lowest_smartrate.nil?
110
+ raise EasyPost::Error.new('No rates found.')
119
111
  end
120
112
 
121
- raise EasyPost::Error.new('No rates found.') if lowest.nil?
122
-
123
- lowest
113
+ lowest_smartrate
124
114
  end
125
115
  end
data/lib/easypost/user.rb CHANGED
@@ -22,6 +22,12 @@ class EasyPost::User < EasyPost::Resource
22
22
  self
23
23
  end
24
24
 
25
+ # Delete a User.
26
+ def delete
27
+ EasyPost.make_request(:delete, url, @api_key)
28
+ self
29
+ end
30
+
25
31
  # Retrieve the authenticated User.
26
32
  def self.retrieve_me
27
33
  all
data/lib/easypost/util.rb CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  # Internal utilities helpful for this libraries operation.
4
4
  module EasyPost::Util
5
+ # Form-encode a multi-layer dictionary to a one-layer dictionary.
6
+ def self.form_encode_params(hash, parent_keys = [], parent_dict = {})
7
+ result = parent_dict or {}
8
+ keys = parent_keys or []
9
+
10
+ hash.each do |key, value|
11
+ if value.instance_of?(Hash)
12
+ keys << key
13
+ result = form_encode_params(value, keys, result)
14
+ else
15
+ dict_key = build_dict_key(keys + [key])
16
+ result[dict_key] = value
17
+ end
18
+ end
19
+ result
20
+ end
21
+
22
+ # Build a dict key from a list of keys.
23
+ # Example: [code, number] -> code[number]
24
+ def self.build_dict_key(keys)
25
+ result = keys[0].to_s
26
+
27
+ keys[1..-1].each do |key|
28
+ result += "[#{key}]"
29
+ end
30
+
31
+ result
32
+ end
33
+
5
34
  # Converts an object to an object ID.
6
35
  def self.objects_to_ids(obj)
7
36
  case obj
@@ -33,6 +62,7 @@ module EasyPost::Util
33
62
  'CarrierAccount' => EasyPost::CarrierAccount,
34
63
  'CustomsInfo' => EasyPost::CustomsInfo,
35
64
  'CustomsItem' => EasyPost::CustomsItem,
65
+ 'EndShipper' => EasyPost::Beta::EndShipper,
36
66
  'Event' => EasyPost::Event,
37
67
  'Insurance' => EasyPost::Insurance,
38
68
  'Order' => EasyPost::Order,
@@ -42,14 +72,15 @@ module EasyPost::Util
42
72
  'PickupRate' => EasyPost::PickupRate,
43
73
  'PostageLabel' => EasyPost::PostageLabel,
44
74
  'Rate' => EasyPost::Rate,
75
+ 'Referral' => EasyPost::Beta::Referral,
45
76
  'Refund' => EasyPost::Refund,
46
77
  'RefundReport' => EasyPost::Report,
47
78
  'Report' => EasyPost::Report,
48
79
  'ScanForm' => EasyPost::ScanForm,
49
80
  'Shipment' => EasyPost::Shipment,
50
- 'TaxIdentifier' => EasyPost::TaxIdentifier,
51
81
  'ShipmentInvoiceReport' => EasyPost::Report,
52
82
  'ShipmentReport' => EasyPost::Report,
83
+ 'TaxIdentifier' => EasyPost::TaxIdentifier,
53
84
  'Tracker' => EasyPost::Tracker,
54
85
  'TrackerReport' => EasyPost::Report,
55
86
  'User' => EasyPost::User,
@@ -63,6 +94,7 @@ module EasyPost::Util
63
94
  'ca' => EasyPost::CarrierAccount,
64
95
  'cstinfo' => EasyPost::CustomsInfo,
65
96
  'cstitem' => EasyPost::CustomsItem,
97
+ 'es' => EasyPost::Beta::EndShipper,
66
98
  'evt' => EasyPost::Event,
67
99
  'hook' => EasyPost::Webhook,
68
100
  'ins' => EasyPost::Insurance,
@@ -110,4 +142,56 @@ module EasyPost::Util
110
142
  response
111
143
  end
112
144
  end
145
+
146
+ # Gets the lowest rate of an EasyPost object such as a Shipment, Order, or Pickup.
147
+ # You can exclude by having `'!'` as the first element of your optional filter lists
148
+ def self.get_lowest_object_rate(easypost_object, carriers = [], services = [], rates_key = 'rates')
149
+ lowest_rate = nil
150
+
151
+ carriers = EasyPost::Util.normalize_string_list(carriers)
152
+ negative_carriers = []
153
+ carriers_copy = carriers.clone
154
+ carriers_copy.each do |carrier|
155
+ if carrier[0, 1] == '!'
156
+ negative_carriers << carrier[1..-1]
157
+ carriers.delete(carrier)
158
+ end
159
+ end
160
+
161
+ services = EasyPost::Util.normalize_string_list(services)
162
+ negative_services = []
163
+ services_copy = services.clone
164
+ services_copy.each do |service|
165
+ if service[0, 1] == '!'
166
+ negative_services << service[1..-1]
167
+ services.delete(service)
168
+ end
169
+ end
170
+
171
+ easypost_object[rates_key].each do |rate|
172
+ rate_carrier = rate.carrier.downcase
173
+ if carriers.size.positive? && !carriers.include?(rate_carrier)
174
+ next
175
+ end
176
+ if negative_carriers.size.positive? && negative_carriers.include?(rate_carrier)
177
+ next
178
+ end
179
+
180
+ rate_service = rate.service.downcase
181
+ if services.size.positive? && !services.include?(rate_service)
182
+ next
183
+ end
184
+ if negative_services.size.positive? && negative_services.include?(rate_service)
185
+ next
186
+ end
187
+
188
+ if lowest_rate.nil? || rate.rate.to_f < lowest_rate.rate.to_f
189
+ lowest_rate = rate
190
+ end
191
+ end
192
+
193
+ raise EasyPost::Error.new('No rates found.') if lowest_rate.nil?
194
+
195
+ lowest_rate
196
+ end
113
197
  end
@@ -13,7 +13,7 @@ class EasyPost::Webhook < EasyPost::Resource
13
13
  instance_url = "#{self.class.url}/#{CGI.escape(id)}"
14
14
 
15
15
  response = EasyPost.make_request(:put, instance_url, @api_key, params)
16
- refresh_from(response, api_key, true)
16
+ refresh_from(response, api_key)
17
17
 
18
18
  self
19
19
  end
data/lib/easypost.rb CHANGED
@@ -36,6 +36,7 @@ require 'easypost/tax_identifier'
36
36
  require 'easypost/tracker'
37
37
  require 'easypost/user'
38
38
  require 'easypost/webhook'
39
+ require 'easypost/beta'
39
40
 
40
41
  module EasyPost
41
42
  DEFAULT_API_BASE = 'https://api.easypost.com'
@@ -133,7 +134,7 @@ module EasyPost
133
134
  )
134
135
  end
135
136
 
136
- json ? JSON.parse(body) : body
137
+ json || !body.nil? && !body.match(/\A\s+\z/) ? JSON.parse(body) : body
137
138
  rescue JSON::ParserError
138
139
  raise "Invalid response object from API, unable to decode.\n#{body}"
139
140
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easypost
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.2
4
+ version: 4.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - EasyPost Developers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-16 00:00:00.000000000 Z
11
+ date: 2022-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -56,16 +56,16 @@ dependencies:
56
56
  name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - '='
60
60
  - !ruby/object:Gem::Version
61
- version: '1.24'
61
+ version: '1.27'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - '='
67
67
  - !ruby/object:Gem::Version
68
- version: '1.24'
68
+ version: '1.27'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rubocop-rspec
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -112,14 +112,14 @@ dependencies:
112
112
  name: vcr
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - "~>"
115
+ - - '='
116
116
  - !ruby/object:Gem::Version
117
117
  version: '6.0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - "~>"
122
+ - - '='
123
123
  - !ruby/object:Gem::Version
124
124
  version: '6.0'
125
125
  - !ruby/object:Gem::Dependency
@@ -143,14 +143,21 @@ executables:
143
143
  extensions: []
144
144
  extra_rdoc_files: []
145
145
  files:
146
+ - ".github/ISSUE_TEMPLATE/bug_report.yml"
147
+ - ".github/ISSUE_TEMPLATE/feature_request.yml"
148
+ - ".github/PULL_REQUEST_TEMPLATE.md"
146
149
  - ".github/workflows/ci.yml"
147
150
  - ".gitignore"
148
151
  - ".rubocop.yml"
149
152
  - CHANGELOG.md
153
+ - CODE_OF_CONDUCT.md
154
+ - CONTRIBUTING.md
150
155
  - Gemfile
151
156
  - LICENSE
152
157
  - README.md
153
158
  - Rakefile
159
+ - SECURITY.md
160
+ - SUPPORT.md
154
161
  - UPGRADE_GUIDE.md
155
162
  - VERSION
156
163
  - bin/easypost-irb
@@ -160,6 +167,9 @@ files:
160
167
  - lib/easypost/address.rb
161
168
  - lib/easypost/api_key.rb
162
169
  - lib/easypost/batch.rb
170
+ - lib/easypost/beta.rb
171
+ - lib/easypost/beta/end_shipper.rb
172
+ - lib/easypost/beta/referral.rb
163
173
  - lib/easypost/brand.rb
164
174
  - lib/easypost/carrier_account.rb
165
175
  - lib/easypost/carrier_type.rb
@@ -206,7 +216,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
216
  - !ruby/object:Gem::Version
207
217
  version: '0'
208
218
  requirements: []
209
- rubygems_version: 3.3.3
219
+ rubygems_version: 3.3.7
210
220
  signing_key:
211
221
  specification_version: 4
212
222
  summary: EasyPost Ruby Client Library