arrow-return

How to migrate an account from one country to another using Stripe? - Part 2

11 min read

Share


This article is divided into two parts.

How to migrate an account from one country to another using Stripe? Part 1
How to migrate an account from one country to another using Stripe? Part 2

1 - Create a new account with a new location

If you already have an account, I suggest you create a new one. This account should have a different location than the one you currently have.

2 -  Fill in the migration form

Both accounts must complete the form. 

For data security, the Stripe account owner needs to submit the form. someone else in a different role can complete this form in order for the request to be initiated, but before the request can be executed, the account owner will need to resubmit the form.

Below are the data that must be filled in.

Link: Migration Form

Be careful to select whether your account is origin or destination

To get the account ID access account settings (destination account).

Link: Get Account ID

3 - Receiving email confirmation from support

The support team will perform the migration. 

We will receive a migration identifier. Input this value in the “additional details” section of the intake request form and to resend form

4 - Recreate subscriptions and plans after moving customer data to a new Stripe account

If you have copied data from one Stripe account to another and need to recreate the old account's subscriptions, plans, products, and promotions. The steps below will show how to recreate a new account.

4.1 - It is necessary to add a new variable in the .env receiving the secret key of the new account (API Key)

STRIPE_SECRET_EUA: key secret here

4.2 - Link Get API Keys

5 - Perform promotion code migration

To carry out the migration of the Promotional Code, we will look for everyone in the old account and save it in the new account with the same information

Below is the code:

$stripeOld = new StripeClient(
    config('settings.billing.stripe_secret')
);
$stripeNew = new StripeClient(
    config('settings.billing.stripe_secret_eua')
);
$promotionCodes = $stripeOld->promotionCodes->all(['limit' => 100])['data'];
foreach ($promotionCodes as $promotionCode) {
    $data = [
        'coupon' => $promotionCode->coupon['id'],
        'code' => $promotionCode['code'],
    ];
    if ($promotionCode['max_redemptions'] != null) {
        $data['max_redemptions'] = $promotionCode['max_redemptions'];
    }
    if ($promotionCode['customer'] != null) {
        $data['customer'] = $promotionCode['customer'];
    }
    if ($promotionCode['expires_at'] != null) {
        $data['expires_at'] = now()->timestamp;
    }
    try {
        $stripeNew->promotionCodes->create($data);
    } catch (Exception $e) {
        echo $e->getMessage();
    }
}
return 'done';

6 - Perform product migration

To carry out the migration of the Products, we will look for all of them in the old account and save them in the new account with the same information

Below is the code:

