Allow paying for Donations and Consumables with Protocoin
This commit is contained in:
parent
d1e4f2ca9d
commit
cfbbe2095d
|
@ -1150,6 +1150,71 @@ class InterestViewSet(Base, Retrieve, Create):
|
||||||
|
|
||||||
|
|
||||||
class ProtocoinViewSet(Base):
|
class ProtocoinViewSet(Base):
|
||||||
|
@action(detail=False, methods=['post'], permission_classes=[AllowMetadata | IsAuthenticated])
|
||||||
|
def spend_request(self, request):
|
||||||
|
try:
|
||||||
|
with transaction.atomic():
|
||||||
|
source_user = self.request.user
|
||||||
|
source_member = source_user.member
|
||||||
|
|
||||||
|
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.'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
category = str(request.data['category'])
|
||||||
|
except KeyError:
|
||||||
|
raise exceptions.ValidationError(dict(category='This field is required.'))
|
||||||
|
if category not in ['Consumables', 'Donation']:
|
||||||
|
raise exceptions.ValidationError(dict(category='Invalid category.'))
|
||||||
|
|
||||||
|
memo = str(request.data.get('memo', ''))
|
||||||
|
|
||||||
|
# also prevents negative spending
|
||||||
|
if amount < 0.25:
|
||||||
|
raise exceptions.ValidationError(dict(amount='Amount too small.'))
|
||||||
|
|
||||||
|
source_user_balance = source_user.transactions.aggregate(Sum('protocoin'))['protocoin__sum'] or 0
|
||||||
|
source_user_balance = float(source_user_balance)
|
||||||
|
|
||||||
|
if abs(source_user_balance - balance) > 0.01: # stupid https://docs.djangoproject.com/en/4.2/ref/databases/#decimal-handling
|
||||||
|
raise exceptions.ValidationError(dict(balance='Incorrect current balance.'))
|
||||||
|
|
||||||
|
if source_user_balance < amount:
|
||||||
|
raise exceptions.ValidationError(dict(amount='Insufficient funds.'))
|
||||||
|
|
||||||
|
tx_memo = 'Protocoin - Transaction spent ₱ {} on {}{}'.format(
|
||||||
|
amount,
|
||||||
|
category,
|
||||||
|
', memo: ' + memo if memo else ''
|
||||||
|
)
|
||||||
|
|
||||||
|
tx = models.Transaction.objects.create(
|
||||||
|
user=source_user,
|
||||||
|
protocoin=-amount,
|
||||||
|
amount=0,
|
||||||
|
number_of_membership_months=0,
|
||||||
|
account_type='Protocoin',
|
||||||
|
category=category,
|
||||||
|
info_source='System',
|
||||||
|
memo=tx_memo,
|
||||||
|
)
|
||||||
|
utils.log_transaction(tx)
|
||||||
|
|
||||||
|
return Response(200)
|
||||||
|
except OperationalError:
|
||||||
|
self.spend_request(request)
|
||||||
|
|
||||||
@action(detail=False, methods=['post'], permission_classes=[AllowMetadata | IsAuthenticated])
|
@action(detail=False, methods=['post'], permission_classes=[AllowMetadata | IsAuthenticated])
|
||||||
def send_to_member(self, request):
|
def send_to_member(self, request):
|
||||||
try:
|
try:
|
||||||
|
@ -1178,6 +1243,7 @@ class ProtocoinViewSet(Base):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise exceptions.ValidationError(dict(amount='Invalid number.'))
|
raise exceptions.ValidationError(dict(amount='Invalid number.'))
|
||||||
|
|
||||||
|
# also prevents negative spending
|
||||||
if amount < 1.00:
|
if amount < 1.00:
|
||||||
raise exceptions.ValidationError(dict(amount='Amount too small.'))
|
raise exceptions.ValidationError(dict(amount='Amount too small.'))
|
||||||
|
|
||||||
|
@ -1318,6 +1384,7 @@ class ProtocoinViewSet(Base):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise exceptions.ValidationError(dict(amount='Invalid number.'))
|
raise exceptions.ValidationError(dict(amount='Invalid number.'))
|
||||||
|
|
||||||
|
# also prevents negative spending
|
||||||
if amount < 0.25:
|
if amount < 0.25:
|
||||||
raise exceptions.ValidationError(dict(amount='Amount too small.'))
|
raise exceptions.ValidationError(dict(amount='Amount too small.'))
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,44 @@ import { PayPalPayNow, PayPalSubscribe } from './PayPal.js';
|
||||||
import { MembersDropdown } from './Members.js';
|
import { MembersDropdown } from './Members.js';
|
||||||
import { requester } from './utils.js';
|
import { requester } from './utils.js';
|
||||||
|
|
||||||
|
export function PayWithProtocoin(props) {
|
||||||
|
const { token, user, refreshUser, amount, setAmount, custom } = props;
|
||||||
|
const member = user.member;
|
||||||
|
const [error, setError] = useState({});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
if (loading) return;
|
||||||
|
setSuccess(false);
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const data = { amount: amount, ...custom, balance: member.protocoin };
|
||||||
|
requester('/protocoin/spend_request/', 'POST', token, data)
|
||||||
|
.then(res => {
|
||||||
|
setLoading(false);
|
||||||
|
setSuccess(true);
|
||||||
|
setAmount('');
|
||||||
|
setError({});
|
||||||
|
refreshUser();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
setLoading(false);
|
||||||
|
console.log(err);
|
||||||
|
setError(err.data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<Form.Button disabled={!amount} color='green' loading={loading} error={error.amount}>
|
||||||
|
Pay with Protocoin
|
||||||
|
</Form.Button>
|
||||||
|
{success && <div>Success!</div>}
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export function SendProtocoin(props) {
|
export function SendProtocoin(props) {
|
||||||
const { token, user, refreshUser } = props;
|
const { token, user, refreshUser } = props;
|
||||||
const member = user.member;
|
const member = user.member;
|
||||||
|
@ -76,10 +114,10 @@ export function Paymaster(props) {
|
||||||
const { token, user, refreshUser } = 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('');
|
||||||
const [buyProtocoin, setBuyProtocoin] = useState('10.00');
|
const [buyProtocoin, setBuyProtocoin] = useState('10.00');
|
||||||
const [consumablesMemo, setConsumablesMemo] = useState('');
|
const [consumablesMemo, setConsumablesMemo] = useState('');
|
||||||
const [donate, setDonate] = useState('20.00');
|
const [donate, setDonate] = useState('');
|
||||||
const [memo, setMemo] = useState('');
|
const [memo, setMemo] = useState('');
|
||||||
|
|
||||||
const monthly_fees = user.member.monthly_fees || 55;
|
const monthly_fees = user.member.monthly_fees || 55;
|
||||||
|
@ -188,6 +226,15 @@ export function Paymaster(props) {
|
||||||
name='Protospace Consumables'
|
name='Protospace Consumables'
|
||||||
custom={JSON.stringify({ category: 'Consumables', member: user.member.id, memo: consumablesMemo })}
|
custom={JSON.stringify({ category: 'Consumables', member: user.member.id, memo: consumablesMemo })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<p/>
|
||||||
|
|
||||||
|
<PayWithProtocoin
|
||||||
|
token={token} user={user} refreshUser={refreshUser}
|
||||||
|
amount={consumables}
|
||||||
|
setAmount={setConsumables}
|
||||||
|
custom={{ category: 'Consumables', memo: consumablesMemo }}
|
||||||
|
/>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
@ -221,6 +268,15 @@ export function Paymaster(props) {
|
||||||
name='Protospace Donation'
|
name='Protospace Donation'
|
||||||
custom={JSON.stringify({ category: 'Donation', member: user.member.id, memo: memo })}
|
custom={JSON.stringify({ category: 'Donation', member: user.member.id, memo: memo })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<p/>
|
||||||
|
|
||||||
|
<PayWithProtocoin
|
||||||
|
token={token} user={user} refreshUser={refreshUser}
|
||||||
|
amount={donate}
|
||||||
|
setAmount={setDonate}
|
||||||
|
custom={{ category: 'Donation', memo: memo }}
|
||||||
|
/>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user