Skip to content

Commit

Permalink
Rewrite the entire gem.
Browse files Browse the repository at this point in the history
- No longer require the user to download and process the fedex wsdl files!
- Better expose the SOAP classes at the library boundary.
- Simplify building out the request contents.
- By default, have request objects constructed with entirely blank request contents to allow the user to modify any part of the request.
- Add helper methods to optionally fill in request contents for common use cases.
  • Loading branch information
brewski committed Oct 15, 2015
1 parent c556b13 commit 6918818
Show file tree
Hide file tree
Showing 39 changed files with 22,363 additions and 724 deletions.
167 changes: 43 additions & 124 deletions README.md
@@ -1,103 +1,34 @@
# fedex-web-services
## Description
This gem provides an interface to the FedEx web services API. It supports version 12 of the ship service and version 13 of the rate service (the current versions as of November 2012). It interfaces with the FedEx web services SOAP API to look up shipping rates, generate labels, and cancel shipments (tracking coming soon).

## Setup
### Overview
This gem requires a large number of classes to communicate with FedEx. These classes are defined by the WSDL files for the FedEx web services API. For copyright reasons this gem does not include the files. You will need to create a FedEx developer account to download these files (this gem works with ShipService_v12.wsdl and RateService_v13.wsdl). I recommend putting them under your project's lib/ directory in lib/fedex/web_services/wsdl.

### Creating the class definitions
Once you have the WSDL files, you will need to create the ruby classes used in the SOAP requests. This is a one time process that can be handled by the gem.

#### Rails
This gem includes a generator to create the class definitions and an initializer to load them if you are using it from within a Rails application. Simply save the FedEx wsdl files to a directory (lib/fedex/web_services/wsdl in this example) and include the gem in your Gemfile:

```ruby
gem 'fedex-web-services', :require => 'fedex'
```

Then run the following generator:

$ rails g fedex:generate_definitions
Added /Users/brewski/nutsonline.com/git/kernelweb/lib/fedex/web_services/definitions
create config/initializers/fedex.rb

For more details, run the generator with the --help option:

$ rails g fedex:generate_definitions --help

#### Manual creation
You can also manually generate the class files. To to this, run the following command:

```ruby
require 'fedex'
Fedex::WebServices::Definitions.generate_definitions('lib', *Dir.glob('lib/fedex/web_services/wsdl/*.wsdl'))
```

This will create the directory lib/fedex/web_services/definitions/ with the FedEx web services class definitions in it. After you have created the classes, simply include the following lines in your application to load them:

```ruby
require 'fedex'
Fedex::WebServices::Definitions.load_definitions('lib')
```
This gem provides an interface to the FedEx web services API. It supports version 12 of the ship service and version 4 of the close service.

## Examples
### Getting shipping rates
### Creating a shipment with multiple packages

```ruby
require 'fedex'
# config/initializers/fedex.rb handles this if you are in a Rails app and have run the generator above
# Fedex::WebServices::Definitions.load_definitions('lib')
require 'fedex_web_services'

include Fedex::WebServices
include Fedex::WebServices::Definitions
include FedexWebServices
include FedexWebServices::Soap

credentials = Service::Base::Credentials.new(
"ACCOUNT#",
"METER#",
"AUTH_KEY",
"SECURITY_CODE",
:test
credentials = Api::Credentials.new(
ENV.fetch('FEDEX_ACCOUNT'),
ENV.fetch('FEDEX_METER'),
ENV.fetch('FEDEX_AUTH_KEY'),
ENV.fetch('FEDEX_SECURITY_CODE'),
:test # or :production
)
api = Api.new(credentials)

# prod_credentials = Service::Base::Credentials.new(
# "ACCOUNT#",
# "METER#",
# "AUTH_KEY",
# "SECURITY_CODE",
# :production
# )

from = Address.new
from.postalCode = "93401"
from.countryCode = "US"
from.residential = true
service = Ship::ServiceType::FEDEX_2_DAY

to = Address.new
to.postalCode = "95630"
to.countryCode = "US"
to.residential = true

weight = Weight.new
weight.units = "LB"
weight.value = 42.42

rate_service = Service::Rate.new(credentials)
rate, response = rate_service.get_rates(
ServiceType::FEDEX_2_DAY, RateRequestType::LIST, from, to, weight
)
puts "List rate for 42.42 lbs, 2 day from 93401 to 95630: #{rate.to_f}"
```

### Creating a shipment with multiple packages

