Implement a Primary Tender Type

Workarea's base platform provides a single primary tender type: credit card. However, a retailer may want to offer its customers alternative payment options, such as PayPal or Afterpay. To do so, application developers can extend their applications with additional primary tender types.

Workarea provides many plugins that do this work for you for various payment services. You should use one of these plugins if you can. However, there are many such payment services, and a retailer may choose a service for which a Workarea plugin does not yet exist.

In that case, you will need to implement the primary tender type yourself, either directly within an application or within a plugin that can be re-used across applications. This document provides the procedure to complete this work, which can be summarized as:

  1. Integrate the gateway for the payment service into the Workarea application
  2. Define operation implementations which use the gateway to process payments of the new type
  3. Integrate the tender type with the payment model to manage tender-specific data and logic
  4. Integrate the tender type with the Storefront to collect and show data specific to the tender type
  5. Integrate the tender type with the Admin to facilitate display and administration for the new tender type

Gateway Integration

A Workarea application communicates with a payment service through a gateway object. So you must identify and locate/install the gateway class(es) you need for your chosen payment service. Consult with the retailer and the payment service provider if necessary.

( In rare cases, you will need to write your own gateway class(es). That task is outside the scope of this document. )

Once you have access to the necessary gateway class(es), you must integrate the gateway into the Workarea application. To do so, complete the following steps:

  1. Define a gateway module method to provide a consistent interface for initializing the gateway when needed
  2. Edit the proxy configuration to allow communication with the payment service over the network (Commerce Cloud only)

Gateway Module Method

To process payments, a Workarea application needs to initialize the appropriate gateway, which will likely differ by environment or use case (e.g. production, development, QA, automated testing).

For this purpose, you should provide a module for your tender type and implement a gateway module method that encapsulates the gateway initialization logic. The following sections provide boilerplate for this module method, as well as a concrete example.

Gateway Module Method Boilerplate

For your gateway module method, start with the following boilerplate and customize it as necessary for your specific implementation. Refer to the inline annotations for guidance.

# your_engine/lib/workarea/your_tender_type.rb [1][2]

module Workarea
  module YourTenderType #[2]
    def self.gateway #[3]
      if Rails.env.test?
        gateway_using_vcr_cassettes
      elsif credentials.present?
        gateway_using_network
      else
        local_gateway
      end
    end

    def self.gateway_using_network #[4]
      YourTenderType::Gateway.new( #[5]
        credentials.deep_symbolize_keys
      )
    end

    def self.gateway_using_vcr_cassettes #[4]
      YourTenderType::Gateway.new( #[6]
        merchant_id: 'foo',
        private_key: 'bar',
        baz: 'qux'
      )
    end

    def self.local_gateway #[4]
      YourTenderType::BogusGateway.new #[7]
    end

    def self.credentials #[4]
      Rails.application.secrets.your_tender_type
    end
  end
end

[1] Replace the pathname your_engine with the pathname for your application or plugin, such as ~/discount-supercenter or ~/workarea-deferred-pay. _ [2] Replace your_tender_type and YourTenderType with the name of your tender type, for example: deferred_pay and DeferredPay.

[3] Implement .gateway to lazily init and return a gateway object. Replace the logic here with your own use cases. You may want to vary your gateway initialization logic based on credentials, rails environment, current site (if multi site), or other application states or combination of states. Work with your retailer and/or other partners to determine the correct gateway class and arguments.

[4] These additional methods are used only to implement .gateway more clearly. Use "private" methods of this sort if it makes sense for your gateway. Remove whatever methods you don't need.

[5] Replace YourTenderType::Gateway with the appropriate gateway class, and initialize the object with the appropriate arguments. Refer to your gateway's documentation. The boilerplate demonstrates the common pattern of fetching the gateway's credentials from Rails secrets. (Alternatively, use Rails credentials.)

