Skip to main content

Core ecommerce functionality for zope and python projects

Project description

This package contains the core functionality of the getpaid framework.

CHANGES

0.7.2 (2008-12-30)

  • Added 2 fixes (for when you use the shipping system):

  • gave a default value to the order.shipments, because it is never set up to anything otherwise and you can’t access your order,

  • fixed the way we calculate the total, so that we have float numbers [lucielejard]

0.7.1 (2008-11-24)

  • Persisted and added Name on Card and Card Phone Num to orders listing viewlet. [ctxlken]

  • Persisted processor transaction id and last-4 digits of credit card to ZODB. Also, modified order-summary.pt to present these two fields. [ctxlken]

  • add: added some missing italian translations [bruno.ripa]

  • update getpaid.po and sync with all .po. add Japanese locales [cjj.ifpeople]

  • add: missing italian translations [bruno.ripa]

0.7 (2008-08-29)

  • Added buildout files and general text documents to project root.

  • removed setup.cfg

  • updated txt files so that restructured text works on pypi

Detailed Documentation

Order Management in GetPaid

Getpaid’s core functionality is represented as an order management system.

Creating an Order
>>> from getpaid.core.order import Order
>>> order = Order()
Carts and Line Items

An order consists of line items. line items can come from a variety of sources, content space payables, gift certificates, ie. anything we potentially want to purchase:

>>> from getpaid.core.item import LineItem
>>> item = LineItem()

Let’s set some attributes expected on line items. The only system invariant here is that item_id should be unique when referring to purchasing the same item:

>>> item.item_id = "event-devtalk-2007-1"
>>> item.name = "Event Registration"
>>> item.cost = 25.00
>>> item.quantity = 5
>>> item.description = "Development Talk"

Line Items are stored in a line item container, such as a shopping cart of shipment:

>>> from getpaid.core.cart import ShoppingCart
>>> cart = ShoppingCart()
>>> cart[ item.item_id ] = item

we can ask the cart how many items it has:

>>> cart.size()
5

Let’s attach our cart to the order:

>>> order.shopping_cart = cart

and now we can ask the order, its total price:

>>> order.getSubTotalPrice()
Decimal("125.0")

[ xxx talk about products and payable line items here ??]

Customer Information

We need some additional information for an order to successfully process it:

>>> from getpaid.core import payment
>>> bill_address = payment.BillingAddress()
>>> bill_address.bill_first_line = '1418 W Street NW'
>>> bill_address.bill_city = 'Washington'
>>> bill_address.bill_state = "DC"
>>> bill_address.bill_country = "US"
>>> bill_address.bill_postal_code = '20009'
>>>
>>>
>>> contact_info = payment.ContactInformation()
>>>
>>> order.contact_information = contact_info
>>> order.billing_address = bill_address

If we don’t need to ship anything to the user, then we can forgo setting a shipping address.

Introspection and Classification

When we create an order, an order inspection component which subscribes to the order created event, gets a chance to look at all the contents of an order and modify it. The default inspector, will add additional marker interfaces to the order to classify it based on its contents as a shippable order, donation order, etc. Based on these marker interfaces and corresponding compnent registration, we can specialize adapation of orders to workflows, payment processing as appropriate for a given order.

>>> try:
...     from zope.lifecycleevent import ObjectCreatedEvent
... except ImportError:
...     from zope.app.event.objectevent import ObjectCreatedEvent
>>> from zope.event import notify
>>> notify( ObjectCreatedEvent( order ))
>>>
Finance Workflow

The finance workflow

Payment Processor Integration

We payment processor integration to support multiple different services and is workflow driven. We dispatch workflow events to a processor integration multi adapter which takes as context the order and the workflow.

The one public payment processor integration attribute on the order is the payment processor id, which corresponds to the name that the payment processor adapter is registered on.

Its also important to note that there are several varieties of asynchronous payment processors, which alsorequire corresponding checkout user interface support, and callback url endpoints, which are outside of the scope of this example. These doctest examples require a synchronous processor api.

Managing Collections of Orders
Querying Orders
Reporting on Orders

