1
0
mirror of synced 2026-08-03 05:37:04 +00:00

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:
Justin DiRose
2020-07-22 11:06:34 -05:00
committed by GitHub
parent fcc90e4fcc
commit 587661fafb
31 changed files with 272 additions and 533 deletions
@@ -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)