class FriendlyShipping::Services::Usps::ParseTimeInTransitResponse

Constants

COMMITMENT_SEQUENCES

This code carries a few details about the shipment:

  • What USPS commits to (1-Day, 2-Day or 3-Day delivery)

  • what time the package should arrive

  • Whether the package is sent from post office to post office ('Hold For Pickup')

A0110 1-Day at 10:30 AM B0110 1-Day at 10:30 AM HFPU A0112 1-Day at 12:00 PM A0115 1-Day at 3:00 PM B0115 1-Day at 3:00 PM HFPU A0210 2-Day at 10:30 AM A0212 2-Day at 12:00 PM A0215 2-Day at 3:00 PM B0210 2-Day at 10:30 AM HFPU B0215 2-Day at 3:00 PM HFPU C0100 1-Day Street C0200 2-Day Street C0300 3-Day Street D0100 1-Day PO Box D0200 2-Day PO Box D0300 3-Day PO Box E0100 1-Day HFPU E0200 2-Day HFPU E0300 3-Day HFPU

MAIL_CLASSES

The USPS docs say the following:

Valid Values: “0” = All Mail Classes “1” = Priority Mail Express “2” = Priority Mail “3” = First Class Mail “4” = Marketing Mail “5” = Periodicals “6” = Package Services

However, no shipping methods really map to “Marketing Mail” or “Periodicals”. This will likely be somewhat more work in the future.

NON_EXPEDITED_DESTINATION_TYPES

Things are different for non-expedited shipping methods.

Public Class Methods

call(request:, response:) click to toggle source

Parse a response from USPS' time in transit API

@param [FriendlyShipping::Request] request The request that was used to obtain this Response @param [FriendlyShipping::Response] response The response that USPS returned @return [Result<ApiResult<Array<FriendlyShipping::Timing>>>] When successfully parsing, an array of timings in a Success Monad.

# File lib/friendly_shipping/services/usps/parse_time_in_transit_response.rb, line 16
def call(request:, response:)
  # Filter out error responses and directly return a failure
  parsing_result = ParseXMLResponse.call(
    request: request,
    response: response,
    expected_root_tag: 'SDCGetLocationsResponse'
  )
  parsing_result.fmap do |xml|
    expedited_commitments = xml.xpath('//Expedited')
    expedited_timings = parse_expedited_commitment_nodes(expedited_commitments)

    non_expedited_commitments = xml.xpath('//NonExpedited')
    non_expedited_timings = parse_non_expedited_commitment_nodes(non_expedited_commitments)

    ApiResult.new(
      expedited_timings + non_expedited_timings,
      original_request: request,
      original_response: response
    )
  end
end

Private Class Methods

parse_expedited_commitment_nodes(expedited_commitment_nodes) click to toggle source
# File lib/friendly_shipping/services/usps/parse_time_in_transit_response.rb, line 40
def parse_expedited_commitment_nodes(expedited_commitment_nodes)
  return [] if expedited_commitment_nodes.empty?

  # All Expedited Commitments have the same acceptance date
  # However, sometimes that date is invalid.
  effective_acceptance_date = [
    Time.parse(expedited_commitment_nodes.at('EAD').text),
    Time.parse(expedited_commitment_nodes.document.at('AcceptDate').text)
  ].max
  expedited_commitment_nodes.xpath('Commitment').map do |commitment_node|
    shipping_method = SHIPPING_METHODS.detect do |potential_shipping_method|
      potential_shipping_method.name == MAIL_CLASSES[commitment_node.at('MailClass').text]
    end
    commitment_sequence = commitment_node.at('CommitmentSeq').text
    properties = COMMITMENT_SEQUENCES[commitment_sequence]
    next unless properties # Sometimes USPS returns an invalid CommitmentSeq

    scheduled_delivery_time = properties.delete(:commitment_time)
    scheduled_delivery_date = commitment_node.at('SDD').text
    parsed_delivery_time = Time.parse("#{scheduled_delivery_date} #{scheduled_delivery_time}")
    guaranteed = commitment_node.at('IsGuaranteed').text == '1'

    FriendlyShipping::Timing.new(
      shipping_method: shipping_method,
      pickup: effective_acceptance_date,
      delivery: parsed_delivery_time,
      guaranteed: guaranteed,
      properties: properties
    )
  end.compact
end
parse_non_expedited_commitment_nodes(non_expedited_commitment_nodes) click to toggle source
# File lib/friendly_shipping/services/usps/parse_time_in_transit_response.rb, line 72
def parse_non_expedited_commitment_nodes(non_expedited_commitment_nodes)
  non_expedited_commitment_nodes.map do |commitment_node|
    shipping_method = SHIPPING_METHODS.detect do |potential_shipping_method|
      potential_shipping_method.name == MAIL_CLASSES[commitment_node.at('MailClass').text]
    end
    # We cannot find a shipping method for Mail Classes 4 and 5 because USPS' docs are not clear
    next unless shipping_method

    warning_text = commitment_node.xpath('HFPU//NonExpeditedTransMsg/Msg')&.text
    warning = warning_text unless warning_text.empty?

    properties = {
      commitment: commitment_node.at('SvcStdMsg')&.text,
      destination_type: NON_EXPEDITED_DESTINATION_TYPES[commitment_node.at('NonExpeditedDestType').text],
      warning: warning
    }.compact

    scheduled_delivery_date = commitment_node.at('SchedDlvryDate')&.text
    parsed_delivery_time = Time.parse(scheduled_delivery_date) if scheduled_delivery_date
    effective_acceptance_date = Time.parse(commitment_node.at('EAD').text)

    FriendlyShipping::Timing.new(
      shipping_method: shipping_method,
      pickup: effective_acceptance_date,
      delivery: parsed_delivery_time,
      guaranteed: false,
      properties: properties
    )
  end.compact
end