We use workflows to model the order lifecycle for finance and fulfillment. We can introspect orders to classify by them interface and adapt to the appropriate workflows. As a consequence we can support online and shipping based from the same order management system. and support virtual delivery, and a shipping lifecycle. we utilize hurry.workflow to implement our workflows, one benefits to make this lifecycle observable via event subscribers.

line items are stored in line item containers, like a shopping cart, or shipment. a line item is unique within these containers based on some unique attribute (at uid, or product sku).

getpaid internaly dispatches workflow changes to the appropriate payment processor for an order.

because we can process workflows asynchronously, we can get pretty good at synchronization / integration with other systems.

an order has both a finance workflow and a fulfillment workflow dependent on its contained items. the finance workflow models things like cc authorization for an order, and capture/charging an order.

Workflow Tests

Let’s first create an Order object to work with:

>>> from getpaid.core.order import Order
>>> testorder = Order()

Now we’ll test the order workflow…

Before we fire the ‘create’ transition we don’t have a workflow states for finance and fulfillment

>>> state = testorder.fulfillment_state
>>> print state
None
>>> state = testorder.finance_state
>>> print state
None

Firing the ‘create’ transition in the finance workflow should put us in the REVIEWING state

>>> testorder.finance_workflow.fireTransition('create')
>>> state = testorder.finance_state
>>> print state
REVIEWING

Firing some more transitions to test the finance workflow.

>>> testorder.finance_workflow.fireTransition('authorize')
>>> state = testorder.finance_state
>>> print state
CHARGEABLE
>>> testorder.finance_workflow.fireTransition('charge-chargeable')
>>> state = testorder.finance_state
>>> print state
CHARGING

Firing the ‘create’ transition in the fulfillment workflow should put us in the REVIEWING state

>>> testorder.fulfillment_workflow.fireTransition('create')
>>> state = testorder.fulfillment_state
>>> print state
NEW

Testing the fulfillment workflow for a delivered order. We need to re-cast the testorder object as we cannot transition back from

>>> testorder = Order()
>>> testorder.fulfillment_workflow.fireTransition('create')
>>> state = testorder.fulfillment_state
>>> print state
NEW
>>> testorder.fulfillment_workflow.fireTransition('process-order')
>>> state = testorder.fulfillment_state
>>> print state
PROCESSING
>>> testorder.fulfillment_workflow.fireTransition('deliver-processing-order')
>>> state = testorder.fulfillment_state
>>> print state
DELIVERED

Testing the fulfillment workflow for a cancelled order. We need to re-cast the testorder object as we cannot transition back from DELIVERED state.

>>> testorder2 = Order()
>>> testorder2.fulfillment_workflow.fireTransition('create')
>>> state = testorder2.fulfillment_state
>>> print state
NEW
>>> testorder2.fulfillment_workflow.fireTransition('process-order')
>>> state = testorder2.fulfillment_state
>>> print state
PROCESSING
>>> testorder2.fulfillment_workflow.fireTransition('cancel-order')
>>> state = testorder2.fulfillment_state
>>> print state
WILL_NOT_DELIVER
Order Id Management

Each order needs an Id with a strong requirement on it being unique and non-guessable. You can get a new, nonguessable id with a reasonable guarantee of it being unique by calling newOrderId().

>>> from zope import component
>>> from getpaid.core import interfaces
>>> from getpaid.core.order import Order
>>> order_manager = component.getUtility( interfaces.IOrderManager )
>>> order = Order()
>>> order.order_id = order_manager.newOrderId()
>>> order_manager.store( order )

now that the order is stored, no amount of calling newOrderId should return the same id. I can’t actually test for uniqueness or nonguessability, can I?

>>> for i in xrange(10000):
...    assert(order_manager.newOrderId() != order.order_id)

but on the other hand, I can test that if I create an order with the same id as an existing order, things will fail:

>>> new_order = Order()
>>> new_order.order_id = order.order_id
>>> order_manager.store( new_order )
Traceback (most recent call last):
   ...
DuplicationError: ...

Download

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

getpaid.core-0.7.2.tar.gz (58.4 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page