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
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 🥳