```ruby
shipper = Party.new.tap do |shipper|
shipper.contact = Contact.new.tap do |contact|
from = Ship::Party.new.tap do |shipper|
shipper.contact = Ship::Contact.new.tap do |contact|
contact.personName = "Joe Shmoe"
contact.phoneNumber = "(123) 456 789"
end
shipper.address = Address.new.tap do |address|

shipper.address = Ship::Address.new.tap do |address|
address.streetLines = [ "123 4th St" ]
address.city = "San Luis Obispo"
address.stateOrProvinceCode = "CA"
Expand All @@ -107,12 +38,12 @@ shipper = Party.new.tap do |shipper|
end
end

recipient = Party.new.tap do |recipient|
recipient.contact = Contact.new.tap do |contact|
to = Ship::Party.new.tap do |recipient|
recipient.contact = Ship::Contact.new.tap do |contact|
contact.personName = "Ahwahnee Hotel"
contact.phoneNumber = "(801) 559-5000"
end
recipient.address = Address.new.tap do |address|
recipient.address = Ship::Address.new.tap do |address|
address.streetLines = [ "9006 Yosemite Lodge Drive" ]
address.city = "Yosemite National Park"
address.stateOrProvinceCode = "CA"
Expand All @@ -122,63 +53,51 @@ recipient = Party.new.tap do |recipient|
end
end

label_specification = LabelSpecification.new
label_specification.labelFormatType = LabelFormatType::COMMON2D
label_specification.imageType = ShippingDocumentImageType::PDF
label_specification.labelStockType = ShippingDocumentStockType::PAPER_LETTER
label_spec = Ship::LabelSpecification.new
label_spec.labelFormatType = Ship::LabelFormatType::COMMON2D
label_spec.imageType = Ship::ShippingDocumentImageType::PDF
label_spec.labelStockType = Ship::ShippingDocumentStockType::PAPER_LETTER

weights = [ 55.34, 10.2 ].map do |weight|
Weight.new.tap do |w|
weights = [ 10, 55.34, 10.2 ].map do |weight|
Ship::Weight.new.tap do |w|
w.units = "LB"
w.value = weight
end
end

ship_service = Service::Ship.new(credentials)

responses = ship_service.process_shipment(
ServiceType::FEDEX_2_DAY, shipper, recipient, label_specification, weights
) do |request_contents|
request_contents.requestedShipment.requestedPackageLineItems.each do |package_line_item|
package_line_item.customerReferences = [
CustomerReference.new.tap do |customer_reference|
customer_reference.customerReferenceType = CustomerReferenceType::INVOICE_NUMBER
customer_reference.value = "INVOICE 1234"
end
]
end
requests = ProcessShipmentRequest.shipment_requests(service, from, to, label_spec, weights)
requests.each do |request|
request.sender_paid!(credentials.account_number)
request.list_rate!
request.regular_pickup!
end

tracking_numbers = responses.map do |(tracking_number, label, charge)|
puts "tracking number: #{tracking_number}"
puts "charge: #{charge.to_f}"
File.open("#{tracking_number}.pdf", "w") { |f| f << label }
tracking_number
tracking_numbers = api.process_shipments(requests).map do |response|
filename = "#{response.tracking_number}.pdf"
File.write(filename, response.label)
puts "Wrote #{filename}"
response.tracking_number
end
```

### Canceling a shipment

```ruby
tracking_numbers.each do |tracking_number|
ship_service.delete_shipment(
TrackingId.new.tap do |tracking_id|
tracking_id.trackingNumber = tracking_number
tracking_id.trackingIdType = TrackingIdType::EXPRESS
end
)
delete_request = DeleteShipmentRequest.new
delete_request.delete_all_packages!(tracking_number, Ship::TrackingIdType::EXPRESS)
api.delete_shipment(delete_request)
puts "Deleted shipment #{tracking_number}"
end
```

### Debugging
You can see the SOAP wiredump by accessing Service::Base#wiredump after issuing a request.
You can see the SOAP wiredump by accessing Api#wiredump after issuing a request.
```ruby
begin
rate, response = rate_service.get_rates(
ServiceType::FEDEX_2_DAY, RateRequestType::LIST, from, to, weight
)
api.process_shipments(...)
rescue
puts rate_service.wiredump
puts api.wiredump
raise $!
end
```
2 changes: 2 additions & 0 deletions Rakefile
@@ -1 +1,3 @@
require "bundler/gem_tasks"

Dir.glob(File.expand_path("../tasks/*.rake", __FILE__)).each(&method(:import))
79 changes: 79 additions & 0 deletions examples/create_shipments.rb
@@ -0,0 +1,79 @@
require 'fedex_web_services'

include FedexWebServices
include FedexWebServices::Soap

