Payment migration: Braintree to Stripe and beyond

Engineering notes from migrating active subscriptions between payment processors - initial planning through final customer transition.

Today, Braintree and Stripe both can handle multiple payment methods, including credit/debit cards, PayPal, Venmo, and digital wallets like Apple Pay and Google Pay, and offer robust fraud protection. In the early days of Apify, we chose Braintree as our payment processor since it was the only major global provider supporting European companies at the time.

However, as Stripe caught up and expanded its capabilities, we began considering a transition. Not only would Stripe offer some advantages, but we also wanted to build in redundancy - relying on a single payment provider created a potential point of failure we wanted to address.

I've implemented several payment gateways during my engineering career, mostly for e-commerce platforms. However, this was my first time handling such a migration for a subscription-based service. The complexity of moving thousands of active subscriptions and stored payment methods while keeping service interruption-free made this project both challenging and very rewarding.

Braintree or Stripe?

The plan

Apify runs on a subscription model, where customers provide their payment information, and we store it securely in Braintree. Although Braintree offers a wide range of payment options, we kept things focused on just credit/debit cards and PayPal. These stored payment methods come into play in two scenarios: when we automatically renew subscriptions and when we need to charge for any usage that exceeds the subscription limits (what we call overages).

One key detail: we built our own subscription management system rather than using external solutions. We handle everything from subscription logic to invoicing in-house, using Braintree solely for charging and storing payment methods. This architectural decision ended up making our migration a lot easier.

We set ourselves a clear goal to migrate all payment methods - no customer actions needed, no double charges, and no disruption to successful payments.

  • We needed to prepare our app to handle both Braintree and Stripe.
  • When everything was tested and ready, we set up Stripe for new customers and existing ones who hadn't added payment methods yet.
  • For existing customers using Braintree, we updated the app so any new payment methods they added would go through Stripe instead.
  • This way, we knew we wouldn’t have any new Braintree payment methods, and we were ready to migrate payment methods.
  • Then, we set up Stripe for existing customers who are still on Braintree after the migration finished.

Moving the payment methods

Payment cards

The card migration process turned out to be the biggest mystery. Braintree limits you to just two card exports - more info can be found in their data migration documentation.

Migrating it in one trip

This is how it goes:

  • You email Braintree support requesting migration to Stripe. You have to mention that it’s a PCI Level 1 Certified Provider.
  • While doing that, fill in the import request at Stripe so they’re prepared.
  • Braintree will ask you for a couple of details, and your authorized signer must sign the “Data Transfer & Indemnification Agreement.”
  • When they have everything, they’ll prepare an encrypted CSV file, upload it to their SFTP server, and send you encrypted credentials. Everything is encrypted with a Stripe PGP key, so you can’t have a sneak peek at the data.
  • Be patient. This dance took us more than 14 days. A slight delay was caused by an expired Stripe PGP key.
  • After 14 days, we could finally tell Stripe that the data were ready!
  • Stripe will verify that the file is okay. They’ll send you Braintree customer IDs and ask for the last four digits of their cards.
  • It’s going to take another few days till everything is imported.
  • When it’s done, Stripe will share two mapping files - one with successful imports and the other with errors. The mapping file contains an object that looks like this:
{ 
    BRAINTREE_USER_ID: { 
        id: "STRIPE_USER_ID",
        cards: {
            BRAINTREE_PAYMENT_METHOD_TOKEN_1: {
                id: "STRIPE_PAYMENT_METHOD_ID",
                last4: "4242",
                exp_month: 2,
                exp_year: 2028,
                brand: "Visa"
            },
            ...
        },
        // This is present only in the error file
        errors: { 
            BRAINTREE_PAYMENT_METHOD_TOKEN_2: "Your card was declined." 
            ...
        },
}

PayPal

Braintree will export only cards, you’ll have to handle PayPal on your own. There wasn’t much information around on how to do it. The only thing Braintree told us was that we have access to “Billing agreement” - and yes, there is billingAgreementId property in PayPal payment method objects in Braintree.

So we had to write a script that:

  • Fetches PayPal payment method from Braintree.
  • Creates payment method placeholder in Stripe like this:
const payPalPaymentMethod = await this.stripe.paymentMethods.create({
    type: 'paypal',
    billing_details: {...}
    metadata: {
        braintreePaymentMethodId: braintreePaymentMethod.token,
    },
});
  • Then, create a confirmed setup intent for PayPal. This will attach the payment method to the customer. Stripe will read payer ID and email from the billing agreement. This worked for us:
const setupIntent = await this.stripe.setupIntents.create({
    customer: stripeCustomerId,
    usage: 'off_session',
    confirm: true,
    return_url: 'https://somepage.com',
    payment_method: payPalPaymentMethod.id,
    description: 'Apify web automation platform subscription',
    payment_method_options: {
        paypal: {
            billing_agreement_id: braintreePaymentMethod.billingAgreementId,
        },
    },
    mandate_data: {
        customer_acceptance: {
            accepted_at: dayjs(braintreePaymentMethod.updatedAt || braintreePaymentMethod.createdAt).unix(),
            type: 'offline',
        },
    },
});

Switching customers

Once we had everything ready in Stripe, we could start switching customers. Around 10,000 of them. We’ve set up a couple of charts to help us monitor the payments to see if there wasn’t some sudden change in the count of failed payments or invoices that we weren’t able to collect.

We started small - picking customers on our most basic plan. Not only that, but we queried our database for the first ten customers who had this plan, had cards from various providers or PayPal and their renewal was in the upcoming day. That way, we didn’t have to wait for a month until they’re charged. We checked on them the next day - and everything was fine. It was a huge relief to see that everything worked!

After that, we gradually migrated all users on the most basic plan. We did that in batches for multiple days while staring at the charts and logs. Then repeated the same with more complex plans.

The results

Adding a payment method in Braintree and Stripe

We’ve migrated more than 10,000 users, 9,000 payment cards, and 2,000 PayPal accounts.

The whole process took 18 working days from our initial Braintree export request until we had all cards and customers in Stripe. We could’ve moved faster if we hadn't been so thorough with questions, but we wanted to understand every detail. Of course, we also hit a classic Friday surprise - an expired public PGP key - which meant waiting for Stripe's engineers to update it, then getting back in Braintree's queue for support.

Moving customers from Braintree to Stripe took another couple of weeks. Since we weren't under time pressure, we chose to move carefully to avoid any issues. Though here's the thing about payment processing - when a charge fails, support will typically blame the card processor or bank and ask you to have the customer try again anyway. So once you've verified everything works with a small test group, you can actually move pretty quickly through this step.

The migration involved countless email threads between all parties. It's a complex process by nature - after all, we're dealing with sensitive payment data that needs careful handling every step of the way.

On this page

Build the scraper you want

No credit card required

Start building