receipts 1.0.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa3e56021ccff37e48f340cea424f548b1f52c6782203cde1002049a1f470571
4
- data.tar.gz: db562a59a36abcc235718f59420bc730ddcb70a3b057536df824692cfbfd4909
3
+ metadata.gz: 875a1ff6a3a1dcca7a1de7c21362ce0837426c3f5b3299cd9f15f38daf97cfa2
4
+ data.tar.gz: 2aed77f22588a1675b558eccbe1122cc94912e04fa692aacaa7a40b16429495e
5
5
  SHA512:
6
- metadata.gz: a2ed85aa3947b15ca345d01b88e9f267335e181ef4a8950b496cd99c4d9c9187844d92f993b82dfc3018ece6dcee9765777842135d330dbe61e08599676b43d8
7
- data.tar.gz: 9d2c8edb71e5d0204431f2053bb1a8518a8e766201950327c0581c8c4914d7edee96ea1f435ee9dd4c800d687886f79dcc6e97596a0cd689fadf6b827d30dcd9
6
+ metadata.gz: f54fc0957bccf87ebd1ae377d275f9dfe0f39a8b2cd0e8c4b5a96635183abd24f229eea142631a98d5a61156fc87cd366864e35a044a53e60ec072de08ada3e5
7
+ data.tar.gz: 87745a1fe2f9cb84c69a00d816e254c4cc7b2dfee02457ece89d9e543a39b65813bfd427e7fa789e5a56feea1a3837ac3e8cc5d4574e4894805b535de76cf630
data/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ### Unreleased
2
+
3
+ ### 2.0.0
4
+
5
+ * New, consistent layouts between Receipts, Invoices, and Statements - @excid3
6
+ * PDFs can now be completely customized - @excid3
7
+ * Add line_items to Receipts - @excid3
8
+
9
+ ### 1.2.0
10
+
11
+ * Update design to give more room for longer product names, addresses, etc - @excid3
12
+
13
+ ### 1.1.1
14
+
15
+ * [FIX] Use `URI.parse().open` instead - @excid3 @reckerswartz
16
+ * Add I18n notes to the readme - @excid3 @reckerswartz
17
+ * Add standardrb formatting - @excid3
18
+ * Add GitHub Actions for standardrb and tests - @excid3
19
+
20
+ ### 1.1.0
21
+
22
+ * [NEW] Add Statements - @anquinn
23
+
1
24
  ### 1.0.3
2
25
 
3
26
  * [FIX] Fixes typo introduced in 1.0.2
data/Gemfile CHANGED
@@ -1,4 +1,9 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in receipts.gemspec
4
4
  gemspec
