i have created a Shopify public app using node with shopify CLI , and for payment i enabled the recurring charge API after that the major issue occurred in calling the App Subscription page
In the oAuth the server will check if the user have any active subscriptions and if not the getSubscription Url is called to activate the subscription.
so if the user cancel the subscription and the app is installed and if the user select the app from the shopify admin dashboard the user could get access to the app. So in order to declined the access if the user have no ACTIVE subscription we implemented a logic to check the subscription and if not show the payment page.
in
server.js
const verifyIfSubscription= async (ctx, next) => {
const shop = process.env.SHOP;
ctx.res.setHeader("Content-Security-Policy", `frame-ancestors https://${shop} https://admin.shopify.com`);
// This shop hasn't been seen yet, go through OAuth to create a session
if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
ctx.redirect(`/auth?shop=${shop}`);
return;
}
const hasSubscription = await handlers.getAppSubscriptionStatus(ctx);
if (!hasSubscription) {
console.log("inside",hasSubscription)
server.context.client = await handlers.createClient(shop, token);
await handlers.getSubscriptionUrl(ctx);
return;
}else {
return next();
}
};
router.get("(.*)", verifyIfSubscription, handleRequest);
So this cause the issue
So because of this during the Initial oAuth of the application the payment page if appeared for the user to process the payment because the hasSubscription value will be false there.
But the issue is that if we click on the “Approve” button to activate the plan after the oAuth, One more time the payment page will appear here there and the payment Approve page will appear twice during the installation.
This page will appear twice
This is because in the “verifyIfSubscription” function the value of the “hasSubscription” function will initially return false so the page will appear and it show some delay in getting the “hasSubscription” to true and redirect to the App dashboard.
server.js
import "@babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "@shopify/koa-shopify-auth";
import Shopify, { ApiVersion } from "@shopify/shopify-api";
import Koa from "koa";
import next from "next";
import Router from "koa-router";
import * as handlers from "./handlers/index";
dotenv.config();
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SHOPIFY_API_SCOPES.toString().split(","),
HOST_NAME: process.env.HOST.toString().replace(/https:\/\//, ""),
API_VERSION: ApiVersion.October20,
IS_EMBEDDED_APP: true,
// This should be replaced with your preferred storage strategy
SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});
const port = parseInt(process.env.PORT, 10) || 8081;
const dev = process.env.NODE_ENV !== "production";
const app = next({
dev,
});
const handle = app.getRequestHandler();
// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS = {};
app.prepare().then(async () => {
const server = new Koa();
const router = new Router();
server.keys = [Shopify.Context.API_SECRET_KEY];
let token;
server.use(
createShopifyAuth({
async afterAuth(ctx) {
// Access token and shop available in ctx.state.shopify
const { shop, accessToken, scope } = ctx.state.shopify;
const host = ctx.query.host;
token = accessToken;
ACTIVE_SHOPIFY_SHOPS[shop] = scope;
ctx.cookies.set("host", host, {
httpOnly: false,
secure: true,
sameSite: "none",
});
const response = await Shopify.Webhooks.Registry.register({
shop,
accessToken,
path: "/webhooks",
topic: "APP_UNINSTALLED",
webhookHandler: async (topic, shop, body) =>
delete ACTIVE_SHOPIFY_SHOPS[shop],
});
if (!response.success) {
console.log(
`Failed to register APP_UNINSTALLED webhook: ${response.result}`
);
}
// Redirect to app with shop parameter upon auth
// ctx.redirect(`/?shop=${shop}&host=${host}`);
//server.context.client = await handlers.createClient(shop, accessToken);
//await handlers.getSubscriptionUrl(ctx);
server.context.client = await handlers.createClient(shop, accessToken);
const hasSubscription = await handlers.getAppSubscriptionStatus(ctx);
console.log("subccrioAuth",hasSubscription)
if (hasSubscription) {
next();
ctx.redirect(`/?shop=${shop}&host=${host}`);
} else {
await handlers.getSubscriptionUrl(ctx);
}
},
})
);
const handleRequest = async (ctx) => {
await handle(ctx.req, ctx.res);
// console.log(shop);
ctx.respond = false;
ctx.res.statusCode = 200;
};
const verifyIfSubscription = async (ctx, next) => {
const shop = process.env.SHOP;
console.log("shop",shop)
ctx.res.setHeader("Content-Security-Policy", `frame-ancestors https://${shop} https://admin.shopify.com`);
// This shop hasn't been seen yet, go through OAuth to create a session
if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
ctx.redirect(`/auth?shop=${shop}`);
return;
}
const hasSubscription = await handlers.getAppSubscriptionStatus(ctx);
console.log("subscr",hasSubscription)
if (!hasSubscription) {
console.log("inside",hasSubscription)
server.context.client = await handlers.createClient(shop, token);
await handlers.getSubscriptionUrl(ctx);
return;
}else {
return next();
}
};
router.post("/webhooks", async (ctx) => {
try {
await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
console.log(`Webhook processed, returned status code 200`);
} catch (error) {
console.log(`Failed to process webhook: ${error}`);
}
});
router.post("/webhooks/customers/data_request", (ctx) => {
console.log("received webhook: ", ctx.state.webhook.topic);
customerDataRequest(ctx.state.webhook);
ctx.res.statusCode = 200;
});
router.post("/webhooks/customers/redact", (ctx) => {
console.log("received webhook: ", ctx.state.webhook.topic);
customerRedact(ctx.state.webhook);
ctx.res.statusCode = 200;
});
router.post("/webhooks/shops/redact", (ctx) => {
console.log("received webhook: ", ctx.state.webhook.topic);
shopRedact(ctx.state.webhook);
ctx.res.statusCode = 200;
});
router.post(
"/graphql",
verifyRequest({ returnHeader: true }),
async (ctx, next) => {
await Shopify.Utils.graphqlProxy(ctx.req, ctx.res);
}
);
router.get("(/_next/static/.*)", handleRequest); // Static content is clear
router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear
router.get("(/ARViewerScript.*)", handleRequest);
// router.get("(.*)", async (ctx) => {
// const shop = process.env.SHOP;
// ctx.res.setHeader("Content-Security-Policy", `frame-ancestors https://${shop} https://admin.shopify.com`);
// // This shop hasn't been seen yet, go through OAuth to create a session
// if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
// ctx.redirect(`/auth?shop=${shop}`);
// } else {
// await handleRequest(ctx);
// }
// });
router.get("(.*)", verifyIfSubscription, handleRequest);
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
If anyone can covey there thoughts on this and share there suggestions on this it will be awesome and very helpful.
if you can provide any input it will be very helpful
Thanks

