Pay It Forward

Pay It ForwardModels, migrations, and meeting deadlines.

Kira GhandhiBlockedUnblockFollowFollowingMar 17Braintree offers an ever-expanding set of payment methods such as PayPal, Venmo, Google Pay, Visa Checkout, Masterpass, and more.

A few months ago, we announced Samsung Pay — our newest payment method.

Our codebase has a high rate of churn to keep up with demand for new payment methods.

As the requirements for each new payment method tend to be very similar, we are tempted to duplicate code to accommodate new functionality.

We strive to balance this time pressure and inclination toward duplication with maintaining our standards of high-quality code.

At Braintree, engineering excellence is a first-class business priority.

We regularly invest in refactoring and reducing tech debt.

However, we rarely allot time to rewrite products completely.

Instead, we bake refactoring, re-evaluating, and optimizing our software designs into meeting product objectives.

As we developed Samsung Pay, we generalized our implementation for the sake of future payment methods.

We leveraged our past design decisions to carve out time in our roadmap.

This additional time allowed us to optimize for future integrations reducing development time and complexity.

This kind of thoughtful re-evaluation is critical in long-lived codebases.

Transacting in one commitIn 2017, we developed Masterpass and Visa Checkout in quick succession by generalizing our modeling for new payment methods.

Prior to these products, we modeled different payment methods as distinct classes capable of processing a transaction.

For example:SomeWalletPaymentMethod { number # DPAN card_type # determined from the number expiration_month # of the number expiration_year # of the number last_4 # digits of the number bin # first 6 digits of the number cryptogram # used to validate the transaction external_id # id from the wallet provider description # description of the payment method}CreditCard { number # PAN card_type # determined from the number expiration_month # of the number expiration_year # of the number last_4 # digits of the number bin # first 6 digits of the number cvv # used to validate the transaction}There were endless case statements to navigate these payment methods and coerce similar fields with slightly different names into identical actions as part of the transaction lifecycle.

case payment_instrumentwhen CreditCard CreditCard .

do_action(params[:credit_card])when FooPaymentMethod FooPaymentMethod .

do_action_foo(params[:foo_payment_method])when BarPaymentMethod BarPaymentMethod .

do_action_bar(params[:bar_payment_method])when .

For each new payment method, we built transaction processing as a parallel code path to credit card processing — the case statements got longer and more complex.

Over time, we noticed a pattern: many new payment methods are funded by credit cards, and thus, inherit much of the same transacting behavior.

They are ultimately small variations on credit cards.

Identifying this pattern allowed us to simplify the way we model payment methods.

We could reuse existing credit card processing code while selectively handling divergent behavior at various points in the transaction lifecycle.

The case statement above was refactored to be something like:payment_method.

origin_behavior.

do_action(params)We refer to payment methods written in this pattern as Credit Card+ because they are credit cards with additional behavior.

Our first Credit Card+ payment method, Visa Checkout, relies heavily on this type of modeling and the “tell, don’t ask” principle.

Instead of implementing Visa Checkout as a new PaymentMethod, we reused the CreditCard class.

We added a Behavior interface, implemented by both credit cards and Visa Checkout cards.

A Behavior is a common protocol for all Credit Card+ payment methods that act on the polymorphic data structure.

At the point in the transaction lifecycle where we would have inspected the type of payment method to determine what mutation to perform, we instead delegate that action to the object that holds that data, the Behavior.

Although implementing Visa Checkout using the new modeling strategy did not save us development time compared to the previous payment methods, we paid it forward by delivering a more robust and general pattern for processing transactions for future payment methods.

We proved this by delivering our next product, Masterpass, in half the time expected.

Similarly, since Samsung Pay is a credit card under the hood, we took advantage of the same Credit Card+ modeling.

Onboarding in one commitBraintree and Samsung collaborated to design a simple, straightforward merchant integration.

Each payment method requires a tokenization flow.

Through this process, merchants swap out sensitive payment information for an ephemeral, single-use token that they use to process a transaction.

Tokenization was a pain point in past integrations, so we used this integration as an opportunity to move our payment methods toward models that are easier to implement and maintain.

Tokenization generally follows one of two models.

For direct tokenization, the partner feeds the encrypted payment data directly to the Braintree SDK.

Braintree is responsible for decrypting the payment data and passing the single-use token back to the merchant.

The encryption keys are maintained between Braintree and the partner on a per merchant basis and require an onboarding step to generate and associate a merchant with a specific key.

Indirect tokenization requires the partner to make a server-to-server call to the tokenization API on behalf of the merchant.

A single-use token belonging to the merchant is returned to their client through the partner.

In our experience, indirect tokenization is a better pattern for a straightforward merchant and developer experience.

The direct integration is in some cases the only feasible option, but it’s more complex, time-consuming, and requires additional merchant effort during onboarding.

With the indirect integration, there is no work on the merchant side to onboard, but the partner must build an integration with Braintree’s tokenization API.

The decision to design Samsung Pay with indirect tokenization allowed us to offer merchants a simple, self-service toggle to enable the payment method.

It also saved us time while building Samsung Pay and laid the groundwork for future indirect integrations.

Designing for the futureLike transaction processing, the tokenization logic had accumulated duplicated code and complex conditionals.

This part of the payment method lifecycle was a clear candidate for improvement when we built Samsung Pay.

Braintree teams had already been migrating tokenization from the existing API available through SDKs into our new public GraphQL API.

This service routes tokenization requests to a new tokenization service.

When we began building Samsung Pay, these new services could only support credit cards.

Adding other payment methods would require a significant refactor; however, the clean, domain-separated tokenization service could be an ideal foundation within which we could thoughtfully engineer the future of tokenization.

We had to decide whether to incorporate the refactor work into the Samsung Pay timeline or write the Samsung Pay tokenization logic in our legacy application.

Benefits of refactoringThe new tokenization service and GraphQL API are the future of tokenization at Braintree.

Developing in the new stack allowed us to influence the new API’s design to better meet our needs.

GraphQL and Java both provide interfaces and union types that our team was trying to replicate in Ruby for our Credit Card+ modeling.

We found these type systems to be better suited than Ruby’s for the “tell, don’t ask” principle.

By generalizing tokenization like we generalized transacting, we could simplify the process of supporting new tokenization paths.

All payment methods that are processed like credit cards can reuse the credit card infrastructure for tokenization.

Drawbacks to refactoringInitially, Samsung would be the only consumer of the Samsung Pay tokenization API.

They would not be relaying the request from the client to form a client-driven response, so would not be taking full advantage of GraphQL.

The refactoring work in the tokenization service — a relatively simple, lightweight application — would be complicated and take significant time.

Ultimately, the decision to refactor was obvious given the time we had saved on the onboarding and transacting segments.

Building tokenization took the majority of our time for this integration, but, we were able to deliver more than the Samsung Pay product within the deadline — we also delivered the majority of tokenization for future payment methods.

Samsung Pay became the first payment method with tokenization solely through our new GraphQL API.

And you can inspect our work in our GraphQL API Explorer!Looking forwardAs we add support for new payment methods, we aim to include an aspect of support for those to come.

With Visa Checkout, we delivered transaction processing for payment methods that are wrappers around credit cards.

Subsequently, with Samsung Pay, we delivered a tokenization interface that will ease the addition of future payment methods.

Each new payment method we add to our codebase is an opportunity to break away from existing patterns and reduce duplication.

And with thoughtful evaluation, sometimes these additions can pave the way for future products.

To learn more about Braintree, visit www.

braintreepayments.

com.

.

. More details

Leave a Reply