[6] Replace YourTenderType::Gateway with the appropriate gateway class, and initialize the object with the appropriate arguments. The boilerplate uses the same gateway class for automated testing as it does for production and production-like (e.g. QA, staging) use cases. When using this pattern, start with actual credentials (via Rails secrets or Rails credentials) so you can communicate with the payment service over the network and record vcr cassettes. After the cassettes are recorded, remove all credentials (or use dummy values if the arguments are required). The credentials are no longer needed since the responses will be read from the cassettes. Don't commit secrets to your repository!

[7] If available for your payment service, default to a bogus gateway that does not require credentials or communicate over the network.

Gateway Module Method Example

To see a concrete example of a gateway module method, refer to Workarea::Afterpay.gateway from Workarea Afterpay 2.1.0.

With Workarea Afterpay 2.1.0 installed, calling Workarea::Afterpay.gateway returns an object of type Workarea::Afterpay::Gateway or Workarea::Afterpay::BogusGateway.

Proxy Configuration

Workarea Commerce Cloud applications must additionally edit the proxy configuration to allow communication with the payment service over the network.

Use the Workarea CLI to access and edit the proxy configuration. Add the endpoint(s) for the payment service. See CLI, Edit.

( This step may not apply to applications hosted outside of Workarea Commerce Cloud. Consult your hosting team or provider. )

Operation Implementations

After integrating the payment service gateway, you can use the gateway to process payments. The objects that handle these transactions are called operation implementations. Each operation implementation class defines #complete!, which completes transactions of this type, and #cancel!, which rolls back completed transactions when necessary.

You must define each operation implementation for your tender type, which you can do in the following steps:

  1. Implement a tender-specific operation mixin to encapsulate shared logic for operations of your tender type
  2. Implement authorize for your tender type
  3. Implement purchase for your tender type
  4. Implement capture for your tender type
  5. Implement refund for your tender type

Be aware that operation implementations depend on data from the payment model. Therefore, the Operation Implementations step overlaps with the Payment Model Integration step. You will likely need to develop them concurrently.

Tender-Specific Operation Mixin

Each operation implementation for your tender type will include the module OperationImplementation, which provides the basic interface for this type of object. However, your tender type will require additional logic that is shared by all of its operation implementations, primarily knowledge about the payment service gateway.

You must therefore implement a tender-specific mixin to be included in all operation implementations for this type. Include in the mixin a reference to the gateway object, exception handling for the gateway (if applicable), and any other shared logic.

The following sections provide boilerplate for this mixin, followed by a concrete example.

Tender-Specific Operation Mixin Boilerplate

Start with the following boilerplate and customize it for your specific implementation.

# your_engine/app/models/workarea/payment/your_tender_type_operation.rb [1][2]

module Workarea
  class Payment
    module YourTenderTypeOperation #[2]
      def gateway #[3]
        Workarea::YourTenderType.gateway #[2]
      end

      def handle_gateway_errors #[4]
        begin
          yield #[5]
        rescue YourTenderType::Gateway::ConnectionError => error #[6]
          YourTenderType::Gateway::Response.new(false, nil) #[7]
        end
      end
    end
  end
end

[1] Replace the pathname your_engine with the pathname for your application or plugin, such as ~/discount-supercenter or ~/workarea-deferred-pay.

[2] Replace your_tender_type_operation and YourTenderTypeOperation with a name matching your tender type, for example: deferred_pay_operation and DeferredPayOperation.

[3] Implement #gateway as an alias for the gateway module method you already implemented. See section Gateway Module Method. Keep the gateway initialization logic in that module (rather than here) so it can be shared by automated tests and any other code that requires gateway access.

[4] Implement #handle_gateway_errors to "wrap" API calls to the gateway. This method should encapsulate all gateway exceptions you want to handle. For an example, refer to Payment::CreditCardOperation#handle_active_merchant_errors from Workarea Core 3.5.0, which encapsulates similar logic for Active Merchant gateways.

[5] Write this method to take a block so that it can "wrap" API calls to the gateway.

