We're moving the community! Starting July 7, the current community will be read-only for approx. 2 weeks. You can browse content, but posting will be temporarily unavailable. Learn more

Why isn't my Shopify App Billing API redirect working?

Why isn't my Shopify App Billing API redirect working?

skn1
Shopify Partner
7 0 1

How to redirect Shopify embedded to shopify billing api confirmation URL.


import { redirect } from "@remix-run/node";


Redirect is not working and window.location.href also not working.

Skn
Replies 9 (9)

frknue
Shopify Partner
5 0 1

Something like this should be working.

```

export const action: ActionFunction = async ({ request }) => {
const { billing } = await authenticate.admin(request);
await billing.require({
plans: [BASIC_TIER],
isTest: true,
onFailure: async () => billing.request({ plan: FREE_TIER }),
});

return null;
};

```
<Card>
<Form method="post">
<Button submit>Subscribe</Button>
</Form>
</Card>

```


```

felixmpaulus
Shopify Partner
59 0 19

How would you require billing for a free plan? I thought a plan must have a price >0? Please provide more code if possible, such as the billingConfig. 🙂

Add bullet points to your productpage with Bloom: Product Feature Bullets
Increase branding and conversion. Set your store apart.
❤️ Free Plan available. Exclusively 5-star reviews.

Transform product photos into interactive 3D models with Fira: AI Video to 3D Model
Boost engagement and reduce returns with immersive shopping experiences.
Easy setup with AI-powered conversion from regular videos to 3D models.
frknue
Shopify Partner
5 0 1

My bad as you said, the amount for a billing plan has to more than zero. Here is an example for one of our Apps.

shopify.server.ts
```
const shopify = shopifyApp({
apiKey: process.env.SHOPIFY_API_KEY,
apiSecretKey: process.env.SHOPIFY_API_SECRET || '',
apiVersion: LATEST_API_VERSION,
scopes: process.env.SCOPES?.split(','),
appUrl: process.env.SHOPIFY_APP_URL || '',
authPathPrefix: '/auth',
sessionStorage: new PrismaSessionStorage(prisma),
distribution: AppDistribution.AppStore,
restResources,
useOnlineTokens: true,
billing: {
[BASIC_TIER]: {
amount: 9.95,
currencyCode: 'USD',
interval: BillingInterval.Every30Days,
},
[PREMIUM_TIER]: {
amount: 39.95,
currencyCode: 'USD',
interval: BillingInterval.Every30Days,
},
[ULTIMATE_TIER]: {
amount: 99.95,
currencyCode: 'USD',
interval: BillingInterval.Every30Days,
},
},
webhooks: {
APP_UNINSTALLED: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: '/webhooks',
},
},
hooks: {
afterAuth: async ({ session }) => {
shopify.registerWebhooks({ session });
},
},
...(process.env.SHOP_CUSTOM_DOMAIN
? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] }
: {}),
});

```
Billing logic example:

```

let PLAN: any;
switch (id) {
case '2':
PLAN = BASIC_TIER;
break;
case '3':
PLAN = PREMIUM_TIER;
break;
case '4':
PLAN = ULTIMATE_TIER;
break;
default:
return json({ message: 'Invalid tier' }, { status: 400 });
}

await billing.require({
plans: [PLAN],
onFailure: async () => billing.request({ plan: PLAN }),
});

```
This will redirect the user to the Shopify billing page, if there is no active subscription.

felixmpaulus
Shopify Partner
59 0 19

Thank you for providing the code! 🙂

Unfortunately this does not help me with how to handle a free plan. 

I created this issue https://github.com/Shopify/shopify-app-template-remix/issues/431.

 

I would really appreciate any input. Thank you!

Add bullet points to your productpage with Bloom: Product Feature Bullets
Increase branding and conversion. Set your store apart.
❤️ Free Plan available. Exclusively 5-star reviews.

Transform product photos into interactive 3D models with Fira: AI Video to 3D Model
Boost engagement and reduce returns with immersive shopping experiences.
Easy setup with AI-powered conversion from regular videos to 3D models.
frknue
Shopify Partner
5 0 1

The "free" plan is just no plan in our app. So if the user has no subscription he is on a "free plan".

frknue
Shopify Partner
5 0 1

For example this is a workaround to get the information of the billing for the current user. The onFailure callback does nothing.
```

let billingInfo;

try {
billingInfo = await billing.require({
plans: [BASIC_TIER, PREMIUM_TIER, ULTIMATE_TIER],
isTest: false,
onFailure: async (error) => {
return new Response(null, { status: 204 }); // No Content response
},
});
} catch (error) {
// Handle any unexpected errors here
}
```
With `billingInfo` you can handle the logic.

felixmpaulus
Shopify Partner
59 0 19

Thank you so much for you input!

I have tried your code and it always ends up throwing this huge statuscode on my screen and it always ends in an error.

I do not want to handle a failure as an error. I simply want to execute my own logic in case the user has no billing.

 

I am new to Remix so again, any help would be greatly appreciated 🙏🏼

Screenshot 2023-11-24 at 18.03.32.png

Add bullet points to your productpage with Bloom: Product Feature Bullets
Increase branding and conversion. Set your store apart.
❤️ Free Plan available. Exclusively 5-star reviews.

Transform product photos into interactive 3D models with Fira: AI Video to 3D Model
Boost engagement and reduce returns with immersive shopping experiences.
Easy setup with AI-powered conversion from regular videos to 3D models.
frknue
Shopify Partner
5 0 1

We had the same problem, the `billing.require` function always expects a `onFailure ` fallback. I found a workaround, that's why I pass a new response with null as a parameter. This won't work outside of a try catch block.

Here you can see the full loader function. I hope this helps. Shopify needs to make the onFailure callback obligatory.

export async function loader({ request }) {
const { admin, billing } = await authenticate.admin(request);

let billingInfo;

try {
billingInfo = await billing.require({
plans: [BASIC_TIER, PREMIUM_TIER, ULTIMATE_TIER],
isTest: false,
onFailure: async (error) => {
return new Response(null, { status: 204 }); // No Content response
},
});
} catch (error) {
// Handle any unexpected errors here
}

// use the id to get more information about the plan
let subscriptionInfo: any;

if (billingInfo?.appSubscriptions[0]) {
// Extract the numerical ID from the GraphQL ID
const fullId = billingInfo.appSubscriptions[0].id;
const idParts = fullId.split('/');
const numericalId = idParts[idParts.length - 1];

try {
const response = await admin.rest.get({
path: `recurring_application_charges/${numericalId}.json`,
});

if (response.ok) {
const jsonResponse = await response.json();
subscriptionInfo = jsonResponse.recurring_application_charge;
} else {
console.error('Response not OK:', response.status);
}
} catch (error) {
console.error('Error fetching subscription info:', error);
}
}

const subscriptionPlan = getSubscriptionPlan(billingInfo, subscriptionPlans);

const graphlqlResponse = await admin.graphql(`{shop {currencyCode}}`);

const shopData = await graphlqlResponse.json();

return json({
apiKey: process.env.SHOPIFY_API_KEY,
subscriptionPlan: subscriptionPlan,
subscriptionInfo: subscriptionInfo,
shopCurrency: Currency[shopData.data.shop.currencyCode],
});
}


PS: This is also my first Remix App, if there is a better way to this, I welcome your input and assistance.

iamgurisehgal
Shopify Partner
1 0 0

window.location does not work here please use window.top.location.replace(yourUrl)