$stripeOld = new \Stripe\StripeClient(
    config('settings.billing.stripe_secret')
);
$stripeNew = new \Stripe\StripeClient(
    config('settings.billing.stripe_secret_eua')
);
foreach ($stripeOld->products->all()['data'] as $product) {
    try {
        $metadata = json_encode($product['metadata']); 
        $metadata = json_decode($metadata, true);
        $stripeNew->products->create([
            'id' => $product['id'],
            'images' => $product['images'],
            'name' => $product['name'],
            'type' => $product['type'],
            'metadata' => $metadata ,
        ]);
    } catch (ApiErrorException $e) {
        Log::channel('stripe-product')->warning('Id product: ' . $product['id'] . '. Error: ' . $e->getMessage());
        continue;
    }
}
foreach ($stripeOld->prices->all(['expand' => ['data.tiers']]) as $price) {
    try {
        $metadata = json_encode($price['metadata']); 
        $metadata = json_decode($metadata, true);
        $recurring = json_encode($price['recurring']); 
        $recurring = json_decode($recurring, true);
        $tiers = [];
        if($recurring != null){
            foreach ($recurring as $key => $value) {                  
                if($value == null) {
                    unset($recurring[$key]);                       
                }
            }
        }           
        if($price['tiers_mode'] == 'volume') {
            $tiers = json_encode($price['tiers']);
            $tiers = json_decode($tiers, true);

            foreach ($tiers as $key => $tier) {
                unset($tiers[$key]['unit_amount_decimal']);
                unset($tiers[$key]['flat_amount_decimal']);
                if($tier['flat_amount'] == null) {
                    unset($tiers[$key]['flat_amount']);                       
                }
                if($tier['up_to'] == null) {
                    $tiers[$key]['up_to'] = 'inf';                             
                }
            }
        }
        $dataPrice = [
            'active' => $price['active'],
            'metadata' => $metadata,
            'product' => $price['product'],
        ];
        if($price['unit_amount'])
            $dataPrice['unit_amount'] = $price['unit_amount'];
        if($price['nickname'])
            $dataPrice['nickname'] = $price['nickname'];
        if($price['recurring'])
            $dataPrice['recurring'] = $recurring;
        if($price['tax_behavior'])
            $dataPrice['tax_behavior'] = $price['tax_behavior'];
        if($price['tiers_mode'])
            $dataPrice['tiers_mode'] = $price['tiers_mode'];
        if($price['tiers_mode'] == 'volume')
            $dataPrice['tiers'] = $tiers;
        if($price['currency'])
            $dataPrice['currency'] = $price['currency'];
        if($price['billing_scheme'])
            $dataPrice['billing_scheme'] = $price['billing_scheme'];
       
        $stripeNew->prices->create([
            $dataPrice,
        ]);
    } catch (ApiErrorException $e) {
        Log::channel('stripe-price')->warning('Id price: ' . $price['id'] . '. Error: ' . $e->getMessage());
        continue;
    }
}

7 - Perform customer subscription migration

To carry out the Subscription migration, we will look for all of them in the old account and save them in the new account with the same information.

To maintain the same subscription payment cycle date, we will send a trial period until the date of the next invoice to the customer. This is done so as not to charge twice for the same subscription.

Below is the code:

$id = $this->ask("Which ID do you want to start");
$stripeOld = new StripeClient(
    config('settings.billing.stripe_secret_old')
);
$stripeEU = new StripeClient(
    config('settings.billing.stripe_secret')
);
$workspaces = Workspace::select('stripe_id', 'id')->where('stripe_id', '!=', null)->orderBy('id')->where('id', '>', $id)->get();
foreach ($workspaces as $workspace) {
    $subscriptions = $stripeOld->subscriptions->all(['customer' => $workspace->stripe_id]);
    foreach ($subscriptions as $key => $billing) {
        $customer = $billing['customer'];
        $dataSubscription = [];
        echo $billing['id'] . PHP_EOL;
        echo $billing['billing_cycle_anchor'] . PHP_EOL;
        try {
            $metadata = json_encode($billing['metadata']);
            $metadata = json_decode($metadata, true);
            $itemsData = json_encode($billing['items']);
            $itemsData = json_decode($itemsData, true);
            $items = [];
            foreach ($itemsData['data'] as $key => $item) {
                $price = $stripeEU->prices->all(['product' => $item['price']['product']])['data'][0]['id'];
                $priceData = [];
                $priceData['price'] = $price;
                $priceData['quantity'] = $item['quantity'];
                $items[] = $priceData;
            }
            $dataSubscription = [
                'customer' => $customer,
                'currency' => $billing['currency'],
                'items' => [
                    $items
                ],
                'metadata' => $metadata,
                'billing_cycle_anchor' => $billing['current_period_end'],
                'cancel_at_period_end' => $billing['cancel_at_period_end'],
                'collection_method' => $billing['collection_method'],
                'default_tax_rates' => $billing['default_tax_rates'],
            ];
            if ($billing['transfer_data']) {
                $dataSubscription['transfer_data'] = $billing['transfer_data'];
            }
            if ($billing['automatic_tax']) {
                $dataSubscription['automatic_tax'] = ['enabled' => $billing['automatic_tax']['enabled']];
            }
            if ($billing['discount']) {
                $dataSubscription['coupon'] = $billing['discount']['coupon']['id'];

                if ($billing['discount']['coupon']['promotion_code']) {
                    $dataSubscription['promotion_code'] = $billing['discount']['coupon']['promotion_code'];
                }
            }
            $dataSubscription['trial_end'] = $billing['current_period_end'];
            if ($billing['description']) {
                $dataSubscription['description'] = $billing['description'];
            }
            $stripeEU->subscriptions->create([
                $dataSubscription
            ]);
        } catch (ApiErrorException $e) {
            echo $e->getMessage();
            Log::channel('stripe-billing')->warning('Id subscriptions: ' . $billing['id'] . '. Error: ' . $e->getMessage());
            continue;
        }
    }
}
return 'Done';

