Allow paying course fees with Protocoin
This commit is contained in:
parent
cfbbe2095d
commit
c8378374b0
|
@ -250,7 +250,7 @@ def check_training(data, training_id, amount):
|
|||
|
||||
if training.attendance_status == 'Waiting for payment':
|
||||
training.attendance_status = 'Confirmed'
|
||||
training.paid_date = datetime.date.today()
|
||||
training.paid_date = utils.today_alberta_tz()
|
||||
training.save()
|
||||
|
||||
logger.info('IPN - Amount valid for training cost, id: ' + str(training.id))
|
||||
|
|
|
@ -1157,6 +1157,8 @@ class ProtocoinViewSet(Base):
|
|||
source_user = self.request.user
|
||||
source_member = source_user.member
|
||||
|
||||
training = None
|
||||
|
||||
try:
|
||||
balance = float(request.data['balance'])
|
||||
except KeyError:
|
||||
|
@ -1175,9 +1177,33 @@ class ProtocoinViewSet(Base):
|
|||
category = str(request.data['category'])
|
||||
except KeyError:
|
||||
raise exceptions.ValidationError(dict(category='This field is required.'))
|
||||
if category not in ['Consumables', 'Donation']:
|
||||
if category not in ['Consumables', 'Donation', 'OnAcct']:
|
||||
raise exceptions.ValidationError(dict(category='Invalid category.'))
|
||||
|
||||
if category == 'OnAcct':
|
||||
try:
|
||||
training_id = int(request.data['training'])
|
||||
except KeyError:
|
||||
raise exceptions.ValidationError(dict(training='This field is required.'))
|
||||
except ValueError:
|
||||
raise exceptions.ValidationError(dict(training='Invalid number.'))
|
||||
|
||||
training = get_object_or_404(models.Training, id=training_id)
|
||||
|
||||
if not training.session:
|
||||
raise exceptions.ValidationError(dict(training='Invalid session.'))
|
||||
|
||||
if training.session.is_cancelled:
|
||||
raise exceptions.ValidationError(dict(training='Class is cancelled.'))
|
||||
|
||||
if training.paid_date:
|
||||
raise exceptions.ValidationError(dict(training='Already paid.'))
|
||||
|
||||
if training.session.cost != amount:
|
||||
msg = 'Protocoin training payment amount mismatch:\n' + str(request.data.dict())
|
||||
utils.alert_tanner(msg)
|
||||
raise exceptions.ValidationError(dict(training='Class cost doesn\'t match amount.'))
|
||||
|
||||
memo = str(request.data.get('memo', ''))
|
||||
|
||||
# also prevents negative spending
|
||||
|
@ -1193,6 +1219,14 @@ class ProtocoinViewSet(Base):
|
|||
if source_user_balance < amount:
|
||||
raise exceptions.ValidationError(dict(amount='Insufficient funds.'))
|
||||
|
||||
if training:
|
||||
tx_memo = 'Protocoin - Transaction spent ₱ {} on {}, session: {}, training: {}'.format(
|
||||
amount,
|
||||
training.session.course.name,
|
||||
str(training.session.id),
|
||||
str(training.id),
|
||||
)
|
||||
else:
|
||||
tx_memo = 'Protocoin - Transaction spent ₱ {} on {}{}'.format(
|
||||
amount,
|
||||
category,
|
||||
|
@ -1211,6 +1245,12 @@ class ProtocoinViewSet(Base):
|
|||
)
|
||||
utils.log_transaction(tx)
|
||||
|
||||
if training:
|
||||
if training.attendance_status == 'Waiting for payment':
|
||||
training.attendance_status = 'Confirmed'
|
||||
training.paid_date = utils.today_alberta_tz()
|
||||
training.save()
|
||||
|
||||
return Response(200)
|
||||
except OperationalError:
|
||||
self.spend_request(request)
|
||||
|
|
|
@ -7,6 +7,7 @@ import { apiUrl, isAdmin, getInstructor, BasicTable, requester, useIsMobile } fr
|
|||
import { NotFound } from './Misc.js';
|
||||
import { InstructorClassDetail, InstructorClassAttendance } from './InstructorClasses.js';
|
||||
import { PayPalPayNow } from './PayPal.js';
|
||||
import { PayWithProtocoin } from './Paymaster.js';
|
||||
import { tags } from './Courses.js';
|
||||
|
||||
function ClassTable(props) {
|
||||
|
@ -693,6 +694,18 @@ export function ClassDetail(props) {
|
|||
name={clazz.course_data.name}
|
||||
custom={JSON.stringify({ training: userTraining.id })}
|
||||
/>
|
||||
|
||||
<p/>
|
||||
|
||||
<PayWithProtocoin
|
||||
token={token} user={user} refreshUser={refreshUser}
|
||||
amount={clazz.cost}
|
||||
onSuccess={() => {
|
||||
refreshUser();
|
||||
refreshClass();
|
||||
}}
|
||||
custom={{ category: 'OnAcct', training: userTraining.id }}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@ import { MembersDropdown } from './Members.js';
|
|||
import { requester } from './utils.js';
|
||||
|
||||
export function PayWithProtocoin(props) {
|
||||
const { token, user, refreshUser, amount, setAmount, custom } = props;
|
||||
const { token, user, refreshUser, amount, onSuccess, custom } = props;
|
||||
const member = user.member;
|
||||
const [error, setError] = useState({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
@ -23,7 +23,9 @@ export function PayWithProtocoin(props) {
|
|||
.then(res => {
|
||||
setLoading(false);
|
||||
setSuccess(true);
|
||||
setAmount('');
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
setError({});
|
||||
refreshUser();
|
||||
})
|
||||
|
@ -232,7 +234,7 @@ export function Paymaster(props) {
|
|||
<PayWithProtocoin
|
||||
token={token} user={user} refreshUser={refreshUser}
|
||||
amount={consumables}
|
||||
setAmount={setConsumables}
|
||||
onSuccess={() => setConsumables('')}
|
||||
custom={{ category: 'Consumables', memo: consumablesMemo }}
|
||||
/>
|
||||
</Grid.Column>
|
||||
|
@ -274,7 +276,7 @@ export function Paymaster(props) {
|
|||
<PayWithProtocoin
|
||||
token={token} user={user} refreshUser={refreshUser}
|
||||
amount={donate}
|
||||
setAmount={setDonate}
|
||||
onSuccess={() => setDonate('')}
|
||||
custom={{ category: 'Donation', memo: memo }}
|
||||
/>
|
||||
</Grid.Column>
|
||||
|
|
Loading…
Reference in New Issue
Block a user