/usr/local/CPAN/Google-Checkout/Google/Checkout/XML/CheckoutXmlWriter.pm


package Google::Checkout::XML::CheckoutXmlWriter;

#--
#-- <checkout-shopping-cart>...</checkout-shopping-cart>
#--

use strict;
use warnings;

use Google::Checkout::XML::Constants;
use Google::Checkout::General::Util qw/make_xml_safe format_tax_rate 
                                       is_gift_certificate_object
				                               is_digital_content/;

use Google::Checkout::General::DigitalContent;

use Google::Checkout::XML::Writer;
our @ISA = qw/Google::Checkout::XML::Writer/;

sub new 
{
  my ($class, %args) = @_;

  delete $args{root};

  my $self = $class->SUPER::new(%args);

  my $xml_schema = '';
  my $currency_supported = '';

  if ($args{gco}->reader()) {

    my $reader = $args{gco}->reader();

    $xml_schema = $reader->get(Google::Checkout::XML::Constants::XML_SCHEMA);
    $currency_supported = $reader->get(Google::Checkout::XML::Constants::CURRENCY_SUPPORTED);

  } else {

    $xml_schema = $args{gco}->{__xml_schema};
    $currency_supported = $args{gco}->{__currency_supported};

  }

  $self->add_element(name => Google::Checkout::XML::Constants::CHECKOUT_ROOT,
                     attr => [xmlns => $xml_schema]);

  if ($args{cart})
  {
    $self->add_element(name => Google::Checkout::XML::Constants::SHOPPING_CART);

    #--
    #-- If the cart has an expiration, add it
    #--
    if ($args{cart}->get_expiration())
    {
      $self->add_element(name => Google::Checkout::XML::Constants::EXPIRATION);
      $self->add_element(name => Google::Checkout::XML::Constants::GOOD_UNTIL, 
                         data => $args{cart}->get_expiration(), close => 1);
      $self->close_element();
    }

    #--
    #-- If we have any merchant private data, add it
    #--
    if ($args{cart}->get_private())
    {
      my @private_data = @{$args{cart}->get_private()};

      $self->add_element(name => Google::Checkout::XML::Constants::MERCHANT_PRIVATE_DATA) 
        if @private_data;

      for my $note (@private_data)
      {
        $self->add_element(name => Google::Checkout::XML::Constants::MERCHANT_PRIVATE_NOTE, 
                           data => $note, close => 1);
      }
      $self->close_element() if @private_data;
    }

    my @items = @{$args{cart}->get_items()};

    $self->add_element(name => Google::Checkout::XML::Constants::ITEMS) if @items;

    for (@items)
    {
      $self->add_element(name => Google::Checkout::XML::Constants::ITEM);
        $self->add_element(name => Google::Checkout::XML::Constants::ITEM_NAME, 
                           data => $_->get_name(), close => 1);
        $self->add_element(name => Google::Checkout::XML::Constants::ITEM_DESCRIPTION,
                           data => $_->get_description(), close => 1);
        $self->add_element(name => Google::Checkout::XML::Constants::ITEM_PRICE,
                           data => $_->get_price(), close => 1,
                           attr => [Google::Checkout::XML::Constants::ITEM_CURRENCY,
                                    $currency_supported]);
        $self->add_element(name => Google::Checkout::XML::Constants::QUANTITY,
                           data =>$_->get_quantity(), close => 1);
        $self->add_element(name => Google::Checkout::XML::Constants::MERCHANT_ITEM_ID,
                           data => $_->get_merchant_item_id(), close => 1)
          if defined $_->get_merchant_item_id();

        if ($_->get_private())
        {
          my @private_data = @{$_->get_private()};

          $self->add_element(name => Google::Checkout::XML::Constants::ITEM_PRIVATE_DATA) 
            if @private_data;

          for my $note (@private_data)
          {
            $self->add_element(name => Google::Checkout::XML::Constants::ITEM_PRIVATE_NOTE, 
                               data => $note, close => 1);
          }
          $self->close_element() if @private_data;
        }

        $self->add_element(name => Google::Checkout::XML::Constants::TAX_TABLE_SELECTOR,
                           data => $_->get_tax_table_selector(), close => 1)
          if $_->get_tax_table_selector();

        #--
        #-- Is this digital delivery?
        #--
        if (Google::Checkout::General::Util::is_digital_content($_)) 
        {
          my $delivery_method = $_->get_delivery_method();

          my $email_delivery   = $delivery_method eq Google::Checkout::General::DigitalContent::EMAIL_DELIVERY;
          my $key_url_delivery = $delivery_method eq Google::Checkout::General::DigitalContent::KEY_URL_DELIVERY;

          $self->add_element(name => Google::Checkout::XML::Constants::DIGITAL_CONTENT)
            if ($email_delivery || $key_url_delivery);

          if ($email_delivery) 
          {
            $self->add_element(name => Google::Checkout::XML::Constants::EMAIL_DELIVERY,
                               data => 'true', close => 1);
          }
          elsif ($key_url_delivery) 
          {
            if ($_->get_download_instruction())
            {
              $self->add_element(name => Google::Checkout::XML::Constants::DOWNLOAD_INSTRUCTION,
                                 data => $_->get_download_instruction(), close => 1);
            }
            if ($_->get_key())
            {
              $self->add_element(name => Google::Checkout::XML::Constants::DOWNLOAD_KEY,
                                 data => $_->get_key(), close => 1);
            }
            if ($_->get_url())
            {
              $self->add_element(name => Google::Checkout::XML::Constants::DOWNLOAD_URL,
                                 data => $_->get_url(), close => 1);
            }
          }

          $self->close_element()
            if ($email_delivery || $key_url_delivery);
        }

      $self->close_element();
    }

    $self->close_element() if @items;

    $self->close_element();
  }

  #--
  #-- Add merchant checkout flow
  #--
  $self->add_element(name => Google::Checkout::XML::Constants::CHECKOUT_FLOW);
  $self->add_element(name => Google::Checkout::XML::Constants::MERCHANT_CHECKOUT_FLOW);

  my $checkout_flow = $args{cart}->get_checkout_flow();

  if ($checkout_flow)
  {
    my $edit_cart_url = $checkout_flow->get_edit_cart_url;
    if ($edit_cart_url)
    {
      $self->add_element(name => Google::Checkout::XML::Constants::EDIT_CART_URL,
                         data => make_xml_safe($edit_cart_url), close => 1);
    }

    my $continue_shopping_url = $checkout_flow->get_continue_shopping_url;
    if ($continue_shopping_url)
    {
      $self->add_element(name => Google::Checkout::XML::Constants::CONTINUE_SHOPPING_URL,
                         data => make_xml_safe($continue_shopping_url), close => 1);
    }

    my $buyer_phone = $checkout_flow->get_buyer_phone;
    if ($buyer_phone)
    {
      $self->add_element(name => Google::Checkout::XML::Constants::BUYER_PHONE_NUMBER,
                         data => "true", close => 1);
    }

    my $analytics_data = $checkout_flow->get_analytics_data;
    if ($analytics_data) {
      $self->add_element(name => Google::Checkout::XML::Constants::ANALYTICS_DATA,
                         data => $analytics_data, close => 1);
    }

    # DEPRECATED
    my $parameterized_url = $checkout_flow->get_parameterized_url;
    if ($parameterized_url && $parameterized_url->get_url) {
      $self->add_element(name => Google::Checkout::XML::Constants::PARAMETERIZED_URLS);
      $self->_add_parameterized_url($parameterized_url);
      $self->close_element();  # Close <parameterized-urls>
    }
    
    my $parameterized_urls = $checkout_flow->get_parameterized_urls;
    if ($parameterized_urls && scalar(@{$parameterized_urls}) > 0)
    {
      $self->add_element(name => Google::Checkout::XML::Constants::PARAMETERIZED_URLS);
      for (@{$parameterized_urls})
      {
        $self->_add_parameterized_url($_);
      }
      $self->close_element();  # Close <parameterized-urls>
    }

    if (defined $checkout_flow->get_platform_id()) {
      $self->add_element(name => Google::Checkout::XML::Constants::PLATFORM_ID, 
                         data => $checkout_flow->get_platform_id(), close => 1);
    }

    #--
    #-- Add tax tables
    #--
    $self->_add_tax_tables($checkout_flow);

    #--
    #-- Add merchant calculations
    #--
    $self->_add_merchant_calculations($checkout_flow);
  }

  my $shippings = $checkout_flow ? $checkout_flow->get_shipping_method() : undef;

  if ($shippings)
  {
    $self->add_element(name => Google::Checkout::XML::Constants::SHIPPING_METHODS) 
      if @{$shippings};

    for my $shipping (@{$shippings})
    {
      #--
      #-- We should also consider raise an error because price is required
      #--
      my $price = $shipping->get_price();
         $price = 0 unless defined $price;

      $self->add_element(name => $shipping->get_name(),
                         attr => [Google::Checkout::XML::Constants::NAME, 
                                  $shipping->get_shipping_name()]);
      $self->add_element(name => Google::Checkout::XML::Constants::PRICE,
                         data => $price, close => 1,
                         attr => [Google::Checkout::XML::Constants::ITEM_CURRENCY,
                                  $currency_supported]);

      my $address_filters = $shipping->get_address_filters();
      $self->_handle_restrictions_filters($address_filters, Google::Checkout::XML::Constants::ADDRESS_FILTERS);

      my $restriction = $shipping->get_restriction();
      $self->_handle_restrictions_filters($restriction, Google::Checkout::XML::Constants::SHIPPING_RESTRICTIONS);

      $self->close_element();

    } #-- END for

    $self->close_element() if @{$shippings};

  }

  $self->close_element(); # MERCHANT_CHECKOUT_FLOW
  $self->close_element(); # CHECKOUT_FLOW
  $self->close_element(); # CHECKOUT_ROOT

  return bless $self => $class;
}

