easypost 4.1.2 → 4.2.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: 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