[6] Enumerate here all the exceptions you'd like to handle. Check the documentation for your gateway for possible exceptions. Use separate rescue statements if needed to vary the return value by exception.

[7] Return an object type that is consistent with API responses from your gateway. See [11], below. The examples in this document assume a custom response object, so that is used here.

Tender-Specific Operation Mixin Example

To see a concrete example of a tender-specific operation mixin, refer to Payment::AfterpayPaymentGateway from Workarea Afterpay 2.1.0.

This module is named differently from the boilerplate, but it performs the same function. It does not implement #handle_gateway_errors because the error handling for this gateway is operation-specific. This plugin writes error handling into each operation implementation instead.

Authorize Implementation

After implementing the shared logic, you can move on to the first transaction type: authorize.

The following sections provide boilerplate and an example for an authorize operation implementation.

Authorize Implementation Boilerplate

Start with the following boilerplate and customize it as necessary. Refer to the inline annotations for guidance.

# your_engine/app/models/workarea/payment/authorize/your_tender_type.rb [1][2]
module Workarea
  class Payment
    module Authorize
      class YourTenderType #[2]
        include OperationImplementation #[8]
        include YourTenderTypeOperation #[9]

        def complete! #[10]
          gateway_response = #[11]
            handle_gateway_errors do #[12]
              gateway.authorize( #[13]
                transaction.amount.cents, #[14]
                tender.foo,
                tender.bar
              )
            end

          transaction.response = #[15]
            ActiveMerchant::Billing::Response.new( #[16]
              gateway_response.success?, #[17]
              gateway_response.message
            )
        end

        def cancel! #[18]
          return unless transaction.success? #[19]

          gateway_response = #[20]
            handle_gateway_errors do #[12]
              gateway.void( #[21]
                transaction.response.authorization #[22]
              )
            end

          transaction.cancellation = #[23]
            ActiveMerchant::Billing::Response.new( #[16]
              gateway_cancellation.success?, #[17]
              gateway_cancellation.message
            )
        end
      end
    end
  end
end

[8] You must include this module to have access to transaction, tender, and options, which depend on the Payment Model Integration.

[9] Rename this module to be specific to your tender type, such as DeferredPayOperation.

[10] You must implement complete! to fulfill the operation implementation contract.

[11] Ultimately, you must assign transaction.response to fulfill the contract for #complete!. See [15]. That value is derived from a response from the gateway, so store the gateway's response for later use. This example assumes the gateway's response is not already an ActiveMerchant::Billing::Response. See [16].

[12] If you implemented gateway exception handling in [4], remember to wrap all calls to the gateway with this method.

[13] Use the appropriate API call for your gateway. See [3]. The correct API call is often authorize, but refer to your gateway's documentation.

[14] Pass the proper arguments for the gateway API call. The first argument is typically the amount in cents. To construct this data, you have access to transaction, tender, and options, which depend on the Payment Model Integration.

[15] You must assign transaction.response to fulfill the contract for #complete!.

[16] The value assigned must be an object of type ActiveMerchant::Billing::Response. The provided boilerplate assumes your gateway does not return this type and therefore constructs an instance manually.

[17] Construct the arguments for the Active Merchant response from the original gateway response. The interface of gateway_response will vary by gateway.

[18] You must implement cancel! to fulfill the operation implementation contract.

[19] You must return early if the transaction wasn't successful to fulfill the contract for #cancel!.

[20] Ultimately, you must assign transaction.cancellation to fulfill the contract for #cancel!. See [23]. That value is derived from a response from the gateway, so store the gateway's response for later use. This example assumes the gateway's response is not already an ActiveMerchant::Billing::Response. See [16].

[21] Use the appropriate API call for your gateway. See [3]. The correct API call is often void, cancel, or refund, but refer to your gateway's documentation.

[22] Pass the proper arguments for the gateway API call. The first argument is typically a reference to the original authorization, such as transaction.response.authorization or transaction.response.params['transaction_id']. To construct this data, you have access to transaction, tender, and options, which depend on the Payment Model Integration.

