mollie / mollie-api-node

Official Mollie API client for Node
http://www.mollie.com
BSD 3-Clause "New" or "Revised" License
231 stars 63 forks source link

Using the iterate function in getserversideprops in nextjs throws an error, but pagination doesn't. #288

Closed muzzy92 closed 2 years ago

muzzy92 commented 2 years ago

I'm getting the following error, but I'm not sure why:

Error: Unexpected process release name undefined. This may indicate that the Mollie API client is integrated into a website or app. This is not recommended, please see https://github.com/mollie/mollie-api-node/#a-note-on-use-outside-of-nodejs. If this is a mistake, please let us know: https://github.com/mollie/mollie-api-node/issues

This is the function I'm calling in getserversideprops:

export async function listMolliePaymentsByCampaign(campaignId) { let payments = []; for await (let payment of mollieClient.payments.iterate()) { if (payment.metadata?.campaign === campaignId && payment.status === 'paid') payments.push({ name: payment.metadata.name, amount: payment.metadata.amount, donation_type: payment.metadata.donation_type, }) } return payments; };

Why is it throwing that error? A similar function doesn't throw that error:

export async function listMolliePayments() { const payments = await mollieClient.payments.page({ limit: 10 }); return payments; }

Pimm commented 2 years ago

Thanks for the issue.

The error you're seeing is one we've introduced to prevent you from shooting yourself in the foot. See ‒ especially when using Next.js ‒ you have to be careful to only communicate with Mollie serverside; never clientside. Communicating with Mollie clientside would expose your API key to the users, thereby compromise your account.

The error is thrown by createMollieClient. Could you please verify that in both examples, you are calling createMollieClient inside getServerSideProps? I can't imagine using iterate vs page could have this effect.

A note on scale

I would like to point out that what you're doing could become problematic over time. You're iterating over all payments to find the ones which meet your requirements. This can become a costly and slow operation once you have a lot of payments, while getServerSideProps is supposed to be light and fast.

One solution would be to limit the search, like so:

export async function listMolliePaymentsByCampaign(campaignId) {
    let payments = [];
    for await (let payment of mollieClient.payments
        .iterate()
        // Only search among the 1000 most recent payments.
        .take(1000)
        .filter(({ metadata, status }) => metadata?.campaign === campaignId && status === "paid")
    ) {
        payments.push({
            name: payment.metadata.name,
            amount: payment.metadata.amount,
            donation_type: payment.metadata.donation_type
        });
    }
    return payments;
}
muzzy92 commented 2 years ago

Thanks for taking the time to respond! At the moment I'm just building a proof of concept, so this won't be used at a bigger scale. I'll probably save the data per campaign in a database somewhere, but that's neither here nor there.

I tried adding the same function directly into getServerSideProps and it didn't throw the error. When I import that same function from my utils.js file, it does throw the error. I believe NextJS removes the code from the client-side when the import is only used in getServerSideProps, so it shouldn't be throwing that error, right? It's weird that it only happens when importing the function with that uses iterate().

Pimm commented 2 years ago

If you're getting this error, this strongly suggests that Next.js included your Mollie code in the client. That is problematic.

Again, I can't imagine using iterate vs page could have this effect. Could you show me the full example? As in, the page with the getServerSideProps, as well as the module which calls createMollieClient?

muzzy92 commented 2 years ago

I've tried creating a minimal version of the page, and noticed I didn't get the error. I think the mollieClient was getting imported in my page, but not used, causing it to throw the error. It's solved then, it was just a misunderstanding of NextJS imports on my part. For future reference, this is in my utils/mollie-actions.js:

import createMollieClient from '@mollie/api-client';
export const mollieClient = createMollieClient({ apiKey: process.env.MOLLIE_API_KEY });

export async function listMolliePayments() {
    const payments = await mollieClient.payments.list();
    return payments;
}

export async function listMolliePaymentsForCampaign(campaignId) {
    const payments = await listMolliePayments();
    const paymentsForThisCampaign = payments.filter(payment => payment.metadata?.campaign === campaignId && payment.status === 'paid')
    return paymentsForThisCampaign;
}

export async function listMolliePaymentsByCampaign(campaignId) {
    let payments = [];
    for await (let payment of mollieClient.payments.iterate()) {
        if (payment.metadata?.campaign === campaignId && payment.status === 'paid') payments.push({
            name: payment.metadata.name,
            amount: payment.metadata.amount,
            donation_type: payment.metadata.donation_type,
        })
    }
    return payments;
};

I'm exporting the mollieClient from there. When I was testing the functions, I was importing the functions AND the mollieClient like this: import { mollieClient, listMolliePaymentsByCampaign } from '../utils/mollie-actions';

But I was only using the listMolliePayments function in getServerSideProps, but not using the mollieClient, thinking that wouldn't matter. Instead, because it's imported but NOT used in getServerSideProps, it gets included in the client. import { listMolliePaymentsByCampaign } from '../utils/mollie-actions';

This solves the error. My bad!

Pimm commented 2 years ago

Thanks for the reply.

There's a lesson here. Frameworks such as Next.js and Remix blur the lines between clientside and serverside. It's an interesting development. However, when it comes to payments ‒ a very security-sensitive subject ‒ it is crucial that some of the logic is strictly serverside.

As engineers, we have to be extra careful not to expose API keys or other secrets. As we've seen, an accidental, unused import can compromised your API key. We can't get too comfortable!