FEATURE: Allow one-time purchases on products (#18)
Building off the foundation of using the Prices API, this PR adds the ability to create a one-time purchase plan for any product, which then can add a user to the specified plan group.
Some things to be aware of:
One-time purchases cannot have trials.
One-time purchases use the Invoice API instead of Subscriptions. Invoices are created then charged immediately.
Users should receive emails for these invoices directly from Stripe just like subscriptions.
This commit is contained in:
@@ -12,7 +12,6 @@ module DiscourseSubscriptions
|
||||
plans = ::Stripe::Price.list(product_params)
|
||||
|
||||
render_json_dump plans.data
|
||||
|
||||
rescue ::Stripe::InvalidRequestError => e
|
||||
render_json_error e.message
|
||||
end
|
||||
@@ -20,12 +19,9 @@ module DiscourseSubscriptions
|
||||
|
||||
def create
|
||||
begin
|
||||
plan = ::Stripe::Price.create(
|
||||
price_object = {
|
||||
nickname: params[:nickname],
|
||||
unit_amount: params[:amount],
|
||||
recurring: {
|
||||
interval: params[:interval],
|
||||
},
|
||||
product: params[:product],
|
||||
currency: params[:currency],
|
||||
active: params[:active],
|
||||
@@ -33,10 +29,17 @@ module DiscourseSubscriptions
|
||||
group_name: params[:metadata][:group_name],
|
||||
trial_period_days: params[:trial_period_days]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if params[:type] == 'recurring'
|
||||
price_object[:recurring] = {
|
||||
interval: params[:interval]
|
||||
}
|
||||
end
|
||||
|
||||
plan = ::Stripe::Price.create(price_object)
|
||||
|
||||
render_json_dump plan
|
||||
|
||||
rescue ::Stripe::InvalidRequestError => e
|
||||
render_json_error e.message
|
||||
end
|
||||
@@ -74,7 +77,6 @@ module DiscourseSubscriptions
|
||||
)
|
||||
|
||||
render_json_dump plan
|
||||
|
||||
rescue ::Stripe::InvalidRequestError => e
|
||||
render_json_error e.message
|
||||
end
|
||||
|
||||
@@ -12,7 +12,6 @@ module DiscourseSubscriptions
|
||||
webhook_secret = SiteSetting.discourse_subscriptions_webhook_secret
|
||||
|
||||
event = ::Stripe::Webhook.construct_event(payload, sig_header, webhook_secret)
|
||||
|
||||
rescue JSON::ParserError => e
|
||||
render_json_error e.message
|
||||
return
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseSubscriptions
|
||||
class InvoicesController < ::ApplicationController
|
||||
include DiscourseSubscriptions::Stripe
|
||||
before_action :set_api_key
|
||||
requires_login
|
||||
|
||||
def index
|
||||
begin
|
||||
customer = find_customer
|
||||
|
||||
if viewing_own_invoices && customer.present?
|
||||
invoices = ::Stripe::Invoice.list(customer: customer.customer_id)
|
||||
|
||||
render_json_dump invoices.data
|
||||
else
|
||||
render_json_dump []
|
||||
end
|
||||
rescue ::Stripe::InvalidRequestError => e
|
||||
render_json_error e.message
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def viewing_own_invoices
|
||||
current_user.id == params[:user_id].to_i
|
||||
end
|
||||
|
||||
def find_customer
|
||||
Customer.find_user(current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,40 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseSubscriptions
|
||||
class PaymentsController < ::ApplicationController
|
||||
include DiscourseSubscriptions::Stripe
|
||||
|
||||
skip_before_action :verify_authenticity_token, only: [:create]
|
||||
before_action :set_api_key
|
||||
|
||||
requires_login
|
||||
|
||||
def create
|
||||
begin
|
||||
customer = Customer.where(user_id: current_user.id, product_id: nil).first_or_create do |c|
|
||||
new_customer = ::Stripe::Customer.create(
|
||||
email: current_user.email
|
||||
)
|
||||
|
||||
c.customer_id = new_customer[:id]
|
||||
end
|
||||
|
||||
payment = ::Stripe::PaymentIntent.create(
|
||||
payment_method_types: ['card'],
|
||||
payment_method: params[:payment_method],
|
||||
amount: params[:amount],
|
||||
currency: params[:currency],
|
||||
customer: customer[:customer_id],
|
||||
confirm: true
|
||||
)
|
||||
|
||||
render_json_dump payment
|
||||
|
||||
rescue ::Stripe::InvalidRequestError => e
|
||||
render_json_error e.message
|
||||
rescue ::Stripe::CardError => e
|
||||
render_json_error I18n.t('discourse_subscriptions.card.declined')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -15,11 +15,10 @@ module DiscourseSubscriptions
|
||||
end
|
||||
|
||||
serialized = plans[:data].map do |plan|
|
||||
plan.to_h.slice(:id, :unit_amount, :currency, :recurring)
|
||||
plan.to_h.slice(:id, :unit_amount, :currency, :type, :recurring)
|
||||
end.sort_by { |plan| plan[:amount] }
|
||||
|
||||
render_json_dump serialized
|
||||
|
||||
rescue ::Stripe::InvalidRequestError => e
|
||||
render_json_error e.message
|
||||
end
|
||||
|
||||
@@ -19,7 +19,6 @@ module DiscourseSubscriptions
|
||||
end
|
||||
|
||||
render_json_dump subscriptions
|
||||
|
||||
rescue ::Stripe::InvalidRequestError => e
|
||||
render_json_error e.message
|
||||
end
|
||||
@@ -29,36 +28,48 @@ module DiscourseSubscriptions
|
||||
begin
|
||||
plan = ::Stripe::Price.retrieve(params[:plan])
|
||||
|
||||
if plan[:metadata] && plan[:metadata][:trial_period_days]
|
||||
trial_days = plan[:metadata][:trial_period_days]
|
||||
recurring_plan = plan[:type] == 'recurring'
|
||||
|
||||
if recurring_plan
|
||||
trial_days = plan[:metadata][:trial_period_days] if plan[:metadata] && plan[:metadata][:trial_period_days]
|
||||
|
||||
transaction = ::Stripe::Subscription.create(
|
||||
customer: params[:customer],
|
||||
items: [{ price: params[:plan] }],
|
||||
metadata: metadata_user,
|
||||
trial_period_days: trial_days
|
||||
)
|
||||
else
|
||||
invoice_item = ::Stripe::InvoiceItem.create(
|
||||
customer: params[:customer],
|
||||
price: params[:plan]
|
||||
)
|
||||
invoice = ::Stripe::Invoice.create(
|
||||
customer: params[:customer]
|
||||
)
|
||||
transaction = ::Stripe::Invoice.pay(invoice[:id])
|
||||
end
|
||||
|
||||
@subscription = ::Stripe::Subscription.create(
|
||||
customer: params[:customer],
|
||||
items: [ { price: params[:plan] } ],
|
||||
metadata: metadata_user,
|
||||
trial_period_days: trial_days
|
||||
)
|
||||
if transaction_ok(transaction)
|
||||
group = plan_group(plan)
|
||||
|
||||
group = plan_group(plan)
|
||||
group.add(current_user) if group
|
||||
|
||||
if subscription_ok && group
|
||||
group.add(current_user)
|
||||
customer = Customer.create(
|
||||
user_id: current_user.id,
|
||||
customer_id: params[:customer],
|
||||
product_id: plan[:product]
|
||||
)
|
||||
|
||||
if transaction[:object] == 'subscription'
|
||||
Subscription.create(
|
||||
customer_id: customer.id,
|
||||
external_id: transaction[:id]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
customer = Customer.create(
|
||||
user_id: current_user.id,
|
||||
customer_id: params[:customer],
|
||||
product_id: plan[:product]
|
||||
)
|
||||
|
||||
Subscription.create(
|
||||
customer_id: customer.id,
|
||||
external_id: @subscription[:id]
|
||||
)
|
||||
|
||||
render_json_dump @subscription
|
||||
|
||||
render_json_dump transaction
|
||||
rescue ::Stripe::InvalidRequestError => e
|
||||
render_json_error e.message
|
||||
end
|
||||
@@ -70,8 +81,8 @@ module DiscourseSubscriptions
|
||||
{ user_id: current_user.id, username: current_user.username_lower }
|
||||
end
|
||||
|
||||
def subscription_ok
|
||||
['active', 'trialing'].include?(@subscription[:status])
|
||||
def transaction_ok(transaction)
|
||||
%w[active trialing paid].include?(transaction[:status])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,9 +21,10 @@ module DiscourseSubscriptions
|
||||
all_invoices = ::Stripe::Invoice.list(customer: customer_id)
|
||||
invoices_with_products = all_invoices[:data].select do |invoice|
|
||||
# i cannot dig it so we must get iffy with it
|
||||
if invoice[:lines] && invoice[:lines][:data] && invoice[:lines][:data][0] && invoice[:lines][:data][0][:plan] && invoice[:lines][:data][0][:plan][:product]
|
||||
product_ids.include?(invoice[:lines][:data][0][:plan][:product])
|
||||
end
|
||||
invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data]
|
||||
invoice_product_id = invoice_lines[:price][:product] if invoice_lines[:price] && invoice_lines[:price][:product]
|
||||
invoice_product_id = invoice_lines[:plan][:product] if invoice_lines[:plan] && invoice_lines[:plan][:product]
|
||||
product_ids.include?(invoice_product_id)
|
||||
end
|
||||
invoice_ids = invoices_with_products.map { |invoice| invoice[:id] }
|
||||
payments = ::Stripe::PaymentIntent.list(customer: customer_id)
|
||||
|
||||
Reference in New Issue
Block a user