#-- PRIVATE --#
sub _handle_restrictions_filters
{
  my ($self, $i, $type) = @_;
  
  if ($i)
  {
    my $as = $i->get_allowed_state();
    my $az = $i->get_allowed_zip();
    my $ac = $i->get_allowed_country_area();
    my $au = $i->get_allowed_allow_us_po_box();
    my $aw = $i->get_allowed_world_area();
    my $ap = $i->get_allowed_postal_area();

    my $has_allowed = (defined $as && @{$as}) ||
                      (defined $az && @{$az}) ||
                      (defined $ac && @{$ac}) ||
                      (defined $aw) ||
                      (defined $ap && @{$ap});

    my $es = $i->get_excluded_state();
    my $ez = $i->get_excluded_zip();
    my $ec = $i->get_excluded_country_area();
    my $ep = $i->get_excluded_postal_area();

    my $has_excluded = (defined $es && @{$es}) ||
                       (defined $ez && @{$ez}) ||
                       (defined $ec && @{$ec}) ||
                       (defined $ep && @{$ep});

    $self->add_element(name => $type) 
      if $has_allowed || $has_excluded || defined $au;

    if (defined $au)
    {
      $self->add_element(name => Google::Checkout::XML::Constants::ALLOW_US_PO_BOX,
                                 data => $au eq 'true' ? 'true' : 'false', close => 1);
    }

    $self->add_element(name => Google::Checkout::XML::Constants::ALLOWED_AREA) 
      if $has_allowed;
    $self->_handle_allow($as, $az, $ac, $aw, $ap);
    $self->close_element() if $has_allowed;

    $self->add_element(name => Google::Checkout::XML::Constants::EXCLUDED_AREA) 
      if $has_excluded;
    $self->_handle_exclude($es, $ez, $ec, $ep);
    $self->close_element() if $has_excluded;

    $self->close_element() if $has_allowed || $has_excluded || defined $au;
  }
}

