payload icon indicating copy to clipboard operation
payload copied to clipboard

feat(plugin-form-builder): Override payment fields

Open mikecebul opened this issue 1 year ago • 5 comments

What?

Override the payment group field in form-submissions when using the form-builder-plugin.

Why?

I and probably others will enjoy using the form-builder-plugin features when using the payment field when creating forms, such as price conditions, redirects, emails, handle payment, ect. But may not need the specific fields pushed on to every form submission when using them.

How?

Before pushing the payment group field to a new form submission it checks if the original form uses the payment field. We also now check if there is a formSubmissionOverride field with the same name 'payments'. Instead of getting a duplicate field error it now bypasses adding the default payment fields.

This is my first PR

mikecebul avatar Nov 22 '24 19:11 mikecebul

I see now that using the fields().some(field) is a pattern far removed from the usual merging from the function pattern Payload uses. I want to ensure the payment fields are always at the end of the submission so we need to push/append the fields. Since this is a plugin I thought it made sense to add a custom property custom.overridePaymentFields. I also added documentation. Anymore feedback will be greatly appreciated.

mikecebul avatar Nov 22 '24 21:11 mikecebul

Ok, I worked really hard on this, learned a bunch, and I think I finally found a pattern that fits Payload. I switched to using a paymentFields(). I have it working in the tests and updated the docs as well explaining how the function can:

  • Return false to disable payment fields entirely
  • Return true to use the default payment fields (with a group field wrapper)
  • Accept defaultPaymentFields (without the group field wrapper) as a parameter and return custom payment fields

In practice it can be used like this:

// payload.config.ts
formBuilder({
  // ...
  formSubmissionOverrides: {
    // ...
    paymentFields: ({ defaultPaymentFields }) => {
      return [
        {
          name: 'payment',
          type: 'group',
          fields: [
            ...defaultPaymentFields,
            {
              name: 'checkoutSession',
              type: 'text',
            },
          ],
        },
      ]
    },
  },
})

mikecebul avatar Nov 23 '24 03:11 mikecebul

Welp, with some help from Paul I see this problem was solved with just a couple lines of code. I reverted all my changes and added the default payment fields in form-submissions to the defaultFields. Then in the config if needed we can map or filter the defaultFields for the payment fields when needed. Something like this:

formSubmissionOverrides: {
  fields: ({ defaultFields }) => {
    return defaultFields.map((field) => {
      if ('name' in field && field.name === 'payment' && 'fields' in field) {
        return {
          ...field,
          fields: [
            ...(field.fields || []),
            {
              name: 'stripeCheckoutSession',
              type: 'text',
            },
          ],
        }
      }
      return field
    })
  },
},

mikecebul avatar Nov 23 '24 06:11 mikecebul

My last implementation loses the default behavior of the payment fields being appended to the submission-form fields after overrides. I tried another way by passing payment fields into the overrides like this: formConfig.formSubmissionOverrides.fields({ defaultFields, defaultPaymentFields }) but I can't retain the default behavior that way either. I have found only 3 ways that avoid any breaking changes or user expected behavior.

  1. Some Function
  • My first implementation uses a some function that checks if the formSubmissionOverride.fields() includes a field named "payment" and if so, does not push default payment fields.
  • Pro: very simple, limited changes
  • Con: Adds an O(n) search function to the plugin
  1. Payment Fields Function
  • Adds a separate function to formSubmissionOverrides to specifically alter the payment fields
  • Pro: adds full customization, type safe and easy to find
  • Con: adds the most complexity to the payload form-builder-plugin package
  1. Custom Property
  • Use the custom property that accepts a boolean to disable the default payment fields, or an array of fields to override the defaultPaymentFields.
  • Pro: limited complexity to the payload form-builder-plugin package, maintains autocomplete
  • Con: hides the feature by placing it in the custom property

My personal vote is option 3 and we can worry about any larger overhauls to form submissions in V4.

edited after completing the feature using custom property

mikecebul avatar Nov 24 '24 16:11 mikecebul

I realized I don't have to nest a property inside of the custom property. I wish I seen this from the beginning. To accomplish all my goals for this PR I have a property called paymentFields inside formSubmissionOverrides to override defaultPaymentFields. Using it looks like this:

// ...
formSubmissionOverrides: {
  paymentFields: [
    {
      name: 'payment',
      type: 'group',
      fields: [
        {
          name: 'stripeCheckoutSession',
          type: 'text',
        },
      ],
    },
  ],
  fields: ({ defaultFields }) => {
    return [
      ...defaultFields,
      {
        name: 'custom',
        type: 'text',
      },
    ]
  },
  },

Also added JSDocs for info in the IDE.

mikecebul avatar Nov 24 '24 19:11 mikecebul

Closed in favour of https://github.com/payloadcms/payload/pull/9522

Thanks for your work here @mikecebul !

paulpopus avatar Nov 26 '24 04:11 paulpopus