paddle_rails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +294 -0
- data/Rakefile +6 -0
- data/app/assets/stylesheets/paddle_rails/application.css +16 -0
- data/app/assets/stylesheets/paddle_rails/tailwind.css +1824 -0
- data/app/assets/tailwind/application.css +1 -0
- data/app/controllers/concerns/paddle_rails/paddle_checkout_error_handler.rb +89 -0
- data/app/controllers/concerns/paddle_rails/subscription_owner.rb +16 -0
- data/app/controllers/paddle_rails/application_controller.rb +21 -0
- data/app/controllers/paddle_rails/checkout_controller.rb +121 -0
- data/app/controllers/paddle_rails/dashboard_controller.rb +37 -0
- data/app/controllers/paddle_rails/onboarding_controller.rb +55 -0
- data/app/controllers/paddle_rails/payments_controller.rb +62 -0
- data/app/controllers/paddle_rails/subscriptions_controller.rb +92 -0
- data/app/controllers/paddle_rails/webhooks_controller.rb +78 -0
- data/app/helpers/paddle_rails/application_helper.rb +121 -0
- data/app/helpers/paddle_rails/subscription_owner_helper.rb +14 -0
- data/app/jobs/paddle_rails/application_job.rb +4 -0
- data/app/jobs/paddle_rails/process_webhook_job.rb +38 -0
- data/app/mailers/paddle_rails/application_mailer.rb +6 -0
- data/app/models/concerns/paddle_rails/subscribable.rb +46 -0
- data/app/models/paddle_rails/application_record.rb +5 -0
- data/app/models/paddle_rails/payment.rb +43 -0
- data/app/models/paddle_rails/price.rb +25 -0
- data/app/models/paddle_rails/product.rb +16 -0
- data/app/models/paddle_rails/subscription.rb +87 -0
- data/app/models/paddle_rails/subscription_item.rb +35 -0
- data/app/models/paddle_rails/webhook_event.rb +51 -0
- data/app/presenters/paddle_rails/payment_presenter.rb +96 -0
- data/app/presenters/paddle_rails/product_presenter.rb +178 -0
- data/app/presenters/paddle_rails/subscription_presenter.rb +145 -0
- data/app/views/layouts/paddle_rails/application.html.erb +170 -0
- data/app/views/paddle_rails/checkout/show.html.erb +128 -0
- data/app/views/paddle_rails/dashboard/_change_plan.html.erb +286 -0
- data/app/views/paddle_rails/dashboard/_current_subscription.html.erb +66 -0
- data/app/views/paddle_rails/dashboard/_payment_history.html.erb +79 -0
- data/app/views/paddle_rails/dashboard/_payment_method.html.erb +48 -0
- data/app/views/paddle_rails/dashboard/show.html.erb +47 -0
- data/app/views/paddle_rails/onboarding/show.html.erb +100 -0
- data/app/views/paddle_rails/shared/configuration_error.html.erb +94 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20251124180624_create_paddle_rails_subscription_plans.rb +18 -0
- data/db/migrate/20251124180817_create_paddle_rails_subscription_prices.rb +26 -0
- data/db/migrate/20251127221947_create_paddle_rails_webhook_events.rb +19 -0
- data/db/migrate/20251128135831_create_paddle_rails_subscriptions.rb +21 -0
- data/db/migrate/20251128142327_create_paddle_rails_subscription_items.rb +16 -0
- data/db/migrate/20251128151334_remove_paddle_price_id_from_subscriptions.rb +7 -0
- data/db/migrate/20251128151401_rename_subscription_plans_to_products.rb +6 -0
- data/db/migrate/20251128151402_rename_subscription_plan_id_to_subscription_product_id.rb +13 -0
- data/db/migrate/20251128151453_remove_subscription_price_id_from_subscriptions.rb +8 -0
- data/db/migrate/20251128151501_add_subscription_product_id_to_subscription_items.rb +8 -0
- data/db/migrate/20251128152025_remove_paddle_item_id_from_subscription_items.rb +6 -0
- data/db/migrate/20251128212046_rename_subscription_products_to_products.rb +6 -0
- data/db/migrate/20251128212047_rename_subscription_prices_to_prices.rb +6 -0
- data/db/migrate/20251128212053_rename_subscription_product_id_to_product_id_in_prices.rb +13 -0
- data/db/migrate/20251128212054_rename_fks_in_subscription_items.rb +20 -0
- data/db/migrate/20251128220016_add_scheduled_cancelation_at_to_subscriptions.rb +6 -0
- data/db/migrate/20251129121336_add_payment_method_to_subscriptions.rb +10 -0
- data/db/migrate/20251129222345_create_paddle_rails_payments.rb +24 -0
- data/lib/paddle_rails/checkout.rb +181 -0
- data/lib/paddle_rails/configuration.rb +121 -0
- data/lib/paddle_rails/engine.rb +49 -0
- data/lib/paddle_rails/product_sync.rb +176 -0
- data/lib/paddle_rails/subscription_sync.rb +303 -0
- data/lib/paddle_rails/version.rb +6 -0
- data/lib/paddle_rails/webhook_processor.rb +102 -0
- data/lib/paddle_rails/webhook_verifier.rb +110 -0
- data/lib/paddle_rails.rb +32 -0
- data/lib/tasks/paddle_rails_tasks.rake +15 -0
- metadata +157 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e582d1858144111153c25ef93bd2e879139574ad812c22f91f5f2bc57ecc7b68
|
|
4
|
+
data.tar.gz: a2df8aae57a9ca5b48e0aea05254b225d45d3e373bdf01cbc55a7cc08475f35a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f2a9b2b04f3e4f53f48cda7fbe01c9d659267a31ab1782ecc49360ace956bf499e10c712f2c6c1ae2b2e26e29fa605e26bc810d4f5b601f48d8b1122e2ab196c
|
|
7
|
+
data.tar.gz: 986d887b59ff62eec3bea43271c38e34cafc33865d223b8d3090cf2c564cd4619169adcdff316911e32a27b370ae5e4d23bd940c41837495576a2c8bb42a64d2
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright Rasmus Kjellberg
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# PaddleRails
|
|
2
|
+
|
|
3
|
+
**The ultimate plug-and-play billing engine for Ruby on Rails and Paddle.**
|
|
4
|
+
|
|
5
|
+
Building a SaaS? Stop wasting weeks on billing integration. **PaddleRails** is a production-ready Rails engine that drops a complete subscription management portal into your application in minutes.
|
|
6
|
+
|
|
7
|
+
It's not just an API wrapper—it's a full-stack billing solution that handles the hard parts of SaaS payments: webhooks, plan upgrades, prorations, cancellation flows, and payment method updates. Fully compliant with Paddle Billing (v2), handling global tax/VAT and localized pricing automatically.
|
|
8
|
+
|
|
9
|
+
No need to build billing UI from scratch. Just mount the engine and focus on your product.
|
|
10
|
+
|
|
11
|
+
- ✅ **No email mismatches** - Users can change email in Paddle checkout without breaking your app
|
|
12
|
+
- ✅ **No customer collisions** - Multiple users can share billing emails safely
|
|
13
|
+
- ✅ **No complex customer lookups** - Identity is always resolved via secure custom_data payloads
|
|
14
|
+
- ✅ **No Pay gem dependency** - Clean, simple implementation without external abstractions
|
|
15
|
+
|
|
16
|
+
Instead of syncing users via Paddle Customers, the gem uses a secure custom_data hash (GlobalID) as the single source of truth. This means your app always links subscriptions to the correct user, regardless of email changes or shared billing addresses.
|
|
17
|
+
|
|
18
|
+
- 🚀 **Instant SaaS Infrastructure** — Mountable billing dashboard ready in minutes
|
|
19
|
+
- 💳 **Paddle Billing V2** — Built for the latest Paddle API
|
|
20
|
+
- 🔄 **Two-Way Sync** — Webhooks keep your local database perfectly synchronized
|
|
21
|
+
- 🎨 **Beautiful UI** — Modern, responsive dashboard built with Tailwind CSS
|
|
22
|
+
- 🛡 **Identity Secure** — Uses GlobalID for bulletproof user mapping (no email mismatch issues)
|
|
23
|
+
- 🏢 **B2B Ready** — Supports subscriptions on Users, Teams, Organizations, or Tenants
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
### 1. Install the gem
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
# Gemfile
|
|
31
|
+
gem "paddle_rails"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
$ bundle install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Install the migrations to create the necessary tables:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
$ bin/rails paddle_rails:install:migrations
|
|
42
|
+
$ bin/rails db:migrate
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. Configure Paddle credentials
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# .env or environment variables
|
|
49
|
+
PADDLE_API_KEY=your_api_key
|
|
50
|
+
PADDLE_PUBLIC_TOKEN=your_public_token
|
|
51
|
+
PADDLE_ENVIRONMENT=sandbox # or "production"
|
|
52
|
+
PADDLE_WEBHOOK_SECRET=your_webhook_secret
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 3. Mount the billing portal
|
|
56
|
+
|
|
57
|
+
Add this to your `config/routes.rb`:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
mount PaddleRails::Engine => "/billing"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 4. Make your model subscribable
|
|
64
|
+
|
|
65
|
+
You can add subscription capabilities to **any** model—`User`, `Organization`, `Team`, `Tenant`, or `Domain`.
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
class User < ApplicationRecord
|
|
69
|
+
include PaddleRails::Subscribable
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 5. Sync your Paddle products
|
|
74
|
+
|
|
75
|
+
To import your products and prices from Paddle, add the sync command to your `db/seeds.rb`:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# db/seeds.rb
|
|
79
|
+
puts "Syncing Paddle products..."
|
|
80
|
+
PaddleRails::ProductSync.call
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Then run:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
$ bin/rails db:seed
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 6. Configure webhooks in Paddle
|
|
90
|
+
|
|
91
|
+
Go to your [Paddle notification settings](https://vendors.paddle.com/notifications-v2) and add:
|
|
92
|
+
|
|
93
|
+
- **URL**: `https://yourdomain.com/paddle_rails/webhooks`
|
|
94
|
+
- **Events**: `subscription.created`, `subscription.updated`, `subscription.canceled`, `transaction.completed`
|
|
95
|
+
|
|
96
|
+
**That's it!** Visit `/billing` to see your billing portal.
|
|
97
|
+
|
|
98
|
+
## The Billing Portal
|
|
99
|
+
|
|
100
|
+
Once mounted, PaddleRails provides these pages automatically:
|
|
101
|
+
|
|
102
|
+
| Path | Description |
|
|
103
|
+
|------|-------------|
|
|
104
|
+
| `/billing` | Main dashboard showing current subscription, payment method, and billing info |
|
|
105
|
+
| `/billing/onboarding` | Plan selection page for new subscribers |
|
|
106
|
+
| `/billing/checkout` | Inline Paddle checkout experience |
|
|
107
|
+
|
|
108
|
+
### Dashboard Features
|
|
109
|
+
|
|
110
|
+
- **Current Subscription** — Shows plan name, price, billing cycle, and next billing date
|
|
111
|
+
- **Subscription Status** — Active, trialing, canceled, or scheduled for cancellation
|
|
112
|
+
- **Payment Method** — Card brand, last 4 digits, expiration with update button
|
|
113
|
+
- **Cancel Subscription** — Schedule cancellation at end of billing period
|
|
114
|
+
- **Revoke Cancellation** — One-click to undo a pending cancellation
|
|
115
|
+
|
|
116
|
+
### Screenshots
|
|
117
|
+
|
|
118
|
+
The billing portal is designed to be clean and professional out of the box. It uses Tailwind CSS classes and can be customized by overriding the views.
|
|
119
|
+
|
|
120
|
+
## Configuration
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
# config/initializers/paddle_rails.rb
|
|
124
|
+
PaddleRails.configure do |config|
|
|
125
|
+
# Paddle API credentials
|
|
126
|
+
config.api_key = ENV["PADDLE_API_KEY"]
|
|
127
|
+
config.public_token = ENV["PADDLE_PUBLIC_TOKEN"]
|
|
128
|
+
config.environment = ENV["PADDLE_ENVIRONMENT"] # "sandbox" or "production"
|
|
129
|
+
config.webhook_secret = ENV["PADDLE_WEBHOOK_SECRET"]
|
|
130
|
+
|
|
131
|
+
# How to identify the subscription owner in the billing portal
|
|
132
|
+
config.subscription_owner_authenticator do
|
|
133
|
+
current_user || warden.authenticate!(scope: :user)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Where the "Back" link goes in the portal
|
|
137
|
+
config.customer_portal_back_path do
|
|
138
|
+
main_app.root_path
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Paddle Dashboard Settings
|
|
144
|
+
|
|
145
|
+
#### Default Payment Link
|
|
146
|
+
|
|
147
|
+
When customers update their payment method, Paddle redirects them back to your app. Configure the **Default payment link** in your Paddle dashboard:
|
|
148
|
+
|
|
149
|
+
1. Go to **Checkout Settings**:
|
|
150
|
+
- Sandbox: https://sandbox-vendors.paddle.com/checkout-settings
|
|
151
|
+
- Production: https://vendors.paddle.com/checkout-settings
|
|
152
|
+
|
|
153
|
+
2. Set **Default payment link** to your billing dashboard URL:
|
|
154
|
+
```
|
|
155
|
+
https://yourdomain.com/billing
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## How It Works
|
|
159
|
+
|
|
160
|
+
### Identity via custom_data
|
|
161
|
+
|
|
162
|
+
PaddleRails uses a secure approach to link Paddle subscriptions to your users:
|
|
163
|
+
|
|
164
|
+
1. When creating a checkout, the gem injects a signed GlobalID into `custom_data`
|
|
165
|
+
2. When webhooks arrive, this ID is used to find the correct user
|
|
166
|
+
3. No reliance on email matching or Paddle customer IDs
|
|
167
|
+
|
|
168
|
+
This means:
|
|
169
|
+
- Users can change their email in Paddle checkout without breaking anything
|
|
170
|
+
- Multiple users can share billing emails safely
|
|
171
|
+
- Identity is always resolved correctly
|
|
172
|
+
|
|
173
|
+
### Automatic Sync
|
|
174
|
+
|
|
175
|
+
The gem automatically keeps your local database in sync with Paddle:
|
|
176
|
+
|
|
177
|
+
- **Products & Prices** — Use `PaddleRails::ProductSync.call` to mirror your Paddle catalog
|
|
178
|
+
- **Subscriptions** — Webhook handlers automatically create/update subscription records
|
|
179
|
+
- **Payment Methods** — Updated automatically when transactions complete
|
|
180
|
+
|
|
181
|
+
## Checking Subscription Status
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
# In your controllers or views
|
|
185
|
+
if current_user.subscription?
|
|
186
|
+
# User has an active subscription
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# More detailed checks
|
|
190
|
+
current_user.subscription.active?
|
|
191
|
+
current_user.subscription.trialing?
|
|
192
|
+
current_user.subscription.scheduled_for_cancellation?
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Customization
|
|
196
|
+
|
|
197
|
+
### Override Views
|
|
198
|
+
|
|
199
|
+
Copy the views to your application to customize:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
$ bin/rails generate paddle_rails:views
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
This copies all views to `app/views/paddle_rails/` where you can modify them.
|
|
206
|
+
|
|
207
|
+
### Custom Webhook Handling
|
|
208
|
+
|
|
209
|
+
Listen to Paddle events using ActiveSupport::Notifications:
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
# config/initializers/paddle_rails.rb
|
|
213
|
+
ActiveSupport::Notifications.subscribe("paddle_rails.subscription.created") do |name, start, finish, id, payload|
|
|
214
|
+
webhook_event = payload[:webhook_event]
|
|
215
|
+
# Send welcome email, provision resources, etc.
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
ActiveSupport::Notifications.subscribe("paddle_rails.subscription.canceled") do |name, start, finish, id, payload|
|
|
219
|
+
# Handle cancellation
|
|
220
|
+
end
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Programmatic Checkout
|
|
224
|
+
|
|
225
|
+
Create checkouts from your own code:
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
# Simple checkout
|
|
229
|
+
checkout_url = current_user.create_paddle_checkout(paddle_price_id: "pri_123")
|
|
230
|
+
redirect_to checkout_url, allow_other_host: true
|
|
231
|
+
|
|
232
|
+
# With custom data
|
|
233
|
+
checkout_url = current_user.create_paddle_checkout(
|
|
234
|
+
paddle_price_id: "pri_123",
|
|
235
|
+
custom_data: { referral_code: "ABC123" }
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Models
|
|
240
|
+
|
|
241
|
+
### PaddleRails::Subscription
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
subscription = current_user.subscription
|
|
245
|
+
|
|
246
|
+
subscription.active? # Currently active
|
|
247
|
+
subscription.trialing? # In trial period
|
|
248
|
+
subscription.scheduled_for_cancellation? # Will cancel at period end
|
|
249
|
+
subscription.current_period_end_at # Next billing date
|
|
250
|
+
subscription.items # All subscription items (for multi-product)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### PaddleRails::Product & PaddleRails::Price
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
# Your Paddle products, synced locally
|
|
257
|
+
PaddleRails::Product.active.each do |product|
|
|
258
|
+
product.name
|
|
259
|
+
product.prices.each do |price|
|
|
260
|
+
price.unit_price # In minor units (cents)
|
|
261
|
+
price.currency
|
|
262
|
+
price.billing_interval # "month", "year"
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Webhook Events
|
|
268
|
+
|
|
269
|
+
All webhooks are stored in `PaddleRails::WebhookEvent` for debugging and replayability:
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
PaddleRails::WebhookEvent.pending # Not yet processed
|
|
273
|
+
PaddleRails::WebhookEvent.processed # Successfully handled
|
|
274
|
+
PaddleRails::WebhookEvent.failed # Had errors
|
|
275
|
+
|
|
276
|
+
event = PaddleRails::WebhookEvent.last
|
|
277
|
+
event.event_type # "subscription.created"
|
|
278
|
+
event.payload # Full JSON from Paddle
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Development
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
$ bin/setup
|
|
285
|
+
$ bin/rails test
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Contributing
|
|
289
|
+
|
|
290
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kjellberg/paddle_rails.
|
|
291
|
+
|
|
292
|
+
## License
|
|
293
|
+
|
|
294
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
|
3
|
+
* listed below.
|
|
4
|
+
*
|
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
|
7
|
+
*
|
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
|
11
|
+
* It is generally better to create a new file per style scope.
|
|
12
|
+
*
|
|
13
|
+
*= require_tree .
|
|
14
|
+
*= require_self
|
|
15
|
+
*/
|
|
16
|
+
|