[23] You must assign transaction.cancellation to fulfill the contract for #cancel!.

Authorize Implementation Example

Here is a concrete example of an authorize operation implementation used in production:

Payment::Authorize::Afterpay from Workarea Afterpay 2.1.0

Purchase Implementation

The following sections provide boilerplate and an example for a purchase operation implementation.

Purchase Implementation Boilerplate

Start with the following boilerplate and customize it as necessary. Refer to the inline annotations for guidance.

# your_engine/app/models/workarea/payment/purchase/your_tender_type.rb [1][2]
module Workarea
  class Payment
    module Purchase
      class YourTenderType #[2]
        include OperationImplementation #[8]
        include YourTenderTypeOperation #[9]

        def complete! #[10]
          gateway_response = #[11]
            handle_gateway_errors do #[12]
              gateway.purchase( #[24]
                transaction.amount.cents, #[14]
                tender.foo,
                tender.bar
              )
            end

          transaction.response = #[15]
            ActiveMerchant::Billing::Response.new( #[16]
              gateway_response.success?, #[17]
              gateway_response.message
            )
        end

        def cancel! #[18]
          return unless transaction.success? #[19]

          gateway_response = #[20]
            handle_gateway_errors do #[12]
              gateway.void( #[21]
                transaction.response.authorization #[22]
              )
            end

          transaction.cancellation = #[23]
            ActiveMerchant::Billing::Response.new( #[16]
              gateway_cancellation.success?, #[17]
              gateway_cancellation.message
            )
        end
      end
    end
  end
end

[24] Use the appropriate API call for your gateway. See [3]. The correct API call is often purchase, but refer to your gateway's documentation.

Purchase Implementation Example

Here is a concrete example of a purchase operation implementation used in production:

Payment::Purchase::Afterpay from Workarea Afterpay 2.1.0

Capture Implementation

The following sections provide boilerplate and an example for a capture operation implementation.

Capture Implementation Boilerplate

Start with the following boilerplate and customize it as necessary. Refer to the inline annotations for guidance.

# your_engine/app/models/workarea/payment/capture/your_tender_type.rb [1][2]
module Workarea
  class Payment
    module Capture
      class YourTenderType #[2]
        include OperationImplementation #[8]
        include YourTenderTypeOperation #[9]

        def complete! #[10]
          validate_reference! #[25]

          gateway_response = #[11]
            handle_gateway_errors do #[12]
              gateway.capture( #[26]
                transaction.amount.cents, #[27]
                transaction.reference.response.authorization
              )
            end

          transaction.response = #[15]
            ActiveMerchant::Billing::Response.new( #[16]
              gateway_response.success?, #[17]
              gateway_response.message
            )
        end

        def cancel! #[28]
          #noop
        end
      end
    end
  end
end

[25] You must use validate_reference! for a capture operation unless this does not apply to your gateway.

[26] Use the appropriate API call for your gateway. See [3]. The correct API call is often capture, but refer to your gateway's documentation.

[27] Pass the proper arguments for the gateway API call. The first argument is typically the amount in cents. The second argument is typically a reference to the original transaction's authorization, such as transaction.reference.response.authorization or transaction.reference.response.params['transaction_id']. To construct this data, you have access to transaction, tender, and options, which depend on the Payment Model Integration.

[28] For most payment services, it doesn't make sense to cancel a capture, so this method should be implemented to do nothing. However, if there is an appropriate response for your gateway (e.g. Workarea PayPal issues a refund), you should implement it here, using other #cancel! implementations for inspiration.

Capture Implementation Example

Here is a concrete example of a capture operation implementation used in production:

Payment::Capture::Afterpay from Workarea Afterpay 2.1.0

Refund Implementation

The following sections provide boilerplate and an example for a refund operation implementation.

Refund Implementation Boilerplate

