Skip to main content
19

PDF Receipts

Episode 51 · April 17, 2015

Learn how to add PDF receipts to your application so users can easily download receipts of their purchases


Transcripts

Tax season just wrapped up and a lot of us have been building PDF receipts into our rails application I'm sure. The thing that I had to do this tax season was to set up receipts for GoRails and for onemonth.com. As I was building that, I noticed that I wanted to copy most of the code into GoRails but display a few different fields, so I started to build a copy of it, and then it made perfect sense to extract it into a gem and to open source it so other people could have access to this as well. We're going to talk about how to add receipts into your rails application today using the receipts gem that I made, and then part two of this episode is going to talk about how actually take some code from a rails app, extract it into a gem and then generalize it to an extent where your users can actually take it and build their own versions or implement this into their own rails applications.

This is an example of one of the receipts that we can generate with this, it takes a custom logo image, it has the id number of the charge so we can look it up for support purpose, you can have a custom message saying: "Thanks", and then you can have a list of line items, these are important things for the charge itself is when it happened, the account was billed to the product they purchased, the amount and the credit card, and you probably have a bunch of other things like your European customers might want to add v80 information for tax purposes as well. This is an example of what you can do with this gem, and you can swap out any of these pieces with your own custom stuff, and it will generate a PDF that looks very similar but with your own information. Let's talk about building this into your own rails app. I've got two models in my rails application that I've scaffolded up, and one is users, and they have an email address and a name, and then the other model is a charge. The charge just keeps transaction logs, this is something that you would keep as a duplicate of your purchases in Stripe, so when you save a purchase in Stripe you keep a copy in your database, and it would keep track of the product, the user id, the amount in cents, so you want to save in cents for saving yourself a little bit of trouble with rounding issues, you would save the card type and the card's last four digits. You will basically have these two models, so you have a user model and then you'll have these charge, and the way that we set up this gem is to first go into our gemfile, add gem 'receipts' and then we'll hop into our terminal and run bundle to install the gems, and once that's done, we can restart our rails server. How do we get a receipt from a charge?

The easiest way is to define:

app/models/charge.rb

class Charge < ActiveRecord::Base
  belongs_to :user
  validates :stripe_id, uniqueness: true

  def receipt
    Receipts::Receipt.new(
      id: id,
      product: "GoRails",
      company: {
        name: "One Month, Inc.",
        address: "37 Great Jones\nFloor 2\nNew York City, NY 10012",
        email: "[email protected]",
        logo: Rails.root.join("app/assets/images/one-month-dark.png")
      },
      line_items: [
        ["Date",           created_at.to_s],
        ["Account Billed", "#{user.name} (#{user.email})"],
        ["Product",        "GoRails"],
        ["Amount",         "$#{amount / 100}.00"],
        ["Charged to",     "#{card_type} (**** **** **** #{card_last4})"],
        ["Transaction ID", uuid]
      ],
      font: {
        bold: Rails.root.join('app/assets/fonts/tradegothic/TradeGothic-Bold.ttf'),
        normal: Rails.root.join('app/assets/fonts/tradegothic/TradeGothic.ttf'),
      }
    )
  end
end

We can break this down a little bit and take a look. It starts out with the id, which is the database id of the charge. You might want to extract this and change it to a uuid of some sort, but this id is what will show up in the receipt at the top, and then you can pass in either the database id or generated one as well. That's up to you, but it's always a good rule of thumb to remind in the receipt that the product is important to save what they purchased, and then always have your company information as well and the logo so they can remember who it was from in a glance. The line items vary a little bit by company, but in our case, all we need is the created_at timestamp, which I already have an example for, we're going to give you the created_at date converted to a string, now you can format this better if you use strf time, you can take that and convert it to a format that you like which the best website for this, this one is phenomenal for taking a a datetime or a time in ruby and converting it to your desired result, and then having a great little reference here to test it all live. You can make modifications witht this and then copy this format into your rails app. That's a pro tip if you haven't seen that site before, but getting back to this, you can see that our items are very customizable, you can just remove one of these if you don't want, it's just an array of names and values. It's really simple, you can add as many of these as you want, and I kept it an array on purpose because these will be the order that it shows up, if we were to use a hash, we wouldn't necessarily keep the same order if we converted it to printing out. This is an important design choice of implementing this gem that we'll talk about in the next episode when I explain how we can build this from scratch. You can also specify custom fonts, we have custom fonts that we use right now, and we allow that to be in the PDF to keep them consistent with us. If you don't want to use them, just delete it and that's all you have to do.

