{
    "componentChunkName": "component---src-templates-post-js",
    "path": "/blog/2013/10/30/how-to-build-a-subscription-service-on-rails-a-noobs-guide",
    "result": {"data":{"site":{"siteMetadata":{"title":"your friend Joel's digital garden","description":"Articles and notes from a collaborator at egghead.io. Musings on software, business, and life from a skilled virtual assistant.","author":{"name":"Joel Hooks"},"keywords":["Video Blogger"]}},"mdx":{"excerpt":"There are few things as nerve-wracking as pushing your first subscription\nwebsite into production. Am I covering all the bases? Will everything break and\nleave me in a pit of customer support sadness? How do I even take payments? Are\nthey…","fields":{"github":"https://github.com/joelhooks/joelhooks-com/tree/master/content/legacy_blog/2013-10-30-how-to-build-a-subscription-service-on-rails-a-noobs-guide.markdown"},"body":"var _excluded = [\"components\"];\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"layout\": \"post\",\n  \"title\": \"How to Build a Subscription Service on Rails: A Noob's Guide\",\n  \"date\": \"2013-10-30T00:00:00.000Z\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, _excluded);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"There are few things as nerve-wracking as pushing your first subscription\\nwebsite into production. Am I covering all the bases? Will everything break and\\nleave me in a pit of customer support sadness? How do I even take payments? Are\\nthey just going to laugh at me?\"), mdx(\"h3\", null, \"Some Background\"), mdx(\"p\", null, \"My friend John makes \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://egghead.io\"\n  }, \"awesome AngularJS training videos\"), \". They are short, topical, and\\npresented clearly. He loves making training videos, but has very little interest\\nin building out a website and marketing the content. \\\"Selling things on the\\ninternet\\\" can be a chore. If you're just selling one off digital things, it is\\nfairly straight forward, but if you are looking to build out a website, allow\\nusers to register, and charge them on a recurring basis it can be anything but.\"), mdx(\"p\", null, \"As it happens, I'm very interested in these problems. For the last couple of\\nyears I've been \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://www.joelhooks.com/blog/2013/06/07/what-i-learned-from-brennan-dunns-consultancy-masterclass-w-slash-sketchnotes/\"\n  }, \"taking\\nclasses\"), \"\\nand \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://www.joelhooks.com/blog/2013/06/06/my-sketchnotes-and-thoughts-from-baconbizconf-2013/\"\n  }, \"going to\\nconferences\"), \"\\nthat boil down to selling things on the internet. Recurring revenue is alluring.\\nMy gurus are \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"all about\"), \" finding pain and providing immense value to people in\\nexchange for money. This is what I want to do too.\"), mdx(\"p\", null, \"Wonder Twin powers; activate!\"), mdx(\"h3\", null, \"Digging in: The Stack\"), mdx(\"p\", null, \"We have content, traffic, and a solid reputation. What's the next step?\"), mdx(\"p\", null, \"To actually build something, of course. \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://www.joelhooks.com/blog/2013/09/15/why-i-built-an-angularjs-training-site-on-rails/\"\n  }, \"Rails was the clear\\nchoice\"), \"\\nwhen it came time to decide what framework to use. It is mature, easy (enough)\\nto use, and has a metric shit-ton of resources for building this type of thing.\"), mdx(\"p\", null, \"It is also astonishingly easy to deploy Rails apps to Heroku. This has been a\\nhuge win. Using the free Heroku tier has allowed me to have a production and\\nstaging environment a few keystrokes away. For production, Heroku quickly becomes not free as you add in background workers, SSL, and other essential pieces, but you can go a \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"long\"), \" way with free.\"), mdx(\"h3\", null, \"ZOMG There is so much to learn!\"), mdx(\"p\", null, \"I've been developing software professionally for a few years. My roles are\\nprimarily on the UI side of things, but I've built a few full-stack solutions in\\nthe past with Django. Over the years I've always \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"wanted\"), \" to learn Rails, I just\\nnever had anything real to build. Tutorials and books can be boring, especially\\nwith no context of something \\\"real\\\" that you actually care about to build. That\\nsaid, there were a few excellent resources that helped me on the way:\"), mdx(\"p\", null, mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://ruby.railstutorial.org/\"\n  }, \"Michael Hartl's Rub on Rails Tutorial\"), \": Outside\\nof basic Ruby syntax, this is where you want to start. The web version is free,\\nand he guides you through building a \\\"real\\\" app with Rails in a clear, easy to\\nunderstand manner.\"), mdx(\"p\", null, mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://railsapps.github.io/\"\n  }, \"Daniel Kehoe's RailsApp Project\"), \": This was a huge\\nboost for me. It covered all the bases. I wanted to build an app with Rails.\\nCheck. I wanted to use Twitter Boostrap with Rails. Check. I wanted to have\\nauthentication and authorization in the app. Check. I wanted to integrate\\nrecurring subscription payments. Check.\"), mdx(\"p\", null, mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://www.petekeen.net/mastering-modern-payments\"\n  }, \"Peter Keen's Mastering Modern\\nPayments\"), \": If you are going\\nto sell stuff via a Rails app, \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"buy this book today\"), \". It saved me \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"so much\\ntime\"), \" and instilled a confidence that I might never have had without it.\\nRailsApp is pretty good, but for me it started to break down when I wanted to\\nadd payment. I needed deeper understanding and control, and Pete's book\\ndelivered the knowledge I needed in a concise 120 page guide. I'd recommend\\ngetting the verion with the code, because it was a huge help to me.\"), mdx(\"p\", null, mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://gilesbowkett.blogspot.com/2013/10/new-ebook-software-as-disservice-fixing.html\"\n  }, \"Giles Bowkett's Software as a\\nDisservice\"), \"\\nis a direct critique of RailsApp. It is harsh, but constructive. It addresses\\ncode smells that I had noticed, enforced some of what I learned in Pete's book,\\nand lays out some solid practices that a Rails noob like me won't learn in 1000\\nsearches that result in a Stack Overflow answer. It's an opinionated style and\\npractices guide. \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"note: RailsApp has its flaws, and it was extremely useful to\\nme. Daniel is passionate and has put out a lot of excellent material, but\\ncharging people real money for a product or service is terrifying. I \", mdx(\"strong\", {\n    parentName: \"em\"\n  }, \"needed\"), \"\\nthis critique to plug some serious holes in the implementation. It is important\\nto recognize that Giles is critiquing the \", mdx(\"strong\", {\n    parentName: \"em\"\n  }, \"code\"), \" and not the developer that\\nwrote it.\")), mdx(\"p\", null, \"These four sources were keys to the successful launch of egghead.io's Pro\\nsubscription service. Along with countless posts, Stack Overflow answers, and\\ndocumentation written by the Ruby/Rails community. It is amazing. Now is a\\n\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"very\"), \" good time to be a nerd.\"), mdx(\"h3\", null, \"Taking Payments: \", \"*\", \"scared face\", \"*\"), mdx(\"p\", null, \"It really is terrifying. Maybe it isn't as scary if you've done it before and\\nknow what you are doing. I didn't qualify.\"), mdx(\"p\", null, \"Luckily we live in a fantastic age of modern convenience.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Stripe\"), \".\"), mdx(\"p\", null, \"What an amazing service.\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"No complicated merchant accounts or setup\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"By developers, for developers\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Wonderful API\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Excellent resources for testing\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Solid documentation\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Easy integration\")), mdx(\"p\", null, \"They handle a huge amount of the drudgery invloved with taking payments from\\npeople on the internet. They remove the horrors of PCI compliance by providing\\nan implementation that allows you to take credit cards without ever actually\\nhaving the actual credit card numbers touch your system. They travel over\\nsecure-socket-layer directly to Stripe, who responds with a token that allows\\nyou to initiate an authorized charge. It is beautiful. Once you've authorized a\\nsubscription, Stripe manages the recurring billing for you.\"), mdx(\"p\", null, \"To help with the subscriptions, I found the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/andrewculver/koudoku\"\n  }, \"Koudoku\\ngem\"), \" to be a great resource. While I\\nhad to fork it and manipulate it to some extent for my specific needs, it does a\\nlot of the heavy lifting. I'm on the fence about using a gem to handle all of\\nthis. On one hand, it is \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"very\"), \" easy. On the other, having your subscription\\nimplementation tucked away in a \\\"black box\\\" can bite you when the shit hits the\\nfan. Give and take.\"), mdx(\"h3\", null, \"What would I do differently?\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Testing\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Testing\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Testing\")), mdx(\"p\", null, \"It is tough, as a noob, to get in and write proper tests. \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"All\"), \" of the\\nresources I listed above discuss testing. I skipped it. You know what is\\n\", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"really hard\"), \"? Going back through a non-trivial system and writing a solid\\ntest suite.\"), mdx(\"p\", null, \"This can't be said enough. Tests should \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"guide design of code\"), \" and when they\\nare tacked on later, they lose a big chunk of value. This isn't to say they\\ndon't still provide immense value, but \\\"test later\\\" often means \\\"test never\\\" and\\nthis isn't a good situation to be in.\"), mdx(\"p\", null, \"I'm working on retrofitting tests, but it is a chore. Test early, test often.\"), mdx(\"h3\", null, \"Summary\"), mdx(\"p\", null, \"Building a subscription service is scary. Luckily the internet is stuffed full\\nof open source projects, examples, and helpful people sharing knowledge. Some of\\nit is free, but some of the best resources cost a few dollars. Worth every\\npenny.\"), mdx(\"p\", null, \"Rails is a great framework for this sort of thing. I'd recommend it highly, if\\nyou are trying to build an application that requires recurring billing for your\\nusers.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Test early, test often\"), \". It's an additional thing to learn and understand if\\nyou are just starting out, but it is worth the effort. If I could go back and do\\nanything differently, it would be to write solid tests along the way. Don't make\\nthe same mistake with your apps!\"));\n}\n;\nMDXContent.isMDXComponent = true;","frontmatter":{"title":"How to Build a Subscription Service on Rails: A Noob's Guide","date":"October 30, 2013","banner":null,"slug":null,"keywords":null}}},"pageContext":{"id":"82355fc4-1862-5124-ab63-39418a65f632","prev":{"id":"3ddd2e99-9978-5cc3-ac49-048d61a25b81","parent":{"name":"2014-02-06-stop-writing-for-loops-start-using-underscorejs","sourceInstanceName":"legacy"},"excerpt":"How many for loops did you write today? This week? Sure. That's harmless enough. Ugly and weird, but not something to really complain about. But this is all too common: Which on the scale of bad code, isn't even  that  bad, but you start throwing…","fields":{"title":"Stop writing For loops. Start using underscore.","slug":"blog/2014/02/06/stop-writing-for-loops-start-using-underscorejs","date":"2014-02-06T00:00:00.000Z"}},"next":{"id":"04728ade-ff7d-56f7-9059-8d8abc95c5aa","parent":{"name":"2013-09-23-how-to-record-a-high-quality-screencast","sourceInstanceName":"legacy"},"excerpt":"Audio:\nconvert mono to stero\nadd a slight delay to one channel\nuse an EQ\nadd a touch of reverb (not too much)\nadd a little compression","fields":{"title":"How to Record a High Quality Screencast","slug":"blog/2013/09/23/how-to-record-a-high-quality-screencast","date":"2013-09-23T00:00:00.000Z"}}}},
    "staticQueryHashes": ["1045846374"]}