Start with the following boilerplate and customize it as necessary. Refer to the inline annotations for guidance.

# your_engine/app/models/workarea/payment/refund/your_tender_type.rb [1][2]
module Workarea
  class Payment
    module Refund
      class YourTenderType #[2]
        include OperationImplementation #[3]
        include YourTenderTypeOperation #[4]

        def complete! #[5]
          validate_reference! #[20]

          gateway_response = #[6]
            handle_gateway_errors do #[7]
              gateway.refund( #[24]
                transaction.amount.cents, #[22]
                transaction.reference.response.authorization
              )
            end

          transaction.response = #[10]
            ActiveMerchant::Billing::Response.new( #[11]
              gateway_response.success?, #[12]
              gateway_response.message
            )
        end

        def cancel! #[25]
          #noop
        end
      end
    end
  end
end

[24] Use the appropriate API call for your gateway. See [3]. The correct API call is often refund, but refer to your gateway's documentation.

[25] For most payment services, it doesn't make sense to cancel a refund, so this method should be implemented to do nothing. However, if there is an appropriate response for your gateway (e.g. Workarea Gift Card re-purchases the amount), you should implement it here, using other #cancel! implementations for inspiration.

Refund Implementation Example

Here is a concrete example of a capture operation implementation used in production:

Payment::Refund::Afterpay from Workarea Afterpay 2.1.0

Payment Model Integration

To complete the implementation of your tender type, you must collect tender-specific data from the customer through the Storefront and pass it to the payment service via the operation implementations. This data is persisted to a Payment model, which also defines related logic.

You must therefore integrate your tender type with the payment model, which requires three steps:

  1. Define a tender model to embed within the payment model
  2. Decorate the payment model to embed the tender model and provide supporting methods
  3. Add a tender types initializer to add your tender type to the embedded collection of all tenders

The Payment Model Integration is effectively a bridge between the Operation Implementations and Storefront Integration. You will likely need to develop those steps concurrently.

Embedded Tender Model

The Payment model that represents the payment for each order embeds a collection of tender models, which store the data and logic specific to each tender. These embedded tenders are of varying types, but each model is a subclass of Payment::Tender.

You must define a model to represent your new tender type. The following sections provide boilerplate for this model and a concrete example.

Embedded Tender Model Boilerplate

Start with the following boilerplate, and customize your model definition as needed.

# your_engine/app/models/workarea/payment/tender/your_tender_type.rb [1][2]

module Workarea
  class Payment::Tender::YourTenderType < Payment::Tender #[2][3]
    field :indentifier, type: String #[4]

    def slug #[5]
      :your_tender_type
    end

    # [4]
    #
    # def foo
    # end
    #
    # def bar
    # end
  end
end

[1] Replace the pathname your_engine with the pathname for your application or plugin, such as ~/discount-supercenter or ~/workarea-deferred-pay.

[2] Replace your_tender_type and YourTenderType with the name of your tender type, for example: deferred_pay and DeferredPay.

[3] You must inherit from Payment::Tender, which provides the base interface for a tender type.

[4] Implement the fields (i.e. data) and methods (i.e. logic) required by your tender type. Consider any data that must be collected and passed on to the payment service to complete a transaction and any data you may need for flow control or display in the Storefront. Store here any data you need to complete your operation implementations, Storefront integration, and Admin integration. At a minimum, you will likely need to store a string representing a specific tender, such as a card number or customer ID. Replace :identifier with the appropriate field name and foo and bar with your own methods (or remove them if not needed).

[5] You must implement #slug to fulfill the tender contract. It should return a symbol that uniquely identifies the tender type. The value is also transformed and displayed in the Storefront and Admin UIs.

Embedded Tender Model Example

Here is a concrete example of a tender model used in production:

Payment::Tender::Afterpay from Workarea Afterpay 2.1.0

In this example, :token identifies the tender, :ready_to_capture is used for Storefront flow control, and :installment_price is used for Storefront display.

