Adding ACH Payment Support to Laravel Spark

TLDR: Skip to the how to.

We’re currently in the process of building our new SAAS offering, Scapegoat. Scapegoat a company management platform specialized for Landscape Companies and retail Garden Centers, loosely based on our 15 years of building and managing software for two leading companies in the these industries.

Since we are starting this project from scratch, we figured this would be a good opportunity to use some frameworks and tools we’ve always wanted to get our hands on. We chose to use Laravel with FilamentPHP for rapid development of the front end, and landed on Laravel Spark to rollout our billing portal.

Spark allowed us to get a Billing Portal powered by Stripe up and running in less than 24 hours. This probably would have been quicker if we were not also having to figure out a few things with Laravel and FilamentPHP along the way. Using existing frameworks has been a joy compared to the years of building and maintaining our legacy project that was all built form scratch. However, that comes with the limitation of playing in the boundaries the package developer set up.

For the most part, that was fine with us. We knew there were many other customization we wanted in how our billing worked, but the trade off to get our product to market was worth it. Well, except one…

ACH Payments.

I don’t know why ACH Payments are so important to me, but it’s always something I want to make sure is in option when it comes to payment systems. Im telling ya, im obsessed. Once upon a time, platforms like Stripe didn’t work with ACH. When Stripe did start to add ACH support about a decade ago, I was part of the alpha and beta test group. And when their ACH api went live, there were restrictions that didn’t work for me. Stripes ACH offering matched the others of the time, limiting transactions to 10k.

I get it. ACH is risky. It does not have the security built into it that Credit Cards do.

But, that was not going to work for a landscape company who’s transactions were often 50-100k. So, I worked with our bank at the time to develop our own ACH Micro Service that interacted directly with the NACHA network through their Secure FTP.

Yeah, so.. I geek out on ACH. It’s a thing.

Good news, Stripe’s ACH offerings has really matured and with their built in bank account verification, and alternate workflows, it’s become a powerful tool.

The Bad News: Laravel Spark does not support ACH.

If you go into your Stripe Dashboard and enable ACH Payments (‘us_bank_account’ payment method), your Stripe check out will gladly display ACH as an option. You can set up an account, and even initiate a payment. However, when the check out routes you back to your Laravel Spark Portal, you get this fun message:


“Unsupported payment method type: us_bank_account”

Well, I did not like seeing that. I took to Google to see if anyone else had solved this issue. That led me here: https://laracasts.com/discuss/channels/spark/does-spark-support-ach-payments-in-stripe. The answer being essentially “Spark is based on Cashier, Cashier does not support ACH, so Spark does not.” Ok… Easy enough. Now I just have to figure out how to get Cashier to support ACH. Let me also say, I knew that Spark was “based on Cashier” but I’m finding more and more just how much they are tightly integrated.

I found this issue on the Cashier GetHub that gave me the clue I needed. This helped me understand that Cashier itself was really just ingesting what was provided by the API and not doing any hard logic with the different payment types. I had remembered from an integration i was working on a while back, Stripe ACH Payment Methods were lagging behind others in the migration to the new PaymentMethod API. However, I knew that this had been finished. So, after a quick review of the Cashier-Stripe source code, my suspicions turned back to Spark.

Spark is closed source, so I wont go into too much detail. But basically I found a class “FrontendState” with the function paymentMethods(). This function was checking the list of payment methods supplied by the API and formatting them as needed. However, since the logic was hardcoded, it only supported the following ‘bacs_debit’, ‘card’, ‘cashapp’, ‘link’, ‘paypal’, ‘sepa_debit’, and if the payment method type was not on the list… it threw an error.

How To Add Support For ACH (And Possibly Other Stripe Payment Methods) To Laravel Spark

The fix was easy enough, override the paymentMethod function of the FrontendState Class. I’ll walk you through it.

1) Create a Service Class for our new logic

Service Classes are great for little modifications like this. There is no Artisan Command for creating services so we’ll have to do it manually. Also, unless you’ve made some services classes already, you likely don’t have the folder needed.

  • In the ‘app’ folder under your root directory, create a new folder called “Services”
  • In that folder, create a new folder called “Spark”
  • In THAT folder create a new file called “FrontendState.php”
  • Copy the code below:
<?php

namespace App\Services\Spark;

use Laravel\Cashier\PaymentMethod;
use Carbon\Carbon;
use RuntimeException;
use Spark\FrontendState as SparkFrontendState;

class FrontendState extends SparkFrontendState
{
    protected function paymentMethods($billable)
    {
        $defaultPaymentMethod = $billable->defaultPaymentMethod();

        return $billable->paymentMethods()->map(function (PaymentMethod $paymentMethod) use ($defaultPaymentMethod) {
            $pm = match ($paymentMethod->type) {
                'bacs_debit' => [
                    'last4' => $paymentMethod->bacs_debit->last4,
                    'brand' => 'BACS Debit',
                    'expiration' => null,
                ],
                'card' => [
                    'last4' => $paymentMethod->card->last4,
                    'brand' => ucfirst($paymentMethod->card->brand),
                    'expiration' => Carbon::createFromDate($paymentMethod->card->exp_year, $paymentMethod->card->exp_month, 1)->format('M Y'),
                ],
                'cashapp' => [
                    'last4' => null,
                    'brand' => 'Cash App: '.$paymentMethod->cashapp->cashtag,
                    'expiration' => null,
                ],
                'link' => [
                    'last4' => null,
                    'brand' => 'Link: '.$paymentMethod->link->email,
                    'expiration' => null,
                ],
                'paypal' => [
                    'last4' => null,
                    'brand' => 'PayPal: '.$paymentMethod->paypal->payer_email,
                    'expiration' => null,
                ],
                'sepa_debit' => [
                    'last4' => $paymentMethod->sepa_debit->last4,
                    'brand' => 'SEPA Direct Debit',
                    'expiration' => null,
                ],
               
                // New payment method added here. You could possibly add others, but last4, brand, and expiration might need adjustment for other types.
                'us_bank_account' => [
                    'last4' => $paymentMethod->us_bank_account->last4,
                    'brand' => 'Bank Account',
                    'expiration' => null,
                ],
                
                default => throw new RuntimeException('Unsupported payment method type: '.$paymentMethod->type),
            };

            return array_merge($pm, [
                'id' => $paymentMethod->id,
                'type' => $paymentMethod->type,
                'default' => $defaultPaymentMethod ? $paymentMethod->id === $defaultPaymentMethod->id : false,
            ]);
        })->toArray();
    }
}

Now we have a class that will override Spark’s built in class and the logic will allow the us_bank_account to be used.

Now we need to tell Laravel to use it.

  • Find your SparkServiceProvider.php in the app/Providers directory.
  • in our “use” area at the top, we need to do two imports
use App\Services\Spark\FrontendState as CustomFrontendState;
use \Spark\FrontendState as SparkFrontendState;
  • Then, in our boot function, we’ll add the following:
// Binding the custom FrontendState class to replace Spark's default FrontendState
$this->app->singleton(SparkFrontendState::class, CustomFrontendState::class);

That’s it. That magically let’s Laravel Spark support ACH payments. This is so simple I would be shocked if this isn’t addressed in a update soon. But for now, this will get you going without modifying the original code.


Leave a Reply

Your email address will not be published. Required fields are marked *