FEATURE: Support for coupons in checkout (#41)
This adds support for Stripe Promo Codes in the user checkout process. Also adds a discounted field to User > Billing > Subscriptions to show the amount or percent discounted. This does not currently add in support for creating promo codes in the Subscriptions interface (that will come at a later point in time). Instead a coupon can be created with a promo code right from the Stripe dashboard.
This commit is contained in:
parent
70b96cf3b8
commit
da9b58398b
|
@ -53,24 +53,31 @@ module DiscourseSubscriptions
|
|||
begin
|
||||
customer = create_customer(params[:source])
|
||||
plan = ::Stripe::Price.retrieve(params[:plan])
|
||||
promo_code = ::Stripe::PromotionCode.list({ code: params[:promo] }) if params[:promo].present?
|
||||
promo_code = promo_code[:data][0] if promo_code && promo_code[:data] # we assume promo codes have a unique name
|
||||
|
||||
recurring_plan = plan[:type] == 'recurring'
|
||||
|
||||
if recurring_plan
|
||||
trial_days = plan[:metadata][:trial_period_days] if plan[:metadata] && plan[:metadata][:trial_period_days]
|
||||
|
||||
promo_code_id = promo_code[:id] if promo_code
|
||||
|
||||
transaction = ::Stripe::Subscription.create(
|
||||
customer: customer[:id],
|
||||
items: [{ price: params[:plan] }],
|
||||
metadata: metadata_user,
|
||||
trial_period_days: trial_days
|
||||
trial_period_days: trial_days,
|
||||
promotion_code: promo_code_id
|
||||
)
|
||||
|
||||
payment_intent = retrieve_payment_intent(transaction[:latest_invoice]) if transaction[:status] == 'incomplete'
|
||||
else
|
||||
coupon_id = promo_code[:coupon][:id] if promo_code && promo_code[:coupon] && promo_code[:coupon][:id]
|
||||
invoice_item = ::Stripe::InvoiceItem.create(
|
||||
customer: customer[:id],
|
||||
price: params[:plan]
|
||||
price: params[:plan],
|
||||
discounts: [{ coupon: coupon_id }]
|
||||
)
|
||||
invoice = ::Stripe::Invoice.create(
|
||||
customer: customer[:id]
|
||||
|
|
|
@ -6,6 +6,7 @@ import { not } from "@ember/object/computed";
|
|||
|
||||
export default Controller.extend({
|
||||
selectedPlan: null,
|
||||
promoCode: null,
|
||||
isAnonymous: not("currentUser"),
|
||||
|
||||
init() {
|
||||
|
@ -32,6 +33,7 @@ export default Controller.extend({
|
|||
const subscription = Subscription.create({
|
||||
source: result.token.id,
|
||||
plan: plan.get("id"),
|
||||
promo: this.promoCode,
|
||||
});
|
||||
|
||||
return subscription.save();
|
||||
|
|
|
@ -12,6 +12,7 @@ const Subscription = EmberObject.extend({
|
|||
const data = {
|
||||
source: this.source,
|
||||
plan: this.plan,
|
||||
promo: this.promo,
|
||||
};
|
||||
|
||||
return ajax("/s/create", { method: "post", data });
|
||||
|
|
|
@ -19,6 +19,22 @@ const UserSubscription = EmberObject.extend({
|
|||
}
|
||||
},
|
||||
|
||||
@discourseComputed("discount")
|
||||
discounted(discount) {
|
||||
if (discount) {
|
||||
const amount_off = discount.coupon.amount_off;
|
||||
const percent_off = discount.coupon.percent_off;
|
||||
|
||||
if (amount_off) {
|
||||
return `${parseFloat(amount_off * 0.01).toFixed(2)}`;
|
||||
} else if (percent_off) {
|
||||
return `${percent_off}%`;
|
||||
}
|
||||
} else {
|
||||
return I18n.t("no_value");
|
||||
}
|
||||
},
|
||||
|
||||
destroy() {
|
||||
return ajax(`/s/user/subscriptions/${this.id}`, {
|
||||
method: "delete",
|
||||
|
|
|
@ -33,6 +33,9 @@
|
|||
{{else if isAnonymous}}
|
||||
{{login-required}}
|
||||
{{else}}
|
||||
<div class='promo-code'>
|
||||
{{input type="text" name="promo_code" placeholderKey="discourse_subscriptions.subscribe.promo_code" value=promoCode}}
|
||||
</div>
|
||||
|
||||
{{d-button
|
||||
disabled=loading
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<th>{{i18n 'discourse_subscriptions.user.subscriptions.id'}}</th>
|
||||
<th>{{i18n 'discourse_subscriptions.user.plans.product'}}</th>
|
||||
<th>{{i18n 'discourse_subscriptions.user.plans.rate'}}</th>
|
||||
<th>{{i18n 'discourse_subscriptions.user.subscriptions.discounted'}}</th>
|
||||
<th>{{i18n 'discourse_subscriptions.user.subscriptions.status'}}</th>
|
||||
<th>{{i18n 'discourse_subscriptions.user.subscriptions.renews'}}</th>
|
||||
<th>{{i18n 'discourse_subscriptions.user.subscriptions.created_at'}}</th>
|
||||
|
@ -14,6 +15,7 @@
|
|||
<td>{{subscription.id}}</td>
|
||||
<td>{{subscription.product.name}}</td>
|
||||
<td>{{subscription.plan.subscriptionRate}}</td>
|
||||
<td>{{subscription.discounted}}</td>
|
||||
<td>{{subscription.status}}</td>
|
||||
<td>{{subscription.endDate}}</td>
|
||||
<td>{{format-unix-date subscription.created}}</td>
|
||||
|
|
|
@ -53,6 +53,7 @@ en:
|
|||
subscriptions:
|
||||
id: Subscription ID
|
||||
status: Status
|
||||
discounted: Discounted
|
||||
renews: Renews
|
||||
created_at: Created
|
||||
cancel: cancel
|
||||
|
@ -68,6 +69,7 @@ en:
|
|||
title: Payment
|
||||
customer:
|
||||
title: Customer Details
|
||||
promo_code: Coupon
|
||||
buttons:
|
||||
subscribe: Subscribe
|
||||
purchased: Purchased
|
||||
|
|
|
@ -128,7 +128,8 @@ module DiscourseSubscriptions
|
|||
customer: 'cus_1234',
|
||||
items: [ price: 'plan_1234' ],
|
||||
metadata: { user_id: user.id, username: user.username_lower },
|
||||
trial_period_days: 0
|
||||
trial_period_days: 0,
|
||||
promotion_code: nil
|
||||
).returns(status: 'active', customer: 'cus_1234')
|
||||
|
||||
expect {
|
||||
|
@ -170,6 +171,63 @@ module DiscourseSubscriptions
|
|||
post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' }
|
||||
}.to change { DiscourseSubscriptions::Customer.count }
|
||||
end
|
||||
|
||||
context "with promo code" do
|
||||
before do
|
||||
::Stripe::PromotionCode.expects(:list).with({ code: '123' }).returns(
|
||||
data: [{
|
||||
id: 'promo123',
|
||||
coupon: { id: 'c123' }
|
||||
}]
|
||||
)
|
||||
end
|
||||
|
||||
it "applies promo code to recurring subscription" do
|
||||
::Stripe::Price.expects(:retrieve).returns(
|
||||
type: 'recurring',
|
||||
product: 'product_12345',
|
||||
metadata: {
|
||||
group_name: 'awesome',
|
||||
trial_period_days: 0
|
||||
}
|
||||
)
|
||||
|
||||
::Stripe::Subscription.expects(:create).with(
|
||||
customer: 'cus_1234',
|
||||
items: [ price: 'plan_1234' ],
|
||||
metadata: { user_id: user.id, username: user.username_lower },
|
||||
trial_period_days: 0,
|
||||
promotion_code: 'promo123'
|
||||
).returns(status: 'active', customer: 'cus_1234')
|
||||
|
||||
post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234', promo: '123' }
|
||||
|
||||
end
|
||||
|
||||
it "applies promo code to one time purchase" do
|
||||
::Stripe::Price.expects(:retrieve).returns(
|
||||
type: 'one_time',
|
||||
product: 'product_12345',
|
||||
metadata: {
|
||||
group_name: 'awesome'
|
||||
}
|
||||
)
|
||||
|
||||
::Stripe::InvoiceItem.expects(:create).with(customer: 'cus_1234', price: 'plan_1234', discounts: [{ coupon: 'c123' }])
|
||||
|
||||
::Stripe::Invoice.expects(:create).returns(status: 'open', id: 'in_123')
|
||||
|
||||
::Stripe::Invoice.expects(:finalize_invoice).returns(id: 'in_123', status: 'open', payment_intent: 'pi_123')
|
||||
|
||||
::Stripe::Invoice.expects(:retrieve).returns(id: 'in_123', status: 'open', payment_intent: 'pi_123')
|
||||
|
||||
::Stripe::PaymentIntent.expects(:retrieve).returns(status: 'successful')
|
||||
|
||||
::Stripe::Invoice.expects(:pay).returns(status: 'paid', customer: 'cus_1234')
|
||||
|
||||
post '/s/create.json', params: { plan: 'plan_1234', source: 'tok_1234', promo: '123' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#finalize strong customer authenticated transaction" do
|
||||
|
|
Loading…
Reference in New Issue