Payment Decorator

For each embedded tender, the Payment model provides methods to set, clear, and query the presence of a tender of that type.

Also, since only one primary tender type may be present at any given time, the setter for each primary tender type must clear the other primary tender types.

Decorate the Payment model to define the setter, clearer, and query for your embedded tender; and decorate the setters for each additional primary tender type.

Payment Decorator Boilerplate

#  your_engine/app/models/workarea/payment.decorator [1]

module Workarea
  decorate Payment, with: :your_engine do #[2]
    decorated do
      embeds_one :your_tender_type, #[3]
        class_name: 'Workarea::Payment::Tender::YourTenderType' #[4]
    end

    def set_your_tender_type(attrs) #[5]
      clear_credit_card #[6]

      # [7]
      #
      # clear_foo_type
      # clear_bar_type

      build_your_tender_type unless your_tender_type #[8]
      your_tender_type.attributes = attrs.slice(
        :foo,
        :bar
      )
      save
    end

    def clear_your_tender_type #[9]
      self.your_tender_type = nil
      save
    end

    def your_tender_type? #[10]
      your_tender_type.present?
    end

    def set_credit_card(*) #[11]
      clear_your_tender_type
      super
    end

    # [12]
    #
    # def set_foo_type(*)
    #   clear_your_tender_type
    #   super
    # end
    #
    # def set_bar_type(*)
    #   clear_your_tender_type
    #   super
    # end
  end
end

[1] Replace the pathname your_engine with the pathname for your application or plugin, such as ~/discount-supercenter or ~/workarea-deferred-pay.

[2] If developing an application, you can omit the with argument. If developing a plugin, replace your_engine with a slug identifying your plugin, such as deferred_pay.

[3] Replace :your_tender_type with a symbol that matches the embedded tender, such as :deferred_pay. (See Embedded Tender Model.)

[4] Replace 'Workarea::Payment::Tender::YourTenderType' with the class name of the embedded tender model, such as 'Workarea::Payment::Tender::DeferredPay'. (See Embedded Tender Model.)

[5] Define a setter for your embedded tender document. Name the method after your tender type, for example: set_deferred_pay.

[6] When setting this tender you must "unset" all other primary tenders. Always unset credit card, since it's the platform's default primary tender.

[7] If you're developing within an application, additional primary tender types may exist. Clear all other primary tenders.

[8] Build the embedded tender, mutate it as necessary, and save the payment. Replace the substring your_tender_type with the name of your embedded tender, for example: build_deferred_pay. (This method name relies on meta programming from the Mongoid library).

[9] You should provide this method by convention. Replace the substring your_tender_type with the name of your embedded tender, for example: clear_deferred_pay.

[10] You should provide this method by convention. Replace the substring your_tender_type with the name of your embedded tender, for example: deferred_pay?.

[11] Decorate the setter for the credit card tender to clear the new primary tender type when the credit card tender is set. There can be only one primary tender set on a payment.

[12] If you're developing within an application, additional primary tender types may exist. Extend the setter for each additional primary tender so that setting it will clear the new primary tender.

Payment Decorator Example

Here is an example of a payment decorator used in production:

Payment decorator from Workarea Afterpay 2.1.0

Tender Types Initializer

The method Payment#tenders returns a collection of all the tenders embedded on that payment. The tenders in this collection are of different types, and the order in which they appear is the order in which the tenders will be charged/refunded during payment processing.

You must configure Workarea.config.tender_types to declare where tenders of your new type should appear within this collection. All primary tenders must be placed after all advance payment tenders. The order among primary tenders isn't important, since only one can be present on a payment at any given time.

You should therefore append your tender type to the config from an initializer. Refer to the following boilerplate and example.

Tender Types Initializer Boilerplate

Start with the following boilerplate for your initializer.

# your_engine/config/initializers/tender_types.rb [1][2]

Workarea.config.tender_types.append(:your_tender_type) #[3]

