We have built a shop to buy digital and print subscriptions as part of the newspaper platform südostschweiz.ch: http://www.suedostschweiz.ch/somedia/shop. The whole site and the user and subscription management is closely integrated into an external subscription management system that takes care of things like recurring billing, both invoices and credit cards. That system has no public interface, so if for example a credit card expires, the user needs to be able to renew the subscription through the website. Product information is synchronized with the external system and completed orders are sent to that system automatically for processing. It also does not support to buy multiple subscriptions at once, so we don’t need a cart.
Commerce 2.x provides an out of the box shop experience that is quite different from our requirements for the suedostschweiz.ch project. Thus we had to change that. We were able to find solutions for all those challenges and worked closely with the CommerceGuys team to find ways to contribute back. We also worked on a few missing features as well as making existing features more flexible, so we could customize it for our use case.
Storing and reusing payment methods… but not really
Commerce 2.x has much improved support for storing payment methods and using them again. The default use case that it supports is to make those stored payment methods visible again to the users in the checkout process, allowing them to avoid entering the payment information again.
We internally need to store payment methods so we can send it to the subscription system. However, the vast majority of users only buys a new subscription if they don’t have one yet or if their old one expired, in which case they either have no credit card yet or it expired.
That’s why we decided to replace the default payment gateway selection checkout pane with a custom one, that does not offer to re-use existing payment methods. Still, having the built-in default storage to store the credit card alias from the external payment gateway is a great improvement over Commerce 1.x.
Different address handling
A second reason for that decision was that a stored payment method also has a corresponding billing address, so the selection of the billing address and the payment method in Commerce 2.x is combined and the payment gateway and billing address is selected/provided in the same pane, before the review step.
Our design/requirements required a different workflow for the address information checkout page
- A required delivery address and optionally a different billing address
- A few additional fields to enable existing customers to connect through a subscription number. By entering a valid subscription number we automatically fetch the address and fill out the address field.
- The ability to provide a later subscription start date
- Custom validation to prevent that users can buy a subscription for a newspaper that they already have.
Additionally to replacing the payment gateway pane, we also replaced the address information pane with our own that had all those fields and validation logic in a single place, which simplifies maintenance.
Renewing an expired credit card
In case a credit card expires or the payment failed for another reason, the customer receives an e-mail with a link. That link leads to a page where they can do a manual payment again, which is then again sent to the external system. We wanted to make this step as fast as possible and jump directly to a review/info checkout page where the customer can just click once to start the payment.
This was surprisingly easy to implement, we defined a second checkout flow where we disabled all unnecessary checkout panes and pages, an order type that used that checkout flow as well as an order item type that used that order type and did not have a referenced product. We then programmatically create an order item, an order and send the user to the checkout process.
Synchronization of product variations and submitted orders
The external system defines the concept of an offer, which is a certain subscription with a given runtime, a price, a title as well as some other information like which payment gateways are allowed (monthly subscriptions can only be paid with credit card, others also with invoices).
There can also be special promotions. To avoid that this information gets out of sync with the website, the shop manager only needs to select the offer as well as providing a few web specific things, everything else is then automatically fetched from the API and updated in the background. Inline Entity Form made this fairly easy, as we can easily control which fields are displayed and hook into the process of building an entity from that.
The opposite happens when a user completes the checkout process, then we need to send that data to the external system, so it can be reviewed and processed. To be able to do this, we defined a custom order workflow, which is easy to define in YAML file, then used that for our order types. When an order reaches a certain step in that workflow, we collect all the information, convert it to the structure that the external system understands and forward it that information.
E-Mail only registration
The site doesn’t use usernames, the registration is customized to hide the username and build it from the e-mail. Commerce has a built-in login or register checkout page that is hardcoded to a few specific fields and shows both the username and the e-mail.
Initially, we started an issue that provides a setting to control whether the e-mail should be shown or not, but that is a bit controversial and likely requires a bunch of additional settings to define how the username is defined etc. As we mentioned in the previous blog post, Commerce 2.x tries to avoid having many settings, so we had to try something else.
Our second and current approach is to improve the checkout pane instead so it is easier to customize and extend. We ended up needing that anyway, as we need to show a few additional fields on the registration form too.
Not having a cart and instead going directly to the checkout is a fairly common requirement, so there was already an issue for this with other people looking for this feature.
We worked on that and provided a first patch that is currently being reviewed. A new setting changes the button to go directly to the checkout page and any messages about cart are skipped. Commerce 2.x currently has the same limitation as 1.x, that anonymous users can only go through the checkout if the order is in the cart session and we currently have to work around that a bit. That also means the checkout and cart functionality are still tightly coupled. Checkout currently depends on cart, so you can not have a checkout without having the cart module completely disabled. That is possibly something that will still be improved.
When combining all those changes, we ended up replacing or extending almost every checkout pane and a few other components that Commerce 2.x provides to be able to achieve the UI and processes we required. We were however able to do so in a very clean way and re-use a lot of code because Commerce 2.x is designed to support exactly that, thanks to the flexibility of the framework underneath the default UI.
Integrating with external systems also became easier in Drupal 8/Commerce 2.x compared to similar requirements in in a Commerce 1.x project. Converting data structures, error handling and general interaction with one or multiple external systems still requires a considerable amount of code and development time, services, plugins and improved APIs support developers in writing clean and maintainable code.
We are convinced that Commerce 2 is a great framework to use for both standard shops with products, shipping and a standard checkout process as well as heavily customized ecommerce solutions. We are looking forward to our next commerce projects.