easypost 4.1.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b2de1c12852ffd59083d6ee132f2071577e7ff774f77cb515b444f75eaf86fc
4
- data.tar.gz: 98b1d4bce4a86d9cdf3480f91e818dc362af2963e38f9bccb434b557aebf6bcc
3
+ metadata.gz: 18f330382213345312caf4cb5981ad506f3d882aaf2f78f556598d5201e28d94
4
+ data.tar.gz: 4a750f473664d70f8d72f252a875e4eab75d256ffe0925fc5027915ecfc4cd3e
5
5
  SHA512:
6
- metadata.gz: 97fceef78d0d5706cd0ddb553f6da95ad5a27fe2491069f2eb416dafcb296a158d6e03bab6232ef0efe63f7503a256830635847288e99f17129920adade55eae
7
- data.tar.gz: 2d3a0058d85435a3b743ef9f1b3f21e42bd77bf5cf830f5522794369f27315a9ca673f65fb9f3900df718668d2207ed63c295b7f36f415979cd53dae8cfe0110
6
+ metadata.gz: 96e3c87b8ff96f797ca25e42e2dc61ce8c814826fca95b7f80506aa96624a5df58fe0f762fb87eab71f0e89422996d31d88f153b434c11268b220893731438a1
7
+ data.tar.gz: 2ea7ea89d3cedea7222de7cbe41af96ef0b912b23e0409cfa2ffc5576915553ed578f7dfb315d0870bf5e6391ed5a7eff396eed7972db4e814223faa3152bc56
@@ -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,7 +13,7 @@ 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:
@@ -25,11 +25,11 @@ jobs:
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,12 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v4.2.0 (2022-05-09)
4
+
5
+ - Adds a `lowest_rate()` function to Orders and Pickups
6
+ - Adds a `Shipment.get_lowest_smartrate()` function and a `shipment.lowest_smartrate()` function
7
+ - Removes the unusable `carrier` param from `Address.create_and_verify` along with the dead `message` conditional check
8
+ - Add beta Referral class for White Label API with these new functions: `create()`, `update_email()`, `all()`, and `add_credit_card()`
9
+
3
10
  ## v4.1.2 (2022-03-16)
4
11
 
5
12
  - 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
25
 
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
-
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
83
- )
84
-
85
- shipment.buy(
86
- :rate => shipment.lowest_rate
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
+ },
87
53
  )
88
54
 
89
- shipment.insure(amount: 100)
55
+ shipment.buy(rate: shipment.lowest_rate)
90
56
 
91
- puts shipment.insurance
92
-
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,27 @@ 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
+ 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.
130
+
131
+ 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** which has a fallback value to our internal testing user's ID. If you are a non-EasyPost employee and are re-recording cassettes, you may need to provide the `USPS_CARRIER_ACCOUNT_ID` environment variable with the ID associated with your USPS account (which will be associated with your API keys in use) for tests that use this fixture.
132
+
133
+ **Note on dates:** Some fixtures use hard-coded dates that may need to be incremented if cassettes get re-recorded (such as reports or pickups).
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.2.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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'easypost/beta/beta_referral'
4
+
5
+ module EasyPost::Beta
6
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EasyPost::BetaReferral < EasyPost::Resource
4
+ # Create a referral user. This function requires the Partner User's API key.
5
+ def self.create(params = {}, api_key = nil)
6
+ response = EasyPost.make_request(:post, '/beta/referral_customers', api_key, { user: params })
7
+ EasyPost::Util.convert_to_easypost_object(response, api_key)
8
+ end
9
+
10
+ # Update a referral user. This function requires the Partner User's API key.
11
+ def self.update_email(email, user_id, api_key = nil)
12
+ wrapped_params = {
13
+ user: {
14
+ email: email,
15
+ },
16
+ }
17
+ _ = EasyPost.make_request(:put, "/beta/referral_customers/#{user_id}", api_key, wrapped_params)
18
+
19
+ # return true if API succeeds, else an error is throw if it fails.
20
+ true
21
+ end
22
+
23
+ # Retrieve a list of referral users. This function requires the Partner User's API key.
24
+ def self.all(params = {}, api_key = nil)
25
+ response = EasyPost.make_request(:get, '/beta/referral_customers', api_key, params)
26
+ EasyPost::Util.convert_to_easypost_object(response, api_key)
27
+ end
28
+
29
+ # Add credit card to a referral user. This function requires the Referral User's API key.
30
+ def self.add_credit_card(referral_api_key, number, expiration_month, expiration_year, cvc, priority = 'primary')
31
+ easypost_stripe_api_key = retrieve_easypost_stripe_api_key
32
+
33
+ begin
34
+ stripe_credit_card_token = create_stripe_token(
35
+ number,
36
+ expiration_month,
37
+ expiration_year,
38
+ cvc,
39
+ easypost_stripe_api_key,
40
+ )
41
+ rescue StandardError
42
+ raise EasyPost::Error.new('Could not send card details to Stripe, please try again later.')
43
+ end
44
+
45
+ response = create_easypost_credit_card(referral_api_key, stripe_credit_card_token, priority)
46
+ EasyPost::Util.convert_to_easypost_object(response, referral_api_key)
47
+ end
48
+
49
+ # Retrieve EasyPost's Stripe public API key.
50
+ def self.retrieve_easypost_stripe_api_key
51
+ response = EasyPost.make_request(:get, '/beta/partners/stripe_public_key', @api_key)
52
+ response['public_key']
53
+ end
54
+
55
+ # Get credit card token from Stripe.
56
+ def self.create_stripe_token(number, expiration_month, expiration_year, cvc, easypost_stripe_token)
57
+ headers = {
58
+ # This Stripe endpoint only accepts URL form encoded bodies.
59
+ Authorization: "Bearer #{easypost_stripe_token}",
60
+ 'Content-type': 'application/x-www-form-urlencoded',
61
+ }
62
+
63
+ credit_card_hash = {
64
+ card: {
65
+ number: number,
66
+ exp_month: expiration_month,
67
+ exp_year: expiration_year,
68
+ cvc: cvc,
69
+ },
70
+ }
71
+
72
+ form_encoded_params = EasyPost::Util.form_encode_params(credit_card_hash)
73
+
74
+ uri = URI.parse('https://api.stripe.com/v1/tokens')
75
+ http = Net::HTTP.new(uri.host, uri.port)
76
+ http.use_ssl = true
77
+ request = Net::HTTP::Post.new(uri.request_uri, headers)
78
+ query = URI.encode_www_form(form_encoded_params)
79
+
80
+ response = http.request(request, query)
81
+ response_json = JSON.parse(response.body)
82
+ response_json['id']
83
+ end
84
+
85
+ # Submit Stripe credit card token to EasyPost.
86
+ def self.create_easypost_credit_card(referral_api_key, stripe_object_id, priority = 'primary')
87
+ wrapped_params = {
88
+ credit_card: {
89
+ stripe_object_id: stripe_object_id,
90
+ priority: priority,
91
+ },
92
+ }
93
+ response = EasyPost.make_request(:post, '/beta/credit_cards', referral_api_key, wrapped_params)
94
+ EasyPost::Util.convert_to_easypost_object(response, referral_api_key)
95
+ end
96
+ 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/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
@@ -110,4 +139,56 @@ module EasyPost::Util
110
139
  response