sub _handle_allow
{
  my ($self, $as, $az, $ac, $aw, $ap) = @_;

  if (defined $as)
  {
    $self->_add_allow_exclude(Google::Checkout::XML::Constants::US_STATE, 
                              Google::Checkout::XML::Constants::STATE, $_) for (@{$as});
  }

  if (defined $az)
  {
    $self->_add_allow_exclude(Google::Checkout::XML::Constants::US_ZIP_AREA, 
                              Google::Checkout::XML::Constants::US_ZIP_PATTERN, $_) for (@{$az});
  }

  if (defined $ac)
  {
    for (@{$ac})
    {
      $self->add_element(name => Google::Checkout::XML::Constants::US_COUNTRY_AREA,
                         attr => [Google::Checkout::XML::Constants::COUNTRY_AREA, $_], close => 1);
    }
  }
  
  if (defined $aw)
  {
    $self->add_element(name => Google::Checkout::XML::Constants::WORLD_AREA,
                       data => undef, close => 1);
  }
  
  if (defined $ap)
  {
    for (@{$ap})
    {
      $self->_add_postal_area($_);
    }
  }
}

sub _handle_exclude
{
  my ($self, $es, $ez, $ec, $ep) = @_;

  if (defined $es)
  {
    $self->_add_allow_exclude(Google::Checkout::XML::Constants::US_STATE, 
                              Google::Checkout::XML::Constants::STATE, $_) for (@{$es});
  }

  if (defined $ez)
  {
    $self->_add_allow_exclude(Google::Checkout::XML::Constants::US_ZIP_AREA, 
                              Google::Checkout::XML::Constants::US_ZIP_PATTERN, $_) for (@{$ez});
  }

  if (defined $ec)
  {
    for (@{$ec})
    {
      $self->add_element(name => Google::Checkout::XML::Constants::US_COUNTRY_AREA,
                         attr => [Google::Checkout::XML::Constants::COUNTRY_AREA, $_], close => 1);
    }
  }
  
  if (defined $ep)
  {
    for (@{$ep})
    {
      $self->_add_postal_area($_);
    }
  }
}

