{"record":{"id":"823d55b860420f15","repo":"antiwork/gumroad","slug":"one-of-the-uploaded-files-could-not-be-processed","errorCode":null,"errorMessage":"One of the uploaded files could not be processed. Please check that every PDF opens correctly and is not password-protected.","messagePattern":"One of the uploaded files could not be processed\\. Please check that every PDF opens correctly and is not password-protected\\.","errorType":"exception","errorClass":"DisputeEvidence::MergeCustomerCommunicationFilesService::MergeError","httpStatus":null,"severity":"error","filePath":"app/services/dispute_evidence/merge_customer_communication_files_service.rb","lineNumber":117,"sourceCode":"      raise FilesTooLargeError, FILES_TOO_LARGE_MESSAGE\n    end\n\n    def merge(downloaded_files, compression)\n      page_paths = []\n      downloaded_files.each do |downloaded_file|\n        if downloaded_file[:content_type].in?(IMAGE_CONTENT_TYPES)\n          page_paths << image_to_pdf_page(downloaded_file[:tempfile].path, compression)\n        else\n          page_paths << downloaded_file[:tempfile].path\n        end\n      end\n\n      merged_path = \"#{Dir.tmpdir}/dispute_evidence_merged_#{SecureRandom.hex}.pdf\"\n      _stdout, stderr, status = Open3.capture3(\"qpdf\", \"--empty\", \"--pages\", *page_paths, \"--\", merged_path)\n      unless QPDF_SUCCESS_EXIT_CODES.include?(status.exitstatus)\n        Rails.logger.error(\"[#{self.class.name}] qpdf failed: #{stderr}\")\n        File.unlink(merged_path) if File.exist?(merged_path)\n        raise MergeError, UNPROCESSABLE_FILE_MESSAGE\n      end\n\n      merged_path\n    ensure\n      (page_paths - downloaded_files.map { _1[:tempfile].path }).each do |generated_path|\n        File.unlink(generated_path) if File.exist?(generated_path)\n      end\n    end\n\n    # Recompressing through JPEG also sidesteps the PNG variants (16-bit depth, interlaced)\n    # that both Stripe and Prawn reject — see\n    # Purchases::DisputeEvidenceController#covert_and_optimize_blob_if_needed.\n    def image_to_pdf_page(image_path, compression)\n      image = MiniMagick::Image.open(image_path)\n      image.auto_orient\n      image.resize(\"#{compression[:max_dimension]}x#{compression[:max_dimension]}>\") if compression[:max_dimension]\n      image.format(\"jpg\")\n      image.quality(compression[:quality]).colorspace(\"sRGB\").strip","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/services/dispute_evidence/merge_customer_communication_files_service.rb#L99-L135","documentation":"merge() shells out via Open3.capture3('qpdf', '--empty', '--pages', *page_paths, '--', merged_path); an exit status outside QPDF_SUCCESS_EXIT_CODES logs qpdf's stderr and raises MergeError with UNPROCESSABLE_FILE_MESSAGE. Typical qpdf failures are password-protected/encrypted input PDFs and malformed or corrupt PDF structure — qpdf cannot copy pages it cannot decrypt or parse.","triggerScenarios":"One of the inputs is a password-protected bank statement or an owner-encrypted PDF; a PDF truncated during download; a zero-byte or fake .pdf (e.g. an HTML error page renamed to .pdf).","commonSituations":"Financial documents that ship with owner passwords; files corrupted in transit; exports from buggy third-party tools that produce non-conforming PDFs.","solutions":["Open every submitted PDF locally and confirm it loads without a password prompt","Re-export/print-to-PDF the encrypted document without a password, then re-upload","Re-download or regenerate the corrupt file and submit again","If you operate the app, check the logged '[...MergeCustomerCommunicationFilesService] qpdf failed:' stderr for the exact qpdf diagnostic"],"exampleFix":"# before\nblobs = [encrypted_statement_pdf, chat_jpg] # qpdf: 'file is encrypted'\n# => MergeError: One of the uploaded files could not be processed...\n\n# after\n# print-to-PDF the statement without encryption -> statement.pdf\nblobs = [statement_pdf, chat_jpg]","handlingStrategy":"try-catch","validationCode":"# pre-flight each PDF the same way the merge does\nOpen3.capture3('qpdf', '--show-encryption', pdf_path).then do |_out, _err, status|\n  raise 'password-protected or unreadable PDF' unless status.success? # exit 0 = unencrypted, parseable\nend","typeGuard":null,"tryCatchPattern":"begin\n  DisputeEvidence::MergeCustomerCommunicationFilesService.perform(blobs:, max_size: budget)\nrescue DisputeEvidence::MergeCustomerCommunicationFilesService::MergeError => e\n  # e.message is already seller-safe; the real qpdf stderr is in the Rails log\n  Rails.logger.warn(\"merge rejected: #{e.message}\")\n  redirect_back alert: e.message\nend","preventionTips":["Open every PDF locally before upload; a password prompt means it will fail the merge","Re-export encrypted statements via print-to-PDF to strip the password","The exact qpdf diagnostic is logged as '[...MergeCustomerCommunicationFilesService] qpdf failed: <stderr>' — search the logs when triaging"],"tags":["ruby","rails","qpdf","pdf","dispute-evidence"],"backgroundTag":"pdf-merge-failed","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}