diff --git a/apiserver/apiserver/api/views.py b/apiserver/apiserver/api/views.py index fe64971..feed24c 100644 --- a/apiserver/apiserver/api/views.py +++ b/apiserver/apiserver/api/views.py @@ -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): serializer_class = serializers.MyRegisterSerializer diff --git a/apiserver/apiserver/urls.py b/apiserver/apiserver/urls.py index 16119b8..7e71831 100644 --- a/apiserver/apiserver/urls.py +++ b/apiserver/apiserver/urls.py @@ -22,6 +22,7 @@ router.register(r'vetting', views.VettingViewSet, basename='vetting') router.register(r'sessions', views.SessionViewSet, basename='session') router.register(r'training', views.TrainingViewSet, basename='training') 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'charts/membercount', views.MemberCountViewSet, basename='membercount') router.register(r'charts/signupcount', views.SignupCountViewSet, basename='signupcount') diff --git a/webclient/src/App.js b/webclient/src/App.js index 516b931..a11422d 100644 --- a/webclient/src/App.js +++ b/webclient/src/App.js @@ -287,7 +287,7 @@ function App() { - + diff --git a/webclient/src/Paymaster.js b/webclient/src/Paymaster.js index b1e2b8b..e929f22 100644 --- a/webclient/src/Paymaster.js +++ b/webclient/src/Paymaster.js @@ -1,11 +1,79 @@ import React, { useState } from 'react'; import { Link } from 'react-router-dom'; 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 { 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 ( +
+ + + + + + + + + + + Send + + {success &&
Success!
} +
+ ); +}; export function Paymaster(props) { - const { user } = props; + const { token, user, refreshUser } = props; const [pop, setPop] = useState('20.00'); const [locker, setLocker] = useState('5.00'); const [consumables, setConsumables] = useState('20.00'); @@ -22,10 +90,12 @@ export function Paymaster(props) {

Use these buttons to send money to Protospace.

Protocoin
-

Protocoin is used to buy things at Protospace's vending machines. Current balance: ₱ {user.member.protocoin}

+

Protocoin is used to buy things from Protospace's vending machines.

- - +

Current balance: ₱ {user.member.protocoin}

+ + + Buy {buyProtocoin} Protocoin:
@@ -44,6 +114,11 @@ export function Paymaster(props) { custom={JSON.stringify({ category: 'Exchange', member: user.member.id })} /> + + +

Send Protocoin:

+ +
Snacks, Pop, Coffee