sub _add_postal_area
{
  my ($self, $hash_ref) = @_;
  
  if (defined $hash_ref->{country_code}) {
    $self->add_element(name => Google::Checkout::XML::Constants::POSTAL_AREA);
    $self->add_element(name => Google::Checkout::XML::Constants::COUNTRY_CODE,
                       data => $_->{country_code}, close => 1);
    if (defined $hash_ref->{postal_code_pattern}) {
      $self->add_element(name => Google::Checkout::XML::Constants::POSTAL_CODE_PATTERN,
                         data => $hash_ref->{postal_code_pattern}, close => 1);
    }
    $self->close_element(); # POSTAL_AREA      
  }
}

sub _add_allow_exclude
{
  my ($self, $pname, $cname, $data) = @_;

  $self->add_element(name => $pname);
  $self->add_element(name => $cname, data => $data, close => 1);
  $self->close_element();
}

sub _add_tax_tables
{
  my ($self, $mcf) = @_;

  return unless $mcf;

  my $tables = $mcf->get_tax_table;

  return unless $tables;

  $self->add_element(name => Google::Checkout::XML::Constants::TAX_TABLES, 
                     attr => [Google::Checkout::XML::Constants::MERCHANT_CALCULATED,
                              $mcf->get_merchant_calculation ? 'true' : 'false']) 
    if @$tables;

  my ($default_tables, $alternate_tables) = $self->_get_alternate_default($tables);

  if (ref $default_tables && @$default_tables)
  {
    #--
    #-- Only allow one default tax table
    #--
    $self->add_element(name => Google::Checkout::XML::Constants::DEFAULT_TAX_TABLE);
    $self->add_element(name => Google::Checkout::XML::Constants::TAX_RULES);

    my $rules = $default_tables->[0]->get_tax_rules;
    if ($rules)
    {
      for my $rule (@$rules)
      {
        $self->_write_tax_area($rule, 1);
      }
    }

    $self->close_element();
    $self->close_element();
  }

  if (@$alternate_tables)
  {
    $self->add_element(name => Google::Checkout::XML::Constants::ALTERNATE_TAX_TABLES);
    for my $atable (@$alternate_tables)
    {
      $self->add_element(name => Google::Checkout::XML::Constants::ALTERNATE_TAX_TABLE,
                         attr => [Google::Checkout::XML::Constants::STANDALONE, $atable->get_standalone,
                                  Google::Checkout::XML::Constants::NAME      , $atable->get_name]);

      $self->add_element(name => Google::Checkout::XML::Constants::ALTERNATE_TAX_RULES);

      my $rules = $atable->get_tax_rules;

      if ($rules)
      {
        for my $rule (@$rules)
        {
          $self->_write_tax_area($rule, 0);
        }
      }

      $self->close_element();
      $self->close_element();
    }
    $self->close_element();
  }

  $self->close_element() if @$tables;
}

