Last updated

How to remove Stripe and payments

In some cases, you might want to remove Stripe integrations or even all the payments from FTW. This article gives you a starting point for these customizations.

Table of Contents

Removing Stripe

In this guide, we will go through the minimum changes needed to make the booking flow work without Stripe. This is not an easy customization project. FTW templates are made for a specific transaction process and payments are a core feature: its ubiquitous nature means that you need to touch quite many components and files. Most likely you need to customize some parts (e.g. transaction process, email templates, checkout page) more depending on your marketplace idea. For example, you might also want to remove unused Stripe components from your project to clean up the code.

###1. Update the transaction process and remove Stripe related actions

You need to remove at least the following actions:

  • :action/stripe-create-payment-intent
  • :action/stripe-capture-payment-intent
  • :action/stripe-confirm-payment-intent
  • :action/stripe-create-payout
  • :action/stripe-refund-charge
  • :action/stripe-refund-payment

If you are using the FTW default process with strong customer authentication (e.g. flex-default-process) you need to remove confirming the payment. Otherwise, the transaction will get stuck. The simplest way to do this is to remove pending-payment and expire-payment states and point request-payment and request-payment-after-enquiry directly to preauthorized state. However, when you are building the transaction process to your marketplace it might make sense to rename some of the states and transitions so that they make more sense in your use-case. E.g. preauthorized state refers to the state of payment - it is preauthorized to be captured.

Edit util/transaction.js to reflect the changes in your transaction process

In src/util/transaction.js file, there are lots of helper functions that are used to determine which state the transaction is. This file should be updated to match the new transaction process. Read more about editing transaction.js

Example:

     [STATE_INITIAL]: {
       on: {
         [TRANSITION_ENQUIRE]: STATE_ENQUIRY,
-        [TRANSITION_REQUEST_PAYMENT]: STATE_PENDING_PAYMENT,
+        [TRANSITION_REQUEST_PAYMENT]: STATE_PREAUTHORIZED,
       },
     },
     [STATE_ENQUIRY]: {
       on: {
-        [TRANSITION_REQUEST_PAYMENT_AFTER_ENQUIRY]: STATE_PENDING_PAYMENT,
+        [TRANSITION_REQUEST_PAYMENT_AFTER_ENQUIRY]: STATE_PREAUTHORIZED,
       },
     },
-
-    [STATE_PENDING_PAYMENT]: {
-      on: {
-        [TRANSITION_EXPIRE_PAYMENT]: STATE_PAYMENT_EXPIRED,
-        [TRANSITION_CONFIRM_PAYMENT]: STATE_PREAUTHORIZED,
-      },
-    },
-
-    [STATE_PAYMENT_EXPIRED]: {},
     [STATE_PREAUTHORIZED]: {
       on: {
         [TRANSITION_DECLINE]: STATE_DECLINED,

###2. Remove Stripe checks from EditListingWizard

In EditListingWizard.js we are checking if the provider has a Stripe account with all the required information before we allow them to publish listings. This check is done in the handlePublishListing function and needs to be removed.

  handlePublishListing(id) {
    this.props.onPublishListingDraft(id);
  }

After changing the handlePublishListing you can remove unused code. You also might want to clean up EditListingPage and remove the Stripe related props we pass to EditListingWizard.

###3. Edit ModalMissingInformation

By default, ModalMissingInformation will remind users to create a Stripe account. The same modal is used for reminding about email verification so we should just remove the Stripe related code from the component.

###4. Edit CheckoutPage

Removing Stripe from CheckoutPage is probably the most complicated task in this list because the Strong Customer Authentication (SCA) and ability to save payment method have added a lot of logic to the current page. We can use the edited version of CheckoutPage from FTW 2.17.1 as a simpler starting point:

Some of the functions on later versions of CheckoutPage.duck.js has changed so here are the main changes done in the edited code:

  • In handleSubmit function we only call onInitiateOrder function which internally decides if the transaction should be initiated or transitioned. We also need to handle sending the possible initial message after that.
  handleSubmit(values) {
    if (this.state.submitting) {
      return;
    }

    this.setState({ submitting: true });

    const initialMessage = values.initialMessage;
    const { history, speculatedTransaction, dispatch, onInitiateOrder, onSendMessage } = this.props;

    // Create order aka transaction
    // NOTE: if unit type is line-item/units, quantity needs to be added.
    // The way to pass it to checkout page is through pageData.bookingData
    const requestParams = {
      listingId: this.state.pageData.listing.id,
      bookingStart: speculatedTransaction.booking.attributes.start,
      bookingEnd: speculatedTransaction.booking.attributes.end,
    };

    const enquiredTransaction = this.state.pageData.enquiredTransaction;
    const transactionIdMaybe = enquiredTransaction ? enquiredTransaction.id : null;

    onInitiateOrder(requestParams, transactionIdMaybe).then(params => {
      onSendMessage({ ...params, message: initialMessage })
        .then(values => {
          const { orderId, messageSuccess } = values;
          this.setState({ submitting: false });
          const routes = routeConfiguration();
          const OrderPage = findRouteByRouteName('OrderDetailsPage', routes);

          // Transaction is already created, but if the initial message
          // sending failed, we tell it to the OrderDetailsPage.
          dispatch(
            OrderPage.setInitialValues({
              initialMessageFailedToTransaction: messageSuccess ? null : orderId,
            })
          );
          const orderDetailsPath = pathByRouteName('OrderDetailsPage', routes, {
            id: orderId.uuid,
          });
          clearData(STORAGE_KEY);
          history.push(orderDetailsPath);
        })
        .catch(() => {
          this.setState({ submitting: false });
        });
    });
  }
  • Replace StripePaymentForm with something else. In this example we have simple bookingForm which has input for initial message and button for booking.
const bookingForm = (
  <FinalForm
    onSubmit={values => this.handleSubmit(values)}
    render={fieldRenderProps => {
      const { handleSubmit } = fieldRenderProps;
      return (
        <Form onSubmit={handleSubmit}>
          {showInitialMessageInput ? (
            <div>
              <h3 className={css.messageHeading}>
                <FormattedMessage id="StripePaymentForm.messageHeading" />
              </h3>

              <FieldTextInput
                type="textarea"
                id={`bookingForm-message`}
                name="initialMessage"
                label={initialMessageLabel}
                placeholder={messagePlaceholder}
                className={css.message}
              />
            </div>
          ) : null}
          <div className={css.submitContainer}>
            <PrimaryButton
              className={css.submitButton}
              type="submit"
              inProgress={this.state.submitting}
              disabled={false}
            >
              Confirm booking
            </PrimaryButton>
          </div>
        </Form>
      );
    }}
  />
);

###5. Hide/remove Stripe related pages

If you are not using Stripe you should remove or at least hide the pages that are meant for managing the information saved to Stripe. These pages are StripePayoutPage and PaymentMethodsPage. Both of these pages are accessible through the account settings so we want to remove them from both navigation component and route configuration.

Changes to LayoutWrapperAccountSettingsSideNav

        name: 'PasswordChangePage',
      },
    },
