initial components

This commit is contained in:
Rimian Perkins 2019-09-11 20:19:50 +10:00
parent af97581911
commit ee2be672f7
11 changed files with 465 additions and 0 deletions

View File

@ -0,0 +1,42 @@
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;
this.setProperties({
confirmation: false,
currency: settings.discourse_donations_currency,
});
},
actions: {
closeModal() {
this.set('paymentError', false);
this.set('confirmation', false);
},
handleConfirmStripeCard(paymentMethod) {
this.set('confirmation', paymentMethod);
},
confirmStripeCard() {
const paymentMethodId = this.confirmation.id;
this.stripePaymentHandler(paymentMethodId, this.amount).then((paymentIntent) => {
if (paymentIntent.error) {
this.set('paymentError', paymentIntent.error);
}
else {
console.log('ok done');
}
});
},
},
});

View File

@ -0,0 +1,5 @@
export default Ember.Component.extend({
classNames: "donation-list",
hasSubscriptions: Ember.computed.notEmpty("subscriptions"),
hasCharges: Ember.computed.notEmpty("charges")
});

View File

@ -0,0 +1,99 @@
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { formatAnchor, formatAmount } from "../lib/donation-utilities";
import { default as computed } from "ember-addons/ember-computed-decorators";
import showModal from "discourse/lib/show-modal";
export default Ember.Component.extend({
classNameBindings: [":donation-row", "canceled", "updating"],
includePrefix: Ember.computed.or("invoice", "charge"),
canceled: Ember.computed.equal("subscription.status", "canceled"),
@computed("subscription", "invoice", "charge", "customer")
data(subscription, invoice, charge, customer) {
if (subscription) {
return $.extend({}, subscription.plan, {
anchor: subscription.billing_cycle_anchor
});
} else if (invoice) {
let receiptSent = false;
if (invoice.receipt_number && customer.email) {
receiptSent = true;
}
return $.extend({}, invoice.lines.data[0], {
anchor: invoice.date,
invoiceLink: invoice.invoice_pdf,
receiptSent
});
} else if (charge) {
let receiptSent = false;
if (charge.receipt_number && charge.receipt_email) {
receiptSent = true;
}
return $.extend({}, charge, {
anchor: charge.created,
receiptSent
});
}
},
@computed("data.currency")
currency(currency) {
return currency ? currency.toUpperCase() : null;
},
@computed("data.amount", "currency")
amount(amount, currency) {
return formatAmount(amount, currency);
},
@computed("data.interval")
interval(interval) {
return interval || "once";
},
@computed("data.anchor", "interval")
period(anchor, interval) {
return I18n.t(`discourse_donations.period.${interval}`, {
anchor: formatAnchor(interval, moment.unix(anchor))
});
},
cancelSubscription() {
const subscriptionId = this.get("subscription.id");
this.set("updating", true);
ajax("/donate/charges/cancel-subscription", {
data: {
subscription_id: subscriptionId
},
method: "put"
})
.then(result => {
if (result.success) {
this.set("subscription", result.subscription);
}
})
.catch(popupAjaxError)
.finally(() => {
this.set("updating", false);
});
},
actions: {
cancelSubscription() {
showModal("cancel-subscription", {
model: {
currency: this.get("currency"),
amount: this.get("amount"),
period: this.get("period"),
confirm: () => this.cancelSubscription()
}
});
}
}
});

View File

@ -0,0 +1,59 @@
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_patrons_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);
}
});
},
actions: {
submitStripeCard() {
this.stripe.createPaymentMethod('card', this.card).then((result) => {
if (result.error) {
this.set('cardError', result.error.message);
}
else {
this.handleConfirmStripeCard(result.paymentMethod);
}
}, () => {
this.set('cardError', 'Unknown error.');
});
},
},
});

View File