sub _write_tax_area
{
  my ($self, $table, $is_default) = @_;

  my $area = $table->get_area;

  return unless $area && @$area;

  if ($is_default)
  {
    $self->add_element(name => Google::Checkout::XML::Constants::DEFAULT_TAX_RULE);
    $self->add_element(name => Google::Checkout::XML::Constants::SHIPPING_TAXED, close => 1,
                       data => $table->require_shipping_tax);
  }
  else
  {
    $self->add_element(name => Google::Checkout::XML::Constants::ALTERNATE_TAX_RULE); 
  }

  $self->add_element(name => Google::Checkout::XML::Constants::RATE, close => 1,
                     data => format_tax_rate($table->get_rate));
  $self->add_element(name => Google::Checkout::XML::Constants::TAX_AREA);

  for my $ar (@$area)
  {
    my $state = $ar->get_state;
    if ($state)
    {
      $self->add_element(name => Google::Checkout::XML::Constants::US_STATE) if @$state;
      for my $s (@$state)
      {
        $self->add_element(name => Google::Checkout::XML::Constants::STATE, data => $s, close => 1);
      }
      $self->close_element() if @$state;
    }

    my $zip = $ar->get_zip;
    if ($zip)
    {
      $self->add_element(name => Google::Checkout::XML::Constants::US_ZIP_AREA) if @$zip;
      for my $z (@$zip)
      {
        $self->add_element(name => Google::Checkout::XML::Constants::US_ZIP_PATTERN, 
                           data => $z, close => 1);
      }
      $self->close_element() if @$zip;
    }

    my $country = $ar->get_country;
    if ($country)
    {
      for my $c (@$country)
      {
        $self->add_element(name => Google::Checkout::XML::Constants::US_COUNTRY_AREA,
                           attr => [Google::Checkout::XML::Constants::COUNTRY_AREA, $c], close => 1);
      }
    }

    if ($ar->get_world)
    {
      $self->add_element(name => Google::Checkout::XML::Constants::WORLD_AREA, close => 1);
    }

    my $postal = $ar->get_postal;
    if ($postal)
    {
      for (@$postal)
      {
      	$self->_add_postal_area($_)
      }
    }
  } 

  $self->close_element();
  $self->close_element(); 
}

sub _get_alternate_default
{
  my ($self, $tables) = @_;

  my @dst_tables = ([], []);

  for my $table ($tables ? @$tables : ())
  {
    push(@{$dst_tables[$table->is_default ? 0 : 1]}, $table);
  }

  return @dst_tables;
}

sub _add_merchant_calculations
{
  my ($self, $mcf) = @_;

  my $calculation = $mcf->get_merchant_calculation;

  return unless $calculation;

  $self->add_element(name => Google::Checkout::XML::Constants::MERCHANT_CALCULATION);

  if ($calculation->get_url)
  {
    $self->add_element(name => Google::Checkout::XML::Constants::MERCHANT_CALCULATION_URL,
                       data => $calculation->get_url, close => 1);
  }

  if ($calculation->get_coupons)
  {
    $self->add_element(name => Google::Checkout::XML::Constants::ACCEPT_MERCHANT_COUPONS,
                       data => $calculation->get_coupons, close => 1);
  }

  my $gift_certificate = $calculation->get_certificates;

  if (Google::Checkout::General::Util::is_gift_certificate_object($gift_certificate)) 
  {
    $self->add_element(name => Google::Checkout::XML::Constants::GIFT_CERTIFICATE_SUPPORT);
    $self->add_element(name => Google::Checkout::XML::Constants::GIFT_CERTIFICATE_ACCEPTED,
                       data => $gift_certificate->get_accepted ? 'true' : 'false', close => 1);
    if ($gift_certificate->get_name) 
    {
      $self->add_element(name => Google::Checkout::XML::Constants::GIFT_CERTIFICATE_NAME,
                         data => $gift_certificate->get_name, close => 1);
    }
    if ($gift_certificate->get_pin) 
    {
      $self->add_element(name => Google::Checkout::XML::Constants::GIFT_CERTIFICATE_PIN_REQUIRED,
                         data => 'true', close => 1);
    }
    $self->close_element();
  }
  else 
  {
    #--
    #-- Old style
    #--
    $self->add_element(name => Google::Checkout::XML::Constants::ACCEPT_GIFT_CERTIFICATES,
                       data => $gift_certificate, close => 1);
  }

  $self->close_element();

}

sub _add_parameterized_url
{
  my ($self, $parameterized_url) = @_;
  
  $self->add_element(name => Google::Checkout::XML::Constants::PARAMETERIZED_URL,
                     attr => [Google::Checkout::XML::Constants::URL, 
                              $parameterized_url->get_url]);
  my $additional_params = $parameterized_url->get_url_params;
  if ($additional_params && %{$additional_params}) {
    $self->add_element(name => Google::Checkout::XML::Constants::PARAMETERS);
    while (my ($name, $value) = each %{$additional_params}) {
      $self->add_element(name => Google::Checkout::XML::Constants::URL_PARAMETER,
                         attr => [Google::Checkout::XML::Constants::NAME, $name,
                                  Google::Checkout::XML::Constants::TYPE, $value],
                         close => 1);
    }
    $self->close_element();  # Close <parameters>
  }
  $self->close_element();  # Close <parameterized-url>
}

1;