Skip to content

Instantly share code, notes, and snippets.

@dwilkie
Last active May 21, 2024 17:17
Show Gist options
  • Save dwilkie/18fcfd29c60a15d1e719d10e06e492df to your computer and use it in GitHub Desktop.
Save dwilkie/18fcfd29c60a15d1e719d10e06e492df to your computer and use it in GitHub Desktop.
Bulk Download all Invoices from Wave
*.csv
*.pdf
.DS_Store
require_relative "wave_client"
wave_client = WaveClient.new
response = wave_client.fetch_invoices
response.json_body.each do |invoice|
wave_client.delete_invoice(invoice.fetch("id"))
end
# Usage
# 1. Use WaveConnect to download a list of invoices ensure you select the PDF Download Link option
# 2. Extract the link using the following formula in Excel/Google Sheets:
# =REGEXEXTRACT(FORMULATEXT(T2),"""(.+)"",")
# 3. Add the column header "Raw Link" for the extracted link
# 4. Export as CSV and save the file as "invoices.csv"
# 5. Run this script with the following WAVE_BUSINESS_ID="change-me" WAVE_API_TOKEN="change-me" ruby download_invoices.rb
require "csv"
require "pry"
require "money"
require "uri"
require "open-uri"
require_relative "wave_client"
INVOICES_CSV = "invoices.csv".freeze
LINK_HEADER = "raw_link".freeze
wave_client = WaveClient.new
CSV.foreach(INVOICES_CSV, headers: true) do |row|
row_data = row.to_h.transform_keys { |key| key.to_s.strip.gsub(/\s/, "_").downcase }
pdf_link = row_data.fetch(LINK_HEADER)
invoice_date = row_data.fetch("invoice_date")
invoice_total = row_data.fetch("invoice_total")
invoice_currency = row_data.fetch("currency")
invoice_status = row_data.fetch("status")
invoice_customer = row_data.fetch("customer_name")
invoice_amount = Money.from_amount(invoice_total.to_d, invoice_currency)
output_filename = [
invoice_date,
invoice_amount.cents.to_s,
invoice_currency,
invoice_status,
invoice_customer.strip.gsub(/\s/, "_").downcase
].join("_")
invoice_id, public_invoice_id = URI(pdf_link).path.split("/").last(2)
response = wave_client.generate_invoice_pdf(invoice_id:, public_invoice_id:)
File.write("#{output_filename}.pdf", URI(response.attributes.fetch("pdfUrl")).open.read)
end
require "base64"
require "uri"
require "net/http"
require "net/https"
require "json"
class WaveClient
class Response
attr_reader :http_response
def initialize(http_response)
@http_response = http_response
end
def json_body
JSON.parse(http_response.body)
end
def fetch(key)
json_body.fetch(key.to_s)
end
end
class GraphQLResponse < Response
attr_reader :key
def initialize(http_response, key: nil)
super(http_response)
@key = key
end
def attributes
json_body.dig("data", key)
end
end
GRAPHQL_API_ENDPOINT = URI("https://gql.waveapps.com/graphql/internal").freeze
API_ENDPOINT = URI("https://api.waveapps.com")
attr_reader :graphql_http_client, :api_http_client, :api_token, :business_id
def initialize(**options)
@graphql_http_client = options.fetch(:graphql_http_client) { default_http_client(endpoint: GRAPHQL_API_ENDPOINT) }
@api_http_client = options.fetch(:api_http_client) { default_http_client(endpoint: API_ENDPOINT) }
@api_token = options.fetch(:api_token) { ENV.fetch("WAVE_API_TOKEN") }
@business_id = options.fetch(:business_id) { ENV.fetch("WAVE_BUSINESS_ID") }
end
def generate_invoice_pdf(public_invoice_id:, invoice_id:)
do_graphql_request(
response_key: "publicInvoiceGeneratePdf",
body: {
operationName: "PublicInvoiceGeneratePdf",
variables: {
input: {
id: Base64.strict_encode64("Business:#{business_id};PublicInvoice:#{public_invoice_id}"),
invoiceId: Base64.strict_encode64("Business:#{business_id};Invoice:#{invoice_id}"),
}
},
query: <<-'GRAPHQL'
mutation PublicInvoiceGeneratePdf($input: PublicInvoiceGeneratePdfInput!) {
publicInvoiceGeneratePdf(input: $input) {
pdfUrl
}
}
GRAPHQL
}
)
end
def delete_invoice(invoice_id)
do_api_request(
"/businesses/#{business_id}/invoices/#{invoice_id}/",
request_method: Net::HTTP::Delete
)
end
def fetch_invoices
do_api_request(
"/businesses/#{business_id}/invoices/",
request_method: Net::HTTP::Get
)
end
private
def default_http_client(endpoint:)
http = Net::HTTP.new(endpoint.host, endpoint.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http
end
def do_graphql_request(body:, response_key:)
request = Net::HTTP::Post.new(GRAPHQL_API_ENDPOINT)
request.add_field("Authorization", "Bearer #{api_token}")
request.add_field("Content-Type", "application/json")
request.body = JSON.dump(body)
GraphQLResponse.new(graphql_http_client.request(request), key: response_key)
end
def do_api_request(path, request_method:)
request = request_method.new("#{API_ENDPOINT}#{path}")
request.add_field("Authorization", "Bearer #{api_token}")
request.add_field("Content-Type", "application/json")
Response.new(api_http_client.request(request))
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment