How to fix preflight error when calling Billing API?

Solved
Excursionist
20 3 2

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.

1. User clicks button to activate subscription (create new charge).

2. GET Request is sent from the front end to the back end.

3. Backend creates a new charge and the user is redirected to confirm the payment.

4. 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?signat...' (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.Screen Shot 2019-08-21 at 10.32.16 PM.png

 

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

0 Likes
Shopify Staff
Shopify Staff
1558 78 233

I'm not super proficient in Node/JS, but it looks to me like you're performing a redirect to the charge approval page within your embedded app, therefore trying to frame a page in the Shopify Admin, which will never work. Have you tried performing a full-page redirect if you're not already?

 

Cheers.

0 Likes
Highlighted

Success.

Excursionist
20 3 2

I didnt think I was going to figure it out but here is how I resolved my issue. Thanks @Alex for the response.

 

app.get("/shopify/new-charge", function (req, res, next) {
	const redirectUrl = "https://another.website.com";
	res.redirect(redirectUrl)
	next
});

 

1 Like

Success.

Shopify Partner
2 1 1

You need to send the confirmation url back to the UI via `res.send({"redirectUi:redirectURI});`

and then in the UI do a page redirect via window.location = dataResponse.redirectURI upon recieving the response.
Hope that helps.

Edit; trying to do window.location inside the iframe of embedded app will cause X-Frame-Options denied error
You need to open confirmation url in new tab and focus on the new tab.

const redirectUri = dataResponse.data.confirmation_url;
var win = window.open(redirectUri, '_blank');
Cheers!!

1 Like
Excursionist
20 3 2

Thank you. This is exactly what I ended up doing in my app (aka stumbling upon it). I thought adding buttons and modifying some workflows would make it better for the customers but realized it was "hell" for the developer side of things. Next time I'll make it easier on myself and go the inline Shopify route for accepting payments.

1 Like