Solving the hard parts of eCommerce

Symfony Live Madrid 2014

Presented by Bojan Zivanovic / @bojan_zivanovic

Bojan Zivanovic
Commerce Development Lead at Commerce Guys

Our vision is for Drupal Commerce to be the number one
open source eCommerce platform in the world.

We're doing something right...

Commerce Guys

Paris | London | Ann Arbor, MI

Creators of and Drupal Commerce

Commerce 2.x Sprint

Once again, we start from scratch

Libraries first

Currency handling

Currency formatting

Turning '1999.99 USD' into '$1,999.99'

  • Decimal separator (.)
  • Thousands separator (,)
  • Position of the currency symbol (before/after? Space?)

Currency formatting

12 345,99 €

12.345,99 €


Currency formatting

د.إ.‏ ٩٩٩٫٩٩

Different numbering systems!

There's an extension for that: intl

But you might not have it

And it might not be up to date

Where does the data come from?

Provides the largest and most extensive standard repository of locale data available.

Coming soon to symfony/intl

Build me an address form!

Data model: Address

  • Recipient, Organization
  • Address line 1, Address line 2
  • Postal code, Sorting code
  • Locality, Dependent locality
  • Administrative area, Country

Data model: Address format

  • Which fields are used, and in which order
  • Which fields are required
  • Which fields need to be uppercased
  • Field labels
  • Regular expression for validating postal codes

Data model: Subdivision

  • Hierarchical, up to 3 levels
  • Multilingual user-facing name
  • Code (used on envelopes, e.g. CA for California)
  • Postal code validation pattern

Form generation

  • AddressType -> Field subscriber
  • Gets the address format for the selected country, generates the fields
  • Experimental


  • Country code is valid.
  • All required fields are filled in.
  • All fields unused by the country's format are empty.
  • All subdivisions are valid (values matched against predefined subdivisions).
  • The postal code is valid (country and subdivision-level patterns).


  • Formats an address according to the destination country format.
  • Appends the country name in the local language
  • Takes care of major-to-minor and minor-to-major field ordering.

Territorial groupings

  • California and Nevada
  • European Union
  • Germany and a set of Austrian postal codes (6691, 6991, 6992, 6993)
  • Austria without specific postal codes (6691, 6991, 6992, 6993)

Data model

Each zone consists out of zone members.

  • ZoneMemberZone
  • ZoneMemberCountry*

Special: include/exclude postal codes

Matching an address

  • against a single zone: $zone->match($address)
  • against all zones: ZoneMatcher

Problem #1: What are my rates?

Problem #1.1: 2015 VOES

  • VAT on Electronically Supplied Services
  • When selling a digital product, VAT is due at the place of the customer.
  • From 2015 this applies to EVERYONE.

Problem #2: Percentages change

Problem #3: Tax zones

German VAT is used in Germany and 5 Austrian postal codes.

Austrian VAT is used in Austria without those 5 postal codes.

Problem #4: Charging the correct rate

  • B2B or B2C?
  • Physical or digital product?
  • Registered for tax in additional countries?
  • Place of supply

Data model

Zone 1-1 TaxType 1-n TaxRate 1-n TaxRateAmount

  • Tax type: French VAT
  • Zone: "France (VAT)" (inc. "France ex. Corsica" and "Monaco")
  • Tax rates: Standard, Intermediate, Reduced, Super Reduced
  • Tax rate amounts for Standard:
    19.6% (until Jan 1st 2014), 20% (from Jan 1st 2014)

Bundled data for EU and Switzerland


  1. Resolve the tax types.
  2. Resolve the tax rate for each resolved tax type.
  3. Get the tax rate amount for each resolved tax rate.

Canada Tax Type Resolver

If selling from a store in Quebec to a customer in Ontario, apply the Ontario HST.

EU Tax Type Resolver

A French store selling physical products (e.g. t-shirts) will charge French VAT to EU customers.
A French store selling digital products (e.g. ebooks) from Jan 1st 2015 will apply the EU customer's tax rates (German customer - German VAT, etc)
A French store will charge the 0% Intra-Community rate if the EU customer has provided a VAT number.

Default Tax Type Resolver

The Serbian store is selling to a Serbian customer, use Serbian VAT.

Default Tax Rate Resolver

Use the default rate ($rate->isDefault()) for the resolved tax type.

Or write your own!

No tax in New York for t-shirts under 200$
No tax for school supplies on september 1st (US tax holiday)
Reduced rate for ebooks in France.

Current explorations


PHP has no support for decimal numbers

2.99, 3.4, 6.78 -> floats, not decimals!

Store and handle prices as integers?


Making it sane

Next steps

  • ComplexPrice (base w/ taxes, discounts, fees)
  • Calculators

Focusing on the interaction between taxes and discounts

The next payment API?


The future

You can help!



Each README points to a blog post on