Add API and UI to send Protocoin
This commit is contained in:
parent
51f31fc4df
commit
dde74fb402
|
@ -1042,6 +1042,91 @@ class InterestViewSet(Base, Retrieve, Create):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ProtocoinViewSet(Base):
|
||||||
|
@action(detail=False, methods=['post'], permission_classes=[AllowMetadata | IsAuthenticated])
|
||||||
|
def send_to_member(self, request):
|
||||||
|
source_user = self.request.user
|
||||||
|
source_member = source_user.member
|
||||||
|
|
||||||
|
try:
|
||||||
|
member_id = int(request.data['member_id'])
|
||||||
|
except KeyError:
|
||||||
|
raise exceptions.ValidationError(dict(member_id='This field is required.'))
|
||||||
|
except ValueError:
|
||||||
|
raise exceptions.ValidationError(dict(member_id='Invalid number.'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
balance = float(request.data['balance'])
|
||||||
|
except KeyError:
|
||||||
|
raise exceptions.ValidationError(dict(balance='This field is required.'))
|
||||||
|
except ValueError:
|
||||||
|
raise exceptions.ValidationError(dict(balance='Invalid number.'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
amount = float(request.data['amount'])
|
||||||
|
except KeyError:
|
||||||
|
raise exceptions.ValidationError(dict(amount='This field is required.'))
|
||||||
|
except ValueError:
|
||||||
|
raise exceptions.ValidationError(dict(amount='Invalid number.'))
|
||||||
|
|
||||||
|
if amount < 1.00:
|
||||||
|
raise exceptions.ValidationError(dict(amount='Amount too small.'))
|
||||||
|
|
||||||
|
|
||||||
|
if member_id == source_member.id:
|
||||||
|
raise exceptions.ValidationError(dict(member_id='Unable to send to self.'))
|
||||||
|
|
||||||
|
destination_member = get_object_or_404(models.Member, id=member_id)
|
||||||
|
destination_user = destination_member.user
|
||||||
|
|
||||||
|
source_user_balance = source_user.transactions.aggregate(Sum('protocoin'))['protocoin__sum']
|
||||||
|
source_user_balance = float(source_user_balance)
|
||||||
|
|
||||||
|
print(source_user_balance)
|
||||||
|
|
||||||
|
if source_user_balance != balance:
|
||||||
|
raise exceptions.ValidationError(dict(balance='Incorrect current balance.'))
|
||||||
|
|
||||||
|
if source_user_balance < amount:
|
||||||
|
raise exceptions.ValidationError(dict(amount='Insufficient funds.'))
|
||||||
|
|
||||||
|
source_delta = -amount
|
||||||
|
destination_delta = amount
|
||||||
|
|
||||||
|
memo = 'Protocoin - Transaction {} ({}) sent ₱ {} to {} ({})'.format(
|
||||||
|
source_member.first_name + ' ' + source_member.last_name,
|
||||||
|
source_member.id,
|
||||||
|
amount,
|
||||||
|
destination_member.first_name + ' ' + destination_member.last_name,
|
||||||
|
destination_member.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
models.Transaction.objects.create(
|
||||||
|
user=source_user,
|
||||||
|
protocoin=source_delta,
|
||||||
|
amount=0,
|
||||||
|
number_of_membership_months=0,
|
||||||
|
account_type='Protocoin',
|
||||||
|
category='Other',
|
||||||
|
info_source='System',
|
||||||
|
memo=memo,
|
||||||
|
)
|
||||||
|
|
||||||
|
models.Transaction.objects.create(
|
||||||
|
user=destination_user,
|
||||||
|
protocoin=destination_delta,
|
||||||
|
amount=0,
|
||||||
|
number_of_membership_months=0,
|
||||||
|
account_type='Protocoin',
|
||||||
|
category='Other',
|
||||||
|
info_source='System',
|
||||||
|
memo=memo,
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(200)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RegistrationView(RegisterView):
|
class RegistrationView(RegisterView):
|
||||||
serializer_class = serializers.MyRegisterSerializer
|
serializer_class = serializers.MyRegisterSerializer
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ router.register(r'vetting', views.VettingViewSet, basename='vetting')
|
||||||
router.register(r'sessions', views.SessionViewSet, basename='session')
|
router.register(r'sessions', views.SessionViewSet, basename='session')
|
||||||
router.register(r'training', views.TrainingViewSet, basename='training')
|
router.register(r'training', views.TrainingViewSet, basename='training')
|
||||||
router.register(r'interest', views.InterestViewSet, basename='interest')
|
router.register(r'interest', views.InterestViewSet, basename='interest')
|
||||||
|
router.register(r'protocoin', views.ProtocoinViewSet, basename='protocoin')
|
||||||
router.register(r'transactions', views.TransactionViewSet, basename='transaction')
|
router.register(r'transactions', views.TransactionViewSet, basename='transaction')
|
||||||
router.register(r'charts/membercount', views.MemberCountViewSet, basename='membercount')
|
router.register(r'charts/membercount', views.MemberCountViewSet, basename='membercount')
|
||||||
router.register(r'charts/signupcount', views.SignupCountViewSet, basename='signupcount')
|
router.register(r'charts/signupcount', views.SignupCountViewSet, basename='signupcount')
|
||||||
|
|
|
@ -287,7 +287,7 @@ function App() {
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path='/paymaster'>
|
<Route path='/paymaster'>
|
||||||
<Paymaster user={user} />
|
<Paymaster token={token} user={user} refreshUser={refreshUser} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path='/cards'>
|
<Route path='/cards'>
|
||||||
|
|
|
@ -1,11 +1,79 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import './light.css';
|
import './light.css';
|
||||||
import { Container, Grid, Header, Input } from 'semantic-ui-react';
|
import { Container, Form, Grid, Header, Input } from 'semantic-ui-react';
|
||||||
import { PayPalPayNow, PayPalSubscribe } from './PayPal.js';
|
import { PayPalPayNow, PayPalSubscribe } from './PayPal.js';
|
||||||
|
import { MembersDropdown } from './Members.js';
|
||||||
|
import { requester } from './utils.js';
|
||||||
|
|
||||||
|
export function SendProtocoin(props) {
|
||||||
|
const { token, user, refreshUser } = props;
|
||||||
|
const member = user.member;
|
||||||
|
const [input, setInput] = useState({});
|
||||||
|
const [error, setError] = useState({});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
|
||||||
|
const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value });
|
||||||
|
const handleChange = (e) => handleValues(e, e.currentTarget);
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
if (loading) return;
|
||||||
|
setSuccess(false);
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const data = { ...input, balance: member.protocoin };
|
||||||
|
requester('/protocoin/send_to_member/', 'POST', token, data)
|
||||||
|
.then(res => {
|
||||||
|
setLoading(false);
|
||||||
|
setSuccess(true);
|
||||||
|
setInput({});
|
||||||
|
setError({});
|
||||||
|
refreshUser();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
setLoading(false);
|
||||||
|
console.log(err);
|
||||||
|
setError(err.data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeProps = (name) => ({
|
||||||
|
name: name,
|
||||||
|
onChange: handleChange,
|
||||||
|
value: input[name] || '',
|
||||||
|
error: error[name],
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<Form.Group widths='equal'>
|
||||||
|
<Form.Field error={error.member_id}>
|
||||||
|
<label>Member (search)</label>
|
||||||
|
<MembersDropdown
|
||||||
|
token={token}
|
||||||
|
{...makeProps('member_id')}
|
||||||
|
onChange={handleValues}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
|
||||||
|
<Form.Input
|
||||||
|
label='Amount (₱)'
|
||||||
|
fluid
|
||||||
|
{...makeProps('amount')}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Button loading={loading} error={error.non_field_errors}>
|
||||||
|
Send
|
||||||
|
</Form.Button>
|
||||||
|
{success && <div>Success!</div>}
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export function Paymaster(props) {
|
export function Paymaster(props) {
|
||||||
const { user } = props;
|
const { token, user, refreshUser } = props;
|
||||||
const [pop, setPop] = useState('20.00');
|
const [pop, setPop] = useState('20.00');
|
||||||
const [locker, setLocker] = useState('5.00');
|
const [locker, setLocker] = useState('5.00');
|
||||||
const [consumables, setConsumables] = useState('20.00');
|
const [consumables, setConsumables] = useState('20.00');
|
||||||
|
@ -22,10 +90,12 @@ export function Paymaster(props) {
|
||||||
<p>Use these buttons to send money to Protospace.</p>
|
<p>Use these buttons to send money to Protospace.</p>
|
||||||
|
|
||||||
<Header size='medium'>Protocoin</Header>
|
<Header size='medium'>Protocoin</Header>
|
||||||
<p>Protocoin is used to buy things at Protospace's vending machines. Current balance: ₱ {user.member.protocoin}</p>
|
<p>Protocoin is used to buy things from Protospace's vending machines.</p>
|
||||||
|
|
||||||
<Grid stackable padded columns={3}>
|
<p>Current balance: ₱ {user.member.protocoin}</p>
|
||||||
<Grid.Column>
|
|
||||||
|
<Grid stackable padded columns={2}>
|
||||||
|
<Grid.Column width={5}>
|
||||||
Buy {buyProtocoin} Protocoin:
|
Buy {buyProtocoin} Protocoin:
|
||||||
|
|
||||||
<div className='pay-custom'>
|
<div className='pay-custom'>
|
||||||
|
@ -44,6 +114,11 @@ export function Paymaster(props) {
|
||||||
custom={JSON.stringify({ category: 'Exchange', member: user.member.id })}
|
custom={JSON.stringify({ category: 'Exchange', member: user.member.id })}
|
||||||
/>
|
/>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
|
|
||||||
|
<Grid.Column width={8}>
|
||||||
|
<p>Send Protocoin:</p>
|
||||||
|
<SendProtocoin token={token} user={user} refreshUser={refreshUser} />
|
||||||
|
</Grid.Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Header size='medium'>Snacks, Pop, Coffee</Header>
|
<Header size='medium'>Snacks, Pop, Coffee</Header>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user