def confirmreg_forreal(self, request, tl, one, two, module, extra, prog, new_reg): """ The page that is shown once the user saves their student reg, giving them the option of printing a confirmation """ self.request = request from esp.program.modules.module_ext import DBReceipt iac = IndividualAccountingController(prog, request.user) context = {} context['one'] = one context['two'] = two context['itemizedcosts'] = iac.get_transfers() user = request.user context['finaid'] = user.hasFinancialAid(prog) if user.appliedFinancialAid(prog): context['finaid_app'] = user.financialaidrequest_set.filter(program=prog).order_by('-id')[0] else: context['finaid_app'] = None context['balance'] = iac.amount_due() context['owe_money'] = ( context['balance'] != Decimal("0.0") ) if not prog.user_can_join(user): raise ESPError("This program has filled! It can't accept any more students. Please try again next session.", log=False) modules = prog.getModules(request.user, tl) completedAll = True for module in modules: if hasattr(module, 'onConfirm'): module.onConfirm(request) if not module.isCompleted() and module.required: completedAll = False context = module.prepare(context) if completedAll: if new_reg: rec = Record.objects.create(user=user, event="reg_confirmed", program=prog) else: raise ESPError("You must finish all the necessary steps first, then click on the Save button to finish registration.", log=False) cfe = ConfirmationEmailController() cfe.send_confirmation_email(request.user, self.program) try: receipt_text = DBReceipt.objects.get(program=self.program, action='confirm').receipt context["request"] = request context["program"] = prog return HttpResponse( Template(receipt_text).render( Context(context, autoescape=False) ) ) except DBReceipt.DoesNotExist: try: receipt = 'program/receipts/'+str(prog.id)+'_custom_receipt.html' return render_to_response(receipt, request, context) except: receipt = 'program/receipts/default.html' return render_to_response(receipt, request, context)
def test_extracosts(self): """ Verify that the "Student Extra Costs" module behaves as specified. """ program_cost = 25.0 # Set up some extra costs options: Item1 (max-qty 1) for $10, Item2 (max-qty 10) for $5, and selectable food pac = ProgramAccountingController(self.program) pac.clear_all_data() pac.setup_accounts() pac.setup_lineitemtypes(program_cost, [('Item1', 10, 1), ('Item2', 5, 10)], [('Food', [('Small', 3), ('Large', 7)])]) # Choose a random student and check that the extracosts page loads student = random.choice(self.students) iac = IndividualAccountingController(self.program, student) self.assertTrue( self.client.login( username=student.username, password='******' ), "Couldn't log in as student %s" % student.username ) response = self.client.get('/learn/%s/extracosts' % self.program.url) self.assertEqual(response.status_code, 200) # Check that they are being charged the program admission fee self.assertEqual(iac.amount_due(), program_cost) # Check that selecting one of the "buy-one" extra items works lit = LineItemType.objects.get(program=self.program, text='Item1') response = self.client.post('/learn/%s/extracosts' % self.program.getUrlBase(), {'%d-cost' % lit.id: 'checked'}) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) self.assertEqual(iac.amount_due(), program_cost + 10) # Check that selecting one or more than one of the "buy many" extra items works lit = LineItemType.objects.get(program=self.program, text='Item2') response = self.client.post('/learn/%s/extracosts' % self.program.getUrlBase(), {'%d-cost' % lit.id: 'checked', '%d-count' % lit.id: '1'}) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) self.assertEqual(iac.amount_due(), program_cost + 5) response = self.client.post('/learn/%s/extracosts' % self.program.getUrlBase(), {'%d-cost' % lit.id: 'checked', '%d-count' % lit.id: '3'}) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) self.assertEqual(iac.amount_due(), program_cost + 15) # Check that selecting an option for a "multiple choice" extra item works lit = LineItemType.objects.get(program=self.program, text='Food') lio = filter(lambda x: x[2] == 'Large', lit.options)[0] response = self.client.post('/learn/%s/extracosts' % self.program.getUrlBase(), {'multi%d-option' % lit.id: str(lio[0])}) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) self.assertEqual(iac.amount_due(), program_cost + 7) # Check that financial aid applies to the "full" cost including the extra items # (e.g. we are not forcing financial aid students to pay for food) request = FinancialAidRequest.objects.create(user=student, program=self.program) (fg, created) = FinancialAidGrant.objects.get_or_create(request=request, percent=100) self.assertEqual(iac.amount_due(), 0.0) fg.delete() # Check that removing items on the form removes their cost for the student response = self.client.post('/learn/%s/extracosts' % self.program.getUrlBase(), {}) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) self.assertEqual(iac.amount_due(), program_cost)
def viewpay_cybersource(self, request, tl, one, two, module, extra, prog): pac = ProgramAccountingController(prog) student_list = list(pac.all_students()) payment_table = [] for student in student_list: iac = IndividualAccountingController(prog, student) payment_table.append((student, iac.get_transfers(), iac.amount_requested(), iac.amount_due())) context = {'program': prog, 'payment_table': payment_table} return render_to_response(self.baseDir() + 'viewpay_cybersource.html', request, context)
def test_extracosts(self): """ Verify that the "Student Extra Costs" module behaves as specified. """ program_cost = 25.0 # Set up some extra costs options: Item1 (max-qty 1) for $10, Item2 (max-qty 10) for $5, and selectable food pac = ProgramAccountingController(self.program) pac.clear_all_data() pac.setup_accounts() pac.setup_lineitemtypes(program_cost, [('Item1', 10, 1), ('Item2', 5, 10)], [('Food', [('Small', 3), ('Large', 7)])]) # Choose a random student and check that the extracosts page loads student = random.choice(self.students) iac = IndividualAccountingController(self.program, student) self.assertTrue( self.client.login( username=student.username, password='******' ), "Couldn't log in as student %s" % student.username ) response = self.client.get('/learn/%s/extracosts' % self.program.url) self.assertEqual(response.status_code, 200) # Check that they are being charged the program admission fee self.assertEqual(iac.amount_due(), program_cost) # Check that selecting one of the "buy-one" extra items works lit = LineItemType.objects.get(program=self.program, text='Item1') response = self.client.post('/learn/%s/extracosts' % self.program.getUrlBase(), {'%d-cost' % lit.id: 'checked'}) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) self.assertEqual(iac.amount_due(), program_cost + 10) # Check that selecting one or more than one of the "buy many" extra items works lit = LineItemType.objects.get(program=self.program, text='Item2') response = self.client.post('/learn/%s/extracosts' % self.program.getUrlBase(), {'%d-cost' % lit.id: 'checked', '%d-count' % lit.id: '1'}) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) self.assertEqual(iac.amount_due(), program_cost + 5) response = self.client.post('/learn/%s/extracosts' % self.program.getUrlBase(), {'%d-cost' % lit.id: 'checked', '%d-count' % lit.id: '3'}) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) self.assertEqual(iac.amount_due(), program_cost + 15) # Check that selecting an option for a "multiple choice" extra item works lit = LineItemType.objects.get(program=self.program, text='Food') lio = filter(lambda x: x[2] == 'Large', lit.options)[0] response = self.client.post('/learn/%s/extracosts' % self.program.getUrlBase(), {'multi%d-option' % lit.id: str(lio[0])}) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) self.assertEqual(iac.amount_due(), program_cost + 7) # Check that financial aid applies to the "full" cost including the extra items # (e.g. we are not forcing financial aid students to pay for food) request = FinancialAidRequest.objects.create(user=student, program=self.program) (fg, created) = FinancialAidGrant.objects.get_or_create(request=request, percent=100) self.assertEqual(iac.amount_due(), 0.0) fg.delete() # Check that removing items on the form removes their cost for the student response = self.client.post('/learn/%s/extracosts' % self.program.getUrlBase(), {}) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) self.assertEqual(iac.amount_due(), program_cost)
def finalize(self): # Create a transfer for the amount of this grant if self.finalized: return from esp.accounting.controllers import IndividualAccountingController iac = IndividualAccountingController(self.program, self.user) source_account = iac.default_finaid_account() dest_account = iac.default_source_account() line_item_type = iac.default_finaid_lineitemtype() (transfer, created) = Transfer.objects.get_or_create(source=source_account, destination=dest_account, user=self.user, line_item=line_item_type, amount_dec=iac.amount_finaid()) self.finalized = True self.save() return transfer
def updatePaid(self, paid=True): """ Create an invoice for the student and, if paid is True, create a receipt showing that they have paid all of the money they owe for the program. """ iac = IndividualAccountingController(self.program, self.student) if not iac.has_paid(): iac.ensure_required_transfers() if paid: iac.submit_payment(iac.amount_due())
def updatePaid(self, paid=True): """ Create an invoice for the student and, if paid is True, create a receipt showing that they have paid all of the money they owe for the program. """ iac = IndividualAccountingController(self.program, self.student) if not iac.has_paid(): iac.ensure_required_transfers() if paid: iac.submit_payment(iac.amount_due())
def donation(self, request, tl, one, two, module, extra, prog): user = request.user iac = IndividualAccountingController(self.program, user) # It's unclear if we support changing line item preferences after # credit card payment has occured. For now, just do the same thing we # do in other accounting modules, and don't allow changes after payment # has occured. if iac.amount_due() <= 0: raise ESPError("You've already paid for this program. Please make any further changes on-site so that we can charge or refund you properly.", log=False) # Donations and non-donations go through different code paths. If a # user chooses to make a donation, set_donation_amount() is called via # an AJAX request. If a user chooses not to make a donation, their # browser makes a request to the studentreg main page. Therefore, it is # impossible set the donation_done Record after the user is actually # done with the module. So our only option is to mark them done when # they first visit the page. This should be fine, since donations are # always optional. If we really care about being correct, if we switch # this page to not use AJAX but instead use a normal form submission, # we can then switch to granting the Record after the user is done with # the page. Record.objects.get_or_create(user=user, program=self.program, event=self.event) context = {} context['module'] = self context['program'] = prog context['user'] = user # Load donation amount separately, since the client-side code needs to know about it separately. donation_prefs = iac.get_preferences([self.line_item_type(),]) if donation_prefs: context['amount_donation'] = Decimal(donation_prefs[0][2]) context['has_donation'] = True else: context['amount_donation'] = Decimal('0.00') context['has_donation'] = False context['institution'] = settings.INSTITUTION_NAME return render_to_response(self.baseDir() + 'donation.html', request, context)
def register_student(self, request, tl, one, two, module, extra, prog): resp = HttpResponse(content_type='application/json') program = self.program success = False student = get_object_or_404(ESPUser,pk=request.POST.get("student_id")) registration_profile = RegistrationProfile.getLastForProgram(student, program) success = registration_profile.student_info is not None if success: registration_profile.save() for extension in ['paid','Attended','medical','liability','OnSite']: Record.createBit(extension, program, student) IndividualAccountingController.updatePaid(self.program, student, paid=True) json.dump({'status':success}, resp) return resp
def payment_success(self, request, tl, one, two, module, extra, prog): """ Receive payment from First Data Global Gateway """ if request.method == 'GET' or request.POST.get('status', '') != 'APPROVED': return self.payment_failure(request, tl, one, two, module, extra, prog) # We should already know what user/program this is for, but it should also be stored. iac = IndividualAccountingController(self.program, request.user) post_locator = request.POST.get('ponumber', '') assert (post_locator == iac.get_id()) post_identifier = request.POST.get('invoice_number', '') # Optional: The line of code below would check consistency of the user's # invoice items against the ones associated with the payment. # assert(post_identifier == iac.get_identifier()) post_amount = Decimal(request.POST.get('total', '0.0')) # Warn for possible duplicate payments prev_payments = iac.get_transfers().filter( line_item=iac.default_payments_lineitemtype()) if prev_payments.count() > 0 and iac.amount_due() <= 0: from django.conf import settings recipient_list = [contact[1] for contact in settings.ADMINS] subject = 'Possible Duplicate Postback/Payment' refs = 'User: %s (%d); Program: %s (%d)' % (request.user.name( ), request.user.id, self.program.niceName(), self.program.id) refs += '\n\nPrevious payments\' Transfer IDs: ' + (u', '.join( [x.id for x in prev_payments])) # Send mail! send_mail('[ ESP CC ] ' + subject + ' by ' + invoice.user.first_name + ' ' + invoice.user.last_name, \ """%s Notification\n--------------------------------- \n\n%s\n\nUser: %s %s (%s)\n\nCardholder: %s\n\nRequest: %s\n\n""" % \ (subject, refs, request.user.first_name, request.user.last_name, request.user.id, request.POST.get('bname', '--'), request) , \ settings.SERVER_EMAIL, recipient_list, True) # Save the payment as a transfer in the database iac.submit_payment(post_amount) context = {} context['postdata'] = request.POST.copy() context['support_email'] = settings.DEFAULT_EMAIL_ADDRESSES['support'] context['prog'] = prog # Don't redirect to receipt just yet, in case they haven't finished all steps of registration # return HttpResponseRedirect("http://%s/learn/%s/%s/confirmreg" % (request.META['HTTP_HOST'], one, two)) return render_to_response(self.baseDir() + 'success.html', request, context)
def viewpay_cybersource(self, request, tl, one, two, module, extra, prog): pac = ProgramAccountingController(prog) student_list = list(pac.all_students()) payment_table = [] # Fetch detailed information for every student associated with the program for student in student_list: iac = IndividualAccountingController(prog, student) payment_table.append((student, iac.get_transfers(), iac.amount_requested(), iac.amount_due())) # Also fetch summary information about the payments (num_payments, total_payment) = pac.payments_summary() context = { 'program': prog, 'payment_table': payment_table, 'num_students': len(student_list), 'num_payments': num_payments, 'total_payment': total_payment, } return render_to_response(self.baseDir() + 'viewpay_cybersource.html', request, context)
def submit_transaction(request): # We might also need to forward post variables to http://shopmitprd.mit.edu/controller/index.php?action=log_transaction if request.POST.has_key( "decision") and request.POST["decision"] != "REJECT": # Figure out which user and program the payment are for. post_identifier = request.POST.get('merchantDefinedData1', '') iac = IndividualAccountingController.from_identifier(post_identifier) post_amount = Decimal(request.POST.get('orderAmount', '0.0')) # Warn for possible duplicate payments prev_payments = iac.get_transfers().filter( line_item=iac.default_payments_lineitemtype()) if prev_payments.count() > 0 and iac.amount_due() <= 0: from django.conf import settings recipient_list = [contact[1] for contact in settings.ADMINS] recipient_list.append(settings.DEFAULT_EMAIL_ADDRESSES['treasury']) refs = 'Cybersource request ID: %s' % post_identifier subject = 'Possible Duplicate Postback/Payment' refs = 'User: %s (%d); Program: %s (%d)' % (iac.user.name( ), iac.user.id, iac.program.niceName(), iac.program.id) refs += '\n\nPrevious payments\' Transfer IDs: ' + (u', '.join( [str(x.id) for x in prev_payments])) # Send mail! send_mail('[ ESP CC ] ' + subject + ' by ' + iac.user.first_name + ' ' + iac.user.last_name, \ """%s Notification\n--------------------------------- \n\n%s\n\nUser: %s %s (%s)\n\nCardholder: %s, %s\n\nRequest: %s\n\n""" % \ (subject, refs, request.user.first_name, request.user.last_name, request.user.id, request.POST.get('billTo_lastName', '--'), request.POST.get('billTo_firstName', '--'), request) , \ settings.SERVER_EMAIL, recipient_list, True) # Save the payment as a transfer in the database iac.submit_payment(post_amount) tl = 'learn' one, two = iac.program.url.split('/') destination = Tag.getProgramTag("cc_redirect", iac.program, default="confirmreg") if destination.startswith('/') or '//' in destination: pass else: # simple urls like 'confirmreg' are relative to the program destination = "/%s/%s/%s/%s" % (tl, one, two, destination) return HttpResponseRedirect(destination) return render_to_response('accounting_docs/credit_rejected.html', request, {})
def set_donation_amount(self, request, tl, one, two, module, extra, prog): """ Set the student's desired donation amount. Creates a line item type for donations if it does not exist. """ amount_donation = Decimal(request.POST.get('amount', '0')) iac = IndividualAccountingController(prog, request.user) # Clear the Transfers by specifying quantity 0 iac.set_preference('Donation to Learning Unlimited', 0) if amount_donation != Decimal('0'): # Specify quantity 1 and the desired amount iac.set_preference('Donation to Learning Unlimited', 1, amount=amount_donation) data = {'amount_donation': amount_donation, 'amount_due': iac.amount_due()} return HttpResponse(json.dumps(data), content_type='application/json')
def _submit_transaction(request, log_record): decision = request.POST['decision'] if decision == "ACCEPT": # Handle payment identifier = request.POST['req_merchant_defined_data1'] amount_paid = Decimal(request.POST['req_amount']) transaction_id = request.POST['transaction_id'] payment = IndividualAccountingController.record_payment_from_identifier( identifier, amount_paid, transaction_id) # Link payment to log record log_record.transfer = payment log_record.save() return _redirect_from_identifier(identifier, "success") elif decision == "DECLINE": identifier = request.POST['req_merchant_defined_data1'] return _redirect_from_identifier(identifier, "declined") else: raise NotImplementedError("Can't handle decision: %s" % decision)
def set_donation_amount(self, request, tl, one, two, module, extra, prog): """ Set the student's desired donation amount. Creates a line item type for donations if it does not exist. """ amount_donation = Decimal(request.POST.get('amount', '0')) iac = IndividualAccountingController(prog, request.user) # Clear the Transfers by specifying quantity 0 iac.set_preference('Donation to Learning Unlimited', 0) if amount_donation != Decimal('0'): # Specify quantity 1 and the desired amount iac.set_preference('Donation to Learning Unlimited', 1, amount=amount_donation) data = { 'amount_donation': amount_donation, 'amount_due': iac.amount_due() } return HttpResponse(json.dumps(data), content_type='application/json')
def _submit_transaction(request, log_record): decision = request.POST['decision'] if decision == "ACCEPT": # Handle payment identifier = request.POST['req_merchant_defined_data1'] amount_paid = Decimal(request.POST['req_amount']) transaction_id = request.POST['transaction_id'] payment = IndividualAccountingController.record_payment_from_identifier( identifier, amount_paid, transaction_id) # Link payment to log record log_record.transfer = payment log_record.save() return _redirect_from_identifier(identifier, "success") elif decision == "DECLINE": identifier = request.POST['req_merchant_defined_data1'] return _redirect_from_identifier(identifier, "declined") else: raise NotImplementedError("Can't handle decision: %s" % decision)
def paiditems(self, request, tl, one, two, module, extra, prog): # Get a user user, found = search_for_user(request) if not found: return user # Get the optional purchases for that user iac = IndividualAccountingController(prog, user) context = {} context['student'] = user context['requireditems'] = iac.get_transfers(required_only=True) context['reserveditems'] = iac.get_transfers(optional_only=True) context['amount_requested'] = iac.amount_requested() context['amount_finaid'] = iac.amount_finaid() context['amount_due'] = iac.amount_due() return render_to_response(self.baseDir()+'paiditems.html', request, context)
def paiditems(self, request, tl, one, two, module, extra, prog): # Get a user filterObj, found = UserSearchController().create_filter( request, self.program) if not found: return filterObj user = filterObj.getList(ESPUser).distinct()[0] # Get the optional purchases for that user iac = IndividualAccountingController(prog, user) context = {} context['student'] = user context['requireditems'] = iac.get_transfers(required_only=True) context['reserveditems'] = iac.get_transfers(optional_only=True) context['amount_requested'] = iac.amount_requested() context['amount_finaid'] = iac.amount_finaid() context['amount_due'] = iac.amount_due() return render_to_response(self.baseDir() + 'paiditems.html', request, context)
def extracosts(self,request, tl, one, two, module, extra, prog): """ Query the user for any extra items they may wish to purchase for this program This module should ultimately deal with things like optional lab fees, etc. Right now it doesn't. """ if self.have_paid(): raise ESPError("You've already paid for this program. Please make any further changes on-site so that we can charge or refund you properly.", log=False) # Determine which line item types we will be asking about iac = IndividualAccountingController(self.program, get_current_request().user) costs_list = iac.get_lineitemtypes(optional_only=True).filter(max_quantity__lte=1, lineitemoptions__isnull=True) multicosts_list = iac.get_lineitemtypes(optional_only=True).filter(max_quantity__gt=1, lineitemoptions__isnull=True) multiselect_list = iac.get_lineitemtypes(optional_only=True).filter(lineitemoptions__isnull=False) # Fetch the user's current preferences prefs = iac.get_preferences() forms_all_valid = True ## Another dirty hack, left as an exercise to the reader if request.method == 'POST': # Initialize a list of forms using the POST data costs_db = [ { 'LineItemType': x, 'CostChoice': CostItem(request.POST, prefix="%s" % x.id) } for x in costs_list ] + \ [ x for x in \ [ { 'LineItemType': x, 'CostChoice': MultiCostItem(request.POST, prefix="%s" % x.id) } for x in multicosts_list ] \ if x['CostChoice'].is_valid() and 'cost' in x['CostChoice'].cleaned_data ] + \ [ { 'LineItemType': x, 'CostChoice': MultiSelectCostItem(request.POST, prefix="multi%s" % x.id, choices=x.option_choices, required=(x.required)) } for x in multiselect_list ] # Get a list of the (line item, quantity) pairs stored in the forms # as well as a list of line items which had invalid forms form_prefs = [] preserve_items = [] for item in costs_db: form = item['CostChoice'] lineitem_type = item['LineItemType'] if form.is_valid(): if isinstance(form, CostItem): if form.cleaned_data['cost'] is True: form_prefs.append((lineitem_type.text, 1, lineitem_type.amount, None)) elif isinstance(form, MultiCostItem): if form.cleaned_data['cost'] is True: form_prefs.append((lineitem_type.text, form.cleaned_data['count'], lineitem_type.amount, None)) elif isinstance(form, MultiSelectCostItem): if form.cleaned_data['option']: form_prefs.append((lineitem_type.text, 1, None, int(form.cleaned_data['option']))) else: # Preserve selected quantity for any items that we don't have a valid form for preserve_items.append(lineitem_type.text) forms_all_valid = False # Merge previous and new preferences (update only if the form was valid) new_prefs = [] for lineitem_name in preserve_items: if lineitem_name in map(lambda x: x[0], prefs): new_prefs.append(prefs[map(lambda x: x[0], prefs).index(lineitem_name)]) new_prefs += form_prefs iac.apply_preferences(new_prefs) # Redirect to main student reg page if all data was recorded properly # (otherwise, the code below will reload the page) if forms_all_valid: bit, created = Record.objects.get_or_create(user=request.user, program=self.program, event=self.event) return self.goToCore(tl) count_map = {} for lineitem_type in iac.get_lineitemtypes(optional_only=True): count_map[lineitem_type.text] = [lineitem_type.id, 0, None, None] for item in iac.get_preferences(): for i in range(1, 4): count_map[item[0]][i] = item[i] forms = [ { 'form': CostItem( prefix="%s" % x.id, initial={'cost': (count_map[x.text][1] > 0) } ), 'LineItem': x } for x in costs_list ] + \ [ { 'form': MultiCostItem( prefix="%s" % x.id, initial={'cost': (count_map[x.text][1] > 0), 'count': count_map[x.text][1] } ), 'LineItem': x } for x in multicosts_list ] + \ [ { 'form': MultiSelectCostItem( prefix="multi%s" % x.id, initial={'option': count_map[x.text][3]}, choices=x.option_choices, required=(x.required)), 'LineItem': x } for x in multiselect_list ] return render_to_response(self.baseDir()+'extracosts.html', request, { 'errors': not forms_all_valid, 'forms': forms, 'financial_aid': request.user.hasFinancialAid(prog), 'select_qty': len(multicosts_list) > 0 })
def _redirect_from_identifier(identifier, result): program = IndividualAccountingController.program_from_identifier(identifier) destination = "/learn/%s/cybersource?result=%s" % (program.getUrlBase(), result) return HttpResponseRedirect(destination)
def test_finaid(self): """ Verify that financial aid behaves as specified. """ program_cost = 25.0 # Set the cost of the program pac = ProgramAccountingController(self.program) pac.clear_all_data() pac.setup_accounts() pac.setup_lineitemtypes(program_cost) # Choose a random student and sign up for a class student = random.choice(self.students) iac = IndividualAccountingController(self.program, student) sec = random.choice(self.program.sections()) sec.preregister_student(student) # Check that the student owes the cost of the program self.assertEqual(iac.amount_due(), program_cost) # Apply for financial aid self.assertTrue( self.client.login( username=student.username, password='******' ), "Couldn't log in as student %s" % student.username ) response = self.client.get( '/learn/%s/finaid' % self.program.url, **{'wsgi.url_scheme': 'https'}) self.assertEqual(response.status_code, 200) form_settings = { 'reduced_lunch': '', 'household_income': '12345', 'extra_explaination': 'No', 'student_prepare': '', } response = self.client.post('/learn/%s/finaid' % self.program.getUrlBase(), form_settings) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) # Check that the student still owes the cost of the program self.assertEqual(iac.amount_due(), program_cost) # Have an admin approve the financial aid app and check a few different cases: request = FinancialAidRequest.objects.get(user=student, program=self.program) # - 100 percent (fg, created) = FinancialAidGrant.objects.get_or_create(request=request, percent=100) self.assertEqual(iac.amount_due(), 0.0) # - absolute discount amount fg.percent = None fg.amount_max_dec = Decimal('15.0') fg.save() self.assertEqual(iac.amount_due(), program_cost - 15.0) # - discount percentage fg.amount_max_dec = None fg.percent = 50 fg.save() self.assertEqual(iac.amount_due(), program_cost / 2) # Check that deleting the financial aid grant restores original program cost fg.delete() self.assertEqual(iac.amount_due(), program_cost) # Check that the 'free/reduced lunch' option on the finaid results in zero amount due form_settings = { 'reduced_lunch': 'checked', 'household_income': '12345', 'extra_explaination': 'No', 'student_prepare': '', } response = self.client.post('/learn/%s/finaid' % self.program.getUrlBase(), form_settings) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) self.assertEqual(iac.amount_due(), 0)
def payonline(self, request, tl, one, two, module, extra, prog): # Check that the user has completed all required modules so that they # are "finished" registering for the program. (In other words, they # should be registered for at least one class, and filled out other # required forms, before paying by credit card.) modules = prog.getModules(request.user, tl) completedAll = True for module in modules: if not module.isCompleted() and module.required: completedAll = False if not completedAll and not request.user.isAdmin(prog): raise ESPError("Please go back and ensure that you have completed all required steps of registration before paying by credit card.", log=False) # Check for setup of module. This is also required to initialize settings. self.check_setup() user = request.user iac = IndividualAccountingController(self.program, request.user) context = {} context['module'] = self context['program'] = prog context['user'] = user context['invoice_id'] = iac.get_id() context['identifier'] = iac.get_identifier() payment_type = iac.default_payments_lineitemtype() sibling_type = iac.default_siblingdiscount_lineitemtype() grant_type = iac.default_finaid_lineitemtype() offer_donation = self.settings['offer_donation'] donate_type = iac.get_lineitemtypes().get(text=self.settings['donation_text']) if offer_donation else None context['itemizedcosts'] = iac.get_transfers().exclude(line_item__in=filter(None, [payment_type, sibling_type, grant_type, donate_type])).order_by('-line_item__required') context['itemizedcosttotal'] = iac.amount_due() # This amount should be formatted as an integer in order to be # accepted by Stripe. context['totalcost_cents'] = int(context['itemizedcosttotal'] * 100) context['subtotal'] = iac.amount_requested() context['financial_aid'] = iac.amount_finaid() context['sibling_discount'] = iac.amount_siblingdiscount() context['amount_paid'] = iac.amount_paid() # Load donation amount separately, since the client-side code needs to know about it separately. donation_prefs = iac.get_preferences([donate_type,]) if offer_donation else None if donation_prefs: context['amount_donation'] = Decimal(donation_prefs[0][2]) context['has_donation'] = True else: context['amount_donation'] = Decimal('0.00') context['has_donation'] = False context['amount_without_donation'] = context['itemizedcosttotal'] - context['amount_donation'] if 'HTTP_HOST' in request.META: context['hostname'] = request.META['HTTP_HOST'] else: context['hostname'] = Site.objects.get_current().domain context['institution'] = settings.INSTITUTION_NAME context['support_email'] = settings.DEFAULT_EMAIL_ADDRESSES['support'] return render_to_response(self.baseDir() + 'cardpay.html', request, context)
def charge_payment(self, request, tl, one, two, module, extra, prog): # Check for setup of module. This is also required to initialize settings. self.check_setup() context = {'postdata': request.POST.copy()} group_name = Tag.getTag('full_group_name') or '%s %s' % ( settings.INSTITUTION_NAME, settings.ORGANIZATION_SHORT_NAME) iac = IndividualAccountingController(self.program, request.user) # Set Stripe key based on settings. Also require the API version # which our code is designed for. stripe.api_key = self.settings['secret_key'] # We are using the 2014-03-13 version of the Stripe API, which is # v1.12.2. stripe.api_version = '2014-03-13' if request.POST.get('ponumber', '') != iac.get_id(): # If we received a payment for the wrong PO: # This is not a Python exception, but an error nonetheless. context['error_type'] = 'inconsistent_po' context['error_info'] = { 'request_po': request.POST.get('ponumber', ''), 'user_po': iac.get_id() } if 'error_type' not in context: # Check the amount in the POST against the amount in our records. # If they don't match, raise an error. amount_cents_post = Decimal(request.POST['totalcost_cents']) amount_cents_iac = Decimal(iac.amount_due()) * 100 if amount_cents_post != amount_cents_iac: context['error_type'] = 'inconsistent_amount' context['error_info'] = { 'amount_cents_post': amount_cents_post, 'amount_cents_iac': amount_cents_iac, } if 'error_type' not in context: try: with transaction.atomic(): # Save a record of the charge if we can uniquely identify the user/program. # If this causes an error, the user will get a 500 error # page, and the card will NOT be charged. # If an exception is later raised by # stripe.Charge.create(), then the transaction will be # rolled back. # Thus, we will never be in a state where the card has been # charged without a record being created on the site, nor # vice-versa. totalcost_dollars = Decimal( request.POST['totalcost_cents']) / 100 # Create a record of the transfer without the transaction ID. transfer = iac.submit_payment(totalcost_dollars, 'TBD') # Create the charge on Stripe's servers - this will charge # the user's card. charge = stripe.Charge.create( amount=amount_cents_post, currency="usd", card=request.POST['stripeToken'], description="Payment for %s %s - %s" % (group_name, prog.niceName(), request.user.name()), statement_descriptor=group_name[ 0: 22], #stripe limits statement descriptors to 22 characters metadata={ 'ponumber': request.POST['ponumber'], }, ) # Now that the charge has been performed by Stripe, save its # transaction ID for our records. transfer.transaction_id = charge.id transfer.save() except stripe.error.CardError, e: context['error_type'] = 'declined' context['error_info'] = e.json_body['error'] except stripe.error.InvalidRequestError, e: # While this is a generic error meaning invalid parameters were supplied # to Stripe's API, we will usually see it because of a duplicate request. context['error_type'] = 'invalid'
def extracosts(self, request, tl, one, two, module, extra, prog): """ Query the user for any extra items they may wish to purchase for this program This module should ultimately deal with things like optional lab fees, etc. Right now it doesn't. """ if self.have_paid(): raise ESPError( "You've already paid for this program. Please make any further changes on-site so that we can charge or refund you properly.", log=False) # Determine which line item types we will be asking about iac = IndividualAccountingController(self.program, get_current_request().user) costs_list = iac.get_lineitemtypes(optional_only=True).filter( max_quantity__lte=1, lineitemoptions__isnull=True) multicosts_list = iac.get_lineitemtypes(optional_only=True).filter( max_quantity__gt=1, lineitemoptions__isnull=True) multiselect_list = iac.get_lineitemtypes(optional_only=True).filter( lineitemoptions__isnull=False) # Fetch the user's current preferences prefs = iac.get_preferences() forms_all_valid = True ## Another dirty hack, left as an exercise to the reader if request.method == 'POST': # Initialize a list of forms using the POST data costs_db = [ { 'LineItemType': x, 'CostChoice': CostItem(request.POST, prefix="%s" % x.id) } for x in costs_list ] + \ [ x for x in \ [ { 'LineItemType': x, 'CostChoice': MultiCostItem(request.POST, prefix="%s" % x.id) } for x in multicosts_list ] \ if x['CostChoice'].is_valid() and 'cost' in x['CostChoice'].cleaned_data ] + \ [ { 'LineItemType': x, 'CostChoice': MultiSelectCostItem(request.POST, prefix="multi%s" % x.id, choices=x.option_choices, required=(x.required)) } for x in multiselect_list ] # Get a list of the (line item, quantity) pairs stored in the forms # as well as a list of line items which had invalid forms form_prefs = [] preserve_items = [] for item in costs_db: form = item['CostChoice'] lineitem_type = item['LineItemType'] if form.is_valid(): if isinstance(form, CostItem): if form.cleaned_data['cost'] is True: form_prefs.append((lineitem_type.text, 1, lineitem_type.amount, None)) elif isinstance(form, MultiCostItem): if form.cleaned_data['cost'] is True: form_prefs.append((lineitem_type.text, form.cleaned_data['count'], lineitem_type.amount, None)) elif isinstance(form, MultiSelectCostItem): if form.cleaned_data['option']: form_prefs.append( (lineitem_type.text, 1, None, int(form.cleaned_data['option']))) else: # Preserve selected quantity for any items that we don't have a valid form for preserve_items.append(lineitem_type.text) forms_all_valid = False # Merge previous and new preferences (update only if the form was valid) new_prefs = [] for lineitem_name in preserve_items: if lineitem_name in map(lambda x: x[0], prefs): new_prefs.append(prefs[map(lambda x: x[0], prefs).index(lineitem_name)]) new_prefs += form_prefs iac.apply_preferences(new_prefs) # Redirect to main student reg page if all data was recorded properly # (otherwise, the code below will reload the page) if forms_all_valid: bit, created = Record.objects.get_or_create( user=request.user, program=self.program, event=self.event) return self.goToCore(tl) count_map = {} for lineitem_type in iac.get_lineitemtypes(optional_only=True): count_map[lineitem_type.text] = [lineitem_type.id, 0, None, None] for item in iac.get_preferences(): for i in range(1, 4): count_map[item[0]][i] = item[i] forms = [ { 'form': CostItem( prefix="%s" % x.id, initial={'cost': (count_map[x.text][1] > 0) } ), 'LineItem': x } for x in costs_list ] + \ [ { 'form': MultiCostItem( prefix="%s" % x.id, initial={'cost': (count_map[x.text][1] > 0), 'count': count_map[x.text][1] } ), 'LineItem': x } for x in multicosts_list ] + \ [ { 'form': MultiSelectCostItem( prefix="multi%s" % x.id, initial={'option': count_map[x.text][3]}, choices=x.option_choices, required=(x.required)), 'LineItem': x } for x in multiselect_list ] return render_to_response( self.baseDir() + 'extracosts.html', request, { 'errors': not forms_all_valid, 'forms': forms, 'financial_aid': request.user.hasFinancialAid(prog), 'select_qty': len(multicosts_list) > 0 })
def _payment_table_row_cached(prog, student): iac = IndividualAccountingController(prog, student) return (student, iac.get_transfers(), iac.amount_requested(), iac.amount_due())
def finaid_app(self, request, tl, one, two, module, extra, prog): """ A way for a student to apply for financial aid. """ from datetime import datetime from esp.dbmail.models import send_mail app, created = FinancialAidRequest.objects.get_or_create( user=request.user, program=self.program) class Form(forms.ModelForm): class Meta: model = FinancialAidRequest tag_data = Tag.getTag('finaid_form_fields') if tag_data: fields = tuple(tag_data.split(',')) if request.method == 'POST': form = Form(request.POST, initial=app.__dict__) if form.is_valid(): app.__dict__.update(form.cleaned_data) if not request.POST.has_key('submitform') or request.POST[ 'submitform'].lower() == 'complete': app.done = True elif request.POST['submitform'].lower( ) == 'mark as incomplete' or request.POST['submitform'].lower( ) == 'save progress': app.done = False else: raise ESPError( ), "Our server lost track of whether or not you were finished filling out this form. Please go back and click 'Complete' or 'Mark as Incomplete'." app.save() # Automatically accept apps for people with subsidized lunches # Send an e-mail announcing the application either way date_str = str(datetime.now()) iac = IndividualAccountingController(self.program, request.user) if app.reduced_lunch: iac.grant_full_financial_aid() subj_str = '%s %s received Financial Aid for %s' % ( request.user.first_name, request.user.last_name, prog.niceName()) msg_str = "\n%s %s received Financial Aid for %s on %s, for stating that they receive a free or reduced-price lunch." else: subj_str = '%s %s applied for Financial Aid for %s' % ( request.user.first_name, request.user.last_name, prog.niceName()) msg_str = "\n%s %s applied for Financial Aid for %s on %s, but did not state that they receive a free or reduced-price lunch." send_mail( subj_str, (msg_str + """ Here is their form data: ======================================== Program: %s User: %s %s <%s> Approved: %s Has Reduced Lunch: %s Household Income: $%s Form Was Filled Out by Non-Student: %s Extra Explanation: %s ======================================== This request can be (re)viewed at: <http://%s/admin/program/financialaidrequest/%s/> """) % ( request.user.first_name, request.user.last_name, prog.niceName(), date_str, str(app.program), request.user.first_name, request.user.last_name, str(app.user), date_str, str(app.reduced_lunch), str(app.household_income), str(app.student_prepare), app.extra_explaination, settings.DEFAULT_HOST, # server hostname str(app.id)), settings.SERVER_EMAIL, [prog.getDirectorConfidentialEmail()]) return self.goToCore(tl) else: form = Form(initial=app.__dict__) return render_to_response(self.baseDir() + 'application.html', request, { 'form': form, 'app': app })
def _redirect_from_identifier(identifier, result): program = IndividualAccountingController.program_from_identifier( identifier) destination = "/learn/%s/cybersource?result=%s" % (program.getUrlBase(), result) return HttpResponseRedirect(destination)
def confirmreg_forreal(self, request, tl, one, two, module, extra, prog, new_reg): """ The page that is shown once the user saves their student reg, giving them the option of printing a confirmation """ self.request = request from esp.program.modules.module_ext import DBReceipt iac = IndividualAccountingController(prog, request.user) context = {} context['one'] = one context['two'] = two context['itemizedcosts'] = iac.get_transfers() user = ESPUser(request.user) context['finaid'] = user.hasFinancialAid(prog) if user.appliedFinancialAid(prog): context['finaid_app'] = user.financialaidrequest_set.filter( program=prog).order_by('-id')[0] else: context['finaid_app'] = None context['balance'] = iac.amount_due() context['owe_money'] = (context['balance'] != Decimal("0.0")) if prog.isFull() and not user.canRegToFullProgram( prog) and not self.program.isConfirmed(user): raise ESPError( log=False ), "This program has filled! It can't accept any more students. Please try again next session." modules = prog.getModules(request.user, tl) completedAll = True for module in modules: if hasattr(module, 'onConfirm'): module.onConfirm(request) if not module.isCompleted() and module.required: completedAll = False context = module.prepare(context) if completedAll: if new_reg: rec = Record.objects.create(user=user, event="reg_confirmed", program=prog) else: raise ESPError( False ), "You must finish all the necessary steps first, then click on the Save button to finish registration." cfe = ConfirmationEmailController() cfe.send_confirmation_email(request.user, self.program) try: receipt_text = DBReceipt.objects.get(program=self.program, action='confirm').receipt context["request"] = request context["program"] = prog return HttpResponse( Template(receipt_text).render( Context(context, autoescape=False))) except DBReceipt.DoesNotExist: try: receipt = 'program/receipts/' + str( prog.id) + '_custom_receipt.html' return render_to_response(receipt, request, context) except: receipt = 'program/receipts/default.html' return render_to_response(receipt, request, context)
def have_paid(self, user): """ Whether the user has paid for this program. """ iac = IndividualAccountingController(self.program, user) return (iac.amount_due() <= 0)
def payonline(self, request, tl, one, two, module, extra, prog): # Check that the user has completed all required modules so that they # are "finished" registering for the program. (In other words, they # should be registered for at least one class, and filled out other # required forms, before paying by credit card.) modules = prog.getModules(request.user, tl) completedAll = True for module in modules: if not module.isCompleted() and module.required: completedAll = False if not completedAll and not request.user.isAdmin(prog): raise ESPError( "Please go back and ensure that you have completed all required steps of registration before paying by credit card.", log=False) # Check for setup of module. This is also required to initialize settings. self.check_setup() user = request.user iac = IndividualAccountingController(self.program, request.user) context = {} context['module'] = self context['program'] = prog context['user'] = user context['invoice_id'] = iac.get_id() context['identifier'] = iac.get_identifier() payment_type = iac.default_payments_lineitemtype() sibling_type = iac.default_siblingdiscount_lineitemtype() grant_type = iac.default_finaid_lineitemtype() offer_donation = self.settings['offer_donation'] donate_type = iac.get_lineitemtypes().get( text=self.settings['donation_text']) if offer_donation else None context['itemizedcosts'] = iac.get_transfers().exclude( line_item__in=filter( None, [payment_type, sibling_type, grant_type, donate_type ])).order_by('-line_item__required') context['itemizedcosttotal'] = iac.amount_due() # This amount should be formatted as an integer in order to be # accepted by Stripe. context['totalcost_cents'] = int(context['itemizedcosttotal'] * 100) context['subtotal'] = iac.amount_requested() context['financial_aid'] = iac.amount_finaid() context['sibling_discount'] = iac.amount_siblingdiscount() context['amount_paid'] = iac.amount_paid() # Load donation amount separately, since the client-side code needs to know about it separately. donation_prefs = iac.get_preferences([ donate_type, ]) if offer_donation else None if donation_prefs: context['amount_donation'] = Decimal(donation_prefs[0][2]) context['has_donation'] = True else: context['amount_donation'] = Decimal('0.00') context['has_donation'] = False context['amount_without_donation'] = context[ 'itemizedcosttotal'] - context['amount_donation'] if 'HTTP_HOST' in request.META: context['hostname'] = request.META['HTTP_HOST'] else: context['hostname'] = Site.objects.get_current().domain context['institution'] = settings.INSTITUTION_NAME context['support_email'] = settings.DEFAULT_EMAIL_ADDRESSES['support'] return render_to_response(self.baseDir() + 'cardpay.html', request, context)
def payonline(self, request, tl, one, two, module, extra, prog): user = ESPUser(request.user) iac = IndividualAccountingController(self.program, request.user) context = {} context['module'] = self context['one'] = one context['two'] = two context['tl'] = tl context['user'] = user context['invoice_id'] = iac.get_id() context['identifier'] = iac.get_identifier() payment_type = iac.default_payments_lineitemtype() sibling_type = iac.default_siblingdiscount_lineitemtype() grant_type = iac.default_finaid_lineitemtype() context['itemizedcosts'] = iac.get_transfers().exclude( line_item__in=[payment_type, sibling_type, grant_type]).order_by( '-line_item__required') context['itemizedcosttotal'] = iac.amount_due() context['subtotal'] = iac.amount_requested() context['financial_aid'] = iac.amount_finaid() context['sibling_discount'] = iac.amount_siblingdiscount() context['amount_paid'] = iac.amount_paid() if 'HTTP_HOST' in request.META: context['hostname'] = request.META['HTTP_HOST'] else: context['hostname'] = Site.objects.get_current().domain context['institution'] = settings.INSTITUTION_NAME context['storename'] = self.store_id context['support_email'] = settings.DEFAULT_EMAIL_ADDRESSES['support'] return render_to_response(self.baseDir() + 'cardpay.html', request, context)
def onsite_create(self, request, tl, one, two, module, extra, prog): if request.method == 'POST': form = OnSiteRegForm(request.POST) if form.is_valid(): new_data = form.cleaned_data username = ESPUser.get_unused_username(new_data['first_name'], new_data['last_name']) new_user = ESPUser.objects.create_user(username = username, first_name = new_data['first_name'], last_name = new_data['last_name'], email = new_data['email']) self.student = new_user regProf = RegistrationProfile.getLastForProgram(new_user, self.program) contact_user = ContactInfo(first_name = new_user.first_name, last_name = new_user.last_name, e_mail = new_user.email, user = new_user) contact_user.save() regProf.contact_user = contact_user student_info = StudentInfo(user = new_user, graduation_year = ESPUser.YOGFromGrade(new_data['grade'], ESPUser.program_schoolyear(self.program))) try: if isinstance(new_data['k12school'], K12School): student_info.k12school = new_data['k12school'] else: if isinstance(new_data['k12school'], int): student_info.k12school = K12School.objects.get(id=int(new_data['k12school'])) else: student_info.k12school = K12School.objects.filter(name__icontains=new_data['k12school'])[0] except: student_info.k12school = None student_info.school = new_data['school'] if not student_info.k12school else student_info.k12school.name student_info.save() regProf.student_info = student_info regProf.save() if new_data['paid']: Record.createBit('paid', self.program, self.user) IndividualAccountingController.updatePaid(True, self.program, self.user) else: IndividualAccountingController.updatePaid(False, self.program, self.user) Record.createBit('Attended', self.program, self.user) if new_data['medical']: Record.createBit('Med', self.program, self.user) if new_data['liability']: Record.createBit('Liab', self.program, self.user) Record.createBit('OnSite', self.program, self.user) new_user.groups.add(Group.objects.get(name="Student")) new_user.recoverPassword() return render_to_response(self.baseDir()+'reg_success.html', request, { 'student': new_user, 'retUrl': '/onsite/%s/classchange_grid?student_id=%s' % (self.program.getUrlBase(), new_user.id) }) else: form = OnSiteRegForm() return render_to_response(self.baseDir()+'reg_info.html', request, {'form':form})
def finaid(self,request, tl, one, two, module, extra, prog): """ A way for a student to apply for financial aid. """ from datetime import datetime from esp.dbmail.models import send_mail app, created = FinancialAidRequest.objects.get_or_create(user = request.user, program = self.program) class Form(forms.ModelForm): class Meta: model = FinancialAidRequest tag_data = Tag.getTag('finaid_form_fields') if tag_data: fields = tuple(tag_data.split(',')) else: fields = '__all__' if request.method == 'POST': form = Form(request.POST, initial = app.__dict__) if form.is_valid(): app.__dict__.update(form.cleaned_data) if not 'submitform' in request.POST or request.POST['submitform'].lower() == 'complete': app.done = True elif request.POST['submitform'].lower() == 'mark as incomplete' or request.POST['submitform'].lower() == 'save progress': app.done = False else: raise ESPError("Our server lost track of whether or not you were finished filling out this form. Please go back and click 'Complete' or 'Mark as Incomplete'.") app.save() # Automatically accept apps for people with subsidized lunches # Send an e-mail announcing the application either way date_str = str(datetime.now()) iac = IndividualAccountingController(self.program, request.user) if app.reduced_lunch: iac.grant_full_financial_aid() subj_str = '%s %s received Financial Aid for %s' % (request.user.first_name, request.user.last_name, prog.niceName()) msg_str = "\n%s %s received Financial Aid for %s on %s, for stating that they receive a free or reduced-price lunch." else: subj_str = '%s %s applied for Financial Aid for %s' % (request.user.first_name, request.user.last_name, prog.niceName()) msg_str = "\n%s %s applied for Financial Aid for %s on %s, but did not state that they receive a free or reduced-price lunch." send_mail(subj_str, (msg_str + """ Here is their form data: ======================================== Program: %s User: %s %s <%s> Approved: %s Has Reduced Lunch: %s Household Income: $%s Form Was Filled Out by Non-Student: %s Extra Explanation: %s ======================================== This request can be (re)viewed at: <http://%s/admin/program/financialaidrequest/%s/> """) % (request.user.first_name, request.user.last_name, prog.niceName(), date_str, str(app.program), request.user.first_name, request.user.last_name, str(app.user), date_str, str(app.reduced_lunch), str(app.household_income), str(app.student_prepare), app.extra_explaination, settings.DEFAULT_HOST, # server hostname str(app.id)), settings.SERVER_EMAIL, [ prog.getDirectorConfidentialEmail() ] ) return self.goToCore(tl) else: form = Form(initial = app.__dict__) return render_to_response(self.baseDir()+'application.html', request, {'form': form, 'app': app})
def hasPaid(self): iac = IndividualAccountingController(self.program, self.student) return Record.user_completed(self.student, "paid", self.program) or \ iac.has_paid(in_full=True)
def isCompleted(self): iac = IndividualAccountingController(self.program, get_current_request().user) return (len(iac.get_preferences()) > 0)
def cybersource(self, request, tl, one, two, module, extra, prog): # Force users to pay for non-optional stuffs user = request.user iac = IndividualAccountingController(self.program, request.user) context = {} context['module'] = self context['one'] = one context['two'] = two context['tl'] = tl context['user'] = user context['contact_email'] = self.program.director_email context['invoice_id'] = iac.get_id() context['identifier'] = iac.get_identifier() payment_type = iac.default_payments_lineitemtype() sibling_type = iac.default_siblingdiscount_lineitemtype() grant_type = iac.default_finaid_lineitemtype() context['itemizedcosts'] = iac.get_transfers().exclude(line_item__in=[payment_type, sibling_type, grant_type]).order_by('-line_item__required') context['itemizedcosttotal'] = iac.amount_due() context['subtotal'] = iac.amount_requested() context['financial_aid'] = iac.amount_finaid() context['sibling_discount'] = iac.amount_siblingdiscount() context['amount_paid'] = iac.amount_paid() context['result'] = request.GET.get("result") context['post_url'] = settings.CYBERSOURCE_CONFIG['post_url'] context['merchant_id'] = settings.CYBERSOURCE_CONFIG['merchant_id'] if (not context['post_url']) or (not context['merchant_id']): raise ESPError("The Cybersource module is not configured") return render_to_response(self.baseDir() + 'cardpay.html', request, context)
def have_paid(self): iac = IndividualAccountingController(self.program, get_current_request().user) return (iac.amount_due() <= 0)
def have_paid(self): iac = IndividualAccountingController(self.program, get_current_request().user) return (iac.amount_due() <= 0)
def charge_payment(self, request, tl, one, two, module, extra, prog): # Check for setup of module. This is also required to initialize settings. self.check_setup() context = {'postdata': request.POST.copy()} group_name = Tag.getTag('full_group_name') or '%s %s' % (settings.INSTITUTION_NAME, settings.ORGANIZATION_SHORT_NAME) iac = IndividualAccountingController(self.program, request.user) # Set Stripe key based on settings. Also require the API version # which our code is designed for. stripe.api_key = self.settings['secret_key'] # We are using the 2014-03-13 version of the Stripe API, which is # v1.12.2. stripe.api_version = '2014-03-13' if request.POST.get('ponumber', '') != iac.get_id(): # If we received a payment for the wrong PO: # This is not a Python exception, but an error nonetheless. context['error_type'] = 'inconsistent_po' context['error_info'] = {'request_po': request.POST.get('ponumber', ''), 'user_po': iac.get_id()} if 'error_type' not in context: # Check the amount in the POST against the amount in our records. # If they don't match, raise an error. amount_cents_post = Decimal(request.POST['totalcost_cents']) amount_cents_iac = Decimal(iac.amount_due()) * 100 if amount_cents_post != amount_cents_iac: context['error_type'] = 'inconsistent_amount' context['error_info'] = { 'amount_cents_post': amount_cents_post, 'amount_cents_iac': amount_cents_iac, } if 'error_type' not in context: try: with transaction.atomic(): # Save a record of the charge if we can uniquely identify the user/program. # If this causes an error, the user will get a 500 error # page, and the card will NOT be charged. # If an exception is later raised by # stripe.Charge.create(), then the transaction will be # rolled back. # Thus, we will never be in a state where the card has been # charged without a record being created on the site, nor # vice-versa. totalcost_dollars = Decimal(request.POST['totalcost_cents']) / 100 # Create a record of the transfer without the transaction ID. transfer = iac.submit_payment(totalcost_dollars, 'TBD') # Create the charge on Stripe's servers - this will charge # the user's card. charge = stripe.Charge.create( amount=amount_cents_post, currency="usd", card=request.POST['stripeToken'], description="Payment for %s %s - %s" % (group_name, prog.niceName(), request.user.name()), statement_descriptor=group_name[0:22], #stripe limits statement descriptors to 22 characters metadata={ 'ponumber': request.POST['ponumber'], }, ) # Now that the charge has been performed by Stripe, save its # transaction ID for our records. transfer.transaction_id = charge.id transfer.save() except stripe.error.CardError, e: context['error_type'] = 'declined' context['error_info'] = e.json_body['error'] except stripe.error.InvalidRequestError, e: # While this is a generic error meaning invalid parameters were supplied # to Stripe's API, we will usually see it because of a duplicate request. context['error_type'] = 'invalid'
def isCompleted(self): """ Whether the user has paid for this program or its parent program. """ return IndividualAccountingController( self.program, get_current_request().user).has_paid()
def test_finaid(self): """ Verify that financial aid behaves as specified. """ program_cost = 25.0 # Set the cost of the program pac = ProgramAccountingController(self.program) pac.clear_all_data() pac.setup_accounts() pac.setup_lineitemtypes(program_cost) # Choose a random student and sign up for a class student = random.choice(self.students) iac = IndividualAccountingController(self.program, student) sec = random.choice(self.program.sections()) sec.preregister_student(student) # Check that the student owes the cost of the program self.assertEqual(iac.amount_due(), program_cost) # Apply for financial aid self.assertTrue( self.client.login( username=student.username, password='******' ), "Couldn't log in as student %s" % student.username ) response = self.client.get( '/learn/%s/finaid' % self.program.url, **{'wsgi.url_scheme': 'https'}) self.assertEqual(response.status_code, 200) form_settings = { 'reduced_lunch': '', 'household_income': '12345', 'extra_explaination': 'No', 'student_prepare': '', } response = self.client.post('/learn/%s/finaid' % self.program.getUrlBase(), form_settings) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) # Check that the student still owes the cost of the program self.assertEqual(iac.amount_due(), program_cost) # Have an admin approve the financial aid app and check a few different cases: request = FinancialAidRequest.objects.get(user=student, program=self.program) # - 100 percent (fg, created) = FinancialAidGrant.objects.get_or_create(request=request, percent=100) self.assertEqual(iac.amount_due(), 0.0) # - absolute discount amount fg.percent = None fg.amount_max_dec = Decimal('15.0') fg.save() self.assertEqual(iac.amount_due(), program_cost - 15.0) # - discount percentage fg.amount_max_dec = None fg.percent = 50 fg.save() self.assertEqual(iac.amount_due(), program_cost / 2) # Check that deleting the financial aid grant restores original program cost fg.delete() self.assertEqual(iac.amount_due(), program_cost) # Check that the 'free/reduced lunch' option on the finaid results in zero amount due form_settings = { 'reduced_lunch': 'checked', 'household_income': '12345', 'extra_explaination': 'No', 'student_prepare': '', } response = self.client.post('/learn/%s/finaid' % self.program.getUrlBase(), form_settings) self.assertEqual(response.status_code, 302) self.assertIn('/learn/%s/studentreg' % self.program.url, response['Location']) self.assertEqual(iac.amount_due(), 0)
def cybersource(self, request, tl, one, two, module, extra, prog): # Force users to pay for non-optional stuffs user = request.user iac = IndividualAccountingController(self.program, request.user) context = {} context['module'] = self context['one'] = one context['two'] = two context['tl'] = tl context['user'] = user context['contact_email'] = self.program.director_email context['invoice_id'] = iac.get_id() context['identifier'] = iac.get_identifier() payment_type = iac.default_payments_lineitemtype() sibling_type = iac.default_siblingdiscount_lineitemtype() grant_type = iac.default_finaid_lineitemtype() context['itemizedcosts'] = iac.get_transfers().exclude( line_item__in=[payment_type, sibling_type, grant_type]).order_by( '-line_item__required') context['itemizedcosttotal'] = iac.amount_due() context['subtotal'] = iac.amount_requested() context['financial_aid'] = iac.amount_finaid() context['sibling_discount'] = iac.amount_siblingdiscount() context['amount_paid'] = iac.amount_paid() context['result'] = request.GET.get("result") context['post_url'] = settings.CYBERSOURCE_CONFIG['post_url'] context['merchant_id'] = settings.CYBERSOURCE_CONFIG['merchant_id'] if (not context['post_url']) or (not context['merchant_id']): raise ESPError("The Cybersource module is not configured") return render_to_response(self.baseDir() + 'cardpay.html', request, context)
def migrate_program(program): docs = Document.objects.filter(anchor=program.anchor, doctype=2) num_uncategorized = 0 num = 0 found_for_program = False student_dict = {} # Clear financial data for this program pac = ProgramAccountingController(program) pac.clear_all_data() lineitem_types = {'one': {}, 'multi': {}, 'select': []} lineitem_choices = {} # Build a database of each student's financial transactions for the program for doc in docs: student_id = doc.user.id if student_id not in student_dict: student_dict[student_id] = { 'admission': {}, 'finaid_amt': Decimal('0'), 'items': [], 'items_select': [], 'payment': [], } lineitems = doc.txn.lineitem_set.all() """ if lineitems.count() > 0: try: print '\nStudent: %s' % doc.user.name() except: print '\nStudent: %s' % doc.user.username """ for li in lineitems: found_for_program = True base_txt = '[%5d] %s: %.2f' % (li.id, li.text, li.amount) # if li.anchor.uri == splash.anchor.uri + '/LineItemTypes/Required': if 'admission' in li.text.lower( ) or 'cost of attending' in li.text.lower(): base_txt = '(Admission) ' + base_txt if li.text not in student_dict[student_id]['admission']: student_dict[student_id]['admission'][ li.text] = li.amount.copy_abs() elif 'financial aid' in li.text.lower(): base_txt = '(Finaid) ' + base_txt student_dict[student_id]['finaid_amt'] += li.amount elif 'payment received' in li.text.lower(): base_txt = '(Payment) ' + base_txt student_dict[student_id]['payment'].append( li.amount.copy_abs()) elif 'expecting on-site payment' in li.text.lower(): base_txt = '(Payment expected) ' + base_txt student_dict[student_id]['payment'].append( li.amount.copy_abs()) elif 'BuyMultiSelect' in li.anchor.uri: base_txt = '(Select: field "%s", choice "%s") ' % ( li.anchor.name, li.text) + base_txt student_dict[student_id]['items_select'].append( (li.anchor.name, li.text, li.amount.copy_abs())) if li.anchor.name not in lineitem_types['select']: lineitem_types['select'].append(li.anchor.name) lineitem_choices[li.anchor.name] = [] if (li.text, li.amount.copy_abs() ) not in lineitem_choices[li.anchor.name]: lineitem_choices[li.anchor.name].append( (li.text, li.amount.copy_abs())) elif 'BuyMany' in li.anchor.uri or 'shirt' in li.text.lower(): base_txt = '(Multi: field "%s") ' % (li.anchor.name) + base_txt student_dict[student_id]['items'].append( (li.text, li.amount.copy_abs())) if li.text not in lineitem_types['multi']: lineitem_types['multi'][li.text] = li.amount.copy_abs() elif 'BuyOne' in li.anchor.uri or 'lunch' in li.text.lower( ) or 'dinner' in li.text.lower() or 'photo' in li.text.lower(): base_txt = '(Single: field "%s") ' % ( li.anchor.name) + base_txt student_dict[student_id]['items'].append( (li.text, li.amount.copy_abs())) if li.text not in lineitem_types['one']: lineitem_types['one'][li.text] = li.amount.copy_abs() else: num_uncategorized += 1 print 'WARNING: Uncategorized line item: %s' % base_txt # raise Exception('Uncategorized line item: %s' % base_txt) num += 1 # print '-- %s' % base_txt """ if student_dict[student_id]['finaid_amt'] > 0: print student_dict[student_id] elif len(student_dict[student_id]['items_multi']) > 0: print student_dict[student_id] """ if found_for_program: num_programs = 1 else: num_programs = 0 # Populate line item types for the program optional_items = [] for item in lineitem_types['one']: optional_items.append((item, lineitem_types['one'][item], 1)) for item in lineitem_types['multi']: optional_items.append((item, lineitem_types['multi'][item], 10)) select_items = [] for item in lineitem_types['select']: select_items.append((item, lineitem_choices[item])) # print optional_items # print select_items pac.setup_accounts() pac.setup_lineitemtypes(0.0, optional_items, select_items) # Create new transfer records for this student for student_id in student_dict: user = ESPUser.objects.get(id=student_id) iac = IndividualAccountingController(program, user) rec = student_dict[student_id] # Admission fee admission_total_cost = 0 for key in rec['admission']: admission_total_cost += rec['admission'][key] if admission_total_cost > 0: initial_transfer = iac.add_required_transfers()[0] initial_transfer.amount_dec = admission_total_cost initial_transfer.save() # Financial aid if rec['finaid_amt'] > 0: if rec['finaid_amt'] >= admission_total_cost: iac.grant_full_financial_aid() else: iac.set_finaid_params(rec['finaid_amt'], None) # Optional items prefs = [] for item in rec['items']: prefs.append((item[0], 1, item[1])) for item in rec['items_select']: prefs.append((item[0], 1, item[2])) iac.apply_preferences(prefs) # Payments for payment in rec['payment']: iac.submit_payment(payment) try: attended_ids = program.students()['attended'].values_list('id', flat=True) # Execute transfers for the students that are marked as attended pac.execute_pending_transfers( ESPUser.objects.filter(id__in=attended_ids)) # Clear transfers for the students that are marked as not attended pac.remove_pending_transfers( ESPUser.objects.exclude(id__in=attended_ids)) except: print 'Unable to determine which students attended the program; all transfers remain unexecuted' print 'Converted %d line items for %s; %d uncategorized' % ( num, program.niceName(), num_uncategorized) return num_programs
def have_paid(self, user): """ Whether the user has paid for this program. """ iac = IndividualAccountingController(self.program, user) return (iac.amount_due() <= 0)