Implement an Advance Payment Tender Type

Workarea's base platform provides a single advance payment tender type: store credit. However, a retailer may want to offer its customers additional forms of advance payment, such as Gift Cards. To do so, application developers can extend their applications with additional advance payment tender types.

Workarea provides plugins that do this work for you for several 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 advance payment 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-reward-points. _ [2] Replace your_tender_type and YourTenderType with the name of your tender type, for example: reward_points and RewardPoints.

[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::GiftCards.gateway from Workarea Gift Cards 4.0.0, which relies on a configuration value set by the configuration.rb initializer from Workarea Gift Cards 4.0.0.

With Workarea Gift Cards 4.0.0 installed, calling Workarea::GiftCards.gateway returns an object of type Workarea::GiftCards::Gateway.

The logic for this gateway module method is simple because the plugin provides its own gateway which uses local documents rather than a service over the network. The plugin provides a configurable gateway because much of the plugin's functionality could be used with an alternative gateway.

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 does not apply if your gateway does not communicate with a payment service over the network, like Workarea Gift Cards. Furthermore, 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-reward-points.

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

[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::GiftCardOperation from Workarea Gift Cards 4.0.0.

This example doesn't implement exception handling because the gateway class (included with the plugin) defines all exception handling.

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 RewardPointsOperation.

[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::GiftCard from Workarea Gift Cards 4.0.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::GiftCard from Workarea Gift Cards 4.0.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::GiftCard from Workarea Gift Cards 4.0.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::GiftCard from Workarea Gift Cards 4.0.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. Also, importantly for an advance payment tender type, this model must implement #amount= to disallow assigning an amount greater than the available advance payment (e.g. the gift card balance).

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

    def amount=(amount) #[6]
      return super(amount) if amount.blank? || balance >= amount
      super(balance)
    end

    def balance
      gateway.balance(:identifier) || 0.to_m #[7]
    end

    def gateway
      YourTenderType.gateway #[8]
    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-reward-points.

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

[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.

[6] Implement #amount= to limit the amount that can be assigned. Do not allow assigning more than the available advance payment (e.g. the gift card balance).

[7] You will likely need to ask the gateway for the available balance. Implement the appropriate call to the gateway here.

[8] Delegate to the gateway module method you implemented. Replace YourTenderType with the name of your module. See section Gateway Module Method.

Embedded Tender Model Example

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

Payment::Tender::GiftCard from Workarea Gift Cards 4.0.0

This example validates and stores a :number field and implements #display_number to format the number for display. It uses the API call Workarea::GiftCards::Gateway#balance to query the gateway for the available balance.

Payment Decorator

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

Decorate the Payment model to define the setter, clearer, and query for your embedded tender.

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]
      build_your_tender_type unless your_tender_type #[6]
      your_tender_type.attributes = attrs.slice(
        :foo,
        :bar
      )
      save
    end

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

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

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

[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 reward_points.

[3] Replace :your_tender_type with a symbol that matches the embedded tender, such as :reward_points. (See Embedded Tender Model.) In some cases, you may want to embed a collection instead of a single object. For an example, see Payment Decorator Example.

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

[5] Define a setter for your embedded tender document. Name the method after your tender type, for example: set_reward_points. If embedding a collection, implement an "adder" instead of a setter. For an example, see Payment Decorator Example.

[6] 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_reward_points. (This method name relies on meta programming from the Mongoid library).

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

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

Payment Decorator Example

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

Payment decorator from Workarea Gift Cards 4.0.0

Workarea Gift Cards embeds a collection of tenders instead of a single tender. The example therefore uses embeds_many instead of embeds_one and add_gift_card instead of set_gift_card. Additionally, the example validates the size of the embedded collection to ensure it is within the configured limit.

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 advance payment tenders must be placed before all primary tenders. The order among advance payment tenders is up to your retailer (e.g. which do they want to apply first: store credit or gift cards?).

As a general rule, plugins for advance payment tender types prepend to the front of the list. If you are developing within an application, you may want to place the new tender type within the list more precisely. To create your own 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.prepend(:your_tender_type) #[3][4]

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

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

[3] Use #prepend or any other method of SwappableList to insert your tender type into the list. (See SwappableList Data Structure.)

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

Tender Types Initializer Example

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

configuration.rb initializer from Workarea Gift Cards 4.0.0

Storefront Integration

Workarea's checkout applies some advance payments to orders automatically (e.g. store credit) and allows shoppers to apply additional advance payment types (e.g. gift cards). The Storefront provides the fields that are necessary for these tender types (e.g. the "add gift card" form). The Storefront also displays information about each advance payment that's applied to an order, presenting them in a manner similar to order-level discounts (i.e. reductions to the order total). When the amount of the advance payments covers the entire order, the shopper can complete checkout without providing a primary tender type (e.g. credit card).

You must integrate your new tender type into the Storefront by extending some or all of the following: checkout flow, checkout fields, payment summary data, and the logic to hide/show the primary tender types. Advance payment 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. Extend the order pricing view models to provide data about the new tender type for order summaries and checkout display logic
  2. Append order summary partials to display a subtotal for the new tender type within order summaries
  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.

Order Pricing View Models

During checkout, Workarea displays order summaries that each include a subtotal for each advance payment tender type (e.g. gift card total). Also, if the total of all advance payment tenders covers the cost of the order, the shopper does not need to provide a primary tender (e.g. credit card). The API call to determine this is Storefront::Checkout::PaymentViewModel#order_covered_by_advance_payments?, which, in turn, relies on Storefront::OrderPricing#advance_payment_amount.

Therefore, for your advance payment tender type, you must implement a method that returns the subtotal for that tender type, and you must extend #advance_payment_amount to incorporate that total for your tender type into the amount for all advance payment tenders.
The latter method is defined in a module that is included in several classes, so Workarea Gift Cards extends all of these classes with its own module, but decorating each of the classes would work equally well.

Review the following examples from Workarea Gift Cards 4.0.0, and adapt them to your own implementation:

Order Summary Partials

Within checkout, the subtotal for each advance payment tender type is displayed within various order summaries. To include your tender type in these order summaries, you must create and append several partials.

Refer to the following examples from Workarea Gift Cards 4.0.0, and adapt them to your own implementation:

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/_gift_card.html.haml partial from Workarea Gift Cards 4.0.0:

.data-card
  .data-card__cell
    %p.data-card__line.data-card__credit-card
      = inline_svg('workarea/storefront/payment_icons/gift_card.svg', title: t('workarea.storefront.orders.tenders.gift_card.title'), class: 'payment-icon')
      %span.data-card__credit-card-number
        = t('workarea.storefront.credit_cards.summary', issuer: t('workarea.storefront.orders.tenders.gift_card.title'), number: tender.display_number)
    %p.data-card__line
      %strong
        #{t('workarea.storefront.orders.amount')}: #{number_to_currency tender.amount}

storefront/order_mailer/tenders/_gift_card.html.haml partial from Workarea Gift Cards 4.0.0:

%p
  = t('workarea.storefront.gift_cards.gift_card')
  %br
  #{tender.display_number}:
  = number_to_currency(tender.amount)

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. Many integrations require fields for collecting tender-specific data, such as a gift card number, and require corresponding changes to the checkout flow.

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 Gift Cards 4.0.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/_gift_card.html.haml partial from Workarea Gift Cards 4.0.0:

%li
  = inline_svg('workarea/admin/payment_icons/gift_card.svg', title: t('workarea.admin.orders.tenders.gift_card.title'), class: 'payment-icon')
  = t('workarea.admin.orders.tenders.gift_card.title')
  = tender.display_number
  = number_to_currency tender.amount

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 gift card number, returning the order purchased with that card.

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