-    {
-      text: <FormattedMessage id="LayoutWrapperAccountSettingsSideNav.paymentsTabTitle" />,
-      selected: currentTab === 'StripePayoutPage',
-      id: 'StripePayoutPageTab',
-      linkProps: {
-        name: 'StripePayoutPage',
-      },
-    },
-    {
-      text: <FormattedMessage id="LayoutWrapperAccountSettingsSideNav.paymentMethodsTabTitle" />,
-      selected: currentTab === 'PaymentMethodsPage',
-      id: 'PaymentMethodsPageTab',
-      linkProps: {
-        name: 'PaymentMethodsPage',
-      },
-    },
  ];

  return <LayoutWrapperSideNav tabs={tabs} />;

Changes to routeConfiguration.js

-export const ACCOUNT_SETTINGS_PAGES = [
-  'ContactDetailsPage',
-  'PasswordChangePage',
-  'StripePayoutPage',
-  'PaymentMethodsPage',
-];
+export const ACCOUNT_SETTINGS_PAGES = ['ContactDetailsPage', 'PasswordChangePage'];

Extra: Removing unused exports

This is not a mandatory step to do but if you want to clean up the code you can go through the components/index.js, containers/index.js and forms/indexs.js, and remove the Stripe components. After this, you should go through the whole codebase and remove all the references to these components. We have tried to follow the naming pattern where Stripe related components have Stripe in their name but there are a couple of exceptions like PaymentMethodsPage. All these components should be removed also from examples.js.

Stripe related components:

  • StripeBankAccountTokenInputField
  • StripeConnectAccountStatusBox
  • StripePaymentAddress
  • StripePayoutPage
  • PaymentMethodsPage
  • PaymentMethodsForm
  • StripePaymentForm
  • StripeConnectAccountForm

You can also remove Stripe script form public/index.html.

Removing payments

If you want to remove payments completely from your marketplace, there are even more things to consider. Again it depends a lot on your use-case what kind of changes are needed. If you e.g. want to add the second transaction process with free transactions you need to build the conditional logic for showing pricing components when needed and hiding them when not. If you want to build the marketplace completely without payments then you most likely want to hide all the references to the listing price.

More changes to the transaction process

Remove the actions related to payments e.g. in flex-default-process :action/privileged-set-line-items and :action/calculate-full-refund. In older processes this means actions related to calculating total price, commission etc. Read more about actions from transaction process actions refrence article. Remember also to update email templates which contain pricing information.

Edit API calls in client app's server

If you are using FTW-daily v6.0.0 or higher (or FTW-hourly v8.0.0 or higher) you need to edit the API calls in your client app's server. If there are no payments used there is most likely no need for calculating the line items so we can remove transactionLineItems function calls from API endpoints. It depends on your use-case if you want to use the privileged transitions at all but in case you are updating e.g. transaction protected data, it's a good practice to do that safely in the FTW-backend.

Other code changes

Especially if you are removing the payment completely from your marketplace you should also go through all these pages to hide or remove the parts related to payments.

  • ListingPage, CheckoutPage, TransactionPage: Remove or hide BookingBreakdown component when the transaction is free
  • EditListingPage: remove pricing tab from EditListingWizard
  • SearchPage: remove price filter & edit ListingCard so that it doesn't show the listing price