Subscriptions are handled differently by each payment processor. Pay does its best to treat them the same.
Pay stores subscriptions in the Pay::Subscription
model. Each subscription has a name
that you can use to handle multiple subscriptions per customer.
To subscribe a user, you can call the subscribe
method.
@user.payment_processor.subscribe(name: "default", plan: "monthly")
You can pass additional options to go directly to the payment processor's API. For example, the quantity
option to subscribe to a plan with for per-seat pricing.
@user.payment_processor.subscribe(name: "default", plan: "monthly", quantity: 3)
Subscribe takes several arguments and options:
name
- A name for the subscription that's used internally. This allows a customer to have multiple subscriptions. Defaults to"default"
plan
- The Plan or Price ID to subscribe to. Defaults to"default"
quantity
- The quantity of the subscription. Defaults to1
trial_period_days
- Number of days for the subscription's trial.- Other options may be passed and will be sent directly to the payment processor's API.
Paddle does not allow you to create a subscription through the API.
Instead, Pay uses webhooks to create the the subscription in the database. The Paddle passthrough parameter is required during checkout to associate the subscription with the correct Pay::Customer
.
In your Javascript, include passthrough
in Checkout using the Pay::Paddle.passthrough
helper.
Paddle.Checkout.open({
product: 12345,
passthrough: "<%= Pay::Paddle.passthrough(owner: current_user) %>"
});
Or with Paddle Button Checkout:
<a href="#!" class="paddle_button" data-product="12345" data-email="<%= current_user.email %>" data-passthrough="<%= Pay::Paddle.passthrough(owner: current_user) %>"
Pay provides a helper method for generating the passthrough
JSON object to associate the purchase with the correct Rails model.
Pay::Paddle.passthrough(owner: current_user, foo: :bar)
#=> { owner_sgid: "xxxxxxxx", foo: "bar" }
# To generate manually without the helper
#=> { owner_sgid: current_user.to_sgid.to_s, foo: "bar" }.to_json
Pay uses a signed GlobalID to ensure that the subscription cannot be tampered with.
When processing Paddle webhooks, Pay parses the passthrough
JSON string and verifies the owner_sgid
hash in order to find the correct Pay::Customer
record.
The passthrough parameter owner_sgid
is only required for creating a subscription.
@user.payment_processor.subscription(name: "default")
There are two types of trials for subscriptions: with or without a payment method upfront.
Stripe is the only payment processor that allows subscriptions without a payment method. Braintree and Paddle require a payment method on file to create a subscription.
To create a trial without a card, we can use the Fake Processor to create a subscription with matching trial and end times.
time = 14.days.from_now
@user.set_payment_processor :fake_processor, allow_fake: true
@user.payment_processor.subscribe(trial_ends_at: time, ends_at: time)
This will create a fake subscription in our database that we can use. Once expired, the customer will need to subscribe using a real payment processor.
@user.payment_processor.on_generic_trial?
#=> true
Braintree and Paddle require payment methods before creating a subscription.
@user.set_payment_processor :braintree
@user.payment_processor.payment_method_token = params[:payment_method_token]
@user.payment_processor.subscribe()
@user.payment_processor.subscribed?
You can also check for a specific subscription or plan:
@user.payment_processor.subscribed?(name: "default", plan: "monthly")
You can check if the user is on a trial by simply asking:
@user.payment_processor.on_trial?
#=> true or false
You can also check if the user is on a trial for a specific subscritpion name or plan.
@user.payment_processor.on_trial?(name: 'default', plan: 'plan')
#=> true or false
For paid features of your app, you'll often want to check if the user is on trial OR subscribed. You can use this method to check both at once:
@user.payment_processor.on_trial_or_subscribed?
You can also check for a specific subscription or plan:
@user.payment_processor.on_trial_or_subscribed?(name: "default", plan: "annual")
Individual subscriptions provide similar helper methods to check their state.
@user.payment_processor.subscription.on_trial? #=> true or false
@user.payment_processor.subscription.cancelled? #=> true or false
@user.payment_processor.subscription.on_grace_period? #=> true or false
@user.payment_processor.subscription.active? #=> true or false
@user.payment_processor.subscription.paused? #=> true or false
@user.payment_processor.subscription.cancel
In addition to the API, Paddle provides a subscription Cancel URL that you can redirect customers to cancel their subscription.
@user.payment_processor.subscription.paddle_cancel_url
@user.payment_processor.subscription.cancel_now!
@user.payment_processor.subscription.pause
If a user wishes to change subscription plans, you can pass in the Plan or Price ID into the swap
method:
@user.payment_processor.subscription.swap("yearly")
Braintree does not allow this via their API, so we cancel and create a new subscription for you (including proration discount).
A user may wish to resume their canceled (or paused) subscription. You can resume a subscription with:
@user.payment_processor.subscription.resume
With Stripe or Braintree, a subscription on grace period may be resumed:
With Paddle, you may resume a paused subscription:
@user.payment_processor.subscription.processor_subscription
#=> #<Stripe::Subscription>
See Webhooks