This commit is contained in:
Angus McLeod
2018-06-25 18:14:50 +10:00
parent 90f5083fb6
commit 8453d5cc03
23 changed files with 406 additions and 103 deletions
@@ -0,0 +1,5 @@
export default Ember.Component.extend({
classNames: 'donation-list',
hasSubscriptions: Ember.computed.notEmpty('subscriptions'),
hasCharges: Ember.computed.notEmpty('charges')
})
@@ -1,5 +1,6 @@
import { ajax } from 'discourse/lib/ajax';
import { getRegister } from 'discourse-common/lib/get-owner';
import { formatAnchor, zeroDecimalCurrencies } from '../lib/donation-utilities';
import { default as computed } from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
@@ -11,12 +12,13 @@ export default Ember.Component.extend({
init() {
this._super();
this.set('anon', (!Discourse.User.current()));
this.set('settings', getRegister(this).lookup('site-settings:main'));
this.set('create_accounts', this.get('anon') && this.get('settings').discourse_donations_enable_create_accounts);
this.set('stripe', Stripe(this.get('settings').discourse_donations_public_key));
const user = this.get('currentUser');
const settings = Discourse.SiteSettings;
const types = Discourse.SiteSettings.discourse_donations_types.split('|') || [];
this.set('create_accounts', !user && settings.discourse_donations_enable_create_accounts);
this.set('stripe', Stripe(settings.discourse_donations_public_key));
const types = settings.discourse_donations_types.split('|') || [];
const amounts = this.get('donateAmounts');
this.setProperties({
@@ -38,21 +40,7 @@ export default Ember.Component.extend({
@computed('type')
period(type) {
let anchor;
if (type === 'weekly') {
anchor = moment().format('dddd');
}
if (type === 'monthly') {
anchor = moment().format('Do');
}
if (type === 'yearly') {
anchor = moment().format('MMMM D');
}
return I18n.t(`discourse_donations.period.${type}`, { anchor });
return I18n.t(`discourse_donations.period.${type}`, { anchor: formatAnchor(type) });
},
@computed
@@ -73,9 +61,23 @@ export default Ember.Component.extend({
@computed('stripe')
card(stripe) {
let elements = stripe.elements();
return elements.create('card', {
hidePostalCode: !this.get('settings').discourse_donations_zip_code
let card = elements.create('card', {
hidePostalCode: !Discourse.SiteSettings.discourse_donations_zip_code
});
card.addEventListener('change', (event) => {
if (event.error) {
this.set('stripeError', event.error.message);
} else {
this.set('stripeError', '');
}
if (event.elementType === 'card' && event.complete) {
this.set('stripeReady', true);
}
});
return card;
},
@computed('amount')
@@ -92,6 +94,21 @@ export default Ember.Component.extend({
return amount;
},
@computed('currentUser', 'email')
userReady(currentUser, email) {
return currentUser || email;
},
@computed('userReady', 'stripeReady')
formIncomplete(userReady, stripeReady) {
return !userReady || !stripeReady;
},
@computed('transactionInProgress', 'formIncomplete')
disableSubmit(transactionInProgress, formIncomplete) {
return transactionInProgress || formIncomplete;
},
didInsertElement() {
this._super();
this.get('card').mount('#card-element');
@@ -130,27 +147,52 @@ export default Ember.Component.extend({
submitStripeCard() {
let self = this;
self.set('transactionInProgress', true);
this.set('transactionInProgress', true);
this.get('stripe').createToken(this.get('card')).then(data => {
self.set('result', []);
if (data.error) {
self.set('result', data.error.message);
this.setProperties({
stripeError: data.error.message,
stripeReady: false
});
self.endTranscation();
} else {
const transactionFeeEnabled = Discourse.SiteSettings.discourse_donations_enable_transaction_fee;
const amount = transactionFeeEnabled ? this.get('totalAmount') : this.get('amount');
const settings = Discourse.SiteSettings;
const transactionFeeEnabled = settings.discourse_donations_enable_transaction_fee;
let amount = transactionFeeEnabled ? this.get('totalAmount') : this.get('amount');
if (zeroDecimalCurrencies.indexOf(setting.discourse_donations_currency) === -1) {
amount = amount * 100;
}
let params = {
stripeToken: data.token.id,
type: self.get('type'),
amount: amount * 100,
amount,
email: self.get('email'),
username: self.get('username'),
create_account: self.get('create_accounts')
};
if(!self.get('paymentSuccess')) {
ajax('/charges', { data: params, method: 'post' }).then(d => {
ajax('/donate/charges', { data: params, method: 'post' }).then(d => {
let donation = d.donation;
if (donation) {
if (donation.object === 'subscription') {
let subscriptions = this.get('subscriptions') || [];
subscriptions.push(donation);
this.set('subscriptions', subscriptions);
} else if (donation.object === 'charge') {
let charges = this.get('charges') || [];
charges.push(donation);
this.set('charges', charges);
}
}
self.concatMessages(d.messages);
self.endTranscation();
});
@@ -0,0 +1,11 @@
import { default as computed } from 'ember-addons/ember-computed-decorators';
export default Ember.Controller.extend({
loadingDonations: false,
@computed('charges', 'subscriptions')
hasDonations(charges, subscriptions) {
return (charges && charges.length > 0) ||
(subscriptions && subscriptions.length > 0);
}
})
@@ -0,0 +1,56 @@
import { registerHelper } from "discourse-common/lib/helpers";
import { formatAnchor, formatAmount } from '../lib/donation-utilities';
registerHelper("donation-subscription", function([subscription]) {
let currency = subscription.plan.currency.toUpperCase();
let html = currency;
html += ` ${formatAmount(subscription.plan.amount, currency)} `;
html += I18n.t(`discourse_donations.period.${subscription.plan.interval}`, {
anchor: formatAnchor(subscription.plan.interval, moment.unix(subscription.billing_cycle_anchor))
});
return new Handlebars.SafeString(html);
});
registerHelper("donation-invoice", function([invoice]) {
let details = invoice.lines.data[0];
let html = I18n.t('discourse_donations.invoice_prefix');
let currency = details.currency.toUpperCase();
html += ` ${currency}`;
html += ` ${formatAmount(details.amount, currency)} `;
html += I18n.t(`discourse_donations.period.once`, {
anchor: formatAnchor('once', moment.unix(invoice.date))
});
if (invoice.invoice_pdf) {
html += ` (<a href='${invoice.invoice_pdf}' target='_blank'>${I18n.t('discourse_donations.invoice')}</a>)`;
}
return new Handlebars.SafeString(html);
});
registerHelper("donation-charge", function([charge]) {
let html = I18n.t('discourse_donations.invoice_prefix');
let currency = charge.currency.toUpperCase();
html += ` ${currency}`;
html += ` ${formatAmount(charge.amount, currency)} `;
html += I18n.t(`discourse_donations.period.once`, {
anchor: formatAnchor('once', moment.unix(charge.created))
});
if (charge.receipt_email) {
html += `. ${I18n.t('discourse_donations.receipt', {
email: charge.receipt_email
})}`;
}
return new Handlebars.SafeString(html);
});
@@ -0,0 +1,31 @@
const formatAnchor = function(type = null, time = moment()) {
let format;
switch(type) {
case 'once':
format = 'Do MMMM YYYY';
break;
case 'week':
format = 'dddd';
break;
case 'month':
format = 'Do';
break;
case 'year':
format = 'MMMM D';
break;
default:
format = 'dddd';
}
return moment(time).format(format);
}
const zeroDecimalCurrencies = ['MGA', 'BIF', 'CLP', 'PYG', 'DFJ', 'RWF', 'GNF', 'UGX', 'JPY', 'VND', 'VUV', 'XAF', 'KMF', 'KRW', 'XOF', 'XPF'];
const formatAmount = function(amount, currency) {
let zeroDecimal = zeroDecimalCurrencies.indexOf(currency) > -1;
return zeroDecimal ? amount : (amount / 100).toFixed(2);
}
export { formatAnchor, formatAmount, zeroDecimalCurrencies }
@@ -0,0 +1,20 @@
import DiscourseRoute from "discourse/routes/discourse";
import { popupAjaxError } from 'discourse/lib/ajax-error';
import { ajax } from 'discourse/lib/ajax';
export default DiscourseRoute.extend({
setupController(controller) {
controller.set('loadingDonations', true);
ajax('/donate/charges').then((result) => {
if (result && (result.charges || result.subscriptions)) {
controller.setProperties({
charges: result.charges,
subscriptions: result.subscriptions
});
}
}).catch(popupAjaxError).finally(() => {
controller.set('loadingDonations', false);
})
}
});
@@ -0,0 +1,23 @@
{{#if hasCharges}}
<h4>{{i18n 'discourse_donations.donations.charges'}}</h4>
<ul>
{{#each charges as |charge|}}
<li>{{donation-charge charge}}</li>
{{/each}}
</ul>
{{/if}}
{{#if hasSubscriptions}}
<h4>{{i18n 'discourse_donations.donations.subscriptions'}}</h4>
<ul>
{{#each subscriptions as |s|}}
<li class="underline">{{donation-subscription s.subscription}}</li>
{{#if s.invoices}}
{{#each s.invoices as |invoice|}}
<li>{{donation-invoice invoice}}</li>
{{/each}}
{{/if}}
{{/each}}
</ul>
{{/if}}
@@ -1,5 +1,4 @@
<form id="payment-form" class="form-horizontal">
<div class="control-group">
<label class="control-label">
{{i18n 'discourse_donations.type'}}
@@ -48,14 +47,20 @@
<div class="control-group" style="width: 550px;">
<label class="control-label" for="card-element">{{i18n 'discourse_donations.card'}}</label>
<div id="card-element" class="controls"></div>
<div class="controls">
<div id="card-element"></div>
{{#if stripeError}}
<div class="instructions stripe-error">{{stripeError}}</div>
{{/if}}
</div>
</div>
{{#if anon}}
{{#unless currentUser}}
<div class="control-group">
<label class="control-label" for="card-element">{{i18n 'user.email.title'}}</label>
<div class="controls">
{{text-field value=email}}
<div class="instructions">{{i18n 'discourse_donations.email_instructions'}}</div>
</div>
</div>
@@ -81,11 +86,11 @@
</div>
</div>
{{/if}}
{{/if}}
{{/unless}}
<div class="control-group save-button">
<div class="controls">
{{#d-button action="submitStripeCard" disabled=transactionInProgress class="btn btn-primary btn-payment"}}
{{#d-button action="submitStripeCard" disabled=disableSubmit class="btn btn-primary btn-payment"}}
{{#if create_accounts}}
{{i18n 'discourse_donations.submit_with_create_account'}}
{{else}}
@@ -1,5 +1,5 @@
{{#if siteSettings.discourse_donations_enabled}}
<a href="/donate">
{{i18n 'discourse_donations.nav_item'}}
</a>
<a href="/donate">
{{i18n 'discourse_donations.nav_item'}}
</a>
{{/if}}
@@ -1,7 +1,24 @@
<h1>{{i18n 'discourse_donations.title' site_name=siteSettings.title}}</h1>
<h3>{{i18n 'discourse_donations.title' site_name=siteSettings.title}}</h3>
<div class="donations-page-description">
{{cook-text siteSettings.discourse_donations_page_description}}
</div>
<div class="donations-page-payment">
{{stripe-card}}
{{stripe-card charges=charges subscriptions=subscriptions}}
</div>
{{#if currentUser}}
<div class="donations-page-donations">
<h3>{{i18n 'discourse_donations.donations.title'}}</h3>
{{#if loadingDonations}}
{{loading-spinner size='small'}}
{{else}}
{{#if hasDonations}}
{{donation-list charges=charges subscriptions=subscriptions}}
{{else}}
{{i18n 'discourse_donations.donations.none'}}
{{/if}}
{{/if}}
</div>
{{/if}}