Author SHA1 Message Date
Rimian Perkins d857cd7fed add link to T&Cs 2017-06-01 13:50:11 +10:00
Rimian Perkins 93c0b02a45 add price per quarter 2017-05-30 10:12:26 +10:00
Rimian Perkins 52468d17b9 disable button earlier to prevent double clicks 2017-05-27 16:58:21 +10:00
Rimian Perkins 17f5fd2841 remove theme component 2017-05-25 09:15:18 +10:00
Rimian Perkins 255fd7f636 bump: change the plans 2017-05-24 15:26:28 +10:00
Rimian Perkins 824f61aab1 change the plans 2017-05-24 15:26:06 +10:00
Rimian Perkins aff99cb20b bump with hide button 2017-05-24 14:58:56 +10:00
Rimian Perkins ccea023274 add hide donate button 2017-05-24 14:58:20 +10:00
Rimian Perkins 2e91a8e3a3 hide button on success 2017-05-24 14:04:05 +10:00
Rimian Perkins 6ab3de9fca fix success variable 2017-05-24 14:02:14 +10:00
Rimian Perkins 3c431b9915 show success message 2017-05-24 13:55:04 +10:00
Rimian Perkins 1853bdd6f5 Update README.md 2017-05-24 12:46:04 +10:00
Rimian Perkins 8e700c4650 Update README.md 2017-05-24 12:45:35 +10:00
Rimian Perkins 4933ae2233 test more cards 2017-05-24 12:38:54 +10:00
Rimian Perkins 3bdef7f582 more documentation 2017-05-24 12:31:40 +10:00
Rimian Perkins 518cedab1b merge 2017-05-24 12:24:40 +10:00
Rimian Perkins e981471217 change output and test more cards 2017-05-24 12:16:31 +10:00
Rimian Perkins a43f79d7a0 handle some standard stripe fail stuff 2017-05-24 12:03:52 +10:00
Rimian Perkins be122744a3 bump 2017-05-24 11:08:03 +10:00
Rimian Perkins b13cbe663e fix the params 2017-05-24 10:39:57 +10:00
Rimian Perkins f870634afd update success message 2017-05-24 10:34:30 +10:00
Rimian Perkins 6a216d81e5 remove price calc 2017-05-24 10:30:03 +10:00
Rimian Perkins 2f33bdf76b add test fixture for subscriptions 2017-05-24 10:24:41 +10:00
Rimian Perkins 545e962291 buy other plans 2017-05-24 10:16:17 +10:00
Rimian Perkins 904cd4d1d9 check subscription response 2017-05-23 20:14:28 +10:00
Rimian Perkins 49bcd392d4 fix params 2017-05-23 19:41:45 +10:00
Rimian Perkins d531684373 permit create account 2017-05-23 14:36:16 +10:00
Rimian Perkins 377d873f2b fix syntax error 2017-05-23 14:25:25 +10:00
Rimian Perkins f942223eac fix syntax error 2017-05-23 14:23:27 +10:00
Rimian Perkins 83b4e72ff4 add inital plan 2017-05-23 13:54:27 +10:00
Rimian Perkins b80440206b stripe service can create subscriptions 2017-05-23 12:27:34 +10:00
12 changed files with 218 additions and 51 deletions
+25 -1
View File
@@ -16,7 +16,7 @@ Accept donations in Discourse! Integrates with [Stripe](https://stripe.com).
**This is an experimental feature.** A user can create a new account if they makes a successful donation. Enable this in settings. When a user is not logged in, they will be asked to enter details for a new user account. This feature doesn't support mandatory custom user fields yet.
## Testing
## Test Suite
* To run the rails specs, install the plugin and run `bundle exec rake plugin:spec[discourse-donations]` in the discourse root directory.
* To run qunit tests: `MODULE='Acceptance: Discourse Donations' bundle exec rake qunit:test[20000]`.
@@ -28,9 +28,33 @@ Accept donations in Discourse! Integrates with [Stripe](https://stripe.com).
## TODO
* Test card fails
* Handle custom fields
* Acceptance test in RSpec not qunit.
## Tested Credit Card Numbers
These numbers can be used in test mode to simulate a transaction. For more information see the [Stripe docs](https://stripe.com/docs/testing).
Card numbers in **bold** have beed tested.
* **4000 0000 0000 0077** Charge succeeds and funds will be added directly to your available balance (bypassing your pending balance).
* **4000 0000 0000 0093** Charge succeeds and domestic pricing is used (other test cards use international pricing). This card is only significant in countries with split pricing.
* **4000 0000 0000 0010** The address_line1_check and address_zip_check verifications fail. If your account is blocking payments that fail postal code validation, the charge is declined.
* **4000 0000 0000 0028** Charge succeeds but the address_line1_check verification fails.
* **4000 0000 0000 0036** The address_zip_check verification fails. If your account is blocking payments that fail postal code validation, the charge is declined.
* **4000 0000 0000 0044** Charge succeeds but the address_zip_check and address_line1_check verifications are both unavailable.
* **4000 0000 0000 0101** If a CVC number is provided, the cvc_check fails. If your account is blocking payments that fail CVC code validation, the charge is declined.
* **4000 0000 0000 0341** Attaching this card to a Customer object succeeds, but attempts to charge the customer fail.
* **4000 0000 0000 9235** Charge succeeds with a risk_level of elevated and placed into review.
* **4000 0000 0000 0002** Charge is declined with a card_declined code.
* **4100 0000 0000 0019** Charge is declined with a card_declined code and a fraudulent reason.
* **4000 0000 0000 0127** Charge is declined with an incorrect_cvc code.
* **4000 0000 0000 0069** Charge is declined with an expired_card code.
* **4000 0000 0000 0119** Charge is declined with a processing_error code.
* 4242 4242 4242 4241 Charge is declined with an incorrect_number code as the card number fails the Luhn check.
## Warranty
This software comes with no warranty of any kind.
>>>>>>> master
@@ -7,7 +7,7 @@ module DiscourseDonations
skip_before_filter :verify_authenticity_token, only: [:create]
def create
params.permit(:name, :username, :email, :password, :stripeToken, :amount, :create_account)
params.permit(:name, :username, :email, :password, :stripeToken, :amount, :plan, :create_account)
output = { 'messages' => [], 'rewards' => [] }
@@ -30,7 +30,11 @@ module DiscourseDonations
payment = DiscourseDonations::Stripe.new(secret_key, stripe_options)
begin
charge = payment.charge(email, params)
if params['amount'].present?
charge = payment.subscribe(email, params.merge(plan: params[:amount]))
else
charge = payment.subscribe(email, params)
end
rescue ::Stripe::CardError => e
err = e.json_body[:error]
@@ -42,7 +46,9 @@ module DiscourseDonations
render(:json => output) and return
end
if charge['paid'] == true
if charge['paid'] == true || charge['status'] == 'active'
output['success'] = true
output['messages'] << I18n.t('donations.payment.success')
output['rewards'] << { type: :group, name: group_name } if group_name
+2 -8
View File
@@ -24,14 +24,8 @@ module DiscourseDonations
end
def subscribe(email, opts)
customer = ::Stripe::Customer.create(
email: email,
source: opts[:stripeToken]
)
@subscription = ::Stripe::Subscription.create(
customer: customer.id,
plan: opts[:plan]
)
customer = ::Stripe::Customer.create(email: email, source: opts[:stripeToken])
@subscription = ::Stripe::Subscription.create(customer: customer.id, plan: opts[:plan])
@subscription
end
@@ -1,14 +1,19 @@
import { ajax } from 'discourse/lib/ajax';
import { getRegister } from 'discourse-common/lib/get-owner';
const { computed: { alias }, observer } = Ember
export default Ember.Component.extend({
routing: Ember.inject.service('-routing'),
params: alias('routing.router.currentState.routerJsState.fullQueryParams'),
donateAmounts: [
{ value: 1, name: '$1.00'},
{ value: 2, name: '$2.00'},
{ value: 5, name: '$5.00'},
{ value: 10, name: '$10.00'},
{ value: 20, name: '$20.00'},
{ value: 50, name: '$50.00'}
{ value: 'consumer-defender-1', name: 'Consumer Defender: $1.00/quarter'},
{ value: 'consumer-defender-3', name: 'Consumer Defender: $3.00/quarter'},
{ value: 'consumer-defender-6', name: 'Consumer Defender: $6.00/quarter'},
{ value: 'consumer-defender-9', name: 'Consumer Defender: $9.00/quarter'},
{ value: 'consumer-defender-12', name: 'Consumer Defender: $12.00/quarter'},
],
result: [],
amount: null,
@@ -16,6 +21,10 @@ export default Ember.Component.extend({
transactionInProgress: null,
settings: null,
consumerDefenderWithMembership: function() {
return this.get('params.plan') == 'consumer-defender-with-membership';
}.property('params'),
init() {
this._super();
this.set('anon', (Discourse.User.current() == null));
@@ -52,28 +61,39 @@ export default Ember.Component.extend({
submitStripeCard() {
let self = this;
self.set('transactionInProgress', true);
this.get('stripe').createToken(this.get('card')).then(data => {
self.set('result', []);
if (data.error) {
self.set('result', data.error.message);
self.set('transactionInProgress', false);
}
else {
self.set('transactionInProgress', true);
let params = {
stripeToken: data.token.id,
amount: self.get('amount') * 100,
email: self.get('email'),
username: self.get('username'),
create_account: self.get('create_accounts')
};
if(this.get('params.plan')) {
params.plan = this.get('params.plan');
}
else {
params.amount = self.get('amount');
}
if(!self.get('paymentSuccess')) {
ajax('/charges', { data: params, method: 'post' }).then(data => {
self.concatMessages(data.messages);
self.endTranscation();
if(data.success) {
self.setSuccess();
}
});
}
}
@@ -1,14 +1,41 @@
<form id="payment-form" class="form-horizontal">
<div class="control-group">
<label class="control-label" for="card-element">
{{i18n 'discourse_donations.amount'}}
{{settings.discourse_donations_currency}}
</label>
<div class="controls controls-dropdown">
{{combo-box valueAttribute="value" content=donateAmounts value=amount}}
{{#if consumerDefenderWithMembership}}
<h3>[$25/quarter] Consumer Defender + CHOICE Membership</h3>
<ul>
<li>Choice.community Access</li>
<li>Defender Badge (while supplies last)</li>
<li>Priority Expert Advice</li>
<li>All the benefits of CHOICE membership</li>
<li>CHOICE Members Only area</li>
</ul>
{{else}}
<h3>[Pay what you want/quarter] Consumer Defender only</h3>
<ul>
<li>Choice.community Access</li>
<li>Defender Badge (while supplies last)</li>
<li>Priority Expert Advice</li>
</ul>
<div class="control-group">
<label class="control-label" for="card-element">
{{i18n 'discourse_donations.amount'}}
{{settings.discourse_donations_currency}}
</label>
<div class="controls controls-dropdown">
{{combo-box valueAttribute="value" content=donateAmounts value=amount}}
</div>
</div>
</div>
{{/if}}
<hr>
<div class="control-group" style="width: 550px;">
<label class="control-label" for="card-element">{{i18n 'discourse_donations.card'}}</label>
@@ -47,15 +74,22 @@
{{/if}}
{{/if}}
<div class="control-group">
<label class="control-label"></label>
<div class="controls">By making a purchase you agree to our <a href="https://www.choice.com.au/policies-terms-and-conditions/terms-and-conditions">terms and conditions</a></div>
</div>
<div class="control-group save-button">
<div class="controls">
{{#d-button action="submitStripeCard" disabled=transactionInProgress class="btn btn-primary btn-payment"}}
{{#if create_accounts}}
{{i18n 'discourse_donations.submit_with_create_account'}}
{{else}}
{{i18n 'discourse_donations.submit'}}
{{/if}}
{{/d-button}}
{{#unless paymentSuccess}}
{{#d-button action="submitStripeCard" disabled=transactionInProgress class="btn btn-primary btn-payment"}}
{{#if create_accounts}}
{{i18n 'discourse_donations.submit_with_create_account'}}
{{else}}
{{i18n 'discourse_donations.submit'}}
{{/if}}
{{/d-button}}
{{/unless}}
{{#if transactionInProgress}}
{{loading-spinner size="small"}}
@@ -65,8 +99,10 @@
<p>{{{message}}}</p>
{{/each}}
{{#if success}}
<p>{{i18n 'discourse_donations.messages.success'}}</p>
{{#if paymentSuccess}}
{{#if create_accounts}}
<p>Please <strong>check your email</strong> to get your log in link.</p>
{{/if}}
{{/if}}
</div>
</div>
@@ -1,3 +1,5 @@
<a href="/donate">
{{i18n 'discourse_donations.nav_item'}}
</a>
{{#if settings.discourse_donations_show_extra_nav_item}}
<a href="/donate">
{{i18n 'discourse_donations.nav_item'}}
</a>
{{/if}}
+1 -1
View File
@@ -8,4 +8,4 @@ en:
submit: Make Payment
submit_with_create_account: Make Payment and Create Account
messages:
success: Thank you for your donation!
success: "Thank you, your payment has been successful."
+2
View File
@@ -8,6 +8,8 @@ en:
discourse_donations_hide_zip_code: Hide Zip Code
discourse_donations_reward_badge_name: Grant this badge to user when a payment is successful
discourse_donations_reward_group_name: Add the user to this group when a payment is successful
discourse_donations_show_extra_nav_item: Show extra nav item
en:
donations:
payment:
+3
View File
@@ -24,3 +24,6 @@ plugins:
discourse_donations_reward_group_name:
client: false
default: 'Donation'
discourse_donations_show_extra_nav_item:
client: true
default: true
+1 -1
View File
@@ -1,6 +1,6 @@
# name: discourse-donations
# about: Integrating Discourse with Stripe for donations
# version: 1.11.0
# version: 2.3.2
# url: https://github.com/choiceaustralia/discourse-donations
# authors: Rimian Perkins
@@ -24,24 +24,24 @@ module DiscourseDonations
end
it 'whitelists the params' do
params = { create_account: 'true', email: '[email protected]', password: 'secret', username: 'mr-pink', name: 'kirsten', amount: 100, stripeToken: 'rrurrrurrrrr' }
should permit(:name, :username, :email, :password, :create_account).for(:create, params: params)
params = { create_account: 'true', email: '[email protected]', password: 'secret', username: 'mr-pink', name: 'kirsten', amount: 100, plan: 'some-plan-id', stripeToken: 'rrurrrurrrrr' }
should permit(:name, :username, :email, :password, :plan, :create_account).for(:create, params: params)
end
it 'responds ok for anonymous users' do
post :create, { email: '[email protected]' }
post :create, { email: '[email protected]', amount: 1000 }
expect(body['messages']).to include(I18n.t('donations.payment.success'))
expect(response).to have_http_status(200)
end
it 'does not expect a username or email if accounts are not being created' do
current_user = log_in(:coding_horror)
post :create, { create_account: 'false' }
post :create, { create_account: 'false', amount: 1200 }
expect(body['messages']).to include(I18n.t('donations.payment.success'))
expect(response).to have_http_status(200)
end
describe 'create accounts' do
describe 'create accounts setting' do
describe 'create acccount disabled' do
let(:params) { { amount: 100, stripeToken: 'rrurrrurrrrr-rrruurrrr' } }
@@ -113,6 +113,16 @@ module DiscourseDonations
end
end
describe 'subscriptions and charges' do
let(:params) { { create_account: 'true', email: '[email protected]', password: 'secret', name: 'dood', username: 'mr-dood' } }
it 'creates a subscription' do
skip 'not done yet'
DiscourseDonations::Stripe.expects(:subscribe).returns('') #.with(params[:email], plan: 'quarterly-description')
post :create, params.merge(plan: 'quarterly-description')
end
end
describe 'rewards' do
let(:body) { JSON.parse(response.body) }
let(:stripe) { ::Stripe::Charge }
@@ -125,7 +135,7 @@ module DiscourseDonations
end
describe 'new user' do
let(:params) { { create_account: 'true', email: '[email protected]', password: 'secret', name: 'dood', username: 'mr-dood' } }
let(:params) { { amount: 5000, create_account: 'true', email: '[email protected]', password: 'secret', name: 'dood', username: 'mr-dood' } }
before { SiteSetting.stubs(:discourse_donations_enable_create_accounts).returns(true) }
@@ -150,7 +160,7 @@ module DiscourseDonations
end
include_examples 'no rewards' do
let(:params) { nil }
let(:params) { { amount: 4500 } }
before do
stripe.stubs(:create).returns({ 'paid' => true })
@@ -164,6 +174,7 @@ module DiscourseDonations
let(:badge_name) { 'Beanie' }
let!(:grp) { Fabricate(:group, name: group_name) }
let!(:badge) { Fabricate(:badge, name: badge_name) }
let(:params) { { amount: 4500 } }
before do
SiteSetting.stubs(:discourse_donations_reward_group_name).returns(group_name)
@@ -172,12 +183,12 @@ module DiscourseDonations
end
it 'awards a group' do
post :create
post :create, params
expect(body['rewards']).to include({'type' => 'group', 'name' => group_name})
end
it 'awards a badge' do
post :create
post :create, params
expect(body['rewards']).to include({'type' => 'badge', 'name' => badge_name})
end
end
+69
View File
@@ -2,6 +2,75 @@ require 'fakeweb'
#TODO register some fixtures
FakeWeb.register_uri(:post, 'https://api.stripe.com/v1/subscriptions',
:body => '{
"id": "sub_AiD01tPjRSDFYu",
"object": "subscription",
"application_fee_percent": null,
"cancel_at_period_end": false,
"canceled_at": null,
"created": 1495582751,
"current_period_end": 1503531551,
"current_period_start": 1495582751,
"customer": "cus_AiD0rheP5e5fWO",
"discount": null,
"ended_at": null,
"items": {
"object": "list",
"data": [
{
"id": "si_1AMmapEfVxQsvRbHOwo0LqL7",
"object": "subscription_item",
"created": 1495582752,
"plan": {
"id": "membership",
"object": "plan",
"amount": 2500,
"created": 1495503265,
"currency": "aud",
"interval": "month",
"interval_count": 3,
"livemode": false,
"metadata": {
},
"name": "Membership",
"statement_descriptor": null,
"trial_period_days": null
},
"quantity": 1
}
],
"has_more": false,
"total_count": 1,
"url": "/v1/subscription_items?subscription=sub_AiD01tPjRFFBYu"
},
"livemode": false,
"metadata": {
},
"plan": {
"id": "membership",
"object": "plan",
"amount": 2500,
"created": 1495503265,
"currency": "aud",
"interval": "month",
"interval_count": 3,
"livemode": false,
"metadata": {
},
"name": "Membership",
"statement_descriptor": null,
"trial_period_days": null
},
"quantity": 1,
"start": 1495582751,
"status": "active",
"tax_percent": null,
"trial_end": null,
"trial_start": null
}'
)
FakeWeb.register_uri(:post, 'https://api.stripe.com/v1/customers',
:body => '{
"id": "cus_AJqrL4OU1sffPl",