Skip to main content

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


Earn a free month

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 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:


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

  def receipt
      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", "#{} (#{})"],
        ["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'),

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.


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

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 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.


Subscribe to the newsletter

Join 31,353+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.