gchecky.model

Package gchecky :: Module model |
[hide private]
[frames] | no frames]

# Source Code for Module gchecky.model

      1  """
      2  gchecky.model module describes the mapping between Google Checkout API (GC API)
      3  XML messages (GC API XML Schema) and data classes.
      4
      5  This module uses L{gchecky.gxml} to automate much of the work so that
      6  the actual logic is not cluttered with machinery.
      7
      8  The module code is simple and self-documenting. Please read the source code for
      9  simple description of the data-structures. Note that it tries to follow exactly
     10  the official GC API XML Schema.
     11
     12  All the comments for the GC API are at U{Google Chackout API documentation
     13  <http://code.google.com/apis/checkout/developer/>}. Please consult it for any
     14  questions about GC API functioning.
     15
     16  @author: etarassov
     17  @version: $Revision: 129 $
     18  @contact: gchecky at gmail
     19  """
     20
     21  from gchecky import gxml
     22  from gchecky.data import CountryCode, PresentOrNot
     23
     24 -def test_document(doc, xml_text=None):
     25      """
     26      Method used in doctests: ensures that a document is properly serialized.
     27      """
     28
     29      def normalize_xml(xml_text):
     30          """
     31          Normalize the xml text to canonical form, so that two xml text chunks
     32          could be compared directly as strings.
     33          """
     34          # If xml misses header, then add it.
     35          if len(xml_text) < 2 or xml_text[0:2] != "<?":
     36              xml_text = "<?xml version='1.0' encoding='UTF-8'?>\n" + xml_text
     37          from xml.dom.minidom import parseString
     38          doc = parseString(xml_text)
     39
     40          text = doc.toprettyxml('')
     41          # To avoid formatting problems we just strip all the line returns and
     42          # spaces (while it breaks XML into a text string it still makes
     43          # this form a 'canonical' one).
     44          return text.replace('\n', '').replace(' ', '')
     45
     46      expected_xml = ((xml_text is not None) and normalize_xml(xml_text)) or None
     47      obtained_xml = normalize_xml(doc.toxml(pretty='  '))
     48
     49      if expected_xml is not None and expected_xml != obtained_xml:
     50          print "Expected:\n\n%s\n\nGot:\n\n%s\n" % (xml_text, doc.toxml('  '))
     51
     52      doc2 = gxml.Document.fromxml(doc.toxml())
     53      if not (doc == doc2):
     54          print '''
     55                Failed to correctly interpret the generated XML for this document:
     56                Original:
     57                %s
     58                Parsed:
     59                %s
     60                ''' % (doc.toxml(pretty=True), doc2.toxml())
     61
     62 -def test_node(node, xml_text=None):
     63      """
     64      Method used in doctests. Ensure that a node is properly serialized.
     65      """
     66      class Dummy(gxml.Document):
     67          tag_name='dummy'
     68          data=gxml.Complex('node', node.__class__, required=True)
     69      if xml_text is not None:
     70          xml_text = xml_text.replace('<', '  <')
     71          # TODO ...just ugly (below)
     72          xml_text = "<dummy xmlns='http://checkout.google.com/schema/2'>%s</dummy>" % (xml_text,)
     73      test_document(Dummy(data=node), xml_text)
     74
     75  CURRENCIES = ('USD', 'GBP')
     76
     77 -class price_t(gxml.Node):
     78      value    = gxml.Double('', default=0)
     79      currency = gxml.String('@currency', values=CURRENCIES)
     80
     81  DISPLAY_DISPOSITION = ('OPTIMISTIC', 'PESSIMISTIC')
     82 -class digital_content_t(gxml.Node):
     83      description    = gxml.Html('description', max_length=1024, required=False)
     84      email_delivery = gxml.Boolean('email-delivery', required=False)
     85      key            = gxml.String('key', required=False)
     86      url            = gxml.String('url', required=False)
     87      display_disposition = gxml.String('display-disposition', required=False,
     88                                        values=DISPLAY_DISPOSITION)
     89
     90 -class item_weight_t(gxml.Node):
     91      value = gxml.Double('@value') # , negative=False
     92      unit  = gxml.String('@unit', values=('LB',))
     93
     94 -class item_t(gxml.Node):
     95      """
     96      >>> test_node(item_t(name='Peter', description='The Great', unit_price=price_t(value=1, currency='GBP'), quantity=1, merchant_item_id='custom_merchant_item_id',
     97      ...                  merchant_private_item_data=['some', {'private':'data', 'to':['test','the'],'thing':None}, '!! Numbers: ', None, False, True, [11, 12., [13.4]]])
     98      ...          )
     99      >>> test_node(item_t(name='Name with empty description', description='', unit_price=price_t(value=1, currency='GBP'), quantity=1))
    100      """
    101      name                = gxml.String('item-name')
    102      description         = gxml.String('item-description')
    103      unit_price          = gxml.Complex('unit-price', price_t)
    104      quantity            = gxml.Decimal('quantity')
    105      item_weight         = gxml.Complex('item-weight', item_weight_t, required=False)
    106      merchant_item_id    = gxml.String('merchant-item-id', required=False)
    107      tax_table_selector  = gxml.String('tax-table-selector', required=False)
    108      digital_content     = gxml.Complex('digital-content', digital_content_t, required=False)
    109      merchant_private_item_data = gxml.Any('merchant-private-item-data',
    110                                            save_node_and_xml=True,
    111                                            required=False)
    112
    113 -class postal_area_t(gxml.Node):
    114      """
    115      >>> test_node(postal_area_t(country_code = 'VU'),
    116      ... '''
    117      ... <node><country-code>VU</country-code></node>
    118      ... '''
    119      ... )
    120      """
    121      country_code        = CountryCode('country-code')
    122      postal_code_pattern = gxml.String('postal-code-pattern', required=False)
    123
    124 -class tax_area_t(gxml.Node):
    125      world_area   = PresentOrNot('world-area', required=False)
    126      postal_area  = gxml.Complex('postal-area', postal_area_t, required=False)
    127      us_state        = gxml.String('us-state-area/state', required=False)
    128      us_zip_pattern  = gxml.String('us-zip-area/zip-pattern', required=False) # regex: [a-zA-Z0-9_]+\*? Note the optional asterisk
    129      us_country_area = gxml.String('us-country-area/country-area', values=('CONTINENTAL_48', 'FULL_50_STATES', 'ALL'), required=False) # enum('CONTINENTAL_48', 'FULL_50_STATES', 'ALL')
    130
    131 -class areas_t(gxml.Node):
    132      """
    133      Represents a list of regions.
    134
    135      >>> test_node(
    136      ...   areas_t(
    137      ...     states = ['LA', 'NY'],
    138      ...     country_areas = ['ALL', 'CONTINENTAL_48']
    139      ...   )
    140      ... ,
    141      ... '''
    142      ... <node>
    143      ... <us-state-area>
    144      ...   <state>LA</state>
    145      ... </us-state-area>
    146      ... <us-state-area>
    147      ...   <state>NY</state>
    148      ... </us-state-area>
    149      ... <us-country-area>
    150      ...   <country-area>ALL</country-area>
    151      ... </us-country-area>
    152      ... <us-country-area>
    153      ...   <country-area>CONTINENTAL_48</country-area>
    154      ... </us-country-area>
    155      ... </node>
    156      ... '''
    157      ... )
    158      """
    159      states        = gxml.List('', gxml.String('us-state-area/state'), required=False)
    160      zip_patterns  = gxml.List('', gxml.String('us-zip-area/zip-pattern'), required=False) # regex: [a-zA-Z0-9_]+\*? Note the optional asterisk
    161      country_areas = gxml.List('', gxml.String('us-country-area/country-area'), values=('CONTINENTAL_48', 'FULL_50_STATES', 'ALL'), required=False) # enum('CONTINENTAL_48', 'FULL_50_STATES', 'ALL')
    162
    163 -class allowed_areas_t(areas_t):
    164      postal_areas  = gxml.List('', gxml.Complex('postal-area', postal_area_t), required=False)
    165      world_area   = PresentOrNot('world-area', required=False)
    166
    167 -class excluded_areas_t(areas_t):
    168      postal_areas  = gxml.List('', gxml.Complex('postal-area', postal_area_t), required=False)
    169
    170 -class tax_rule_t(gxml.Node):
    171      rate     = gxml.Double('rate', default=0.)
    172      tax_area = gxml.Complex('tax-area', tax_area_t, required=False)
    173      tax_areas = gxml.List('tax-areas', gxml.Complex('', tax_area_t), required=False)
    174
    175 -class default_tax_rule_t(tax_rule_t):
    176      shipping_taxed = gxml.Boolean('shipping-taxed', required=False)
    177
    178 -class alternate_tax_rule_t(tax_rule_t):
    179      pass
    180
    181 -class default_tax_table_t(gxml.Node):
    182      tax_rules = gxml.List('tax-rules', gxml.Complex('default-tax-rule', default_tax_rule_t))
    183
    184 -class alternate_tax_table_t(gxml.Node):
    185      name                = gxml.String('@name')
    186      standalone          = gxml.Boolean('@standalone')
    187      alternate_tax_rules = gxml.List('alternate-tax-rules', gxml.Complex('alternate-tax-rule', alternate_tax_rule_t))
    188
    189 -class tax_tables_t(gxml.Node):
    190      merchant_calculated = gxml.Boolean('@merchant-calculated', default=False)
    191      default             = gxml.Complex('default-tax-table', default_tax_table_t)
    192      alternates          = gxml.List('alternate-tax-tables', gxml.Complex('alternate-tax-table', alternate_tax_table_t), required=False)
    193
    194 -class merchant_calculations_t(gxml.Node):
    195      merchant_calculations_url = gxml.Url('merchant-calculations-url')
    196      accept_merchant_coupons   = gxml.Boolean('accept-merchant-coupons', required=False)
    197      accept_gift_certificates  = gxml.Boolean('accept-gift-certificates', required=False)
    198
    199 -class shipping_option_t(gxml.Node):
    200      """
    201      Represents information about shipping costs.
    202
    203      >>> test_node(
    204      ...   shipping_option_t(
    205      ...       name = 'Testing',
    206      ...         price = price_t(
    207      ...             currency = 'GBP',
    208      ...             value = 9.99,
    209      ...             ),
    210      ...         allowed_areas = allowed_areas_t(
    211      ...             world_area = True,
    212      ...             ),
    213      ...         excluded_areas = excluded_areas_t(
    214      ...             postal_areas = [postal_area_t(
    215      ...                 country_code = 'US',
    216      ...                 )],
    217      ...             ),
    218      ...         )
    219      ... , '''
    220      ... <node name='Testing'>
    221      ...   <price currency='GBP'>9.990</price>
    222      ...   <shipping-restrictions>
    223      ...     <allowed-areas>
    224      ...       <world-area/>
    225      ...     </allowed-areas>
    226      ...     <excluded-areas>
    227      ...       <postal-area>
    228      ...         <country-code>US</country-code>
    229      ...       </postal-area>
    230      ...     </excluded-areas>
    231      ...   </shipping-restrictions>
    232      ... </node>
    233      ... ''')
    234      """
    235      name = gxml.String('@name') # Attribute, len <= 255, not-empty
    236      price = gxml.Complex('price', price_t)
    237      allowed_areas  = gxml.Complex('shipping-restrictions/allowed-areas', allowed_areas_t, required=False)
    238      excluded_areas = gxml.Complex('shipping-restrictions/excluded-areas', excluded_areas_t, required=False)
    239
    240 -class address_filters_t(gxml.Node):
    241      allowed_areas   = gxml.Complex('allowed-areas', allowed_areas_t, required=False)
    242      excluded_areas  = gxml.Complex('excluded-areas', excluded_areas_t, required=False)
    243      allow_us_po_box = gxml.Boolean('allow-us-po-box', required=False)
    244
    245 -class flat_rate_shipping_t(shipping_option_t):
    246      pass
    247 -class merchant_calculated_shipping_t(shipping_option_t):
    248      address_filters = gxml.Complex('address-filters',
    249                                     address_filters_t, required=False)
    250
    251  # TODO: Add some special type of validation to:
    252  # shipping_company and shipping_type fields. See:
    253  # http://code.google.com/apis/checkout/developer/Google_Checkout_XML_API_Tag_Reference.html#tag_shipping-type
    254 -class carrier_calculated_shipping_option_t(gxml.Node):
    255      price = gxml.Complex('price', price_t)
    256      shipping_company = gxml.String('shipping-company', values=('FedEx', 'UPS', 'USPS'))
    257      carrier_pickup = gxml.String('carrier-pickup', values=('REGULAR_PICKUP',
    258                                                             'SPECIAL_PICKUP',
    259                                                             'DROP_OFF'),
    260                                                     default='DROP_OFF',
    261                                                     required=False)
    262      shipping_type = gxml.String('shipping-type')
    263      additional_fixed_charge = gxml.Complex('additional-fixed-charge', price_t, required=False)
    264      additional_variable_charge_percent = gxml.Double('additional-variable-charge-percent', required=False)
    265
    266 -class measure_t(gxml.Node):
    267      unit  = gxml.String('@unit', values=('IN',), default='IN')
    268      value = gxml.Decimal('@value')
    269
    270 -class ship_from_t(gxml.Node):
    271      id           = gxml.String('id')
    272      city         = gxml.String('city')
    273      region       = gxml.String('region', empty=True)
    274      postal_code  = gxml.Zip('postal-code')
    275      country_code = CountryCode('country-code')
    276
    277 -class shipping_package_t(gxml.Node):
    278      delivery_address_category = gxml.String('delivery-address-category',
    279                                              values=('RESIDENTIAL',
    280                                                      'COMMERCIAL'), required=False)
    281      height = gxml.Complex('height', measure_t)
    282      length = gxml.Complex('length', measure_t)
    283      ship_from = gxml.Complex('ship-from', ship_from_t)
    284      width  = gxml.Complex('width', measure_t)
    285
    286 -class carrier_calculated_shipping_t(gxml.Node):
    287      carrier_calculated_shipping_options = gxml.List('carrier-calculated-shipping-options',
    288                                                      gxml.Complex('carrier-calculated-shipping-option',
    289                                                                   carrier_calculated_shipping_option_t))
    290      shipping_packages                   = gxml.List('shipping-packages',
    291                                                      gxml.Complex('shipping-package',
    292                                                                   shipping_package_t))
    293
    294 -class pickup_t(gxml.Node):
    295      name = gxml.String('@name')
    296      price = gxml.Complex('price', price_t)
    297
    298 -class shipping_methods_t(gxml.Node):
    299      carrier_calculated_shippings  = gxml.List('', gxml.Complex('carrier-calculated-shipping', carrier_calculated_shipping_t), required=False) # list of carrier_calculated_shipping_t
    300      flat_rate_shippings           = gxml.List('', gxml.Complex('flat-rate-shipping', flat_rate_shipping_t), required=False) # list of flat_rate_shipping_t
    301      merchant_calculated_shippings = gxml.List('', gxml.Complex('merchant-calculated-shipping', merchant_calculated_shipping_t), required=False) # list of merchant_calculated_shipping_t
    302      pickups                       = gxml.List('', gxml.Complex('pickup', pickup_t), required=False) # list of pickup_t
    303
    304  URL_PARAMETER_TYPES=(
    305      'buyer-id', # A Google-assigned value that uniquely identifies a customer email address.
    306      'order-id', # A Google-assigned value that uniquely identifies an order. This value is displayed in the Merchant Center for each order. If you have implemented the Notification API, you will also see this value in all Google Checkout notifications.
    307      'order-subtotal', # The total cost for all of the items in the order including coupons and discounts but excluding taxes and shipping charges.
    308      'order-subtotal-plus-tax', # The total cost for all of the items in the order, including taxes, coupons and discounts, but excluding shipping charges.
    309      'order-subtotal-plus-shipping', # The total cost for all of the items in the order, including shipping charges, coupons and discounts, but excluding taxes.
    310      'order-total', # The total cost for all of the items in the order, including taxes, shipping charges, coupons and discounts.
    311      'tax-amount', # The total amount of taxes charged for an order.
    312      'shipping-amount', # The shipping cost associated with an order.
    313      'coupon-amount', # The total amount of all coupons factored into the order total.
    314      'billing-city', # The city associated with the order's billing address.
    315      'billing-region', # The U.S. state associated with the order's billing address.
    316      'billing-postal-code', # The five-digit U.S. zip code associated with the order's billing address.
    317      'billing-country-code', # The two-letter ISO 3166 country code associated with the order's billing address.
    318      'shipping-city', # The city associated with the order's shipping address.
    319      'shipping-region', # The U.S. state associated with the order's shipping address.
    320      'shipping-postal-code', # The five-digit U.S. zip code associated with the order's shipping address.
    321      'shipping-country-code', # The two-letter ISO 3166 country code associated with the order's shipping address.',
    322  )
    323
    324 -class url_parameter_t(gxml.Node):
    325      name = gxml.String('@name')
    326      type = gxml.String('@type', values=URL_PARAMETER_TYPES)
    327
    328 -class parameterized_url_t(gxml.Node):
    329      url        = gxml.Url('@url', required=True)
    330      parameters = gxml.List('parameters', gxml.Complex('url-parameter', url_parameter_t), required=True)
    331
    332
    333 -class rounding_policy_t(gxml.Node):
    334      mode = gxml.String('mode', choices=('UP',
    335                                          'DOWN',
    336                                          'CEILING',
    337                                          'FLOOR',
    338                                          'HALF_UP',
    339                                          'HALF_DOWN',
    340                                          'HALF_EVEN',
    341                                          'UNNECESSARY'))
    342      rule = gxml.String('rule', choices=('PER_LINE',
    343                                          'TOTAL'))
    344
    345 -class checkout_flow_support_t(gxml.Node):
    346      """
    347      >>> test_node(
    348      ...   checkout_flow_support_t(
    349      ...     parameterized_urls = [
    350      ...         parameterized_url_t(
    351      ...             url='http://google.com/',
    352      ...             parameters=[url_parameter_t(name='a', type='buyer-id')]
    353      ...         ),
    354      ...         parameterized_url_t(
    355      ...             url='http://yahoo.com/',
    356      ...             parameters=[url_parameter_t(name='a', type='shipping-city'),
    357      ...                         url_parameter_t(name='b', type='tax-amount')]
    358      ...         ),
    359      ...         parameterized_url_t(
    360      ...             url='http://mozilla.com/',
    361      ...             parameters=[url_parameter_t(name='a', type='order-total'),
    362      ...                         url_parameter_t(name='b', type='shipping-region'),
    363      ...                         url_parameter_t(name='c', type='shipping-country-code')]
    364      ...         )
    365      ...     ],
    366      ...   )
    367      ... ,
    368      ... '''
    369      ... <node>
    370      ...   <parameterized-urls>
    371      ...     <parameterized-url url="http://google.com/">
    372      ...       <parameters>
    373      ...         <url-parameter name="a" type="buyer-id"/>
    374      ...       </parameters>
    375      ...     </parameterized-url>
    376      ...     <parameterized-url url="http://yahoo.com/">
    377      ...       <parameters>
    378      ...         <url-parameter name="a" type="shipping-city"/>
    379      ...         <url-parameter name="b" type="tax-amount"/>
    380      ...       </parameters>
    381      ...     </parameterized-url>
    382      ...     <parameterized-url url="http://mozilla.com/">
    383      ...       <parameters>
    384      ...         <url-parameter name="a" type="order-total"/>
    385      ...         <url-parameter name="b" type="shipping-region"/>
    386      ...         <url-parameter name="c" type="shipping-country-code"/>
    387      ...       </parameters>
    388      ...     </parameterized-url>
    389      ...   </parameterized-urls>
    390      ... </node>
    391      ... '''
    392      ... )
    393      """
    394      edit_cart_url              = gxml.Url('edit-cart-url', required=False)         # optional, URL
    395      continue_shopping_url      = gxml.Url('continue-shopping-url', required=False) # optional, URL
    396      tax_tables                 = gxml.Complex('tax-tables', tax_tables_t, required=False) # optional, tax_tables_t
    397      shipping_methods           = gxml.Complex('shipping-methods', shipping_methods_t, required=False) # optional, shipping_methods_t
    398      parameterized_urls         = gxml.List('parameterized-urls', gxml.Complex('parameterized-url', parameterized_url_t), required=False)
    399      merchant_calculations      = gxml.Complex('merchant-calculations', merchant_calculations_t, required=False) # optional, merchant_calculations_t
    400      request_buyer_phone_number = gxml.Boolean('request-buyer-phone-number', required=False) # optional, Boolean
    401      analytics_data             = gxml.String('analytics-data', required=False)
    402      platform_id                = gxml.Long('platform-id', required=False)
    403      rounding_policy            = gxml.Complex('rounding-policy', rounding_policy_t, required=False)
    404
    405 -class shopping_cart_t(gxml.Node):
    406      expiration            = gxml.Timestamp('cart-expiration/good-until-date', required=False)
    407      items                 = gxml.List('items', gxml.Complex('item', item_t))
    408      merchant_private_data = gxml.Any('merchant-private-data',
    409                                       save_node_and_xml=True,
    410                                       required=False)
    411
    412 -class hello_t(gxml.Document):
    413      """
    414      Represents a simple test that verifies that your server communicates
    415      properly with Google Checkout. The fourth step of
    416      the U{Getting Started with Google Checkout<http://code.google.com/apis/checkout/developer/index.html#integration_overview>}
    417      section of the Developer's Guide explains how to execute this test.
    418
    419      >>> test_document(hello_t(),
    420      ...               "<hello xmlns='http://checkout.google.com/schema/2'/>"
    421      ... )
    422      """
    423      tag_name='hello'
    424
    425 -class bye_t(gxml.Document):
    426      """
    427      Represents a response that indicates that Google correctly received
    428      a <hello> request.
    429
    430      >>> test_document(
    431      ...     bye_t(serial_number="7315dacf-3a2e-80d5-aa36-8345cb54c143")
    432      ... ,
    433      ...     '''
    434      ...     <bye xmlns="http://checkout.google.com/schema/2"
    435      ...           serial-number="7315dacf-3a2e-80d5-aa36-8345cb54c143" />
    436      ...     '''
    437      ... )
    438      """
    439      tag_name = 'bye'
    440      serial_number = gxml.ID('@serial-number')
    441
    442 -class checkout_shopping_cart_t(gxml.Document):
    443      tag_name = 'checkout-shopping-cart'
    444      shopping_cart                = gxml.Complex('shopping-cart', shopping_cart_t)
    445      checkout_flow_support        = gxml.Complex('checkout-flow-support/merchant-checkout-flow-support', checkout_flow_support_t)
    446      request_initial_auth_details = gxml.Boolean('order-processing-support/request-initial-auth-details', required=False)
    447
    448 -class coupon_gift_adjustment_t(gxml.Node):
    449      code              = gxml.String('code')
    450      calculated_amount = gxml.Complex('calculated-amount', price_t, required=False)
    451      applied_amount    = gxml.Complex('applied-amount', price_t)
    452      message           = gxml.String('message', required=False)
    453
    454 -class merchant_codes_t(gxml.Node):
    455      gift_certificate_adjustment = gxml.List('', gxml.Complex('gift-certificate-adjustment', coupon_gift_adjustment_t))
    456      coupon_adjustment           = gxml.List('', gxml.Complex('coupon-adjustment', coupon_gift_adjustment_t))
    457
    458 -class shipping_adjustment_t(gxml.Node):
    459      shipping_name = gxml.String('shipping-name')
    460      shipping_cost = gxml.Complex('shipping-cost', price_t)
    461
    462 -class carrier_calculated_shipping_adjustment_t(gxml.Node):
    463      shipping_name = gxml.String('shipping-name')
    464      shipping_cost = gxml.Complex('shipping-cost', price_t)
    465
    466  # Two classes below represent the single 'shipping' tag, which content
    467  # depends on the context the XML Node is present.
    468  # http://code.google.com/apis/checkout/developer/index.html#tag_shipping
    469 -class shipping_in_order_adjustment_t(gxml.Node):
    470      carrier_calculated_shipping_adjustment  = gxml.Complex('carrier-calculated-shipping-adjustment',
    471                                                             carrier_calculated_shipping_adjustment_t,
    472                                                             required=False)
    473      flat_rate_shipping_adjustment           = gxml.Complex('flat-rate-shipping-adjustment',
    474                                                             shipping_adjustment_t,
    475                                                             required=False)
    476      merchant_calculated_shipping_adjustment = gxml.Complex('merchant-calculated-shipping-adjustment',
    477                                                             shipping_adjustment_t,
    478                                                             required=False)
    479      pickup_shipping_adjustment              = gxml.Complex('pickup-shipping-adjustment',
    480                                                             shipping_adjustment_t,
    481                                                             required=False)
    482      methods                                 = gxml.List('', gxml.String('method/@name'))
    483
    484 -class shipping_in_calculate_t(gxml.Node):
    485      methods                                 = gxml.List('', gxml.String('method/@name'))
    486
    487 -class order_adjustment_t(gxml.Node):
    488      adjustment_total                = gxml.Complex('adjustment-total', price_t, required=False)
    489      merchant_calculation_successful = gxml.Boolean('merchant-calculation-successful', required=False)
    490      merchant_codes                  = gxml.Complex('merchant-codes', merchant_codes_t, required=False)
    491      shipping                        = gxml.Complex('shipping', shipping_in_order_adjustment_t, required=False)
    492      total_tax                       = gxml.Complex('total-tax', price_t, required=False)
    493
    494 -class structured_name_t(gxml.Node):
    495      first_name = gxml.String('first-name')
    496      last_name  = gxml.String('last-name')
    497
    498 -class address_t(gxml.Node):
    499      address1     = gxml.String('address1')
    500      address2     = gxml.String('address2', required=False)
    501      city         = gxml.String('city')
    502      company_name = gxml.String('company-name', required=False)
    503      contact_name = gxml.String('contact-name', required=False)
    504      country_code = gxml.String('country-code')
    505      email        = gxml.Email('email', required=False)
    506      fax          = gxml.Phone('fax', required=False, empty=True)
    507      phone        = gxml.Phone('phone', required=False, empty=True)
    508      postal_code  = gxml.Zip('postal-code')
    509      region       = gxml.String('region', empty=True)
    510      structured_name = gxml.Complex('structured-name',
    511                                     structured_name_t, required=False)
    512
    513 -class buyer_billing_address_t(address_t):
    514      pass
    515 -class buyer_shipping_address_t(address_t):
    516      pass
    517 -class billing_address_t(address_t):
    518      # google docs do not say address_id is optional, but sandbox omits it.. :S bug?
    519      # address_id = gxml.ID('@address-id', required=False)
    520      pass
    521
    522 -class buyer_marketing_preferences_t(gxml.Node):
    523      email_allowed = gxml.Boolean('email-allowed')
    524 -    def read(self, node):
    525          return gxml.Node.read(self, node)
    526 -class abstract_notification_t(gxml.Document):
    527      tag_name = '-notification'
    528      serial_number       = gxml.ID('@serial-number')
    529      google_order_number = gxml.ID('google-order-number')
    530      timestamp                   = gxml.Timestamp('timestamp')
    531
    532 -class promotion_t(gxml.Node):
    533      description  = gxml.String('description', required=False, max_length=1024)
    534      id           = gxml.Long('id')
    535      name         = gxml.String('name')
    536      total_amount = gxml.Complex('total-amount', price_t)
    537
    538  FINANCIAL_ORDER_STATE=('REVIEWING', 'CHARGEABLE', 'CHARGING', 'CHARGED', 'PAYMENT_DECLINED', 'CANCELLED', 'CANCELLED_BY_GOOGLE')
    539  FULFILLMENT_ORDER_STATE=('NEW', 'PROCESSING', 'DELIVERED', 'WILL_NOT_DELIVER')
    540
    541 -class new_order_notification_t(abstract_notification_t):
    542      tag_name = 'new-order-notification'
    543      buyer_billing_address       = gxml.Complex('buyer-billing-address', buyer_billing_address_t)
    544      buyer_id                    = gxml.Long('buyer-id')
    545      buyer_marketing_preferences = gxml.Complex('buyer-marketing-preferences', buyer_marketing_preferences_t)
    546      buyer_shipping_address      = gxml.Complex('buyer-shipping-address', buyer_shipping_address_t)
    547      financial_order_state       = gxml.String('financial-order-state', values=FINANCIAL_ORDER_STATE)
    548      fulfillment_order_state     = gxml.String('fulfillment-order-state', values=FULFILLMENT_ORDER_STATE)
    549      order_adjustment            = gxml.Complex('order-adjustment', order_adjustment_t)
    550      order_total                 = gxml.Complex('order-total', price_t)
    551      shopping_cart               = gxml.Complex('shopping-cart', shopping_cart_t)
    552      promotions                  = gxml.List('promotions',
    553                                              gxml.Complex('promotion', promotion_t),
    554                                              required=False)
    555
    556 -class checkout_redirect_t(gxml.Document):
    557      """
    558      Try doctests:
    559      >>> a = checkout_redirect_t(serial_number='blabla12345',
    560      ...                         redirect_url='http://www.somewhere.com')
    561      >>> b = gxml.Document.fromxml(a.toxml())
    562      >>> a == b
    563      True
    564      """
    565      tag_name = 'checkout-redirect'
    566      serial_number = gxml.ID('@serial-number')
    567      redirect_url  = gxml.Url('redirect-url')
    568
    569 -class notification_acknowledgment_t(gxml.Document):
    570      tag_name      = 'notification-acknowledgment'
    571      serial_number = gxml.ID('@serial-number')
    572
    573 -class order_state_change_notification_t(abstract_notification_t):
    574      tag_name = 'order-state-change-notification'
    575      new_fulfillment_order_state      = gxml.String('new-fulfillment-order-state', values=FINANCIAL_ORDER_STATE)
    576      new_financial_order_state        = gxml.String('new-financial-order-state', values=FULFILLMENT_ORDER_STATE)
    577      previous_financial_order_state   = gxml.String('previous-financial-order-state', values=FINANCIAL_ORDER_STATE)
    578      previous_fulfillment_order_state = gxml.String('previous-fulfillment-order-state', values=FULFILLMENT_ORDER_STATE)
    579      reason                           = gxml.String('reason', required=False)
    580
    581  AVS_VALUES=('Y', 'P', 'A', 'N', 'U')
    582  CVN_VALUES=('M', 'N', 'U', 'E')
    583
    584 -class risk_information_t(gxml.Node):
    585      avs_response            = gxml.String('avs-response', values=AVS_VALUES)
    586      billing_address         = gxml.Complex('billing-address', billing_address_t)
    587      buyer_account_age       = gxml.Integer('buyer-account-age')
    588      cvn_response            = gxml.String('cvn-response', values=CVN_VALUES)
    589      eligible_for_protection = gxml.Boolean('eligible-for-protection')
    590      ip_address              = gxml.IP('ip-address')
    591      partial_cc_number       = gxml.String('partial-cc-number') # partial CC Number
    592
    593 -class risk_information_notification_t(abstract_notification_t):
    594      tag_name = 'risk-information-notification'
    595      risk_information = gxml.Complex('risk-information', risk_information_t)
    596
    597 -class abstract_order_t(gxml.Document):
    598      tag_name='-order'
    599      google_order_number = gxml.ID('@google-order-number')
    600
    601 -class charge_order_t(abstract_order_t):
    602      tag_name = 'charge-order'
    603      amount = gxml.Complex('amount', price_t, required=False)
    604
    605 -class refund_order_t(abstract_order_t):
    606      tag_name = 'refund-order'
    607      amount  = gxml.Complex('amount', price_t, required=False)
    608      comment = gxml.String('comment', max_length=140, required=False)
    609      reason  = gxml.String('reason', max_length=140)
    610
    611 -class cancel_order_t(abstract_order_t):
    612      """
    613      Represents an order that should be canceled. A <cancel-order> command
    614      sets the financial-order-state and the fulfillment-order-state to canceled.
    615
    616      >>> test_document(
    617      ...     cancel_order_t(google_order_number = "841171949013218",
    618      ...                    comment = 'Buyer found a better deal.',
    619      ...                    reason = 'Buyer cancelled the order.'
    620      ...     )
    621      ... ,
    622      ...     '''
    623      ...     <cancel-order xmlns="http://checkout.google.com/schema/2" google-order-number="841171949013218">
    624      ...       <comment>Buyer found a better deal.</comment>
    625      ...       <reason>Buyer cancelled the order.</reason>
    626      ...     </cancel-order>
    627      ...     '''
    628      ... )
    629      """
    630      tag_name = 'cancel-order'
    631      comment = gxml.String('comment', max_length=140, required=False)
    632      reason  = gxml.String('reason', max_length=140)
    633
    634 -class authorize_order_t(abstract_order_t):
    635      tag_name = 'authorize-order'
    636
    637 -class process_order_t(abstract_order_t):
    638      tag_name = 'process-order'
    639
    640 -class add_merchant_order_number_t(abstract_order_t):
    641      tag_name = 'add-merchant-order-number'
    642      merchant_order_number = gxml.String('merchant-order-number')
    643
    644  CARRIER_VALUES=('DHL', 'FedEx', 'UPS', 'USPS', 'Other')
    645
    646 -class tracking_data_t(gxml.Node):
    647      carrier         = gxml.String('carrier', values=CARRIER_VALUES)
    648      tracking_number = gxml.String('tracking-number')
    649
    650 -class deliver_order_t(abstract_order_t):
    651      tag_name='deliver-order'
    652      tracking_data = gxml.Complex('tracking-data', tracking_data_t, required=False)
    653      send_email    = gxml.Boolean('send-email', required=False)
    654
    655 -class add_tracking_data_t(abstract_order_t):
    656      """
    657      Represents a tag containing a request to add a shipper's tracking number
    658      to an order.
    659
    660      >>> test_document(
    661      ...     add_tracking_data_t(
    662      ...         google_order_number = '841171949013218',
    663      ...         tracking_data = tracking_data_t(
    664      ...             carrier = 'UPS',
    665      ...             tracking_number = 'Z9842W69871281267'
    666      ...         )
    667      ...     )
    668      ... ,
    669      ...     '''
    670      ...     <add-tracking-data xmlns="http://checkout.google.com/schema/2"
    671      ...                        google-order-number="841171949013218">
    672      ...       <tracking-data>
    673      ...         <tracking-number>Z9842W69871281267</tracking-number>
    674      ...         <carrier>UPS</carrier>
    675      ...       </tracking-data>
    676      ...     </add-tracking-data>
    677      ...     '''
    678      ... )
    679      """
    680      tag_name='add-tracking-data'
    681      tracking_data = gxml.Complex('tracking-data', tracking_data_t)
    682
    683 -class send_buyer_message_t(abstract_order_t):
    684      tag_name='send-buyer-message'
    685      send_email = gxml.Boolean('send-email', required=False)
    686      message    = gxml.String('message')
    687
    688 -class archive_order_t(abstract_order_t):
    689      """
    690      Represents a request to archive a particular order. You would archive
    691      an order to remove it from your Merchant Center Inbox, indicating that
    692      the order has been delivered.
    693
    694      >>> test_document(archive_order_t(google_order_number = '841171949013218'),
    695      ...     '''<archive-order xmlns="http://checkout.google.com/schema/2"
    696      ...                       google-order-number="841171949013218" />'''
    697      ... )
    698      """
    699      tag_name='archive-order'
    700
    701 -class unarchive_order_t(abstract_order_t):
    702      tag_name='unarchive-order'
    703
    704 -class charge_amount_notification_t(abstract_notification_t):
    705      """
    706      Represents information about a successful charge for an order.
    707
    708      >>> from datetime import datetime
    709      >>> import iso8601
    710      >>> test_document(
    711      ...     charge_amount_notification_t(
    712      ...         serial_number='95d44287-12b1-4722-bc56-cfaa73f4c0d1',
    713      ...         google_order_number = '841171949013218',
    714      ...         timestamp = iso8601.parse_date('2006-03-18T18:25:31.593Z'),
    715      ...         latest_charge_amount = price_t(currency='USD', value=2226.06),
    716      ...         total_charge_amount = price_t(currency='USD', value=2226.06)
    717      ...     )
    718      ... ,
    719      ...     '''
    720      ...     <charge-amount-notification xmlns="http://checkout.google.com/schema/2" serial-number="95d44287-12b1-4722-bc56-cfaa73f4c0d1">
    721      ...       <latest-charge-amount currency="USD">2226.060</latest-charge-amount>
    722      ...       <google-order-number>841171949013218</google-order-number>
    723      ...       <total-charge-amount currency="USD">2226.060</total-charge-amount>
    724      ...       <timestamp>2006-03-18T18:25:31.593000+00:00</timestamp>
    725      ...     </charge-amount-notification>
    726      ...     '''
    727      ... )
    728      """
    729      tag_name='charge-amount-notification'
    730      latest_charge_amount = gxml.Complex('latest-charge-amount',
    731                                          price_t)
    732      latest_promotion_charge_amount = gxml.Complex('latest-promotion-charge-amount',
    733                                                    price_t, required=False)
    734      total_charge_amount  = gxml.Complex('total-charge-amount', price_t)
    735
    736 -class refund_amount_notification_t(abstract_notification_t):
    737      tag_name='refund-amount-notification'
    738      latest_refund_amount = gxml.Complex('latest-refund-amount', price_t)
    739      latest_promotion_refund_amount = gxml.Complex('latest-promotion-refund-amount',
    740                                                    price_t, required=False)
    741      total_refund_amount  = gxml.Complex('total-refund-amount', price_t)
    742
    743 -class chargeback_amount_notification_t(abstract_notification_t):
    744      tag_name='chargeback-amount-notification'
    745      latest_chargeback_amount = gxml.Complex('latest-chargeback-amount', price_t)
    746      latest_promotion_chargeback_amount = gxml.Complex('latest-promotion-chargeback-amount',
    747                                                        price_t, required=False)
    748      total_chargeback_amount  = gxml.Complex('total-chargeback-amount', price_t)
    749
    750 -class authorization_amount_notification_t(abstract_notification_t):
    751      tag_name='authorization-amount-notification'
    752      authorization_amount          = gxml.Complex('authorization-amount', price_t)
    753      authorization_expiration_date = gxml.Timestamp('authorization-expiration-date')
    754      avs_response                  = gxml.String('avs-response', values=AVS_VALUES)
    755      cvn_response                  = gxml.String('cvn-response', values=CVN_VALUES)
    756
    757 -class anonymous_address_t(gxml.Node):
    758      id           = gxml.String('@id')
    759      city         = gxml.String('city')
    760      region       = gxml.String('region', empty=True)
    761      postal_code  = gxml.Zip('postal-code')
    762      country_code = gxml.String('country-code')
    763
    764 -class merchant_code_string_t(gxml.Node):
    765      code = gxml.String('@code')
    766
    767 -class calculate_t(gxml.Node):
    768      addresses             = gxml.List('addresses',
    769                                        gxml.Complex('anonymous-address', anonymous_address_t),
    770                                        required=False)
    771      merchant_code_strings = gxml.Complex('merchant-code-strings/merchant-code-string',
    772                                           merchant_code_string_t,
    773                                           required=False)
    774      shipping              = gxml.Complex('shipping', shipping_in_calculate_t, required=False)
    775      tax                   = gxml.Boolean('tax')
    776
    777 -class merchant_calculation_callback_t(gxml.Document):
    778      tag_name = 'merchant-calculation-callback'
    779      serial_number    = gxml.ID('@serial-number')
    780      buyer_id         = gxml.Long('buyer-id', required=False)
    781      buyer_language   = gxml.LanguageCode('buyer-language')
    782      calculate        = gxml.Complex('calculate', calculate_t)
    783      shopping_cart    = gxml.Complex('shopping-cart', shopping_cart_t)
    784
    785 -class discount_result_t(gxml.Node):
    786      valid             = gxml.Boolean('valid')
    787      calculated_amount = gxml.Complex('calculated-amount', price_t)
    788      code              = gxml.String('code')
    789      message           = gxml.String('message', max_length=255)
    790
    791 -class merchant_code_results_t(gxml.Node):
    792      coupon_result           = gxml.List('', gxml.Complex('coupon-result', discount_result_t))
    793      gift_certificate_result = gxml.List('', gxml.Complex('gift-certificate-result', discount_result_t))
    794
    795 -class result_t(gxml.Node):
    796      shipping_name         = gxml.String('@shipping-name')
    797      address_id            = gxml.String('@address-id')
    798      total_tax             = gxml.Complex('total-tax', price_t, required=False)
    799      shipping_rate         = gxml.Complex('shipping-rate', price_t, required=False)
    800      shippable             = gxml.Boolean('shippable', required=False)
    801      merchant_code_results = gxml.Complex('merchant-code-results',
    802                                           merchant_code_results_t,
    803                                           required=False)
    804
    805 -class merchant_calculation_results_t(gxml.Document):
    806      tag_name = 'merchant-calculation-results'
    807      results = gxml.List('results', gxml.Complex('result', result_t))
    808
    809 -class request_received_t(gxml.Document):
    810      tag_name = 'request-received'
    811      serial_number = gxml.ID('@serial-number')
    812
    813  # This is custom message type which is only suitable for returning to google
    814  # the 'Ok' response.
    815 -class ok_t(gxml.Document):
    816      tag_name = 'ok'
    817
    818 -class error_t(gxml.Document):
    819      """
    820      Represents a response containing information about an invalid API request.
    821      The information is intended to help you debug the problem causing the error.
    822
    823      >>> test_document(
    824      ...     error_t(serial_number = '3c394432-8270-411b-9239-98c2c499f87f',
    825      ...             error_message='Bad username and/or password for API Access.',
    826      ...             warning_messages = ['IP address is suspicious.',
    827      ...                                 'MAC address is shadowed.']
    828      ...     )
    829      ... ,
    830      ... '''
    831      ... <error xmlns="http://checkout.google.com/schema/2" serial-number="3c394432-8270-411b-9239-98c2c499f87f">
    832      ...   <error-message>Bad username and/or password for API Access.</error-message>
    833      ...   <warning-messages>
    834      ...     <string>IP address is suspicious.</string>
    835      ...     <string>MAC address is shadowed.</string>
    836      ...   </warning-messages>
    837      ... </error>
    838      ... '''
    839      ... )
    840      """
    841      tag_name = 'error'
    842      serial_number    = gxml.ID('@serial-number')
    843      error_message    = gxml.String('error-message')
    844      warning_messages = gxml.List('warning-messages',
    845                                   gxml.String('string'),
    846                                   required=False)
    847
    848 -class diagnosis_t(gxml.Document):
    849      """
    850      Represents a diagnostic response to an API request. The diagnostic
    851      response contains the parsed XML in your request as well as any warnings
    852      generated by your request.
    853      Please see the U{Validating XML Messages to Google Checkout
    854      <http://code.google.com/apis/checkout/developer/index.html#validating_xml_messages>}
    855      section for more information about diagnostic requests and responses.
    856      """
    857      tag_name      = 'diagnosis'
    858      input_xml     = gxml.Any('input-xml')
    859      warnings      = gxml.List('warnings',
    860                                gxml.String('string'),
    861                                required=False)
    862      serial_number = gxml.ID('@serial-number')
    863
    864 -class demo_failure_t(gxml.Document):
    865      """
    866      >>> test_document(
    867      ...     demo_failure_t(message='Demo Failure Message')
    868      ... ,
    869      ...     '''<demo-failure xmlns="http://checkout.google.com/schema/2"
    870      ...                      message="Demo Failure Message" />'''
    871      ... )
    872      """
    873      tag_name = 'demo-failure'
    874      message = gxml.String('@message', max_length=25)
    875
    876 -class item_id_t(gxml.Node):
    877      merchant_item_id = gxml.String('merchant-item-id')
    878
    879 -class backorder_items_t(abstract_order_t):
    880      tag_name   = 'backorder-items'
    881      item_ids   = gxml.List('item-ids', gxml.Complex('item-id', item_id_t))
    882      send_email = gxml.Boolean('send-email', required=False)
    883
    884 -class cancel_items_t(abstract_order_t):
    885      tag_name = 'cancel-items'
    886      reason = gxml.String('comment')
    887      comment = gxml.String('comment')
    888      item_ids   = gxml.List('item-ids', gxml.Complex('item-id', item_id_t))
    889      send_email = gxml.Boolean('send-email', required=False)
    890
    891 -class reset_items_shipping_information_t(abstract_order_t):
    892      tag_name   = 'reset-items-shipping-information'
    893      item_ids   = gxml.List('item-ids', gxml.Complex('item-id', item_id_t))
    894      send_email = gxml.Boolean('send-email', required=False)
    895
    896 -class return_items_t(abstract_order_t):
    897      tag_name   = 'return-items'
    898      item_ids   = gxml.List('item-ids', gxml.Complex('item-id', item_id_t))
    899      send_email = gxml.Boolean('send-email', required=False)
    900
    901 -class item_shipping_information_t(gxml.Node):
    902      item_id = gxml.Complex('item-id', item_id_t)
    903      tracking_data_list = gxml.List('tracking-data-list',
    904                                     gxml.Complex('tracking-data', tracking_data_t))
    905
    906 -class ship_items_t(abstract_order_t):
    907      tag_name   = 'ship-items'
    908      item_shipping_information_list = gxml.List('item-shipping-information-list',
    909                                                 gxml.Complex('item-shipping-information',
    910                                                              item_shipping_information_t))
    911      send_email = gxml.Boolean('send-email')
    912
    913  if __name__ == "__main__":
    914 -    def run_doctests():
    915          import doctest
    916          doctest.testmod()
    917      run_doctests()
    918