5
+
6
+ gem "minitest", "~> 5.0"
7
+ gem "rake", "~> 13.0"
8
+ gem "standard"
9
+ gem "matrix", "~> 0.4"
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  ![travisci](https://api.travis-ci.org/excid3/receipts.svg)
2
2
 
3
- # Receipts
3
+ # Receipts Gem
4
4
 
5
- Receipts for your Rails application that works with any payment provider.
5
+ Receipts, Invoices, and Statements for your Rails application that works with any payment provider. Receipts uses Prawn to generate the PDFs.
6
6
 
7
- Check out the [example receipt](https://github.com/excid3/receipts/blob/master/examples/receipt.pdf?raw=true) and [example invoice](https://github.com/excid3/receipts/blob/master/examples/invoice.pdf?raw=true) PDFs.
7
+ Check out the [example PDFs](https://github.com/excid3/receipts/blob/master/examples/).
8
8
 
9
9
  ## Installation
10
10
 
@@ -24,127 +24,157 @@ Or install it yourself as:
24
24
 
25
25
  ## Usage
26
26
 
27
- Adding receipts to your application is pretty simple. All you need is a
28
- model that stores your transaction details. In this example our
29
- application has a model named `Charge` that we will use.
30
-
31
- We're going to add a method called `receipt` on our model called `Charge`
32
- that will create a new receipt for the charge using attributes from the
33
- model.
34
-
35
- Video Tutorial:
36
- [GoRails Episode #51](https://gorails.com/episodes/pdf-receipts)
27
+ To generate a Receipt, Invoice, or Statement, create an instance and provide content to render:
37
28
 
38
29
  ```ruby
39
- # == Schema Information
40
- #
41
- # Table name: charges
42
- #
43
- # id :integer not null, primary key
44
- # user_id :integer
45
- # stripe_id :string(255)
46
- # amount :integer
47
- # card_last4 :string(255)
48
- # card_type :string(255)
49
- # card_exp_month :integer
50
- # card_exp_year :integer
51
- # uuid :string
52
- # created_at :datetime
53
- # updated_at :datetime
54
- #
55
-
56
- class Charge < ActiveRecord::Base
57
- belongs_to :user
58
- validates :stripe_id, uniqueness: true
59
-
60
- def receipt
61
- Receipts::Receipt.new(
62
- id: id,
63
- subheading: "RECEIPT FOR CHARGE #%{id}",
64
- product: "GoRails",
65
- company: {
66
- name: "GoRails, LLC.",
67
- address: "123 Fake Street\nNew York City, NY 10012",
68
- email: "support@example.com",
69
- logo: Rails.root.join("app/assets/images/logo.png")
70
- },
71
- line_items: [
72
- ["Date", created_at.to_s],
73
- ["Account Billed", "#{user.name} (#{user.email})"],
74
- ["Product", "GoRails"],
75
- ["Amount", "$#{amount / 100}.00"],
76
- ["Charged to", "#{card_type} (**** **** **** #{card_last4})"],
77
- ["Transaction ID", uuid]
78
- ],
79
- font: {
80
- bold: Rails.root.join('app/assets/fonts/tradegothic/TradeGothic-Bold.ttf'),
81
- normal: Rails.root.join('app/assets/fonts/tradegothic/TradeGothic.ttf'),
82
- }
83
- )
84
- end
85
- end
30
+ r = Receipts::Receipt.new(
31
+ details: [
32
+ ["Receipt Number", "123"],
33
+ ["Date paid", Date.today],
34
+ ["Payment method", "ACH super long super long super long super long super long"]
35
+ ],
36
+ company: {
37
+ name: "Example, LLC",
38
+ address: "123 Fake Street\nNew York City, NY 10012",
39
+ email: "support@example.com",
40
+ logo: File.expand_path("./examples/images/logo.png")
41
+ },
42
+ recipient: [
43
+ "Customer",
44
+ "Their Address",
45
+ "City, State Zipcode",
46
+ nil,
47
+ "customer@example.org"
48
+ ],
49
+ line_items: [
50
+ ["<b>Item</b>", "<b>Unit Cost</b>", "<b>Quantity</b>", "<b>Amount</b>"],
51
+ ["Subscription", "$19.00", "1", "$19.00"],
52
+ [nil, nil, "Subtotal", "$19.00"],
53
+ [nil, nil, "Tax", "$1.12"],
54
+ [nil, nil, "Total", "$20.12"],
55
+ [nil, nil, "<b>Amount paid</b>", "$20.12"],
56
+ [nil, nil, "Refunded on #{Date.today}", "$5.00"]
57
+ ],
58
+ footer: "Thanks for your business. Please contact us if you have any questions."
59
+ )
60
+
61
+ # Returns a string of the raw PDF
62
+ r.render
63
+
64
+ # Writes the PDF to disk
65
+ r.render_file "examples/receipt.pdf"
86
66
  ```
87
67
 
88
- Update the options for the receipt according to the data you want to
89
- display.
68
+ ### Options
69
+
70
+ You can pass the following options to generate a PDF:
71
+
72
+ * `recipient` - Array of customer details to include. Typically, this is name, address, email, VAT ID, etc.
73
+
74
+ * `company` - Hash of your company details
75
+
76
+ * `name` - Company name
77
+
78
+ * `address` - Company address
79
+
80
+ * `email` - Company support email address
90
81
 
91
- ## Customizing Your Receipts
82
+ * `phone` - Company phone number - _Optional_
92
83
 
93
- * `id` - **Required**
84
+ * `logo` - Logo to be displayed on the receipt - _Optional_
85
+ This can be either a Path, File, StringIO, or URL. Here are a few examples:
94
86
 
95
- This sets the ID of the charge on the receipt
87
+ ```ruby
88
+ logo: Rails.root.join("app/assets/images/logo.png")
89
+ logo: File.expand_path("./logo.png")
90
+ logo: File.open("app/assets/images/logo.png", "rb")
91
+ logo: "https://www.ruby-lang.org/images/header-ruby-logo@2x.png" # Downloaded with OpenURI
92
+ ```
96
93
 
97
- * `product` or `message` - **Required**
94
+ * `details` - Array of details about the Receipt, Invoice, Statement. Typically, this is receipt numbers, issue date, due date, status, etc.
98
95
 
99
- You can set either the product or message options. If you set product, it will use the default message. If you want a custom message, you can set the message option to populate it with custom text.
96
+ * `line_items` - Array of line items to be displayed in table format.
100
97
 
101
- * `company` - **Required**
98
+ * `footer` - String for a message at the bottom of the PDF.
102
99
 
103
- Company consists of several required nested attributes.
100
+ * `font` - Hash of paths to font files - _Optional_
101
+
102
+ ```ruby
103
+ font: {
104
+ bold: Rails.root.join('app/assets/fonts/tradegothic/TradeGothic-Bold.ttf'),
105
+ normal: Rails.root.join('app/assets/fonts/tradegothic/TradeGothic.ttf'),
106
+ }
107
+ ```
108
+
109
+ Here's an example of where each option is displayed.
110
+
111
+ ![options](examples/images/options.jpg)
112
+
113
+ ### Formatting
114
+
115
+ `details` and `line_items` allow inline formatting with Prawn. This allows you to use HTML tags to format text: `<b>` `<i>` `<u>` `<strikethrough>` `<sub>` `<sup>` `<font>` `<color>` `<link>`
116
+
117
+ See [the Prawn docs](https://prawnpdf.org/api-docs/2.3.0/Prawn/Text.html#text-instance_method) for more information.
118
+
119
+ ### Internationalization (I18n)
120
+
121
+ You can use `I18n.t` when rendering your receipts to internationalize them.
122
+
123
+ ```ruby
124
+ line_items: [
125
+ [I18n.t("receipts.date"), created_at.to_s],
126
+ [I18n.t("receipts.product"), "GoRails"],
127
+ [I18n.t("receipts.transaction"), uuid]
128
+ ]
129
+ ```
104
130
 
105
- * `name` - **Required**
106
- * `address` - **Required**
107
- * `email` - **Required**
108
- * `line_items` - **Required**
131
+ ### Custom PDF Content
109
132
 
110
- You can set as many line items on the receipts as you want. Just pass in an array with each item containing a name and a value to display on the receipt.
133
+ You can change the entire PDF content by instantiating an Receipts object without any options.
111
134
 
112
- * `logo` - *Optional*
135
+ ```ruby
136
+ receipt = Receipts::Receipt.new # creates an empty PDF
137
+ ```
113
138
 
114
- The logo must be either a string path to a file or a file-like object.
139
+ Each Receipts object inherits from Prawn::Document. This allows you to choose what is rendered and include any custom Prawn content you like.
115
140
 
116
141
  ```ruby
117
- logo: Rails.root.join("app/assets/images/logo.png")
118
- # or
119
- logo: File.open("app/assets/images/logo.png", "rb")
142
+ receipt.text("hello world")
120
143
  ```
121
144
 
122
- To use an image from a URL, we recommend using `open-uri` to open the remote file as a StringIO object.
145
+ You can also use the Receipts helpers in your custom PDFs at the current cursor position.
146
+
147
+ ```ruby
148
+ receipt.text("Custom header")
149
+ receipt.render_line_items([
150
+ ["my line items"]
151
+ ])
152
+ receipt.render_footer("This is a custom footer using the Receipts helper")
153
+ ```
123
154
 
124
- `require 'open-uri'`
155
+ ### Rendering PDFs
125
156
 
126
- `logo: URI.open("https://www.ruby-lang.org/images/header-ruby-logo@2x.png")`
157
+ To render a PDF in memory, use `render`. This is recommended for serving PDFs in your Rails controllers.
127
158
 
128
- * `font` - *Optional*
159
+ ```ruby
160
+ receipt.render
161
+ ```
129
162
 
130
- If you'd like to use your own custom font, you can pass in the file paths to the `normal` and `bold` variations of your font. The bold font variation is required because it is used in the default message. If you wish to override that, you can pass in your own custom message instead.
163
+ To render a PDF to disk, use `render_file`:
131
164
 
165
+ ```ruby
166
+ receipt.render_file "receipt.pdf"
167
+ ```
132
168
 
133
- ## Rendering the Receipt PDF in your Controller
169
+ ## Rendering PDFs in Rails controller actions
134
170
 
135
- Here we have a charges controller that responds to the show action. When
136
- you visit it with the PDF format, it calls the `receipt` method that we
137
- just created on the `Charge` model.
171
+ Here's an example Rails controller action you can use for serving PDFs. We'll first look up the database record for the `Charge` we want to render a receipt for.
138
172
 
139
- We set the filename to be the date plus the product name. You can
140
- customize the filename to your liking.
173
+ The `Charge` model has a `receipt` method that returns a `Receipts::Receipt` instance with all the receipt data filled out.
141
174
 
142
- Next we set the response type which will be `application/pdf`
175
+ Then we can `render` to generate the PDF in memory. This produces a String with the raw PDF data in it.
143
176
 
144
- Optionally we can set the `disposition` to `:inline` which allows us to
145
- render the PDF in the browser without forcing the download. If you
146
- delete this option or change it to `:attachment` then the receipt will
147
- be downloaded instead.
177
+ Using `send_data` from Rails, we can send the PDF contents and provide the browser with a recommended filename, content type and disposition.
148
178
 
149
179
  ```ruby
150
180
  class ChargesController < ApplicationController
@@ -153,12 +183,7 @@ class ChargesController < ApplicationController
153
183
 
154
184
  def show
155
185
  respond_to do |format|
156
- format.pdf {
157
- send_data @charge.receipt.render,
158
- filename: "#{@charge.created_at.strftime("%Y-%m-%d")}-gorails-receipt.pdf",
159
- type: "application/pdf",
160
- disposition: :inline
161
- }
186
+ format.pdf { send_pdf }
162
187
  end
163
188
  end
164
189
 
@@ -167,13 +192,18 @@ class ChargesController < ApplicationController
167
192
  def set_charge
168
193
  @charge = current_user.charges.find(params[:id])
169
194
  end
195
+
196
+ def send_pdf
197
+ # Render the PDF in memory and send as the response
198
+ send_data @charge.receipt.render,
199
+ filename: "#{@charge.created_at.strftime("%Y-%m-%d")}-gorails-receipt.pdf",
200
+ type: "application/pdf",
201
+ disposition: :inline # or :attachment to download
202
+ end
170
203
  end
171
204
  ```
172
205
 
173
- And that's it! Just create a `link_to` to your charge with the format of
174
- `pdf` and you're good to go.
175
-
176
- For example:
206
+ Then, just `link_to` to your charge with the format of `pdf`. For example:
177
207
 
178
208
  ```ruby
179
209
  # config/routes.rb
@@ -181,50 +211,78 @@ resources :charges
181
211
  ```
182
212
 
183
213
  ```erb
184
- <%= link_to "Download Receipt", charge_path(@charge, format: :pdf) %>
214
+ <%= link_to "View Receipt", charge_path(@charge, format: :pdf) %>
185
215
  ```
186
216
 
187
217
  ## Invoices
188
218
 
189
- Invoices follow the exact same set of steps as above, with a few minor changes and have a few extra arguments you can use:
190
-
191
- * `issue_date` - Date the invoice was issued
192
-
193
- * `due_date` - Date the invoice payment is due
219
+ Invoices follow the exact same set of steps as above. You'll simply want to modify the `details` to include other information for the Invoice such as the Issue Date, Due Date, etc.
194
220
 
195
- * `status` - A status for the invoice (Pending, Paid, etc)
221
+ ```ruby
222
+ Receipts::Invoice.new(
223
+ details: [
224
+ ["Invoice Number", "123"],
225
+ ["Issue Date", Date.today.strftime("%B %d, %Y")],
226
+ ["Due Date", Date.today.strftime("%B %d, %Y")],
227
+ ["Status", "<b><color rgb='#5eba7d'>PAID</color></b>"]
228
+ ],
229
+ recipient: [
230
+ "<b>Bill To</b>",
231
+ "Customer",
232
+ "Address",
233
+ "City, State Zipcode",
234
+ "customer@example.org"
235
+ ],
236
+ company: {
237
+ name: "Example, LLC",
238
+ address: "123 Fake Street\nNew York City, NY 10012",
239
+ phone: "(555) 867-5309",
240
+ email: "support@example.com",
241
+ logo: File.expand_path("./examples/images/logo.png")
242
+ },
243
+ line_items: [
244
+ ["<b>Item</b>", "<b>Unit Cost</b>", "<b>Quantity</b>", "<b>Amount</b>"],
245
+ ["Subscription", "$19.00", "1", "$19.00"],
246
+ [nil, nil, "Subtotal", "$19.00"],
247
+ [nil, nil, "Tax Rate", "0%"],
248
+ [nil, nil, "Amount Due", "$19.00"]
249
+ ]
250
+ )
251
+ ```
196
252
 
197
- * `bill_to` - A string or Array of lines with billing details
253
+ ## Statements
198
254
 
199
- You can also use line_items to flexibly generate and display the table with items in it, including subtotal, taxes, and total amount.
255
+ Statements follow the exact same set of steps as above. You'll simply want to modify the `details` to include other information for the Invoice such as the Issue Date, Start and End Dates, etc.
200
256
 
201
257
  ```ruby
202
- Receipts::Invoice.new(
203
- id: "123",
204
- issue_date: Date.today,
205
- due_date: Date.today + 30,
206
- status: "<b><color rgb='#5eba7d'>PAID</color></b>",
207
- bill_to: [
208
- "GoRails, LLC",
209
- "123 Fake Street",
210
- "New York City, NY 10012",
211
- nil,
212
- "mail@example.com",
213
- ],
214
- company: {
215
- name: "GoRails, LLC",
216
- address: "123 Fake Street\nNew York City, NY 10012",
217
- email: "support@example.com",
218
- logo: File.expand_path("./examples/gorails.png")
219
- },
220
- line_items: [
221
- ["<b>Item</b>", "<b>Unit Cost</b>", "<b>Quantity</b>", "<b>Amount</b>"],
222
- ["GoRails Subscription", "$19.00", "1", "$19.00"],
223
- [nil, nil, "Subtotal", "$19.00"],
224
- [nil, nil, "Tax Rate", "0%"],
225
- [nil, nil, "Total", "$19.00"],
226
- ],
227
- )
258
+ Receipts::Statement.new(
259
+ details: [
260
+ ["Statement Number", "123"],
261
+ ["Issue Date", Date.today.strftime("%B %d, %Y")],
262
+ ["Period", "#{(Date.today - 30).strftime("%B %d, %Y")} - #{Date.today.strftime("%B %d, %Y")}"]
263
+ ],
264
+ recipient: [
265
+ "<b>Bill To</b>",
266
+ "Customer",
267
+ "Address",
268
+ "City, State Zipcode",
269
+ "customer@example.org"
270
+ ],
271
+ company: {
272
+ name: "Example, LLC",
273
+ address: "123 Fake Street\nNew York City, NY 10012",
274
+ email: "support@example.com",
275
+ phone: "(555) 867-5309",
276
+ logo: File.expand_path("./examples/images/logo.png")
277
+ },
278
+ line_items: [
279
+ ["<b>Item</b>", "<b>Unit Cost</b>", "<b>Quantity</b>", "<b>Amount</b>"],
280
+ ["Subscription", "$19.00", "1", "$19.00"],
281
+ [nil, nil, "Subtotal", "$19.00"],
282
+ [nil, nil, "Tax Rate", "0%"],
283
+ [nil, nil, "Total", "$19.00"]
284
+ ]
285
+ )
228
286
  ```
229
287
 
230
288
  ## Contributing
data/Rakefile CHANGED
@@ -1,70 +1,126 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
- require "open-uri"
4
+ require "rake/testtask"
4
5
 
5
- RSpec::Core::RakeTask.new('spec')
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
6
11
 
7
- # If you want to make this the default task
8
- task :default => :spec
9
- task :test => :spec
12
+ task default: :test
10
13
 
11
14
  task :console do
12
15
  exec "irb -r receipts -I ./lib"
13
16
  end
14
17
 
18
+ task :examples do
19
+ [:receipt, :invoice, :statement].each { |t| Rake::Task[t].invoke }
20
+ puts "Use `open examples` to view example PDFs."
21
+ end
22
+
15
23
  task :receipt do
16
24
  require "./lib/receipts"
17
25
 
18
- Receipts::Receipt.new(
19
- id: "123",
20
- subheading: "RECEIPT FOR CHARGE #1",
21
- product: "GoRails",
26
+ r = Receipts::Receipt.new(
27
+ recipient: [
28
+ "<b>Bill To</b>",
29
+ "Customer",
30
+ "Address",
31
+ "City, State Zipcode",
32
+ "customer@example.org"
33
+ ],
22
34
  company: {
23
- name: "GoRails, LLC",
35
+ name: "Example, LLC",
24
36
  address: "123 Fake Street\nNew York City, NY 10012",
37
+ phone: "(555) 867-5309",
25
38
  email: "support@example.com",
26
- logo: URI.open("https://www.ruby-lang.org/images/header-ruby-logo@2x.png")
39
+ logo: File.expand_path("./examples/images/logo.png")
27
40
  },
28
- line_items: [
29
- ["Date", Time.now.to_s],
30
- ["Account Billed", "Example User (user@example.com)"],
31
- ["Product", "GoRails"],
32
- ["Amount", "$19.00"],
33
- ["Charged to", "Visa (**** **** **** 1234)"],
34
- ["Transaction ID", "123456"]
41
+ details: [
42
+ ["Receipt Number", "123"],
43
+ ["Date paid", Date.today.strftime("%B %d, %Y")],
44
+ ["Payment method", "ACH super long super long super long super long super long"]
35
45
  ],
36
- ).render_file "examples/receipt.pdf"
46
+ line_items: [
47
+ ["<b>Item</b>", "<b>Unit Cost</b>", "<b>Quantity</b>", "<b>Amount</b>"],
48
+ ["Subscription", "$19.00", "1", "$19.00"],
49
+ [nil, nil, "Subtotal", "$19.00"],
50
+ [nil, nil, "Tax", "$1.12"],
51
+ [nil, nil, "Total", "$20.12"],
52
+ [nil, nil, "<b>Amount paid</b>", "$20.12"],
53
+ [nil, nil, "Refunded on #{Date.today.strftime("%B %d, %Y")}", "$5.00"]
54
+ ]
55
+ )
56
+ r.render_file "examples/receipt.pdf"
37
57
  end
38
58
 
39
59
  task :invoice do
40
60
  require "./lib/receipts"
41
61
 
42
62
  Receipts::Invoice.new(
43
- id: "123",
44
- issue_date: Date.today,
45
- due_date: Date.today + 30,
46
- status: "<b><color rgb='#5eba7d'>PAID</color></b>",
47
- bill_to: [
48
- "GoRails, LLC",
63
+ details: [
64
+ ["Invoice Number", "123"],
65
+ ["Issue Date", Date.today.strftime("%B %d, %Y")],
66
+ ["Due Date", Date.today.strftime("%B %d, %Y")],
67
+ ["Status", "<b><color rgb='#5eba7d'>PAID</color></b>"]
68
+ ],
69
+ recipient: [
70
+ "<b>Bill To</b>",
71
+ "Customer",
49
72
  "Address",
50
73
  "City, State Zipcode",
51
- nil,
52
- "mail@example.com",
74
+ "customer@example.org"
53
75
  ],
54
76
  company: {
55
- name: "GoRails, LLC",
77
+ name: "Example, LLC",
56
78
  address: "123 Fake Street\nNew York City, NY 10012",
79
+ phone: "(555) 867-5309",
57
80
  email: "support@example.com",
58
- #logo: Rails.root.join("app/assets/images/gorails.png")
59
- #logo: File.expand_path("./examples/gorails.png")
60
- logo: File.open("./examples/gorails.png")
81
+ # logo: Rails.root.join("app/assets/images/logo.png")
82
+ # logo: File.open("./examples/images/logo.png")
83
+ logo: File.expand_path("./examples/images/logo.png")
61
84
  },
62
85
  line_items: [
63
86
  ["<b>Item</b>", "<b>Unit Cost</b>", "<b>Quantity</b>", "<b>Amount</b>"],
64
- ["GoRails Subscription", "$19.00", "1", "$19.00"],
87
+ ["Subscription", "$19.00", "1", "$19.00"],
65
88
  [nil, nil, "Subtotal", "$19.00"],
66
89
  [nil, nil, "Tax Rate", "0%"],
67
- [nil, nil, "Amount Due", "$19.00"],
68
- ],
90
+ [nil, nil, "Amount Due", "$19.00"]
91
+ ]
69
92
  ).render_file "examples/invoice.pdf"
70
93
  end
94
+
95
+ task :statement do
96
+ require "./lib/receipts"
97
+
98
+ Receipts::Statement.new(
99
+ details: [
100
+ ["Statement Number", "123"],
101
+ ["Issue Date", Date.today.strftime("%B %d, %Y")],
102
+ ["Period", "#{(Date.today - 30).strftime("%B %d, %Y")} - #{Date.today.strftime("%B %d, %Y")}"]
103
+ ],
104
+ recipient: [
105
+ "<b>Bill To</b>",
106
+ "Customer",
107
+ "Address",
108
+ "City, State Zipcode",
109
+ "customer@example.org"
110
+ ],
111
+ company: {
112
+ name: "Example, LLC",
113
+ address: "123 Fake Street\nNew York City, NY 10012",
114
+ email: "support@example.com",
115
+ phone: "(555) 867-5309",
116
+ logo: File.expand_path("./examples/images/logo.png")
117
+ },
118
+ line_items: [
119
+ ["<b>Item</b>", "<b>Unit Cost</b>", "<b>Quantity</b>", "<b>Amount</b>"],
120
+ ["Subscription", "$19.00", "1", "$19.00"],
121
+ [nil, nil, "Subtotal", "$19.00"],
122
+ [nil, nil, "Tax Rate", "0%"],
123
+ [nil, nil, "Total", "$19.00"]
124
+ ]
125
+ ).render_file "examples/statement.pdf"
126
+ end
File without changes
Binary file
data/examples/invoice.pdf CHANGED
Binary file
data/examples/receipt.pdf CHANGED
Binary file
Binary file
@@ -0,0 +1,102 @@
1
+ module Receipts
2
+ class Base < Prawn::Document
3
+ attr_accessor :title, :company
4
+
5
+ class << self
6
+ attr_reader :title
7
+ end
8
+
9
+ def initialize(attributes = {})
10
+ super(page_size: "LETTER")
11
+ setup_fonts attributes[:font]
12
+
13
+ @title = attributes.fetch(:title, self.class.title)
14
+
15
+ generate_from(attributes)
16
+ end
17
+
18
+ def generate_from(attributes)
19
+ return if attributes.empty?
20
+
21
+ company = attributes.fetch(:company)
22
+ header company: company
23
+ render_details attributes.fetch(:details)
24
+ render_billing_details company: company, recipient: attributes.fetch(:recipient)
25
+ render_line_items attributes.fetch(:line_items)
26
+ render_footer attributes.fetch(:footer, default_message(company: company))
27
+ end
28
+
29
+ def setup_fonts(custom_font = nil)
30
+ if !!custom_font
31
+ font_families.update "Primary" => custom_font
32
+ font "Primary"
33
+ end
34
+
35
+ font_size 8
36
+ end
37
+
38
+ def load_image(logo)
39
+ if logo.is_a? String
40
+ logo.start_with?("http") ? URI.parse(logo).open : File.open(logo)
41
+ else
42
+ logo
43
+ end
44
+ end
45
+
46
+ def header(company: {}, height: 16)
47
+ logo = company[:logo]
48
+
49
+ if logo.nil?
50
+ text company.fetch(:name), align: :right, style: :bold, size: 16, color: "4b5563"
51
+ else
52
+ image load_image(logo), height: height, position: :right
53
+ end
54
+
55
+ move_up height
56
+ text title, style: :bold, size: 16
57
+ end
58
+
59
+ def render_details(details, margin_top: 16)
60
+ move_down margin_top
61
+ table(details, cell_style: {borders: [], inline_format: true, padding: [0, 8, 2, 0]})
62
+ end
63
+
64
+ def render_billing_details(company:, recipient:, margin_top: 16)
65
+ move_down margin_top
66
+
67
+ company_details = [
68
+ company[:address],
69
+ company[:phone],
70
+ company[:email]
71
+ ].compact.join("\n")
72
+
73
+ line_items = [
74
+ [
75
+ {content: "<b>#{company.fetch(:name)}</b>\n#{company_details}", padding: [0, 12, 0, 0]},
76
+ {content: Array(recipient).join("\n"), padding: [0, 12, 0, 0]}
77
+ ]
78
+ ]
79
+ table(line_items, width: bounds.width, cell_style: {borders: [], inline_format: true, overflow: :expand})
80
+ end
81
+
82
+ def render_line_items(line_items, margin_top: 30)
83
+ move_down margin_top
84
+
85
+ borders = line_items.length - 2
86
+ table(line_items, width: bounds.width, cell_style: {border_color: "eeeeee", inline_format: true}) do
87
+ cells.padding = 6
88
+ cells.borders = []
89
+ row(0..borders).borders = [:bottom]
90
+ end
91
+ end
92
+
93
+ def render_footer(message, margin_top: 30)
94
+ move_down margin_top
95
+ text message, inline_format: true
96
+ end
97
+
98
+ def default_message(company:)
99
+ "For questions, contact us anytime at <color rgb='326d92'><link href='mailto:#{company.fetch(:email)}?subject=Question about my receipt'><b>#{company.fetch(:email)}</b></link></color>."
100
+ end
101
+ end
102
+ end
@@ -1,125 +1,5 @@
1
- require 'prawn'
2
- require 'prawn/table'
3
-
4
1
  module Receipts
5
- class Invoice < Prawn::Document
6
- attr_reader :attributes, :id, :company, :custom_font, :line_items, :logo, :message, :product, :subheading, :bill_to, :issue_date, :due_date, :status
7
-
8
- def initialize(attributes)
9
- @attributes = attributes
10
- @id = attributes.fetch(:id)
11
- @company = attributes.fetch(:company)
12
- @line_items = attributes.fetch(:line_items)
13
- @custom_font = attributes.fetch(:font, {})
14
- @message = attributes.fetch(:message) { default_message }
15
- @subheading = attributes.fetch(:subheading) { default_subheading }
16
- @bill_to = Array(attributes.fetch(:bill_to)).join("\n")
17
- @issue_date = attributes.fetch(:issue_date)
18
- @due_date = attributes.fetch(:due_date)
19
- @status = attributes.fetch(:status)
20
-
21
- super(margin: 0)
22
-
23
- setup_fonts if custom_font.any?
24
- generate
25
- end
26
-
27
- private
28
-
29
- def default_message
30
- "For questions, contact us anytime at <color rgb='326d92'><link href='mailto:#{company.fetch(:email)}?subject=Charge ##{id}'><b>#{company.fetch(:email)}</b></link></color>."
31
- end
32
-
33
- def default_subheading
34
- "INVOICE #%{id}"
35
- end
36
-
37
- def setup_fonts
38
- font_families.update "Primary" => custom_font
39
- font "Primary"
40
- end
41
-
42
- def generate
43
- bounding_box [0, 792], width: 612, height: 792 do
44
- bounding_box [85, 792], width: 442, height: 792 do
45
- header
46
- charge_details
47
- footer
48
- end
49
- end
50
- end
51
-
52
- def header
53
- move_down 60
54
-
55
- logo = company[:logo]
56
-
57
- if logo.nil?
58
- move_down 32
59
- elsif logo.is_a?(String)
60
- image open(logo), height: 32
61
- else
62
- image logo, height: 32
63
- end
64
-
65
- move_down 8
66
- label (subheading % {id: id})
67
-
68
- move_down 10
69
-
70
- # Cache the Y value so we can have both boxes at the same height
71
- top = y
72
- bounding_box([0, y], width: 200) do
73
- label "BILL TO"
74
-
75
- move_down 5
76
- text_box bill_to, at: [0, cursor], width: 200, height: 75, inline_format: true, size: 10, leading: 4, overflow: :shrink_to_fit
77
-
78
- end
79
-
80
- bounding_box([250, top], width: 200) do
81
- label "INVOICE DATE"
82
-
83
- move_down 5
84
- text issue_date.to_s, inline_format: true, size: 12, leading: 4
85
-
86
- move_down 10
87
- label "DUE DATE"
88
-
89
- move_down 5
90
- text due_date.to_s, inline_format: true, size: 12, leading: 4
91
-
92
- move_down 10
93
- label "STATUS"
94
-
95
- move_down 5
96
- text status, inline_format: true, size: 12, leading: 4
97
- end
98
- end
99
-
100
- def charge_details
101
- move_down 30
102
-
103
- borders = line_items.length - 2
104
-
105
- table(line_items, width: bounds.width, cell_style: { border_color: 'cccccc', inline_format: true }) do
106
- cells.padding = 12
107
- cells.borders = []
108
- row(0..borders).borders = [:bottom]
109
- end
110
- end
111
-
112
- def footer
113
- move_down 30
114
- text message, inline_format: true, size: 12, leading: 4
115
-
116
- move_down 30
117
- text company.fetch(:name), inline_format: true
118
- text "<color rgb='888888'>#{company.fetch(:address)}</color>", inline_format: true
119
- end
120
-
121
- def label(text)
122
- text "<color rgb='a6a6a6'>#{text}</color>", inline_format: true, size: 8
123
- end
2
+ class Invoice < Base
3
+ @title = "Invoice"
124
4
  end
125
5
  end
@@ -1,86 +1,5 @@
1
- require 'prawn'
2
- require 'prawn/table'
3
-
4
1
  module Receipts
5
- class Receipt < Prawn::Document
6
- attr_reader :attributes, :id, :company, :custom_font, :line_items, :logo, :message, :product, :subheading
7
-
8
- def initialize(attributes)
9
- @attributes = attributes
10
- @id = attributes.fetch(:id)
11
- @company = attributes.fetch(:company)
12
- @line_items = attributes.fetch(:line_items)
13
- @custom_font = attributes.fetch(:font, {})
14
- @message = attributes.fetch(:message) { default_message }
15
- @subheading = attributes.fetch(:subheading) { default_subheading }
16
-
17
- super(margin: 0)
18
-
19
- setup_fonts if custom_font.any?
20
- generate
21
- end
22
-
23
- private
24
-
25
- def default_message
26
- "We've received your payment for #{attributes.fetch(:product)}. You can keep this receipt for your records. For questions, contact us anytime at <color rgb='326d92'><link href='mailto:#{company.fetch(:email)}?subject=Charge ##{id}'><b>#{company.fetch(:email)}</b></link></color>."
27
- end
28
-
29
- def default_subheading
30
- "RECEIPT FOR CHARGE #%{id}"
31
- end
32
-
33
- def setup_fonts
34
- font_families.update "Primary" => custom_font
35
- font "Primary"
36
- end
37
-
38
- def generate
39
- bounding_box [0, 792], width: 612, height: 792 do
40
- bounding_box [85, 792], width: 442, height: 792 do
41
- header
42
- charge_details
43
- footer
44
- end
45
- end
46
- end
47
-
48
- def header
49
- move_down 60
50
-
51
- logo = company[:logo]
52
-
53
- if logo.nil?
54
- move_down 32
55
- elsif logo.is_a?(String)
56
- image open(logo), height: 32
57
- else
58
- image logo, height: 32
59
- end
60
-
61
- move_down 8
62
- text "<color rgb='a6a6a6'>#{subheading % { id: id }}</color>", inline_format: true
63
-
64
- move_down 30
65
- text message, inline_format: true, size: 12.5, leading: 4
66
- end
67
-
68
- def charge_details
69
- move_down 30
70
-
71
- borders = line_items.length - 2
72
-
73
- table(line_items, width: bounds.width, cell_style: { border_color: 'cccccc', inline_format: true }) do
74
- cells.padding = 12
75
- cells.borders = []
76
- row(0..borders).borders = [:bottom]
77
- end
78
- end
79
-
80
- def footer
81
- move_down 45
82
- text company.fetch(:name), inline_format: true
83
- text "<color rgb='888888'>#{company.fetch(:address)}</color>", inline_format: true
84
- end
2
+ class Receipt < Base
3
+ @title = "Receipt"
85
4
  end
86
5
  end
@@ -0,0 +1,5 @@
1
+ module Receipts
2
+ class Statement < Base
3
+ @title = "Statement"
4
+ end
5
+ end
@@ -1,3 +1,3 @@
1
1
  module Receipts
2
- VERSION = "1.0.4"
2
+ VERSION = "2.0.0"
3
3
  end
data/lib/receipts.rb CHANGED
@@ -1,3 +1,11 @@
1
1
  require "receipts/version"
2
- require "receipts/receipt"
3
- require "receipts/invoice"
2
+ require "open-uri"
3
+ require "prawn"
4
+ require "prawn/table"
5
+
6
+ module Receipts
7
+ autoload :Base, "receipts/base"
8
+ autoload :Invoice, "receipts/invoice"
9
+ autoload :Receipt, "receipts/receipt"
10
+ autoload :Statement, "receipts/statement"
11
+ end
data/receipts.gemspec CHANGED
@@ -1,28 +1,33 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'receipts/version'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/receipts/version"
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "receipts"
8
- spec.version = Receipts::VERSION
9
- spec.authors = ["Chris Oliver"]
10
- spec.email = ["excid3@gmail.com"]
11
- spec.summary = %q{Receipts for your Rails application that works with any payment provider.}
12
- spec.description = %q{Receipts for your Rails application that works with any payment provider.}
13
- spec.homepage = ""
14
- spec.license = "MIT"
6
+ spec.name = "receipts"
7
+ spec.version = Receipts::VERSION
8
+ spec.authors = ["Chris Oliver"]
9
+ spec.email = ["excid3@gmail.com"]
15
10
 
16
- spec.files = `git ls-files -z`.split("\x0")
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
11
+ spec.summary = "Receipts for your Rails application that works with any payment provider."
12
+ spec.description = "Receipts for your Rails application that works with any payment provider."
13
+ spec.homepage = "https://github.com/excid3/receipts"
14
+ spec.license = "MIT"
20
15
 
21
- spec.add_development_dependency "bundler"
22
- spec.add_development_dependency "rake", "~> 10.0"
23
- spec.add_development_dependency "rspec"
24
- spec.add_development_dependency "pry"
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+ spec.metadata["changelog_uri"] = "https://github.com/excid3/receipts/blob/master/CHANGELOG.md"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
+ end
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
25
30
 
26
- spec.add_dependency 'prawn', '>= 1.3.0', '< 3.0.0'
27
- spec.add_dependency 'prawn-table', '~> 0.2.1'
31
+ spec.add_dependency "prawn", ">= 1.3.0", "< 3.0.0"
32
+ spec.add_dependency "prawn-table", "~> 0.2.1"
28
33
  end
metadata CHANGED
@@ -1,71 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: receipts
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Oliver
8
- autorequire:
9
- bindir: bin
8
+ autorequire:
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2020-08-02 00:00:00.000000000 Z
11
+ date: 2022-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '10.0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '10.0'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: pry
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
13
  - !ruby/object:Gem::Dependency
70
14
  name: prawn
71
15
  requirement: !ruby/object:Gem::Requirement
@@ -107,29 +51,31 @@ executables: []
107
51
  extensions: []
108
52
  extra_rdoc_files: []
109
53
  files:
110
- - ".github/FUNDING.yml"
111
- - ".gitignore"
112
- - ".travis.yml"
113
54
  - CHANGELOG.md
114
55
  - Gemfile
115
56
  - LICENSE.txt
116
57
  - README.md
117
58
  - Rakefile
118
- - examples/gorails.png
59
+ - examples/images/logo.png
60
+ - examples/images/options.jpg
119
61
  - examples/invoice.pdf
120
62
  - examples/receipt.pdf
63
+ - examples/statement.pdf
121
64
  - lib/receipts.rb
65
+ - lib/receipts/base.rb
122
66
  - lib/receipts/invoice.rb
123
67
  - lib/receipts/receipt.rb
68
+ - lib/receipts/statement.rb
124
69
  - lib/receipts/version.rb
125
70
  - receipts.gemspec
126
- - spec/receipts_spec.rb
127
- - spec/spec_helper.rb
128
- homepage: ''
71
+ homepage: https://github.com/excid3/receipts
129
72
  licenses:
130
73
  - MIT
131
- metadata: {}
132
- post_install_message:
74
+ metadata:
75
+ homepage_uri: https://github.com/excid3/receipts
76
+ source_code_uri: https://github.com/excid3/receipts
77
+ changelog_uri: https://github.com/excid3/receipts/blob/master/CHANGELOG.md
78
+ post_install_message:
133
79
  rdoc_options: []
134
80
  require_paths:
135
81
  - lib
@@ -144,10 +90,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
90
  - !ruby/object:Gem::Version
145
91
  version: '0'
146
92
  requirements: []
147
- rubygems_version: 3.1.2
148
- signing_key:
93
+ rubygems_version: 3.2.32
94
+ signing_key:
149
95
  specification_version: 4
150
96
  summary: Receipts for your Rails application that works with any payment provider.
151
- test_files:
152
- - spec/receipts_spec.rb
153
- - spec/spec_helper.rb
97
+ test_files: []
data/.github/FUNDING.yml DELETED
@@ -1,12 +0,0 @@
1
- # These are supported funding model platforms
2
-
3
- github: [excid3] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4
- patreon: # Replace with a single Patreon username
5
- open_collective: # Replace with a single Open Collective username
6
- ko_fi: # Replace with a single Ko-fi username
7
- tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8
- community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9
- liberapay: # Replace with a single Liberapay username
10
- issuehunt: # Replace with a single IssueHunt username
11
- otechie: # Replace with a single Otechie username
12
- custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
data/.gitignore DELETED
@@ -1,15 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- *.bundle
11
- *.so
12
- *.o
13
- *.a
14
- mkmf.log
15
- .DS_Store
data/.travis.yml DELETED
@@ -1,4 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.3.3
4
- - 2.4.0
@@ -1,18 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Receipts do
4
- it "lets you create a new receipt" do
5
- expect(Receipts::Receipt.new(
6
- id: 1,
7
- product: "GoRails",
8
- company: {
9
- name: "One Month, Inc.",
10
- address: "37 Great Jones\nFloor 2\nNew York City, NY 10012",
11
- email: "teachers@onemonth.com"
12
- },
13
- line_items: [
14
- ["Product", "GoRails"],
15
- ],
16
- ).class.name).to eq("Receipts::Receipt")
17
- end
18
- end
data/spec/spec_helper.rb DELETED
@@ -1,9 +0,0 @@
1
- begin
2
- require 'pry'
3
- rescue LoadError
4
- end
5
-
6
- require 'receipts'
7
-
8
- RSpec.configure do |config|
9
- end