easypost 4.1.2 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
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