credentials = Api::Credentials.new(
ENV.fetch('FEDEX_ACCOUNT'),
ENV.fetch('FEDEX_METER'),
ENV.fetch('FEDEX_AUTH_KEY'),
ENV.fetch('FEDEX_SECURITY_CODE'),
:test # or :production
)
api = Api.new(credentials)

service = Ship::ServiceType::FEDEX_2_DAY

from = Ship::Party.new.tap do |shipper|
shipper.contact = Ship::Contact.new.tap do |contact|
contact.personName = "Joe Shmoe"
contact.phoneNumber = "(123) 456 789"
end

shipper.address = Ship::Address.new.tap do |address|
address.streetLines = [ "123 4th St" ]
address.city = "San Luis Obispo"
address.stateOrProvinceCode = "CA"
address.postalCode = "93401"
address.countryCode = "US"
address.residential = true
end
end

to = Ship::Party.new.tap do |recipient|
recipient.contact = Ship::Contact.new.tap do |contact|
contact.personName = "Ahwahnee Hotel"
contact.phoneNumber = "(801) 559-5000"
end
recipient.address = Ship::Address.new.tap do |address|
address.streetLines = [ "9006 Yosemite Lodge Drive" ]
address.city = "Yosemite National Park"
address.stateOrProvinceCode = "CA"
address.postalCode = "95389"
address.countryCode = "US"
address.residential = true
end
end

label_spec = Ship::LabelSpecification.new
label_spec.labelFormatType = Ship::LabelFormatType::COMMON2D
label_spec.imageType = Ship::ShippingDocumentImageType::PDF
label_spec.labelStockType = Ship::ShippingDocumentStockType::PAPER_LETTER

weights = [ 10, 55.34, 10.2 ].map do |weight|
Ship::Weight.new.tap do |w|
w.units = "LB"
w.value = weight
end
end

requests = ProcessShipmentRequest.shipment_requests(service, from, to, label_spec, weights)
requests.each do |request|
request.sender_paid!(credentials.account_number)
request.list_rate!
request.regular_pickup!
end

tracking_numbers = api.process_shipments(requests).map do |response|
filename = "#{response.tracking_number}.pdf"
File.write(filename, response.label)
puts "Wrote #{filename}"
response.tracking_number
end

tracking_numbers.each do |tracking_number|
delete_request = DeleteShipmentRequest.new
delete_request.delete_all_packages!(tracking_number, Ship::TrackingIdType::EXPRESS)
api.delete_shipment(delete_request)
puts "Deleted shipment #{tracking_number}"
end
44 changes: 21 additions & 23 deletions fedex-web-services.gemspec
@@ -1,28 +1,26 @@
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "fedex/version"
require "rake"
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'fedex_web_services/version'

Gem::Specification.new do |s|
s.name = "fedex-web-services"
s.version = Fedex::VERSION
s.authors = ["Brian Abreu"]
s.email = ["brian@nut.com"]
s.homepage = "https://github.com/brewski/fedex-web-services"
s.summary = %q{Provies an interface to the FedEx web services API (version 10)}
s.description = %q{Interfaces with the FedEx web services API to look up shipping rates, generate labels, and cancel shipments}
s.license = 'MIT'
Gem::Specification.new do |spec|
spec.name = "fedex-web-services"
spec.version = FedexWebServices::VERSION
spec.authors = ["Brian Abreu"]
spec.email = ["brian@nuts.com"]
spec.description = %q{Interfaces with the FedEx web services API to look up shipping rates, generate labels, and cancel shipments}
spec.summary = %q{Provides an interface to the FedEx web services API}
spec.homepage = "https://github.com/brewski/fedex-web-services"
spec.license = "MIT"

s.rubyforge_project = "fedex-web-services"
spec.files = `git ls-files`.split($/)
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]

s.files = FileList[ "lib/**/*" ]
s.test_files = [ ]
s.executables = [ ]
s.require_paths = ["lib"]
spec.add_dependency "soap4r-ng", '~> 2.0'
spec.required_ruby_version = '>= 2.0.0'

s.add_dependency "soap4r-ng", '~> 2.0'
s.required_ruby_version = '>= 1.9.0'
# specify any dependencies here; for example:
# s.add_development_dependency "rspec"
# s.add_runtime_dependency "rest-client"
spec.add_development_dependency "bundler", "~> 1.3"
spec.add_development_dependency "rake"
end
2 changes: 0 additions & 2 deletions lib/fedex.rb

This file was deleted.

12 changes: 0 additions & 12 deletions lib/fedex/web_services.rb

This file was deleted.

0 comments on commit 6918818

Please sign in to comment.