Solved

Why did my Shopify app submission fail and how can I resolve it?

marioguy08
Tourist
4 1 0
Hey guys, I am a student in college developing my first shopify app and I ended up submitting my app for review by the team twice so far and it got rejected both times for the same reason. See the screencast here to see the reason it was rejected: https://screenshot.click/17-22-25901-94059.webm. I also want to note that the app works perfectly fine when I test it on development stores. I was wondering if you guys had any idea on how to test an app on a real product store? But either way I think I found the issue, I checked the logs on my heroku server after the submission and normally the logs should display a metafield that is created by the app itself, which is where the app settings are actually stored. But instead I saw a bunch of random unrelated metafields being displayed. This means that the app is not getting the right settings which would explain why it never fully loads. My app also assumed that the store would have 0 metafields by default, and if the store already had other existing metafields then it would not work, which would explain the issue. I ended up modifying the code to scan through all of the store metafields and create a new one if the correct metafield was not found. I think this was the issue and was wondering if you guys thought the same before I try to submit the app again, and like I mentioned earlier the app works perfectly on development stores. Also I used the Shopify CLI to create the app so OAuth shouldn't be an issue. Thanks!
 
Here is my server.js code as well so you can let me know if there is anything wrong there!
const cors = require('@koa/cors');
import {
  storeCallback,
  loadCallback,
  deleteCallback
from './redis-store.js';

dotenv.config();
const axios = require('axios');
const koaBody = require('koa-body');
const port = parseInt(process.env.PORT10) || 8081;
const dev = process.env.NODE_ENV !== "production";
const app = next({
  dev,
});
const handle = app.getRequestHandler();


///*
Shopify.Context.initialize({
  API_KEY: process.env.SHOPIFY_API_KEY,
  API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
  SCOPES: process.env.SCOPES.split(","),
  HOST_NAME: process.env.HOST.replace(/https:\/\//""),
  API_VERSION: ApiVersion.October20,
  IS_EMBEDDED_APP: true,
  // This should be replaced with your preferred storage strategy
  SESSION_STORAGE: new Shopify.Session.CustomSessionStorage(
    storeCallback,
    loadCallback,
    deleteCallback,
  ),
});
//*/

// 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 = {};

var filename = './script.js'
var newHeader = "const HOSTURL = \"https://" + process.env.HOST.replace(/https:\/\//"") + "\";\n"
replaceFirstLineOfFile(filenamenewHeaderfunction (err) {
  if (errthrow err
  /* ... */
})

app.prepare().then(async () => {
  const server = new Koa();
  const router = new Router();
  server.keys = [Shopify.Context.API_SECRET_KEY];
  server.use(cors());
  server.use(
    createShopifyAuth({
      async afterAuth(ctx) {
        // Access token and shop available in ctx.state.shopify
        const { shopaccessTokenscope } = ctx.state.shopify;
        const host = ctx.query.host;

        var flag = 0;
        ACTIVE_SHOPIFY_SHOPS[shop] = [scopeaccessToken];
        console.log(ACTIVE_SHOPIFY_SHOPS);
        console.log(accessToken);
        const response = await Shopify.Webhooks.Registry.register({
          shop,
          accessToken,
          path: "/webhooks",
          topic: "APP_UNINSTALLED",
          webhookHandler: async (topicshopbody=>
            delete ACTIVE_SHOPIFY_SHOPS[shop],
        });



        if (!response.success) {
          console.log(
            `Failed to register APP_UNINSTALLED webhook: ${response.result}`
          );
        }
        if (response.success) {
          console.log(response.result);
        }


        axios.get(`https://${shop}/admin/api/2021-07/script_tags.json`, {
          headers: {
            'X-Shopify-Access-Token': accessToken
          }
        })
          .then(response => {
            const scripttags = response.data.script_tags;
            console.log(scripttags);
            var found = false;
            for (let i = 0i < scripttags.lengthi++) {
              var curScriptSrc = scripttags[i].src;
              if (curScriptSrc.substr(-10) === "/scriptLSE") {
                found = true;
                console.log("Found existing LSE scripttag, update!");
                var data = JSON.stringify({ "script_tag": { "id": scripttags[i].id"src": "https://" + process.env.HOST.replace(/https:\/\//"") + "/scriptLSE" } });
                var config = {
                  method: 'put',
                  url: `https://${shop}/admin/api/2021-07/script_tags/` + scripttags[i].id + '.json',
                  headers: {
                    'X-Shopify-Access-Token': accessToken,
                    'Content-Type': 'application/json'
                  },
                  data: data
                };
                axios(config)
                  .then(function (response) {
                    console.log(JSON.stringify(response.data));
                  })
                  .catch(function (error) {
                    console.log(error);
                  });
                break;
              }
            }
            if (found === false) {
              console.log("Existing LSE scripttag not found, install scripttag");
              var data = JSON.stringify({ "script_tag": { "event": "onload""src": "https://" + process.env.HOST.replace(/https:\/\//"") + "/scriptLSE" } });
              var config = {
                method: 'post',
                url: `https://${shop}/admin/api/2021-07/script_tags.json`,
                headers: {
                  'X-Shopify-Access-Token': accessToken,
                  'Content-Type': 'application/json'
                },
                data: data
              };
              axios(config)
                .then(function (response) {
                  console.log(JSON.stringify(response.data));
                })
                .catch(function (error) {
                  console.log(error);
                });
            }
          })
          .catch(error => {
            console.log(error);
          });

        var config = {
          method: 'get',
          url: `https://${shop}/admin/api/2021-07/metafields.json`,
          headers: {
            'X-Shopify-Access-Token': accessToken
          }
        };
        axios(config)
          .then(function (response) {
            var metafields = response.data.metafields;
            var found = false;
            //look for metafield with namespace LSESETTINGS
            for (let i = 0i < metafields.lengthi++) {
              if (metafields[i].namespace === "LSESETTINGS") {
                console.log("Found existing LSE metafield!");
                found = true;
                ACTIVE_SHOPIFY_SHOPS[shop].push(JSON.parse(metafields[i].id));
                break;
              }
            }
            if (found === false) {
              console.log("Did not find metafield, creating new one!");
              var data = JSON.stringify({
                "metafield": {
                  "namespace": "LSESETTINGS",
                  "key": "appsettings",
                  "value": "{\"isenabled\": true, \"buttonclasses\": [],\"buttontwoclasses\": [],\"modalcolor\"\"rgb(255,255,255)\",\"modaloverlay\"\"rgba(0,0,0,0.4)\",\"bordercolor\"\"rgb(0,0,0)\",\"toptextcolor\":\"undefined\",\"modaltext\"\"Do you want to add additional products?\",\"crosssellbutton\"\"Add more products\",\"cartbutton\":\"Go to cart\",\"addedtocarttext\":\"added to cart\",\"borderradius\": 0}",
                  "type": "json"
                }
              });

              var config = {
                method: 'post',
                url: `https://${shop}/admin/api/2021-07/metafields.json`,
                headers: {
                  'Content-Type': 'application/json',
                  'X-Shopify-Access-Token': accessToken
                },
                data: data
              };

              axios(config)
                .then(function (response) {
                  ACTIVE_SHOPIFY_SHOPS[shop].push(JSON.parse(response.data.metafield.id));
                  console.log(response.data.metafield);
                  console.log(ACTIVE_SHOPIFY_SHOPS);
                })
                .catch(function (error) {
                  console.log(error);
                });
            }
          })
          .catch(function (error) {
            console.log(error);
          });

        // Redirect to app with shop parameter upon auth
        ctx.redirect(`/?shop=${shop}&host=${host}`);


      },
    })
  );
 
Accepted Solution (1)

marioguy08
Tourist
4 1 0

This is an accepted solution.

Hey nevermind I figured out the issue

View solution in original post

Reply 1 (1)

marioguy08
Tourist
4 1 0

This is an accepted solution.

Hey nevermind I figured out the issue