Allow paying course fees with Protocoin
This commit is contained in:
		| @@ -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,11 +1219,19 @@ class ProtocoinViewSet(Base): | ||||
|                 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 '' | ||||
|                 ) | ||||
|                 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, | ||||
|                         ', memo: ' + memo if memo else '' | ||||
|                     ) | ||||
|  | ||||
|                 tx = models.Transaction.objects.create( | ||||
|                     user=source_user, | ||||
| @@ -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> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user