@ -0,0 +1,57 @@
{{#if confirmation}}
{{#d-modal closeModal=(action "closeModal") modalStyle="inline-modal" title=(i18n "discourse_donations.confirm")}}
{{#d-modal-body}}
<table class="discourse-donations-confirmation">
<tr>
<td>Amount</td>
<td class="discourse-donations-amount">{{amount}}</td>
</tr>
<tr>
<td>Card</td>
<td class="discourse-donations-last4">.... .... .... {{last4}}</td>
</tr>
</table>
{{/d-modal-body}}
<div class='modal-footer'>
{{#d-button action="confirmStripeCard" class="btn btn-primary btn-payment"}}
{{i18n 'discourse_donations.submit'}} {{amount}}
{{/d-button}}
</div>
{{/d-modal}}
{{#if paymentError}}
<div class="popup-tip bad">
{{paymentError}}
</div>
{{/if}}
{{else}}
<div class="discourse-donations-section-columns">
<div class="section-column">
<h3>Your information</h3>
<div class="user-controls">
<div class="display-row">
<div class="field">Payment Amount</div>
<div class="value">
{{input value=amount}}<br>
Enter {{currency}}
</div>
</div>
</div>
</div>
<div class="section-column">
<div style>
{{stripe-card
amount=amount
currency=currency
handleConfirmStripeCard=(action "handleConfirmStripeCard")
}}
</div>
</div>
</div>
{{/if}}

View File

@ -0,0 +1,28 @@
{{#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}}

View File

@ -0,0 +1,41 @@
{{#if includePrefix}}
<span>{{i18n 'discourse_donations.invoice_prefix'}}</span>
{{/if}}
<span class="donation-row-currency">{{currency}}</span>
<span class="donation-row-amount">{{amount}}</span>
<span class="donation-row-period">{{period}}</span>
{{#if invoice}}
<a href='{{data.invoiceLink}}' target='_blank'>({{i18n 'discourse_donations.invoice'}})</a>
{{/if}}
{{#if currentUser}}
{{#if subscription}}
<span class="donation-row-subscription">
{{#if updating}}
{{loading-spinner size='small'}}
{{else}}
{{#unless canceled}}
<a {{action 'cancelSubscription'}}>
{{i18n 'cancel'}}
</a>
{{/unless}}
{{/if}}
</span>
{{/if}}
{{/if}}
{{#if receiptSent}}
<span></span>
<span>{{i18n 'discourse_donations.receipt' email=customer.email}}</span>
{{/if}}
{{#if new}}
<span class="new-flag">
{{d-icon 'circle'}}
<span>{{i18n 'new_item'}}</span>
</span>
{{/if}}

View File

@ -0,0 +1,16 @@
<h3>Credit card information</h3>
<div id="card-element"></div>
<div id="card-action">
{{#d-button action="submitStripeCard" class="btn btn-primary btn-payment"}}
{{i18n 'discourse_donations.confirm'}}<br>{{amount}}
{{/d-button}}
{{#if cardError}}
<div class="popup-tip bad">
{{cardError}}
</div>
{{/if}}
</div>

View File

@ -0,0 +1,45 @@
import componentTest from "helpers/component-test";
moduleForComponent("donation-form", { integration: true });
componentTest("Discourse Patrons donation form has content", {
template: `{{donation-form}}`,
beforeEach() {
this.registry.register(
"component:stripe-card",
Ember.Component.extend({ tagName: "dummy-component-tag" })
);
},
async test(assert) {
assert.ok(find("#payment-form").length, "The form renders");
assert.ok(
find("dummy-component-tag").length,
"The stripe component renders"
);
}
});
componentTest("donation form has a confirmation", {
template: `{{donation-form confirmation=confirmation}}`,
beforeEach() {
this.registry.register(
"component:stripe-card",
Ember.Component.extend()
);
},
async test(assert) {
this.set("confirmation", { "card": { "last4": "4242" }});
const confirmExists = find(".discourse-donations-confirmation").length;
assert.ok(confirmExists, "The confirmation form renders");
const last4 = find(".discourse-donations-last4").text().trim();
assert.equal(last4, ".... .... .... 4242", "The last 4 digits are correct");
}
});

View File

@ -0,0 +1,33 @@
import componentTest from "helpers/component-test";
moduleForComponent("donation-row", { integration: true });
componentTest("Discourse Patrons donation-row", {
template: `{{donation-row currency=3 amount=21 period='monthly'}}`,
test(assert) {
assert.equal(find(".donation-row-currency").text(), "3", "It has currency");
assert.equal(find(".donation-row-amount").text(), "21", "It has an amount");
assert.equal(
find(".donation-row-period").text(),
"monthly",
"It has a period"
);
}
});
componentTest("donation-row cancels subscription", {
template: `{{donation-row currentUser=currentUser subscription=subscription}}`,
beforeEach() {
this.set("currentUser", true);
this.set("subscription", true);
},
async test(assert) {
assert.ok(
find(".donation-row-subscription").length,
"It has a subscription"
);
}
});

View File

@ -0,0 +1,40 @@
import componentTest from "helpers/component-test";
moduleForComponent("stripe-card", { integration: true });
componentTest("Discourse Patrons stripe card success", {
template: `{{stripe-card handleConfirmStripeCard=onSubmit}}`,
beforeEach() {
window.Stripe = () => {
return {
createPaymentMethod() {
return new Ember.RSVP.Promise((resolve) => {
resolve('payment-method-response');
});
},
elements() {
return {
create() {
return {
on() {},
card() {},
mount() {},
};
},
};
},
};
};
},
async test(assert) {
assert.expect(1);
this.set("onSubmit", (arg) => {
assert.equal(arg, "payment-method-response", "payment method created");
});
await click(".btn-payment");
},
});