[1] Replace the pathname your_engine with the pathname for your application or plugin, such as ~/discount-supercenter or ~/workarea-deferred-pay.

[2] If desired, change the name of the initializer or add the code that follows to an existing initializer.

[3] Replace :your_tender_type with the symbolized name of your embedded tender (as implemented on the Payment model), such as :deferred_pay.

Tender Types Initializer Example

To see a concrete example of this type of initializer, refer to the following:

workarea.rb initializer from Workarea Afterpay 2.1.0

Storefront Integration

The Storefront allows customers to choose their primary tender type during checkout, and it displays information about the tender of that type when showing the placed order. During checkout, the Storefront UI may collect tender-specific data from the shopper, either from explicit fields (e.g. card number) or hidden/implicit parameters (e.g. Afterpay token). A tender type may require deeper Storefront integration, such as changes to checkout flow (e.g. to go "offsite" for payment data collection), changes to cart (e.g. "Pay with X" button), or changes to product detail pages (e.g. pay-by-installment pricing).

Primary tender types vary considerably in their Storefront integrations, but there are a few commonalities. To integrate your tender type into the Storefront, complete the following:

  1. Decorate the Storefront's checkout payment view model to ensure the new tender type doesn't impact existing behavior
  2. Append a checkout payment partial to add the new tender type to the list of available payment options in checkout
  3. Create order tender partials to handle the display of the new tender type when showing placed orders
  4. Complete other tender-specific integrations as needed for your tender type

Data collected by the Storefront Integration is persisted by the Payment Model Integration. These steps therefore overlap. You will likely need to develop them concurrently.

Checkout Payment View Model

The payment step of checkout presents the primary tender types within a radio button set. The set includes one entry for each saved credit card (if any) and one entry for a new credit card, which expands when selected to show the "new credit card" fields.

Each additional primary tender type must add itself to this list and must also ensure it doesn't break the expand/collapse functionality for the "new credit card" fields. That functionality relies on Storefront::Checkout::PaymentViewModel#using_new_card?, which is aware of only the credit card tender type.

To maintain the existing expand/collapse behavior for the "new credit card" option, you must decorate this method to make it aware of your new primary tender type. Using the original implementation and the follow example as references, create your own decorator that incorporates your tender type into the #using_new_card? implementation.

Storefront::Checkout::PaymentViewModel decorator from Workarea Afterpay 2.1.0 (fragment):

module Workarea
  decorate Storefront::Checkout::PaymentViewModel, with: :afterpay do
    decorated do
      delegate :afterpay?, to: :payment
    end

    def using_new_card?
      super && !afterpay?
    end
  end
end

Checkout Payment Partial

In the Storefront, Workarea presents the primary tender types as a list of payment options, within a radio button set. You must append to this radio button set a partial that adds a radio button for your new tender type.

Using the following examples as a reference, implement the necessary partial and initializer to append the partial.

storefront/checkouts/_afterpay_payment.html.haml partial from Workarea Afterpay 2.1.0:

-if @step.afterpay.show?
  -if @step.afterpay.order_total_in_range?
    .checkout-payment__primary-method{ class: ('checkout-payment__primary-method--selected' if @step.afterpay?) }
      .button-property
        = hidden_field_tag 'from_checkout', 'from_checkout', id: nil
        .value= radio_button_tag 'payment', 'afterpay', step.afterpay?, data: { afterpay_token: (@step.afterpay_token if @step.allow_redirect_to_afterpay?), afterpay_country: @step.afterpay.afterpay_country }
        = label_tag 'payment[afterpay]', nil, class: 'button-property__name' do
          %span.button-property__text= image_tag('https://static.afterpay.com/integration/product-page/logo-afterpay-colour.png')
      %p.checkout-payment__primary-method-description
        %span #{t('workarea.storefront.checkouts.afterpay', installment_price: number_to_currency(@step.afterpay.installment_price), installment_count: Workarea.config.afterpay[:installment_count])}
        %span= link_to(t('workarea.storefront.afterpay.learn_more'), learn_more_link(@cart.total_price), data: { popup_button: { width: 600, height: 800 }})
      %p
        %span= t('workarea.storefront.afterpay.on_continue')
  - else
    %p
      #{image_tag('https://static.afterpay.com/integration/product-page/logo-afterpay-colour.png')}
      #{t('workarea.storefront.afterpay.ineligible_order_total', min: number_to_currency(@cart.afterpay.min_price), max: number_to_currency(@cart.afterpay.max_price))}

