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: 91 $
     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_t(gxml.Node):
     91      """
     92      >>> 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',
     93      ...                  merchant_private_item_data=['some', {'private':'data', 'to':['test','the'],'thing':None}, '!! Numbers: ', None, False, True, [11, 12., [13.4]]])
     94      ...          )
     95      >>> test_node(item_t(name='Peter', description='The Great', unit_price=price_t(value=1, currency='GBP'), quantity=1))
     96      """
     97      name                = gxml.String('item-name')
     98      description         = gxml.String('item-description')
     99      unit_price          = gxml.Complex('unit-price', price_t)
    100      quantity            = gxml.Decimal('quantity')
    101      merchant_item_id    = gxml.String('merchant-item-id', required=False)
    102      tax_table_selector  = gxml.String('tax-table-selector', required=False)
    103      digital_content     = gxml.Complex('digital-content', digital_content_t, required=False)
    104      merchant_private_item_data = gxml.Any('merchant-private-item-data',
    105                                            save_node_and_xml=True,
    106                                            required=False)
    107
    108 -class postal_area_t(gxml.Node):
    109      """
    110      >>> test_node(postal_area_t(country_code = 'VU'),
    111      ... '''
    112      ... <node><country-code>VU</country-code></node>
    113      ... '''
    114      ... )
    115      """
    116      country_code        = CountryCode('country-code')
    117      postal_code_pattern = gxml.String('postal-code-pattern', required=False)
    118
    119 -class tax_area_t(gxml.Node):
    120      world_area   = PresentOrNot('world-area', required=False)
    121      postal_area  = gxml.Complex('postal-area', postal_area_t, required=False)
    122      us_state        = gxml.String('us-state-area/state', required=False)
    123      us_zip_pattern  = gxml.String('us-zip-area/zip-pattern', required=False) # regex: [a-zA-Z0-9_]+\*? Note the optional asterisk
    124      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')
    125
    126 -class areas_t(gxml.Node):
    127      """
    128      Represents a list of regions.
    129
    130      >>> test_node(
    131      ...   areas_t(
    132      ...     states = ['LA', 'NY'],
    133      ...     country_areas = ['ALL', 'CONTINENTAL_48']
    134      ...   )
    135      ... ,
    136      ... '''
    137      ... <node>
    138      ... <us-state-area>
    139      ...   <state>LA</state>
    140      ... </us-state-area>
    141      ... <us-state-area>
    142      ...   <state>NY</state>
    143      ... </us-state-area>
    144      ... <us-country-area>
    145      ...   <country-area>ALL</country-area>
    146      ... </us-country-area>
    147      ... <us-country-area>
    148      ...   <country-area>CONTINENTAL_48</country-area>
    149      ... </us-country-area>
    150      ... </node>
    151      ... '''
    152      ... )
    153      """
    154      states        = gxml.List('', gxml.String('us-state-area/state'), required=False)
    155      zip_patterns  = gxml.List('', gxml.String('us-zip-area/zip-pattern'), required=False) # regex: [a-zA-Z0-9_]+\*? Note the optional asterisk
    156      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')
    157
    158 -class allowed_areas_t(areas_t):
    159      postal_areas  = gxml.List('', gxml.Complex('postal-area', postal_area_t), required=False)
    160      world_area   = PresentOrNot('world-area', required=False)
    161
    162 -class excluded_areas_t(areas_t):
    163      postal_areas  = gxml.List('', gxml.Complex('postal-area', postal_area_t), required=False)
    164
    165 -class tax_rule_t(gxml.Node):
    166      rate     = gxml.Double('rate', default=0.)
    167      tax_area = gxml.Complex('tax-area', tax_area_t)
    168
    169 -class default_tax_rule_t(tax_rule_t):
    170      shipping_taxed = gxml.Boolean('shipping-taxed', required=False)
    171
    172 -class alternate_tax_rule_t(tax_rule_t):
    173      pass
    174
    175 -class default_tax_table_t(gxml.Node):
    176      tax_rules = gxml.List('tax-rules', gxml.Complex('default-tax-rule', default_tax_rule_t))
    177
    178 -class alternate_tax_table_t(gxml.Node):
    179      name                = gxml.String('@name')
    180      standalone          = gxml.Boolean('@standalone')
    181      alternate_tax_rules = gxml.List('alternate-tax-rules', gxml.Complex('alternate-tax-rule', alternate_tax_rule_t))
    182
    183 -class tax_tables_t(gxml.Node):
    184      merchant_calculated = gxml.Boolean('@merchant-calculated', default=False)
    185      default             = gxml.Complex('default-tax-table', default_tax_table_t)
    186      alternates          = gxml.List('alternate-tax-tables', gxml.Complex('alternate-tax-table', alternate_tax_table_t), required=False)
    187
    188 -class merchant_calculations_t(gxml.Node):
    189      merchant_calculations_url = gxml.Url('merchant-calculations-url')
    190      accept_merchant_coupons   = gxml.Boolean('accept-merchant-coupons', required=False)
    191      accept_gift_certificates  = gxml.Boolean('accept-gift-certificates', required=False)
    192
    193 -class shipping_option_t(gxml.Node):
    194      """
    195      Represents information about shipping costs.
    196
    197      >>> test_node(
    198      ...   shipping_option_t(
    199      ...       name = 'Testing',
    200      ...         price = price_t(
    201      ...             currency = 'GBP',
    202      ...             value = 9.99,
    203      ...             ),
    204      ...         allowed_areas = allowed_areas_t(
    205      ...             world_area = True,
    206      ...             ),
    207      ...         excluded_areas = excluded_areas_t(
    208      ...             postal_areas = [postal_area_t(
    209      ...                 country_code = 'US',
    210      ...                 )],
    211      ...             ),
    212      ...         )
    213      ... , '''
    214      ... <node name='Testing'>
    215      ...   <price currency='GBP'>9.990</price>
    216      ...   <shipping-restrictions>
    217      ...     <allowed-areas>
    218      ...       <world-area/>
    219      ...     </allowed-areas>
    220      ...     <excluded-areas>
    221      ...       <postal-area>
    222      ...         <country-code>US</country-code>
    223      ...       </postal-area>
    224      ...     </excluded-areas>
    225      ...   </shipping-restrictions>
    226      ... </node>
    227      ... ''')
    228      """
    229      name = gxml.String('@name') # Attribute, len <= 255, not-empty
    230      price = gxml.Complex('price', price_t)
    231      allowed_areas  = gxml.Complex('shipping-restrictions/allowed-areas', allowed_areas_t, required=False)
    232      excluded_areas = gxml.Complex('shipping-restrictions/excluded-areas', excluded_areas_t, required=False)
    233
    234 -class flat_rate_shipping_t(shipping_option_t):
    235      pass
    236 -class merchant_calculated_shipping_t(shipping_option_t):
    237      pass
    238
    239 -class pickup_t(gxml.Node):
    240      name = gxml.String('@name')
    241      price = gxml.Complex('price', price_t)
    242
    243 -class shipping_methods_t(gxml.Node):
    244      flat_rate_shippings           = gxml.List('', gxml.Complex('flat-rate-shipping', flat_rate_shipping_t), required=False) # list of flat_rate_shipping_t
    245      merchant_calculated_shippings = gxml.List('', gxml.Complex('merchant-calculated-shipping', merchant_calculated_shipping_t), required=False) # list of merchant_calculated_shipping_t
    246      pickups                       = gxml.List('', gxml.Complex('pickup', pickup_t), required=False) # list of pickup_t
    247
    248  URL_PARAMETER_TYPES=(
    249      'buyer-id', # A Google-assigned value that uniquely identifies a customer email address.
    250      '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.
    251      'order-subtotal', # The total cost for all of the items in the order including coupons and discounts but excluding taxes and shipping charges.
    252      'order-subtotal-plus-tax', # The total cost for all of the items in the order, including taxes, coupons and discounts, but excluding shipping charges.
    253      'order-subtotal-plus-shipping', # The total cost for all of the items in the order, including shipping charges, coupons and discounts, but excluding taxes.
    254      'order-total', # The total cost for all of the items in the order, including taxes, shipping charges, coupons and discounts.
    255      'tax-amount', # The total amount of taxes charged for an order.
    256      'shipping-amount', # The shipping cost associated with an order.
    257      'coupon-amount', # The total amount of all coupons factored into the order total.
    258      'billing-city', # The city associated with the order's billing address.
    259      'billing-region', # The U.S. state associated with the order's billing address.
    260      'billing-postal-code', # The five-digit U.S. zip code associated with the order's billing address.
    261      'billing-country-code', # The two-letter ISO 3166 country code associated with the order's billing address.
    262      'shipping-city', # The city associated with the order's shipping address.
    263      'shipping-region', # The U.S. state associated with the order's shipping address.
    264      'shipping-postal-code', # The five-digit U.S. zip code associated with the order's shipping address.
    265      'shipping-country-code', # The two-letter ISO 3166 country code associated with the order's shipping address.',
    266  )
    267
    268 -class url_parameter_t(gxml.Node):
    269      name = gxml.String('@name')
    270      type = gxml.String('@type', values=URL_PARAMETER_TYPES)
    271
    272 -class parameterized_url_t(gxml.Node):
    273      url        = gxml.Url('@url', required=True)
    274      parameters = gxml.List('parameters', gxml.Complex('url-parameter', url_parameter_t), required=True)
    275
    276 -class checkout_flow_support_t(gxml.Node):
    277      """
    278      >>> test_node(
    279      ...   checkout_flow_support_t(
    280      ...     parameterized_urls = [
    281      ...         parameterized_url_t(
    282      ...             url='http://google.com/',
    283      ...             parameters=[url_parameter_t(name='a', type='buyer-id')]
    284      ...         ),
    285      ...         parameterized_url_t(
    286      ...             url='http://yahoo.com/',
    287      ...             parameters=[url_parameter_t(name='a', type='shipping-city'),
    288      ...                         url_parameter_t(name='b', type='tax-amount')]
    289      ...         ),
    290      ...         parameterized_url_t(
    291      ...             url='http://mozilla.com/',
    292      ...             parameters=[url_parameter_t(name='a', type='order-total'),
    293      ...                         url_parameter_t(name='b', type='shipping-region'),
    294      ...                         url_parameter_t(name='c', type='shipping-country-code')]
    295      ...         )
    296      ...     ],
    297      ...   )
    298      ... ,
    299      ... '''
    300      ... <node>
    301      ...   <parameterized-urls>
    302      ...     <parameterized-url url="http://google.com/">
    303      ...       <parameters>
    304      ...         <url-parameter name="a" type="buyer-id"/>
    305      ...       </parameters>
    306      ...     </parameterized-url>
    307      ...     <parameterized-url url="http://yahoo.com/">
    308      ...       <parameters>
    309      ...         <url-parameter name="a" type="shipping-city"/>
    310      ...         <url-parameter name="b" type="tax-amount"/>
    311      ...       </parameters>
    312      ...     </parameterized-url>
    313      ...     <parameterized-url url="http://mozilla.com/">
    314      ...       <parameters>
    315      ...         <url-parameter name="a" type="order-total"/>
    316      ...         <url-parameter name="b" type="shipping-region"/>
    317      ...         <url-parameter name="c" type="shipping-country-code"/>
    318      ...       </parameters>
    319      ...     </parameterized-url>
    320      ...   </parameterized-urls>
    321      ... </node>
    322      ... '''
    323      ... )
    324      """
    325      edit_cart_url              = gxml.Url('edit-cart-url', required=False)         # optional, URL
    326      continue_shopping_url      = gxml.Url('continue-shopping-url', required=False) # optional, URL
    327      tax_tables                 = gxml.Complex('tax-tables', tax_tables_t, required=False) # optional, tax_tables_t
    328      shipping_methods           = gxml.Complex('shipping-methods', shipping_methods_t, required=False) # optional, shipping_methods_t
    329      merchant_calculations      = gxml.Complex('merchant-calculations', merchant_calculations_t, required=False) # optional, merchant_calculations_t
    330      request_buyer_phone_number = gxml.Boolean('request-buyer-phone-number', required=False) # optional, Boolean
    331      platform_id                = gxml.Long('platform-id', required=False)
    332      analytics_data             = gxml.String('analytics-data', required=False)
    333      parameterized_urls         = gxml.List('parameterized-urls', gxml.Complex('parameterized-url', parameterized_url_t), required=False)
    334
    335 -class shopping_cart_t(gxml.Node):
    336      expiration            = gxml.Timestamp('cart-expiration/good-until-date', required=False)
    337      items                 = gxml.List('items', gxml.Complex('item', item_t))
    338      merchant_private_data = gxml.Any('merchant-private-data',
    339                                       save_node_and_xml=True,
    340                                       required=False)
    341
    342 -class hello_t(gxml.Document):
    343      """
    344      Represents a simple test that verifies that your server communicates
    345      properly with Google Checkout. The fourth step of
    346      the U{Getting Started with Google Checkout<http://code.google.com/apis/checkout/developer/index.html#integration_overview>}
    347      section of the Developer's Guide explains how to execute this test.
    348
    349      >>> test_document(hello_t(),
    350      ...               "<hello xmlns='http://checkout.google.com/schema/2'/>"
    351      ... )
    352      """
    353      tag_name='hello'
    354
    355 -class bye_t(gxml.Document):
    356      """
    357      Represents a response that indicates that Google correctly received
    358      a <hello> request.
    359
    360      >>> test_document(
    361      ...     bye_t(serial_number="7315dacf-3a2e-80d5-aa36-8345cb54c143")
    362      ... ,
    363      ...     '''
    364      ...     <bye xmlns="http://checkout.google.com/schema/2"
    365      ...           serial-number="7315dacf-3a2e-80d5-aa36-8345cb54c143" />
    366      ...     '''
    367      ... )
    368      """
    369      tag_name = 'bye'
    370      serial_number = gxml.ID('@serial-number')
    371
    372 -class checkout_shopping_cart_t(gxml.Document):
    373      tag_name = 'checkout-shopping-cart'
    374      shopping_cart                = gxml.Complex('shopping-cart', shopping_cart_t)
    375      checkout_flow_support        = gxml.Complex('checkout-flow-support/merchant-checkout-flow-support', checkout_flow_support_t)
    376      request_initial_auth_details = gxml.Boolean('order-processing-support/request-initial-auth-details', required=False)
    377
    378 -class coupon_gift_adjustment_t(gxml.Node):
    379      code              = gxml.String('code')
    380      calculated_amount = gxml.Complex('calculated-amount', price_t, required=False)
    381      applied_amount    = gxml.Complex('applied-amount', price_t)
    382      message           = gxml.String('message', required=False)
    383
    384 -class merchant_codes_t(gxml.Node):
    385      gift_certificate_adjustment = gxml.List('', gxml.Complex('gift-certificate-adjustment', coupon_gift_adjustment_t))
    386      coupon_adjustment           = gxml.List('', gxml.Complex('coupon-adjustment', coupon_gift_adjustment_t))
    387
    388 -class shipping_adjustment_t(gxml.Node):
    389      shipping_name = gxml.String('shipping-name')
    390      shipping_cost = gxml.Complex('shipping-cost', price_t)
    391
    392  # Two classes below represent the single 'shipping' tage, which content
    393  # depends on the context the XML Node is present.
    394  # http://code.google.com/apis/checkout/developer/index.html#tag_shipping
    395 -class shipping_in_order_adjustment_t(gxml.Node):
    396      merchant_calculated_shipping_adjustment = gxml.Complex('merchant-calculated-shipping-adjustment', shipping_adjustment_t, required=False)
    397      flat_rate_shipping_adjustment           = gxml.Complex('flat-rate-shipping-adjustment', shipping_adjustment_t, required=False)
    398      pickup_shipping_adjustment              = gxml.Complex('pickup-shipping-adjustment', shipping_adjustment_t, required=False)
    399
    400 -class shipping_in_calculate_t(gxml.Node):
    401      methods                                 = gxml.List('', gxml.String('method/@name'))
    402
    403 -class order_adjustment_t(gxml.Node):
    404      adjustment_total                = gxml.Complex('adjustment-total', price_t, required=False)
    405      merchant_calculation_successful = gxml.Boolean('merchant-calculation-successful', required=False)
    406      merchant_codes                  = gxml.Complex('merchant-codes', merchant_codes_t, required=False)
    407      shipping                        = gxml.Complex('shipping', shipping_in_order_adjustment_t, required=False)
    408      total_tax                       = gxml.Complex('total-tax', price_t, required=False)
    409
    410 -class address_t(gxml.Node):
    411      address1     = gxml.String('address1')
    412      address2     = gxml.String('address2', required=False)
    413      city         = gxml.String('city')
    414      company_name = gxml.String('company-name', required=False)
    415      contact_name = gxml.String('contact-name', required=False)
    416      country_code = gxml.String('country-code')
    417      email        = gxml.Email('email', required=False)
    418      fax          = gxml.Phone('fax', required=False, empty=True)
    419      phone        = gxml.Phone('phone', required=False, empty=True)
    420      postal_code  = gxml.Zip('postal-code')
    421      region       = gxml.String('region', empty=True)
    422
    423 -class buyer_billing_address_t(address_t):
    424      pass
    425 -class buyer_shipping_address_t(address_t):
    426      pass
    427 -class billing_address_t(address_t):
    428      # google docs do not say address_id is optional, but sandbox omits it.. :S bug?
    429      address_id = gxml.ID('@address-id', required=False)
    430
    431 -class buyer_marketing_preferences_t(gxml.Node):
    432      email_allowed = gxml.Boolean('email-allowed')
    433 -    def read(self, node):
    434          return gxml.Node.read(self, node)
    435 -class abstract_notification_t(gxml.Document):
    436      tag_name = '-notification'
    437      serial_number       = gxml.ID('@serial-number')
    438      google_order_number = gxml.ID('google-order-number')
    439      timestamp                   = gxml.Timestamp('timestamp')
    440
    441  FINANCIAL_ORDER_STATE=('REVIEWING', 'CHARGEABLE', 'CHARGING', 'CHARGED', 'PAYMENT_DECLINED', 'CANCELLED', 'CANCELLED_BY_GOOGLE')
    442  FULFILLMENT_ORDER_STATE=('NEW', 'PROCESSING', 'DELIVERED', 'WILL_NOT_DELIVER')
    443
    444 -class new_order_notification_t(abstract_notification_t):
    445      tag_name = 'new-order-notification'
    446      buyer_billing_address       = gxml.Complex('buyer-billing-address', buyer_billing_address_t)
    447      buyer_id                    = gxml.Long('buyer-id')
    448      buyer_marketing_preferences = gxml.Complex('buyer-marketing-preferences', buyer_marketing_preferences_t)
    449      buyer_shipping_address      = gxml.Complex('buyer-shipping-address', buyer_shipping_address_t)
    450      financial_order_state       = gxml.String('financial-order-state', values=FINANCIAL_ORDER_STATE)
    451      fulfillment_order_state     = gxml.String('fulfillment-order-state', values=FULFILLMENT_ORDER_STATE)
    452      order_adjustment            = gxml.Complex('order-adjustment', order_adjustment_t)
    453      order_total                 = gxml.Complex('order-total', price_t)
    454      shopping_cart               = gxml.Complex('shopping-cart', shopping_cart_t)
    455
    456 -class checkout_redirect_t(gxml.Document):
    457      """
    458      Try doctests:
    459      >>> a = checkout_redirect_t(serial_number='blabla12345',
    460      ...                         redirect_url='http://www.somewhere.com')
    461      >>> b = gxml.Document.fromxml(a.toxml())
    462      >>> a == b
    463      True
    464      """
    465      tag_name = 'checkout-redirect'
    466      serial_number = gxml.ID('@serial-number')
    467      redirect_url  = gxml.Url('redirect-url')
    468
    469 -class notification_acknowledgment_t(gxml.Document):
    470      tag_name = 'notification-acknowledgment'
    471
    472 -class order_state_change_notification_t(abstract_notification_t):
    473      tag_name = 'order-state-change-notification'
    474      new_fulfillment_order_state      = gxml.String('new-fulfillment-order-state', values=FINANCIAL_ORDER_STATE)
    475      new_financial_order_state        = gxml.String('new-financial-order-state', values=FULFILLMENT_ORDER_STATE)
    476      previous_financial_order_state   = gxml.String('previous-financial-order-state', values=FINANCIAL_ORDER_STATE)
    477      previous_fulfillment_order_state = gxml.String('previous-fulfillment-order-state', values=FULFILLMENT_ORDER_STATE)
    478      reason                           = gxml.String('reason', required=False)
    479
    480  AVS_VALUES=('Y', 'P', 'A', 'N', 'U')
    481  CVN_VALUES=('M', 'N', 'U', 'E')
    482
    483 -class risk_information_t(gxml.Node):
    484      avs_response            = gxml.String('avs-response', values=AVS_VALUES)
    485      billing_address         = gxml.Complex('billing-address', billing_address_t)
    486      buyer_account_age       = gxml.Integer('buyer-account-age')
    487      cvn_response            = gxml.String('cvn-response', values=CVN_VALUES)
    488      eligible_for_protection = gxml.Boolean('eligible-for-protection')
    489      ip_address              = gxml.IP('ip-address')
    490      partial_cc_number       = gxml.String('partial-cc-number') # partial CC Number
    491
    492 -class risk_information_notification_t(abstract_notification_t):
    493      tag_name = 'risk-information-notification'
    494      risk_information = gxml.Complex('risk-information', risk_information_t)
    495
    496 -class abstract_order_t(gxml.Document):
    497      tag_name='-order'
    498      google_order_number = gxml.ID('@google-order-number')
    499
    500 -class charge_order_t(abstract_order_t):
    501      tag_name = 'charge-order'
    502      amount = gxml.Complex('amount', price_t, required=False)
    503
    504 -class refund_order_t(abstract_order_t):
    505      tag_name = 'refund-order'
    506      amount  = gxml.Complex('amount', price_t, required=False)
    507      comment = gxml.String('comment', max_length=140, required=False)
    508      reason  = gxml.String('reason', max_length=140)
    509
    510 -class cancel_order_t(abstract_order_t):
    511      """
    512      Represents an order that should be canceled. A <cancel-order> command
    513      sets the financial-order-state and the fulfillment-order-state to canceled.
    514
    515      >>> test_document(
    516      ...     cancel_order_t(google_order_number = "841171949013218",
    517      ...                    comment = 'Buyer found a better deal.',
    518      ...                    reason = 'Buyer cancelled the order.'
    519      ...     )
    520      ... ,
    521      ...     '''
    522      ...     <cancel-order xmlns="http://checkout.google.com/schema/2" google-order-number="841171949013218">
    523      ...       <comment>Buyer found a better deal.</comment>
    524      ...       <reason>Buyer cancelled the order.</reason>
    525      ...     </cancel-order>
    526      ...     '''
    527      ... )
    528      """
    529      tag_name = 'cancel-order'
    530      comment = gxml.String('comment', max_length=140, required=False)
    531      reason  = gxml.String('reason', max_length=140)
    532
    533 -class authorize_order_t(abstract_order_t):
    534      tag_name = 'authorize-order'
    535
    536 -class process_order_t(abstract_order_t):
    537      tag_name = 'process-order'
    538
    539 -class add_merchant_order_number_t(abstract_order_t):
    540      tag_name = 'add-merchant-order-number'
    541      merchant_order_number = gxml.String('merchant-order-number')
    542
    543  CARRIER_VALUES=('DHL', 'FedEx', 'UPS', 'USPS', 'Other')
    544
    545 -class tracking_data_t(gxml.Node):
    546      carrier         = gxml.String('carrier', values=CARRIER_VALUES)
    547      tracking_number = gxml.String('tracking-number')
    548
    549 -class deliver_order_t(abstract_order_t):
    550      tag_name='deliver-order'
    551      tracking_data = gxml.Complex('tracking-data', tracking_data_t, required=False)
    552      send_email    = gxml.Boolean('send-email', required=False)
    553
    554 -class add_tracking_data_t(abstract_order_t):
    555      """
    556      Represents a tag containing a request to add a shipper's tracking number
    557      to an order.
    558
    559      >>> test_document(
    560      ...     add_tracking_data_t(
    561      ...         google_order_number = '841171949013218',
    562      ...         tracking_data = tracking_data_t(
    563      ...             carrier = 'UPS',
    564      ...             tracking_number = 'Z9842W69871281267'
    565      ...         )
    566      ...     )
    567      ... ,
    568      ...     '''
    569      ...     <add-tracking-data xmlns="http://checkout.google.com/schema/2"
    570      ...                        google-order-number="841171949013218">
    571      ...       <tracking-data>
    572      ...         <tracking-number>Z9842W69871281267</tracking-number>
    573      ...         <carrier>UPS</carrier>
    574      ...       </tracking-data>
    575      ...     </add-tracking-data>
    576      ...     '''
    577      ... )
    578      """
    579      tag_name='add-tracking-data'
    580      tracking_data = gxml.Complex('tracking-data', tracking_data_t)
    581
    582 -class send_buyer_message_t(abstract_order_t):
    583      tag_name='send-buyer-message'
    584      send_email = gxml.Boolean('send-email', required=False)
    585      message    = gxml.String('message')
    586
    587 -class archive_order_t(abstract_order_t):
    588      """
    589      Represents a request to archive a particular order. You would archive
    590      an order to remove it from your Merchant Center Inbox, indicating that
    591      the order has been delivered.
    592
    593      >>> test_document(archive_order_t(google_order_number = '841171949013218'),
    594      ...     '''<archive-order xmlns="http://checkout.google.com/schema/2"
    595      ...                       google-order-number="841171949013218" />'''
    596      ... )
    597      """
    598      tag_name='archive-order'
    599
    600 -class unarchive_order_t(abstract_order_t):
    601      tag_name='unarchive-order'
    602
    603 -class charge_amount_notification_t(abstract_notification_t):
    604      """
    605      Represents information about a successful charge for an order.
    606
    607      >>> from datetime import datetime
    608      >>> import iso8601
    609      >>> test_document(
    610      ...     charge_amount_notification_t(
    611      ...         serial_number='95d44287-12b1-4722-bc56-cfaa73f4c0d1',
    612      ...         google_order_number = '841171949013218',
    613      ...         timestamp = iso8601.parse_date('2006-03-18T18:25:31.593Z'),
    614      ...         latest_charge_amount = price_t(currency='USD', value=2226.06),
    615      ...         total_charge_amount = price_t(currency='USD', value=2226.06)
    616      ...     )
    617      ... ,
    618      ...     '''
    619      ...     <charge-amount-notification xmlns="http://checkout.google.com/schema/2" serial-number="95d44287-12b1-4722-bc56-cfaa73f4c0d1">
    620      ...       <latest-charge-amount currency="USD">2226.060</latest-charge-amount>
    621      ...       <google-order-number>841171949013218</google-order-number>
    622      ...       <total-charge-amount currency="USD">2226.060</total-charge-amount>
    623      ...       <timestamp>2006-03-18T18:25:31.593000+00:00</timestamp>
    624      ...     </charge-amount-notification>
    625      ...     '''
    626      ... )
    627      """
    628      tag_name='charge-amount-notification'
    629      latest_charge_amount = gxml.Complex('latest-charge-amount', price_t)
    630      total_charge_amount  = gxml.Complex('total-charge-amount', price_t)
    631
    632 -class refund_amount_notification_t(abstract_notification_t):
    633      tag_name='refund-amount-notification'
    634      latest_refund_amount = gxml.Complex('latest-refund-amount', price_t)
    635      total_refund_amount  = gxml.Complex('total-refund-amount', price_t)
    636
    637 -class chargeback_amount_notification_t(abstract_notification_t):
    638      tag_name='chargeback-amount-notification'
    639      latest_chargeback_amount = gxml.Complex('latest-chargeback-amount', price_t)
    640      total_chargeback_amount  = gxml.Complex('total-chargeback-amount', price_t)
    641
    642 -class authorization_amount_notification_t(abstract_notification_t):
    643      tag_name='authorization-amount-notification'
    644      authorization_amount          = gxml.Complex('authorization-amount', price_t)
    645      authorization_expiration_date = gxml.Timestamp('authorization-expiration-date')
    646      avs_response                  = gxml.String('avs-response', values=AVS_VALUES)
    647      cvn_response                  = gxml.String('cvn-response', values=CVN_VALUES)
    648
    649 -class anonymous_address_t(gxml.Node):
    650      id           = gxml.String('@id')
    651      city         = gxml.String('city')
    652      region       = gxml.String('region', empty=True)
    653      postal_code  = gxml.Zip('postal-code')
    654      country_code = gxml.String('country-code')
    655
    656 -class merchant_code_string_t(gxml.Node):
    657      code = gxml.String('@code')
    658
    659 -class calculate_t(gxml.Node):
    660      addresses             = gxml.List('addresses',
    661                                        gxml.Complex('anonymous-address', anonymous_address_t),
    662                                        required=False)
    663      merchant_code_strings = gxml.Complex('merchant-code-strings/merchant-code-string',
    664                                           merchant_code_string_t,
    665                                           required=False)
    666      shipping              = gxml.Complex('shipping', shipping_in_calculate_t, required=False)
    667      tax                   = gxml.Boolean('tax')
    668
    669 -class merchant_calculation_callback_t(gxml.Document):
    670      tag_name = 'merchant-calculation-callback'
    671      serial_number    = gxml.ID('@serial-number')
    672      buyer_id         = gxml.Long('buyer-id', required=False)
    673      buyer_language   = gxml.LanguageCode('buyer-language')
    674      calculate        = gxml.Complex('calculate', calculate_t)
    675      shopping_cart    = gxml.Complex('shopping-cart', shopping_cart_t)
    676
    677 -class discount_result_t(gxml.Node):
    678      valid             = gxml.Boolean('valid')
    679      calculated_amount = gxml.Complex('calculated-amount', price_t)
    680      code              = gxml.String('code')
    681      message           = gxml.String('message', max_length=255)
    682
    683 -class merchant_code_results_t(gxml.Node):
    684      coupon_result           = gxml.List('', gxml.Complex('coupon-result', discount_result_t))
    685      gift_certificate_result = gxml.List('', gxml.Complex('gift-certificate-result', discount_result_t))
    686
    687 -class result_t(gxml.Node):
    688      shipping_name         = gxml.String('@shipping-name')
    689      address_id            = gxml.String('@address-id')
    690      total_tax             = gxml.Complex('total-tax', price_t, required=False)
    691      shipping_rate         = gxml.Complex('shipping-rate', price_t, required=False)
    692      shippable             = gxml.Boolean('shippable', required=False)
    693      merchant_code_results = gxml.Complex('merchant-code-results',
    694                                           merchant_code_results_t,
    695                                           required=False)
    696
    697 -class merchant_calculation_results_t(gxml.Document):
    698      tag_name = 'merchant-calculation-results'
    699      results = gxml.List('results', gxml.Complex('result', result_t))
    700
    701 -class request_received_t(gxml.Document):
    702      tag_name = 'request-received'
    703      serial_number = gxml.ID('@serial-number')
    704
    705  # This is custom message type which is only suitable for returning to google
    706  # the 'Ok' response.
    707 -class ok_t(gxml.Document):
    708      tag_name = 'ok'
    709
    710 -class error_t(gxml.Document):
    711      """
    712      Represents a response containing information about an invalid API request.
    713      The information is intended to help you debug the problem causing the error.
    714
    715      >>> test_document(
    716      ...     error_t(serial_number = '3c394432-8270-411b-9239-98c2c499f87f',
    717      ...             error_message='Bad username and/or password for API Access.',
    718      ...             warning_messages = ['IP address is suspicious.',
    719      ...                                 'MAC address is shadowed.']
    720      ...     )
    721      ... ,
    722      ... '''
    723      ... <error xmlns="http://checkout.google.com/schema/2" serial-number="3c394432-8270-411b-9239-98c2c499f87f">
    724      ...   <error-message>Bad username and/or password for API Access.</error-message>
    725      ...   <warning-messages>
    726      ...     <string>IP address is suspicious.</string>
    727      ...     <string>MAC address is shadowed.</string>
    728      ...   </warning-messages>
    729      ... </error>
    730      ... '''
    731      ... )
    732      """
    733      tag_name = 'error'
    734      serial_number    = gxml.ID('@serial-number')
    735      error_message    = gxml.String('error-message')
    736      warning_messages = gxml.List('warning-messages',
    737                                   gxml.String('string'),
    738                                   required=False)
    739
    740 -class diagnosis_t(gxml.Document):
    741      """
    742      Represents a diagnostic response to an API request. The diagnostic
    743      response contains the parsed XML in your request as well as any warnings
    744      generated by your request.
    745      Please see the U{Validating XML Messages to Google Checkout
    746      <http://code.google.com/apis/checkout/developer/index.html#validating_xml_messages>}
    747      section for more information about diagnostic requests and responses.
    748      """
    749      tag_name = 'diagnosis'
    750      input_xml = gxml.Any('input-xml')
    751      warnings  = gxml.List('warnings',
    752                            gxml.String('string'),
    753                            required=False)
    754
    755 -class demo_failure_t(gxml.Document):
    756      """
    757      >>> test_document(
    758      ...     demo_failure_t(message='Demo Failure Message')
    759      ... ,
    760      ...     '''<demo-failure xmlns="http://checkout.google.com/schema/2"
    761      ...                      message="Demo Failure Message" />'''
    762      ... )
    763      """
    764      tag_name = 'demo-failure'
    765      message = gxml.String('@message', max_length=25)
    766
    767  if __name__ == "__main__":
    768 -    def run_doctests():
    769          import doctest
    770          doctest.testmod()
    771      run_doctests()
    772