One interesting design choice I made on this configuration of your receipt was to make line items an array, I didn't want to require a date or account or a product or charge a card or an amount, I wanted this to be configurable, I wanted you to be able to add or remove these dynamically if you wanted, and the way that you could do that is by making a line items method here, and you could do your items equals this array of all these, and then maybe optionally here at the end, you say: Maybe we want additional information, like maybe our European customers need to add in that ids into the receipt, so we have this additional information field on the user that gets automatically printed out on all of the receipts. Then, you can take this and you could get all of these moved into your items and then you could return items for that and replace this array with a method call to line items, and we can render a dynamic array of line items on the receipt with the additional information as necessary. This gives you a lot of flexibility on how you design your receipt line items and you can make as many as you want, or you can stick with the default, maybe you don't need to support the additional information, so you can just hardcode it, but you also have this flexibility as well.

This method generates a new receipt object, but it doesn't actually render it to a file or do anything with it, it just stores the information in memory so that prawn. can generate the receipt behind the scenes inside of the gem, but there's no real magic going on here. We've got everything connected, and we are ready to go and now we need to serve it up. That means that we're going to have to go into our charges controller and actually render this PDF out. This show action is actually where we need to be writing that code. Sometimes, when you view the show, you want the JSON response, sometimes you want the html response, but in this case, we want the PDF response. This is something that you might not have been aware of, you can do a response to a format, and you can have your format html, you can have your format .json, as you would normally expect, you can also have a format .PDF, which will detect the .pdf in the url and then will render out a response, it can be any type of response, in this case, we're going to follow the rules and return a pdf on that pdf format. We're going to pass this a block and use the send_data method. This is a method that allows us to send arbitrary data back, and we'll use the type option for this to set the content type so the browser knows what we're sending back and how to interpret the data. Here we're going to take the charge object that comes from the before_action for setting the charge, we're going to ask for the receipt, and then we're going to call the render method on that, and this render method is going to tell prawn. to take that object that we designed, and passed in our data and to actually make a PDF out of it, and that's going to return the PDF object back to us as a file type object. Now, we have to pass a couple other options here. One is filename:and this allows you to do anything that you want, so you can specify this file name and I'm actually going to paste in some code here, that will take the charge'screated_at` and format the time to a date, then we're going to have -gorails-receipt.pdf. This will be the file name that will send over, and the browser will read that and see that is the file name to save the file as. The other options we want is we want to set the type to *application/pdf, and this is important so that the browser knows how to interpret this data that we're sending over, it's arbitrary up until this point. So this is a very important option to pass in. The browser may or may not be able to interpret that or make a guess at it, but you're going to want to set the type to make sure it knows, and then one often useful feature here is the disposition setting that inline, and this will allow you to set your PDF to render inside of the browser, so it's a really useful thing to do, and I'm going to format this method call like so, and this will allow it to render inside of the browser.

charges_controller.rb

def show
  respond_to do |format|
    format.html
    format.json  
    format.pdf {
      send_date(@charge.receipt.render,
        filename: "#{@charge.created_at.strftime("%Y-%m-%d")}-gorails-receipt.pdf",
        type: "application/pdf",
        disposition: :inline
        )
    }
  end
end

The simplest way to access this is to test it out by going to the charge itself, and then you can do .json in the url to make sure that still works, and then you can also do .pdf. This will take a second to load because it's gotta generate the PDF, and then it is automatically set up for you in the browser. That's it, you already have a working PDF generation working. This is awesome. The last thing to check here the save file name, and as you can see, this is the file name we set up in the controller, it is that option that defines this suggested file name and it doesn't have to match the url as you might have noticed here. That's totally fine, so it's nice to maybe have the url match, but the browser has the ability to interpret these file name header and set that appropriately. There's also this attachment disposition that you could use, I originally set this up with the disposition inline which means that it renders in the browser and with the disposition attachment, it's the one that's going to force the browser to download it when you view the PDF. I prefer to do inline because it's so much more convenient than downloading these files and it seems a little overkill to force users to download these files all the time. Take a look at send_data on apidock.com if you want to learn anything more about it, it's really pretty simple, but it's useful for when you want to do things like send tarbals down or raw image or something like that, this is actually a great way of using rails to send files instead of just rendering html or text of some sort back.

That's a quick introduction to the receipts gem, there's not a whole lot to it yet, but I definitely recommend you make a pull request if you have an idea for a feature, or if you just want to dive into it, wait till next gorails episode where I'm going to talk about how we extracted this from a rails application, turned it into a gem, made the API for this a little bit more generic, and then also built this using prawn. Prawn is the PDF generator that we use behind the scenes and we'll be talking about that as well in the future.

Discussion


Fallback

hashes are ordered too (for the design choice) i think since ruby 1.9 the only real difference is that "keys" can be duplicated in the array style

Fallback

Ah yeah, you're right. I have always been careful about hashes when it comes to ordering because I wasn't sure it was a requirement for the ruby implementation.


Fallback

Is it possible to render the pdf without controller? maybe sending out via emails, or saving to s3

Fallback

Yep, you can call the "render" method like you normally would and send it as an attachment to email or upload it to S3


Fallback

Can you give me direction on using "endless scrolling in rails" instead of pagination.
Thanks in advance.

Fallback
Fallback

Thanks for the reply, It worked :)


Fallback

This is a pretty nice way to create PDF receipts . Have you tried using http://wkhtmltopdf.org/ and https://github.com/pdfkit/p...

Fallback

I haven't yet, but really want to. Obviously the HTML to PDF will be way easier than how Prawn does it. The programmatic generation is no fun at all.

Fallback

I love wkhtmltopdf. It has saved my ass on a bunch of projects in the past. It's become my goto for PDF generation in almost any language I use. Prawn is a pain in the butt, I've used it on two projects and it has been very painful, but you do get a lot of control.

Fallback

I will check it out! I did some research and can't remember why I picked prawn other than I saw enough other people using it and documentation seemed robust. That said, I bet wkhtmltopdf is way easier now that I've used Prawn more than a little.

The hardest part was building our completion certificates at OneMonth.com in Prawn. It was is finicky and time consuming.


Fallback

Hey Chris,

I'm sure you're aware of livecoding.tv, but I think it would be awesome for you to livestream the creation of gems like this so we can see the process. Plus, then we'd have the recorded streams to go back to and reference.

There aren't very many Rails streams, so it would also be a great opportunity to gain some followers for GoRails!


Fallback

Hey, the gem it's pretty nice, I'll try with a project that i'm working, but i have a question first, Can I put footer and header to the PDF as Prawn gem?.

Fallback

It's basically designed to be simpler rather than customizable, but you can take the PDF code that's in the gem and adapt it easily to add your own header and footer. Take a look at this code: https://github.com/excid3/r...

Fallback

Hi, I'm not familiar with extending a class from a gem. I tried to copy your gem receipt.rb into my models folder, but no change I make to the style in this receipt.rb will have any effect. What do I do wrong?
Thanks a lot

Fallback

You basically need to just copy paste the module and class into a file in your app (say config/initializers/receipts.rb) and then override the methods you want to change inside the class. You can also copy and paste the entire file there into your app if you want to modify all of it.


Fallback

How to make a receipt from a form after client clicked submit button?


Fallback

Hey,

Do you think it's possible to generate a new receipt everytime the customer get charged?
I assume there's something to do with the transaction logs to automate this?

TY and good job! ;)

Fallback

Yeah! So normally with subscriptions, I listen to the charge.created webhook and save a copy of it to the database as a Charge record. That's like the example I use. The reason for needing the webhook is that subscriptions charge the user monthly and they don't have to initiate anything in your app.

For one-time charges, you can create a Charge object immediately during checkout and use that.

And if you want to store the receipt PDF files instead of generating them dynamically each time, you can save the file it generates to S3 and save it using Shrine or Carrierwave, etc and just link to that file from your view.

Fallback

Ok, that's a really smart way to do it!

TY and keep doing GoRails ;)!

Fallback

I definitely will and thanks for the support! :D


Login or create an account to join the conversation.