Create invoice attachment - filename in the response includes url parameters

I have been able to attach a pdf file to an invoice. However, the file name of the attachment gets URL parameters in it and looks bad on the invoice:
Please see it in this image:

I download my pdf file from a Firebase Storage public URL and pass it as a readable stream to the FileWrapper which I then pass to the createInvoiceAttachment API call. In the description field, I add the name of the file which I want to show on the invoice but it gets shown with URL parameters. Here’s my code:

// 6. Upload the attachment
   async function createSquareInvoiceAttachment(invoiceId, fileUrl) {
    try {     
          // 1. Create a readable stream from the public URL
          const fileStream = new Promise((resolve, reject) => {
            https.get(fileUrl, (response) => {
              resolve(response);
            }).on('error', (error) => {
              reject(error);
            });
          });
      
          // 2. Create a FileWrapper with the file stream AND filename
          const fileWrapper = new FileWrapper(await fileStream); 
      
          // 3. Create the invoice attachment
          const response = await client.invoicesApi.createInvoiceAttachment(
            invoiceId,
            {
              idempotencyKey: require("crypto").randomBytes(22).toString("hex"),
              description: 'Service terms',
            },
            fileWrapper
          );
      
          console.log(response.result);
      
  
    } catch (error) {
      console.error(error);
    }
  };
  
  const fileUrl = 'https://firebasestorage.googleapis.com/v0/b/dt-translations.appspot.com/o/000000000000%2FService%20terms.pdf?alt=media&token=cebf7331-cb9d-42dd-a9ae-d96f16d8c294';
  await createSquareInvoiceAttachment(invoiceId, fileUrl);

Here’s the response I get:

{
  attachment: {
    id: 'inva:0-ChD8Ar3v9wyY_6u2XbB2P5s-',
    filename: 'Service terms.pdf?alt=media&token=cebf7331-cb9d-42dd-a9ae-d96f16d8c294',
    description: 'Service terms',
    filesize: 238896,
    hash: 'cdded65e5503a2bf94cb05328ef582d4',
    mimeType: 'application/pdf',
    uploadedAt: '2024-10-15T00:49:55Z'
  }
}

I have tried various solutions and troubleshooting steps for many hours but none of them worked. I even tried it with the data-form npm package and downloading the file to a temporary location on the server side to try to bypass the URL being used to derive the file name from. However, the above code is the only code that actually attaches the file to an invoice. The rest of the codes I tried did not attach the file. It’s simply missing with the other solutions I tried. I have consulted various sources including AI technologies like Gemini and Chat GPT but nothing worked. I am using Node JS, JS on the client-side, Firebase Cloud functions and other Firebase technologies.

I would appreciate some guidance on how I can resolve the issue of the file name including URL parameters.

Kind regards

Thank you.

Don

:wave: I’m Square Dev AI, here to help answer your question!
Based on the information you’ve provided, the following documentation will help answer your question:

Additional Documentation

Troubleshoot In-App Payments SDK Problems
Pay or Refund Invoices
Upload and Attach Images

If this documentation helped you find a solution, please check the box indicating that this reply solves the problem.
Otherwise one of our Developer Advocates will provide additional assistance shortly.

The issue you’re experiencing is due to the way the filename is being extracted from the URL. The filename is being set as the entire path, including the query parameters.

To resolve this, you can parse the URL and extract only the pathname before passing it to the FileWrapper. This can be done using the URL module in Node.js. Here’s an example of how you can modify your code:

const url = require('url');

// ...

// 1. Create a readable stream from the public URL
const fileStream = new Promise((resolve, reject) => {
  https.get(fileUrl, (response) => {
    resolve(response);
  }).on('error', (error) => {
    reject(error);
  });
});

// 2. Parse the URL and extract the pathname
const parsedUrl = url.parse(fileUrl);
const filename = path.basename(parsedUrl.pathname);

// 3. Create a FileWrapper with the file stream AND filename
const fileWrapper = new FileWrapper(await fileStream, filename);

// ...

This way, the filename passed to the FileWrapper will not include the query parameters, and should appear correctly on the invoice.

This answer was reviewed by @Bryan-Square.

Hello,

Thank you for your response.

I have tried your solution.

Unfortunately, it did not work for some reason.

The file name still contains URL parameters (filename: ‘Service terms.pdf?alt=media&token=cebf7331-cb9d-42dd-a9ae-d96f16d8c294’)

I have even updated your code because url.parse is deprected and tried again but the same issue persists:

// 2. Parse the URL and extract the pathname
          const parsedUrl = new URL(fileUrl);
          const filename = path.basename(parsedUrl.pathname);

          console.log('Filename:', filename);

          // 3. Create a FileWrapper with the file stream AND filename
          const fileWrapper = new FileWrapper(await fileStream, filename);

By the way, console.log(‘Filename:’, filename); returns Filename: 000000000000%2FService%20terms.pdf. Is that acceptable?

Does the FileWrapper actually take a second argument for the file name?

It seems that the initial complete URL is still being used to derive the file name from even through we pass the file name to the FileWrapper as a second argument.

If it does, is there a required format? Does it need to have the file format extension like “service terms.pdf” or “service terms” without the extension is okay too?

I tried passing the file name as a string to the FileWrapper like this: const fileWrapper = new FileWrapper(await fileStream, ‘Service terms.pdf’);. It still did not work. The file name continues to contain URL parameters.

I have also just inspected the FileWrapper before it is passed to the API call. It seems that its second argument places the value into the FileWrapper’s options field which I have no idea what it is for.

I figured it out. It should be const fileWrapper = new FileWrapper(await fileStream, { filename: filename });. This works and solves the issue. Hope someone can find this useful.

Glad to hear that you figured it out and thanks for sharing your results for the community. :slightly_smiling_face:

Thank you very much! Happy to be part of the community.