mirror of
https://github.com/discourse/discourse-subscriptions.git
synced 2025-07-06 14:32:11 +00:00
Merge branch 'feature/one-time-payment' of github.com:rimian/discourse-subscriptions into feature/one-time-payment
This commit is contained in:
commit
2bc780c4f9
@ -80,5 +80,7 @@ Many thanks to Chris Beach and Angus McLeod who helped on the [previous version]
|
|||||||

|

|
||||||
### Subscription User
|
### Subscription User
|
||||||

|

|
||||||
|
### Payments User
|
||||||
|

|
||||||
### Subscribe
|
### Subscribe
|
||||||

|

|
||||||
|
@ -11,22 +11,21 @@ module DiscourseSubscriptions
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
begin
|
begin
|
||||||
customer = ::Stripe::Customer.create(
|
customer = DiscourseSubscriptions::Customer.where(user_id: current_user.id, product_id: nil).first_or_create do |c|
|
||||||
email: current_user.email,
|
new_customer = ::Stripe::Customer.create(
|
||||||
)
|
email: current_user.email
|
||||||
|
)
|
||||||
|
|
||||||
DiscourseSubscriptions::Customer.create(
|
c.customer_id = new_customer[:id]
|
||||||
user_id: current_user.id,
|
end
|
||||||
customer_id: customer[:id],
|
|
||||||
)
|
|
||||||
|
|
||||||
payment = ::Stripe::PaymentIntent.create(
|
payment = ::Stripe::PaymentIntent.create(
|
||||||
payment_method_types: ['card'],
|
payment_method_types: ['card'],
|
||||||
payment_method: params[:payment_method],
|
payment_method: params[:payment_method],
|
||||||
amount: params[:amount],
|
amount: params[:amount],
|
||||||
currency: params[:currency],
|
currency: params[:currency],
|
||||||
confirm: true,
|
customer: customer[:customer_id],
|
||||||
customer: customer[:id],
|
confirm: true
|
||||||
)
|
)
|
||||||
|
|
||||||
render_json_dump payment
|
render_json_dump payment
|
||||||
@ -34,7 +33,7 @@ module DiscourseSubscriptions
|
|||||||
rescue ::Stripe::InvalidRequestError => e
|
rescue ::Stripe::InvalidRequestError => e
|
||||||
render_json_error e.message
|
render_json_error e.message
|
||||||
rescue ::Stripe::CardError => e
|
rescue ::Stripe::CardError => e
|
||||||
render_json_error 'Card Declined'
|
render_json_error I18n.t('discourse_subscriptions.card.declined')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
29
app/controllers/user/payments_controller.rb
Normal file
29
app/controllers/user/payments_controller.rb
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module DiscourseSubscriptions
|
||||||
|
module User
|
||||||
|
class PaymentsController < ::ApplicationController
|
||||||
|
include DiscourseSubscriptions::Stripe
|
||||||
|
before_action :set_api_key
|
||||||
|
requires_login
|
||||||
|
|
||||||
|
def index
|
||||||
|
begin
|
||||||
|
customer = DiscourseSubscriptions::Customer.find_by(user_id: current_user.id, product_id: nil)
|
||||||
|
|
||||||
|
data = []
|
||||||
|
|
||||||
|
if customer.present?
|
||||||
|
payments = ::Stripe::PaymentIntent.list(customer: customer[:customer_id])
|
||||||
|
data = payments[:data]
|
||||||
|
end
|
||||||
|
|
||||||
|
render_json_dump data
|
||||||
|
|
||||||
|
rescue ::Stripe::InvalidRequestError => e
|
||||||
|
render_json_error e.message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,50 +0,0 @@
|
|||||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
|
||||||
@computed("confirmation.card.last4")
|
|
||||||
last4() {
|
|
||||||
return this.get("confirmation.card.last4");
|
|
||||||
},
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this._super(...arguments);
|
|
||||||
|
|
||||||
const settings = Discourse.SiteSettings;
|
|
||||||
const amounts = settings.discourse_patrons_amounts.split("|");
|
|
||||||
|
|
||||||
this.setProperties({
|
|
||||||
confirmation: false,
|
|
||||||
currency: settings.discourse_donations_currency,
|
|
||||||
amounts,
|
|
||||||
amount: amounts[0]
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
closeModal() {
|
|
||||||
this.set("paymentError", false);
|
|
||||||
this.set("confirmation", false);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleConfirmStripeCard(paymentMethod, receiptEmail) {
|
|
||||||
this.set("receiptEmail", receiptEmail);
|
|
||||||
this.set("confirmation", paymentMethod);
|
|
||||||
},
|
|
||||||
|
|
||||||
confirmStripeCard() {
|
|
||||||
const data = {
|
|
||||||
payment_method_id: this.confirmation.id,
|
|
||||||
amount: this.amount,
|
|
||||||
receipt_email: this.receiptEmail
|
|
||||||
};
|
|
||||||
|
|
||||||
this.stripePaymentHandler(data).then(paymentIntent => {
|
|
||||||
if (paymentIntent.error) {
|
|
||||||
this.set("paymentError", paymentIntent.error);
|
|
||||||
} else {
|
|
||||||
this.paymentSuccessHandler(paymentIntent.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,5 +0,0 @@
|
|||||||
export default Ember.Component.extend({
|
|
||||||
classNames: "donation-list",
|
|
||||||
hasSubscriptions: Ember.computed.notEmpty("subscriptions"),
|
|
||||||
hasCharges: Ember.computed.notEmpty("charges")
|
|
||||||
});
|
|
@ -1,77 +0,0 @@
|
|||||||
export default Ember.Component.extend({
|
|
||||||
init() {
|
|
||||||
this._super(...arguments);
|
|
||||||
|
|
||||||
const settings = Discourse.SiteSettings;
|
|
||||||
|
|
||||||
this.setProperties({
|
|
||||||
cardError: false,
|
|
||||||
color: jQuery("body").css("color"),
|
|
||||||
backgroundColor: jQuery("body").css("background-color"),
|
|
||||||
stripe: Stripe(settings.discourse_subscriptions_public_key)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
didInsertElement() {
|
|
||||||
this._super(...arguments);
|
|
||||||
|
|
||||||
const color = this.get("color");
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
base: {
|
|
||||||
color,
|
|
||||||
iconColor: color,
|
|
||||||
"::placeholder": { color }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const elements = this.stripe.elements();
|
|
||||||
const card = elements.create("card", { style, hidePostalCode: true });
|
|
||||||
|
|
||||||
card.mount("#card-element");
|
|
||||||
|
|
||||||
this.set("card", card);
|
|
||||||
|
|
||||||
card.on("change", result => {
|
|
||||||
this.set("cardError", false);
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
this.set("cardError", result.error.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
validateBilling() {
|
|
||||||
const billing = this.get("billing");
|
|
||||||
const deleteEmpty = key => {
|
|
||||||
if (Ember.isEmpty(billing.get(key))) {
|
|
||||||
billing.set(key, undefined);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
["name", "phone"].forEach(key => deleteEmpty(key));
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
submitStripeCard() {
|
|
||||||
this.validateBilling();
|
|
||||||
|
|
||||||
const paymentOptions = { billing_details: this.get("billing") };
|
|
||||||
|
|
||||||
this.stripe.createPaymentMethod("card", this.card, paymentOptions).then(
|
|
||||||
result => {
|
|
||||||
if (result.error) {
|
|
||||||
this.set("cardError", result.error.message);
|
|
||||||
} else {
|
|
||||||
this.handleConfirmStripeCard(
|
|
||||||
result.paymentMethod,
|
|
||||||
this.get("billing.email")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.set("cardError", "Unknown error.");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
@ -18,6 +18,10 @@ export default Ember.Controller.extend({
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
this.set(
|
||||||
|
"paymentsAllowed",
|
||||||
|
Discourse.SiteSettings.discourse_subscriptions_allow_payments
|
||||||
|
);
|
||||||
this.set(
|
this.set(
|
||||||
"stripe",
|
"stripe",
|
||||||
Stripe(Discourse.SiteSettings.discourse_subscriptions_public_key)
|
Stripe(Discourse.SiteSettings.discourse_subscriptions_public_key)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
import EmberObject from "@ember/object";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
|
||||||
const Payment = Discourse.Model.extend({
|
const Payment = EmberObject.extend({
|
||||||
save() {
|
save() {
|
||||||
const data = {
|
const data = {
|
||||||
payment_method: this.payment_method,
|
payment_method: this.payment_method,
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import computed from "ember-addons/ember-computed-decorators";
|
import EmberObject from "@ember/object";
|
||||||
|
import computed from "discourse-common/utils/decorators";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
|
||||||
const Plan = Discourse.Model.extend({
|
const Plan = EmberObject.extend({
|
||||||
amountDollars: Ember.computed("amount", {
|
amountDollars: Ember.computed("amount", {
|
||||||
get() {
|
get() {
|
||||||
return parseFloat(this.get("amount") / 100).toFixed(2);
|
return parseFloat(this.get("amount") / 100).toFixed(2);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
import EmberObject from "@ember/object";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
|
||||||
const Product = Discourse.Model.extend({});
|
const Product = EmberObject.extend({});
|
||||||
|
|
||||||
Product.reopenClass({
|
Product.reopenClass({
|
||||||
findAll() {
|
findAll() {
|
||||||
|
22
assets/javascripts/discourse/models/user-payment.js.es6
Normal file
22
assets/javascripts/discourse/models/user-payment.js.es6
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import EmberObject from "@ember/object";
|
||||||
|
import computed from "discourse-common/utils/decorators";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
|
||||||
|
const UserPayment = EmberObject.extend({
|
||||||
|
@computed("amount")
|
||||||
|
amountDollars(amount) {
|
||||||
|
return parseFloat(amount / 100).toFixed(2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
UserPayment.reopenClass({
|
||||||
|
findAll() {
|
||||||
|
return ajax("/s/user/payments", { method: "get" }).then(result =>
|
||||||
|
result.map(payment => {
|
||||||
|
return UserPayment.create(payment);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default UserPayment;
|
@ -1,8 +1,9 @@
|
|||||||
|
import EmberObject from "@ember/object";
|
||||||
import computed from "ember-addons/ember-computed-decorators";
|
import computed from "ember-addons/ember-computed-decorators";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import Plan from "discourse/plugins/discourse-subscriptions/discourse/models/plan";
|
import Plan from "discourse/plugins/discourse-subscriptions/discourse/models/plan";
|
||||||
|
|
||||||
const UserSubscription = Discourse.Model.extend({
|
const UserSubscription = EmberObject.extend({
|
||||||
@computed("status")
|
@computed("status")
|
||||||
canceled(status) {
|
canceled(status) {
|
||||||
return status === "canceled";
|
return status === "canceled";
|
||||||
|
@ -7,7 +7,7 @@ export default Route.extend({
|
|||||||
const product_id = params["subscription-id"];
|
const product_id = params["subscription-id"];
|
||||||
|
|
||||||
const product = Product.find(product_id);
|
const product = Product.find(product_id);
|
||||||
const plans = Plan.findAll({ product_id: product_id });
|
const plans = Plan.findAll({ product_id });
|
||||||
|
|
||||||
return Ember.RSVP.hash({ plans, product });
|
return Ember.RSVP.hash({ plans, product });
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import Route from "@ember/routing/route";
|
import Route from "@ember/routing/route";
|
||||||
import Invoice from "discourse/plugins/discourse-subscriptions/discourse/models/invoice";
|
import UserPayment from "discourse/plugins/discourse-subscriptions/discourse/models/user-payment";
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
templateName: "user/billing/payments",
|
templateName: "user/billing/payments",
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
return Invoice.findAll();
|
return UserPayment.findAll();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
{{#if hasSubscriptions}}
|
|
||||||
<div class="subscription-list">
|
|
||||||
<div class="underline">{{i18n 'discourse_donations.donations.subscriptions'}}</div>
|
|
||||||
<ul>
|
|
||||||
{{#each subscriptions as |s|}}
|
|
||||||
<li>{{donation-row subscription=s.subscription customer=customer new=s.new}}</li>
|
|
||||||
{{#if s.invoices}}
|
|
||||||
<ul>
|
|
||||||
{{#each s.invoices as |invoice|}}
|
|
||||||
<li>{{donation-row invoice=invoice customer=customer new=s.new}}</li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
{{/if}}
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if hasCharges}}
|
|
||||||
<div class="charge-list">
|
|
||||||
<div class='underline'>{{i18n 'discourse_donations.donations.charges'}}</div>
|
|
||||||
<ul>
|
|
||||||
{{#each charges as |charge|}}
|
|
||||||
<li>{{donation-row charge=charge customer=customer new=charge.new}}</li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
@ -1,23 +1,25 @@
|
|||||||
|
|
||||||
<div class="subscribe-buttons">
|
{{#if paymentsAllowed}}
|
||||||
{{#ds-button
|
<div class="subscribe-buttons">
|
||||||
id="discourse-subscriptions-payment-type-plan"
|
{{#ds-button
|
||||||
selected=planButtonSelected
|
id="discourse-subscriptions-payment-type-plan"
|
||||||
action="selectPlans"
|
selected=planButtonSelected
|
||||||
class="btn-discourse-subscriptions-payment-type"
|
action="selectPlans"
|
||||||
}}
|
class="btn-discourse-subscriptions-payment-type"
|
||||||
{{i18n "discourse_subscriptions.plans.purchase"}}
|
}}
|
||||||
{{/ds-button}}
|
{{i18n "discourse_subscriptions.plans.purchase"}}
|
||||||
|
{{/ds-button}}
|
||||||
|
|
||||||
{{#ds-button
|
{{#ds-button
|
||||||
id="discourse-subscriptions-payment-type-payment"
|
id="discourse-subscriptions-payment-type-payment"
|
||||||
selected=paymentButtonSelected
|
selected=paymentButtonSelected
|
||||||
action="selectPayments"
|
action="selectPayments"
|
||||||
class="btn-discourse-subscriptions-payment-type"
|
class="btn-discourse-subscriptions-payment-type"
|
||||||
}}
|
}}
|
||||||
{{i18n "discourse_subscriptions.payment.purchase"}}
|
{{i18n "discourse_subscriptions.payment.purchase"}}
|
||||||
{{/ds-button}}
|
{{/ds-button}}
|
||||||
</div>
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
|
|
||||||
<div id="stripe-elements">
|
|
||||||
<div id="card-element"></div>
|
|
||||||
<div id="card-action">
|
|
||||||
{{#d-button action="submitStripeCard" class="btn btn-primary btn-payment btn-discourse-patrons"}}
|
|
||||||
{{i18n 'discourse_subscriptions.buttons.make_payment'}} {{format-curency amount}}
|
|
||||||
{{/d-button}}
|
|
||||||
|
|
||||||
{{#if cardError}}
|
|
||||||
<div class="popup-tip bad">
|
|
||||||
{{cardError}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -4,6 +4,9 @@
|
|||||||
<h2>
|
<h2>
|
||||||
{{model.product.name}}
|
{{model.product.name}}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{{model.product.description}}
|
{{model.product.description}}
|
||||||
</p>
|
</p>
|
||||||
@ -14,8 +17,11 @@
|
|||||||
{{i18n 'discourse_subscriptions.subscribe.card.title'}}
|
{{i18n 'discourse_subscriptions.subscribe.card.title'}}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
{{payment-options
|
{{payment-options
|
||||||
plans=model.plans
|
plans=model.plans
|
||||||
|
paymentsAllowed=paymentsAllowed
|
||||||
planTypeIsSelected=planTypeIsSelected
|
planTypeIsSelected=planTypeIsSelected
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
@ -1,22 +1,15 @@
|
|||||||
|
|
||||||
{{#if model}}
|
{{#if model}}
|
||||||
<table class="topic-list">
|
<table class="table discourse-subscriptions-user-table">
|
||||||
<thead>
|
<thead>
|
||||||
<th>{{i18n 'discourse_subscriptions.user.billing.invoices.amount'}}</th>
|
<th>{{i18n 'discourse_subscriptions.user.payments.id'}}</th>
|
||||||
<th>{{i18n 'discourse_subscriptions.user.billing.invoices.number'}}</th>
|
<th>{{i18n 'discourse_subscriptions.user.payments.amount'}}</th>
|
||||||
<th>{{i18n 'discourse_subscriptions.user.billing.invoices.created_at'}}</th>
|
<th>{{i18n 'discourse_subscriptions.user.payments.created_at'}}</th>
|
||||||
<th></th>
|
|
||||||
</thead>
|
</thead>
|
||||||
{{#each model as |invoice|}}
|
{{#each model as |payment|}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{invoice.amount_paid}}</td>
|
<td>{{payment.id}}</td>
|
||||||
<td>{{invoice.number}}</td>
|
<td>{{format-currency payment.currency payment.amountDollars}}</td>
|
||||||
<td>{{format-unix-date invoice.created}}</td>
|
<td>{{format-unix-date payment.created}}</td>
|
||||||
<td class="td-right">
|
|
||||||
<a href="{{invoice.invoice_pdf}}" class="btn btn-icon">
|
|
||||||
{{d-icon "download"}}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</table>
|
</table>
|
||||||
|
@ -1,34 +1,32 @@
|
|||||||
{{#d-section pageClass="user-subscriptions" class="user-content"}}
|
{{#if model}}
|
||||||
{{#if model}}
|
<table class="table discourse-subscriptions-user-table">
|
||||||
<table class="table discourse-subscriptions-user-table">
|
<thead>
|
||||||
<thead>
|
<th>{{i18n 'discourse_subscriptions.user.subscriptions.id'}}</th>
|
||||||
<th>{{i18n 'discourse_subscriptions.user.subscriptions.id'}}</th>
|
<th>{{i18n 'discourse_subscriptions.user.plans.product'}}</th>
|
||||||
<th>{{i18n 'discourse_subscriptions.user.plans.product'}}</th>
|
<th>{{i18n 'discourse_subscriptions.user.plans.rate'}}</th>
|
||||||
<th>{{i18n 'discourse_subscriptions.user.plans.rate'}}</th>
|
<th>{{i18n 'discourse_subscriptions.user.subscriptions.status'}}</th>
|
||||||
<th>{{i18n 'discourse_subscriptions.user.subscriptions.status'}}</th>
|
<th>{{i18n 'discourse_subscriptions.user.subscriptions.created_at'}}</th>
|
||||||
<th>{{i18n 'discourse_subscriptions.user.subscriptions.created_at'}}</th>
|
<th></th>
|
||||||
<th></th>
|
</thead>
|
||||||
</thead>
|
{{#each model as |subscription|}}
|
||||||
{{#each model as |subscription|}}
|
<tr>
|
||||||
<tr>
|
<td>{{subscription.id}}</td>
|
||||||
<td>{{subscription.id}}</td>
|
<td>{{subscription.product.name}}</td>
|
||||||
<td>{{subscription.product.name}}</td>
|
<td>{{subscription.plan.subscriptionRate}}</td>
|
||||||
<td>{{subscription.plan.subscriptionRate}}</td>
|
<td>{{subscription.status}}</td>
|
||||||
<td>{{subscription.status}}</td>
|
<td>{{format-unix-date subscription.created}}</td>
|
||||||
<td>{{format-unix-date subscription.created}}</td>
|
<td class="td-right">
|
||||||
<td class="td-right">
|
{{#if subscription.loading}}
|
||||||
{{#if subscription.loading}}
|
{{loading-spinner size="small"}}
|
||||||
{{loading-spinner size="small"}}
|
{{else}}
|
||||||
{{else}}
|
{{d-button disabled=subscription.canceled label="cancel" action=(route-action "cancelSubscription" subscription) icon="times"}}
|
||||||
{{d-button disabled=subscription.canceled label="cancel" action=(route-action "cancelSubscription" subscription) icon="times"}}
|
{{/if}}
|
||||||
{{/if}}
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
{{/each}}
|
||||||
{{/each}}
|
</table>
|
||||||
</table>
|
{{else}}
|
||||||
{{else}}
|
<div class="alert alert-info">
|
||||||
<div class="alert alert-info">
|
{{i18n 'discourse_subscriptions.user.subscriptions_help'}}
|
||||||
{{i18n 'discourse_subscriptions.user.subscriptions_help'}}
|
</div>
|
||||||
</div>
|
{{/if}}
|
||||||
{{/if}}
|
|
||||||
{{/d-section}}
|
|
||||||
|
@ -4,13 +4,8 @@ en:
|
|||||||
discourse_subscriptions_extra_nav_subscribe: Show the subscribe button in the primary navigation
|
discourse_subscriptions_extra_nav_subscribe: Show the subscribe button in the primary navigation
|
||||||
discourse_subscriptions_public_key: Stripe Publishable Key
|
discourse_subscriptions_public_key: Stripe Publishable Key
|
||||||
discourse_subscriptions_secret_key: Stripe Secret Key
|
discourse_subscriptions_secret_key: Stripe Secret Key
|
||||||
discourse_subscriptions_currency: Default Currency Code. This can be overridden when creating a subscription plan
|
discourse_subscriptions_currency: Default Currency Code. This can be overridden when creating a subscription plan.
|
||||||
discourse_patrons_zip_code: "Show Zip Code"
|
discourse_subscriptions_allow_payments: Allow single payments
|
||||||
discourse_patrons_billing_address: "Collect billing address"
|
|
||||||
discourse_patrons_payment_page: "Text to be added to enter payments page. Markdown is supported."
|
|
||||||
discourse_patrons_success_page: "Text to be added to success page. Markdown is supported."
|
|
||||||
discourse_patrons_payment_description: "This is sent to Stripe and shows in the payment information"
|
|
||||||
discourse_patrons_amounts: "Payment amounts a user can select"
|
|
||||||
errors:
|
errors:
|
||||||
discourse_patrons_amount_must_be_currency: "Currency amounts must be currencies without dollar symbol (eg 1.50)"
|
discourse_patrons_amount_must_be_currency: "Currency amounts must be currencies without dollar symbol (eg 1.50)"
|
||||||
js:
|
js:
|
||||||
@ -50,19 +45,11 @@ en:
|
|||||||
validate:
|
validate:
|
||||||
payment_options:
|
payment_options:
|
||||||
required: Please select a payment option.
|
required: Please select a payment option.
|
||||||
one_time:
|
|
||||||
heading:
|
|
||||||
payment: Make a Payment
|
|
||||||
success: Thank you!
|
|
||||||
payment:
|
|
||||||
optional: Optional
|
|
||||||
receipt_info: A receipt is sent to this email address
|
|
||||||
your_information: Your information
|
|
||||||
payment_information: Payment information
|
|
||||||
payment_confirmation: Confirm information
|
|
||||||
amount: Amount
|
|
||||||
payment_intent_id: Payment ID
|
|
||||||
user:
|
user:
|
||||||
|
payments:
|
||||||
|
id: Payment ID
|
||||||
|
amount: Amount
|
||||||
|
created_at: Created
|
||||||
payments_help: There are no payments
|
payments_help: There are no payments
|
||||||
plans:
|
plans:
|
||||||
rate: Rate
|
rate: Rate
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
en:
|
en:
|
||||||
discourse_subscriptions:
|
discourse_subscriptions:
|
||||||
customer_not_found: Customer not found
|
customer_not_found: Customer not found
|
||||||
|
card:
|
||||||
|
declined: Card Declined
|
||||||
|
@ -13,6 +13,7 @@ DiscourseSubscriptions::Engine.routes.draw do
|
|||||||
end
|
end
|
||||||
|
|
||||||
namespace :user do
|
namespace :user do
|
||||||
|
resources :payments, only: [:index]
|
||||||
resources :subscriptions, only: [:index, :destroy]
|
resources :subscriptions, only: [:index, :destroy]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -10,21 +10,9 @@ plugins:
|
|||||||
discourse_subscriptions_secret_key:
|
discourse_subscriptions_secret_key:
|
||||||
default: ''
|
default: ''
|
||||||
client: false
|
client: false
|
||||||
discourse_patrons_payment_page:
|
discourse_subscriptions_allow_payments:
|
||||||
|
default: false
|
||||||
client: true
|
client: true
|
||||||
default: ''
|
|
||||||
discourse_patrons_success_page:
|
|
||||||
client: true
|
|
||||||
default: ''
|
|
||||||
discourse_patrons_payment_description:
|
|
||||||
client: true
|
|
||||||
default: ''
|
|
||||||
discourse_patrons_amounts:
|
|
||||||
client: true
|
|
||||||
type: list
|
|
||||||
default: '1.00|2.00|5.00|10.00|20.00|50.00|100.00'
|
|
||||||
regex: "^([0-9]+.[0-9]{2}\\|)+[0-9]+.[0-9]{2}$"
|
|
||||||
regex_error: "site_settings.errors.discourse_patrons_amount_must_be_currency"
|
|
||||||
discourse_subscriptions_currency:
|
discourse_subscriptions_currency:
|
||||||
client: true
|
client: true
|
||||||
default: "USD"
|
default: "USD"
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 74 KiB |
BIN
doc/user-payments.png
Normal file
BIN
doc/user-payments.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 36 KiB |
@ -57,6 +57,7 @@ after_initialize do
|
|||||||
"../app/controllers/admin/plans_controller",
|
"../app/controllers/admin/plans_controller",
|
||||||
"../app/controllers/admin/products_controller",
|
"../app/controllers/admin/products_controller",
|
||||||
"../app/controllers/admin/subscriptions_controller",
|
"../app/controllers/admin/subscriptions_controller",
|
||||||
|
"../app/controllers/user/payments_controller",
|
||||||
"../app/controllers/user/subscriptions_controller",
|
"../app/controllers/user/subscriptions_controller",
|
||||||
"../app/controllers/customers_controller",
|
"../app/controllers/customers_controller",
|
||||||
"../app/controllers/invoices_controller",
|
"../app/controllers/invoices_controller",
|
||||||
|
36
spec/requests/user/payments_controller_spec.rb
Normal file
36
spec/requests/user/payments_controller_spec.rb
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
module DiscourseSubscriptions
|
||||||
|
RSpec.describe User::PaymentsController do
|
||||||
|
it 'is a subclass of ApplicationController' do
|
||||||
|
expect(DiscourseSubscriptions::User::PaymentsController < ::ApplicationController).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "not authenticated" do
|
||||||
|
it "does not get the payment intents" do
|
||||||
|
::Stripe::PaymentIntent.expects(:list).never
|
||||||
|
get "/s/user/payments.json"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "authenticated" do
|
||||||
|
let(:user) { Fabricate(:user, email: 'zasch@example.com') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
Fabricate(:customer, customer_id: 'c_345678', user_id: user.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "gets payment intents" do
|
||||||
|
::Stripe::PaymentIntent.expects(:list).with(
|
||||||
|
customer: 'c_345678'
|
||||||
|
)
|
||||||
|
|
||||||
|
get "/s/user/payments.json"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -17,16 +17,22 @@ componentTest("Discourse Subscriptions payment options have no plans", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
componentTest("Discourse Subscriptions payment options has content", {
|
componentTest("Discourse Subscriptions payment options has content", {
|
||||||
template: `{{payment-options plans=plans planTypeIsSelected=planTypeIsSelected}}`,
|
template: `{{payment-options
|
||||||
|
paymentsAllowed=paymentsAllowed
|
||||||
|
plans=plans
|
||||||
|
planTypeIsSelected=planTypeIsSelected}}`,
|
||||||
|
|
||||||
async test(assert) {
|
beforeEach() {
|
||||||
this.set("plans", [
|
this.set("plans", [
|
||||||
{ currency: "aud", interval: "year", amountDollars: "44.99" },
|
{ currency: "aud", interval: "year", amountDollars: "44.99" },
|
||||||
{ currency: "gdp", interval: "month", amountDollars: "9.99" }
|
{ currency: "gdp", interval: "month", amountDollars: "9.99" }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.set("planTypeIsSelected", true);
|
this.set("planTypeIsSelected", true);
|
||||||
|
this.set("paymentsAllowed", true);
|
||||||
|
},
|
||||||
|
|
||||||
|
async test(assert) {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
find(".btn-discourse-subscriptions-payment-type").length,
|
find(".btn-discourse-subscriptions-payment-type").length,
|
||||||
2,
|
2,
|
||||||
@ -59,14 +65,46 @@ componentTest("Discourse Subscriptions payment options has content", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
componentTest("Discourse Subscriptions payments allowed setting", {
|
||||||
|
template: `{{payment-options plans=plans paymentsAllowed=paymentsAllowed}}`,
|
||||||
|
|
||||||
|
async test(assert) {
|
||||||
|
this.set("paymentsAllowed", true);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
find("#discourse-subscriptions-payment-type-plan").length,
|
||||||
|
"The plan type button displayed"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
find("#discourse-subscriptions-payment-type-payment").length,
|
||||||
|
"The payment type button displayed"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.set("paymentsAllowed", false);
|
||||||
|
|
||||||
|
assert.notOk(
|
||||||
|
find("#discourse-subscriptions-payment-type-plan").length,
|
||||||
|
"The plan type button hidden"
|
||||||
|
);
|
||||||
|
assert.notOk(
|
||||||
|
find("#discourse-subscriptions-payment-type-payment").length,
|
||||||
|
"The payment type button hidden"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
componentTest("Discourse Subscriptions payment type plan", {
|
componentTest("Discourse Subscriptions payment type plan", {
|
||||||
template: `{{payment-options plans=plans planTypeIsSelected=planTypeIsSelected}}`,
|
template: `{{payment-options
|
||||||
|
paymentsAllowed=paymentsAllowed
|
||||||
|
plans=plans
|
||||||
|
planTypeIsSelected=planTypeIsSelected}}`,
|
||||||
|
|
||||||
async test(assert) {
|
async test(assert) {
|
||||||
this.set("plans", [
|
this.set("plans", [
|
||||||
{ currency: "aud", interval: "year", amountDollars: "44.99" }
|
{ currency: "aud", interval: "year", amountDollars: "44.99" }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
this.set("paymentsAllowed", true);
|
||||||
this.set("planTypeIsSelected", true);
|
this.set("planTypeIsSelected", true);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import componentTest from "helpers/component-test";
|
|
||||||
import { stubStripe } from "discourse/plugins/discourse-subscriptions/helpers/stripe";
|
|
||||||
|
|
||||||
moduleForComponent("stripe-card", { integration: true });
|
|
||||||
|
|
||||||
componentTest("Discourse Patrons stripe card success", {
|
|
||||||
template: `{{stripe-card handleConfirmStripeCard=onSubmit billing=billing}}`,
|
|
||||||
|
|
||||||
beforeEach() {
|
|
||||||
stubStripe();
|
|
||||||
|
|
||||||
this.set(
|
|
||||||
"billing",
|
|
||||||
Ember.Object.create({
|
|
||||||
name: "",
|
|
||||||
email: "",
|
|
||||||
phone: ""
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
async test(assert) {
|
|
||||||
assert.expect(1);
|
|
||||||
|
|
||||||
this.set("onSubmit", () => {
|
|
||||||
assert.ok(true, "payment method created");
|
|
||||||
});
|
|
||||||
|
|
||||||
await click(".btn-payment");
|
|
||||||
}
|
|
||||||
});
|
|
Loading…
x
Reference in New Issue
Block a user