Some background…
App is developed using Node / Express
I have been working on my simple app since Aril 25th and for the last three weeks have been struggling to complete the billing portion of my app. As a junior developer and first time Shopify App developer I am confident Shopify + The Shopify Community can clear up this mystery.
Lets start here…
I decided to build my own free trial instead of using the Shopify free trial option that is built into the Billing APIs because I wanted to have more control over it. So I successfully built that.
So after the free trial ends my app locks up unless a user clicks on a button to activate their subscription, and this is where the problems start.
This is how I imagine the workflow.
-
User clicks button to activate subscription (create new charge).
-
GET Request is sent from the front end to the back end.
-
Backend creates a new charge and the user is redirected to confirm the payment.
-
User confirms payment and is redirected back into the app.
Of course when I tried this I ran into the infamous ‘preflight CORs error’:
Access to XMLHttpRequest at ‘https://teststore.myshopify.com/admin/charges/5555555555/confirm_recurring_application_charge?signature=BBBBBBBBBBB’ (redirected from ‘https://ngrok.io/apps/new-charge?shop=teststore.myshopify.com’) from origin ‘https://ngrok.io’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
I read and re-read the API documents and the implementationguid.
I attempted adding CORs to the server via, but this did not resolve anything.
var cors = require('cors')
app.use(cors())
I attempted adding headers to the server responses, but this did not resolve anything.
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "https://ngrok.io"); // update to match the domain you will make the request from
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
I attempted to setup an app proxy (which could be incorrectly set up), but this did not resolve anything.
I attempted to whitelist additional URLs:
I also reached out to Shopify App Developer groups on Facebook and Reddit took their suggestions and nothing worked.
- Is there a way to accomplish my desired workflow?
- If yes, what am I doing wrong?
- If no, what is the best way to go about setting up a recurring charge?
I have made other successful calls to the other Shopify APIs and the Billing API is by far the one giving me the most frustrations. And of course this is the one thing from holding up my app from beta testing and ultimately submitting for review. Go figure.
Here is my ajax call:
$.ajax('/apps/new-charge', {
type: "GET",
data: {
shop: shop,
},
contentType: "application/x-www-form-urlencoded",
headers: {
'Access-Control-Allow-Origin': '*',
},
success: function (result) {
console.log('Success - Paid');
},
error: function (jqXHR, exception) {
console.log('Error - No Paid');
console.log('jqXHR :', jqXHR);
},
});
Here is my Express route:
app.get('/apps/new-charge', function (req, res) {
const shop_domain = req.query.shop;
const shopRequestUrl = 'https://' + shop_domain + '/admin/api/2019-04/recurring_application_charges.json';
const newCharge = {
"recurring_application_charge": {
"name": "Professional Plan",
"price": 4.99,
"return_url": ngrokAddress + "\/apps\/activate?shop=" + encodeURIComponent(shop_domain),
"test": true,
}
};
if (shop_domain) {
table = "order_minimum_account_table";
condition = "shop_domain='" + shop_domain + "'";
store.getRowMetafield(table, condition, function (data) {
request.post(shopRequestUrl,
{
headers: {
'X-Shopify-Access-Token': data[0].access_token,
},
json: newCharge
}, function (error, response, body) {
if (error) {
console.log('error with billing');
console.error(error)
return
}
else {
console.log(`statusCode: ${res.statusCode}`);
// Redirect User To Confirmation Page //
const redirectURL = body.recurring_application_charge.confirmation_url;
res.redirect(redirectURL);
}
})
})
}
else {
res.send('Error: No shop_domain.');
}
});
Again thanks and any direction would be greatly appreciated.
Chris