At a minimum, this partial must include a radio button whose name is payment and whose value is your tender type. That much will allow shoppers to select your tender type while ensuring other primary tender types will be deselected.

It may need to include additional fields, like a number or other identifying information that you need to collect for the tender type. In this case, refer to the base implementation of the "new credit card" payment option.

appends.rb initializer from Workarea Afterpay 2.1.0 (fragment):

Workarea::Plugin.append_partials(
  'storefront.payment_method',
  'workarea/storefront/checkouts/afterpay_payment'
)

Order Tender Partials

When showing placed orders in the Storefront, Workarea needs to know how to display information about tenders of your new type. This information includes the type of tender, the amount, and tender-specific details such as the number of installments or a gift card number. When displaying this information, Workarea automatically looks for specific partials to render.

You must create these partials for your tender type. Refer to the following examples, and create corresponding partials for your tender type implementation.

storefront/orders/tenders/_afterpay.html.haml partial from Workarea Afterpay 2.1.0:

.data-card
  .data-card__cell
    %p.data-card__line
      = t('workarea.afterpay.tender_description', installment_price: number_to_currency(tender.installment_price), installment_count: Workarea.config.afterpay[:installment_count])

storefront/order_mailer/tenders/_afterpay.html.haml partial from Workarea Afterpay 2.1.0:

%p{ style: "margin: 0 0 6px; font: 13px/1.5 arial; color: #{@config.text_color};" }
  = t('workarea.afterpay.tender_description', installment_price: number_to_currency(tender.installment_price), installment_count: Workarea.config.afterpay[:installment_count])

Tender-Specific Integrations

Your Storefront integration will likely require more—possibly much more—than the preceding steps, but the remaining work will be specific to your tender type and difficult to outline here as a general procedure. You may need to implement Storefront routes, controllers, views, view models, helpers, assets, libraries, configuration, translations, and tests to complete your integration. These extensions may be limited to checkout or may start "earlier", such as in the cart or on product detail pages.

Work with your retailer and payment service provider to determine what additional work is necessary. Then refer to concrete examples (e.g. Workarea plugin sources) to see what similar integrations look like.

For an example, review the following files, which are the more significant aspects of the Workarea Afterpay 2.1.0 Storefront integration:

Admin Integration

Like the Storefront, the Admin shows placed orders and therefore must know how to display tender-specific information for all tender types. Your tender type may require or benefit from additional Admin integration, but these will be specific to your tender.

The process to integrate your tender type into the Admin therefore looks like this:

  1. Create an order tender partial to handle the display of the new tender type when showing placed orders
  2. Complete other tender-specific integrations as needed for your tender type

Order Tender Partial

To properly display placed orders in the Admin, you must provide a partial for your new tender type.

Refer to the following example, and create a similar partial for your specific tender.

admin/orders/tenders/_afterpay.html.haml partial from Workarea Afterpay 2.1.0:

%li= t('workarea.afterpay.tender_description', installment_price: number_to_currency(tender.installment_price), installment_count: Workarea.config.afterpay[:installment_count])

Tender-Specific Integrations

You may need or want more tender-specific integrations with the Admin. For example, you could allow administrators to search for a specific DeferredPay token, returning the order purchased with that token.

In these cases, work with your retailer and payment service provider to determine what additional work is necessary. Then refer to concrete examples (e.g. Workarea plugin sources) to see what similar integrations look like.

Now on GitHub