8 - Sync price subscriptions items

After migrating the customers' subscriptions, we have to update the subscription item id and price id in our database.

$id = $this->ask("Which ID do you want to start");
$stripeNew = new StripeClient(
    config('settings.billing.stripe_secret')
);
$workspaces = Workspace::select('stripe_id', 'id')->where('stripe_id', '!=', null)->orderBy('id')->where('id', '>', $id)->limit(500)->get();
foreach ($workspaces as $workspace) {
    $customer = $workspace->stripe_id;
    echo "Workspace:" . $workspace->id . PHP_EOL;
    $subscriptions = $stripeNew->subscriptions->all(['customer' => $customer])['data'];
    if (empty($subscriptions)) {
        echo "No subscriptions" . PHP_EOL;
        continue;
    }
    $subscriptionId = $subscriptions[0]['id'];
    $subscriptionItems = json_encode($subscriptions[0]['items']['data']);
    $subscriptionItems = json_decode($subscriptionItems, true);
    $IdsubscriptionCashier = Subscription::where('stripe_id', $subscriptionId)->orderBy('id', 'ASC')->first()->id;
    foreach ($subscriptionItems as $item) {
        try {
            $subscriptionItem = SubscriptionItem::where('subscription_id', $IdsubscriptionCashier)->where('stripe_product', $item['price']['product'])->first();
            if ($subscriptionItem && $subscriptionItem->stripe_id != $item['id'] || $subscriptionItem->stripe_price != $item['price']['id']) {
                $subscriptionItem->stripe_id = $item['id'];
                $subscriptionItem->stripe_price = $item['price']['id'];
                $subscriptionItem->save();
                echo "ID Success:" . $item['id'] . PHP_EOL;
            } else {
                echo "ID Already:" . $item['id'] . PHP_EOL;
            }
        } catch (Exception $e) {
            echo "ID Error:" . $item['id'] . PHP_EOL;
        }
    }
}

9 - Configure webhook

We need to register a new webhook

Link add new webhook

Here are some important events we use in our webhooks

URL Endpoint: https://{url-webhook}/stripe/webhook

 Events to send:

  • payment_intent.succeeded

  • payment_intent.requires_action

  • payment_intent.payment_failed

  • payment_method.attached

  • payment_method.updated

  • customer.tax_id.created

  • customer.subscription.created

  • customer.deleted

  • customer.updated

  • customer.subscription.deleted

  • customer.subscription.updated

  • customer.created

  • invoice.upcoming

  • invoice.payment_succeeded

  • invoice.payment_failed

  • charge.failed

  • charge.succeeded

10 - Cancel subscriptions from the old account

In order not to have two invoices for the customer, you need to cancel all customer subscriptions in the old account.

$stripeOld = new StripeClient(
    config('settings.billing.stripe_secret')
);
foreach (Workspace::select('id, stripe_id')->whereNotNull('stripe_id')->get() as $workspace) {
    echo "Workspace: " . $workspace->id . PHP_EOL;
    $subscriptions = $stripeOld->subscriptions->all(['customer' => $workspace->stripe_id]);
    foreach ($subscriptions['data'] as $subscription) {
        echo "Subscription: " . $subscription['id'] . PHP_EOL;
        $stripeOld->subscriptions->cancel($subscription['id']);
    }
}

After all the steps above have been performed, we need to change the secret key and stripe key for the new account.

And that completes the migration 🥳