111
140
  end
112
141
  end
142
+
143
+ # Gets the lowest rate of an EasyPost object such as a Shipment, Order, or Pickup.
144
+ # You can exclude by having `'!'` as the first element of your optional filter lists
145
+ def self.get_lowest_object_rate(easypost_object, carriers = [], services = [], rates_key = 'rates')
146
+ lowest_rate = nil
147
+
148
+ carriers = EasyPost::Util.normalize_string_list(carriers)
149
+ negative_carriers = []
150
+ carriers_copy = carriers.clone
151
+ carriers_copy.each do |carrier|
152
+ if carrier[0, 1] == '!'
153
+ negative_carriers << carrier[1..-1]
154
+ carriers.delete(carrier)
155
+ end
156
+ end
157
+
158
+ services = EasyPost::Util.normalize_string_list(services)
159
+ negative_services = []
160
+ services_copy = services.clone
161
+ services_copy.each do |service|
162
+ if service[0, 1] == '!'
163
+ negative_services << service[1..-1]
164
+ services.delete(service)
165
+ end
166
+ end
167
+
168
+ easypost_object[rates_key].each do |rate|
169
+ rate_carrier = rate.carrier.downcase
170
+ if carriers.size.positive? && !carriers.include?(rate_carrier)
171
+ next
172
+ end
173
+ if negative_carriers.size.positive? && negative_carriers.include?(rate_carrier)
174
+ next
175
+ end
176
+
177
+ rate_service = rate.service.downcase
178
+ if services.size.positive? && !services.include?(rate_service)
179
+ next
180
+ end
181
+ if negative_services.size.positive? && negative_services.include?(rate_service)
182
+ next
183
+ end
184
+
185
+ if lowest_rate.nil? || rate.rate.to_f < lowest_rate.rate.to_f
186
+ lowest_rate = rate
187
+ end
188
+ end
189
+
190
+ raise EasyPost::Error.new('No rates found.') if lowest_rate.nil?
191
+
192
+ lowest_rate
193
+ end
113
194
  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/beta'
39
40
 
40
41
  module EasyPost
41
42
  DEFAULT_API_BASE = 'https://api.easypost.com'
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.2.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-09 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,8 @@ files:
160
167
  - lib/easypost/address.rb
161
168
  - lib/easypost/api_key.rb
162
169
  - lib/easypost/batch.rb
170
+ - lib/easypost/beta/beta.rb
171
+ - lib/easypost/beta/beta_referral.rb
163
172
  - lib/easypost/brand.rb
164
173
  - lib/easypost/carrier_account.rb
165
174
  - lib/easypost/carrier_type.rb
@@ -206,7 +215,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
215
  - !ruby/object:Gem::Version
207
216
  version: '0'
208
217
  requirements: []
209
- rubygems_version: 3.3.3
218
+ rubygems_version: 3.3.7
210
219
  signing_key:
211
220
  specification_version: 4
212
221
  summary: EasyPost Ruby Client Library