def submit_renew_member(): """Processing the member renewal form. """ user_email = flask_login.current_user.id renew_member = gapps.member_dict_from_request(flask.request, user_email, 'renew') gapps.renew_member_from_dict(renew_member) # Enqueue the renewal email gapps.enqueue_task('/tasks/renew-member-mail', renew_member) return 'success'
def submit_new_member(): """Create the new member. '409 Conflict' is thrown if the email address is already associated with an existing member. """ user_email = flask_login.current_user.id new_member = gapps.member_dict_from_request(flask.request, user_email, 'join') join_or_renew = gapps.join_or_renew_member_from_dict(new_member) if join_or_renew == 'join': # Enqueue the welcome email gapps.enqueue_task('/tasks/new-member-mail', new_member) # else the member already existed and we're going to email. This is especially # important because App Engine 500s even after a successful member creation. We don't # want a retry to spam the member. return f'success: {join_or_renew}'
def submit_volunteer(): """Handle submission of the embedded volunteer self-registration form. """ logging.info('self_serve.submit_volunteer') logging.info('headers: %s', list(flask.request.headers.items())) logging.info('values: %s', list(flask.request.values.items())) referrer = flask.request.values.get( _EMBEDDER_VALUE_KEY) or flask.request.referrer or flask.request.origin # Create a dict of the volunteer info. new_volunteer = gapps.volunteer_dict_from_request(flask.request, referrer) gapps.join_volunteer_from_dict(new_volunteer) # Enqueue the welcome email gapps.enqueue_task('/tasks/new-volunteer-mail', new_volunteer) return 'success'
def paypal_ipn(): """This is the Paypal IPN address. Paypal will make calls to it for various events, including and especially a successful payment. https://developer.paypal.com/webapps/developer/docs/classic/ipn/gs_IPN/ """ logging.info('self_serve_tasks.paypal_ipn') logging.info('self_serve_tasks.paypal_ipn: headers: %s', list(flask.request.headers.items())) # get_data MUST be called before accessing the form fields, or else the data won't # be available. https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request.get_data req_data = flask.request.get_data(as_text=True) logging.info('self_serve_tasks.paypal_ipn: values: %s', list(flask.request.values.items())) # First check with Paypal to see if this notification is legit validation_url = config.PAYPAL_IPN_VALIDATION_URL % req_data validation_response = requests.post(validation_url) if validation_response.status_code != 200 or validation_response.text != 'VERIFIED': # NOT LEGIT logging.warning( 'self_serve_tasks.paypal_ipn: invalid IPN request; %d; %s', validation_response.status_code, validation_response.text) flask.abort(403) # Check if this actually represents a payment if flask.request.values.get('payment_status') != 'Completed': # Not a completed payment, but some intermediate event. We don't # do anything with these. logging.info( 'self_serve_tasks.paypal_ipn: rejecting IPN with status: %s', flask.request.values.get('payment_status')) return flask.make_response('', 200) # Check if this is a membership payment. We want to ignore any other kind of payment. if flask.request.values.get('item_name') != config.PAYPAL_TXN_item_name: logging.info( 'self_serve_tasks.paypal_ipn: rejecting IPN with item_name: "%s"; %s', flask.request.values.get('item_name'), list(flask.request.values.items())) return flask.make_response('', 200) # Check if the payment values are valid if flask.request.values.get( 'receiver_email') != config.PAYPAL_TXN_receiver_email: # Alert our admins about this subject = 'ALERT: bad values in PayPal transaction' body = f''' We received a valid PayPal IPN transaction that contained incorrect or unexpected values. Specifically, the PayPal recipient email address doesn't match ours; should be '{config.PAYPAL_TXN_receiver_email}', got '{flask.request.values.get('receiver_email')}'. Here are the transaction values: {pprint.pformat(list(flask.request.values.items()))} Current URL: {flask.request.full_path} [This email was sent automatically.] ''' emailer.send_to_admins(subject, body) logging.info('self_serve_tasks.paypal_ipn: IPN had bad values') return flask.make_response('', 200) logging.info( 'self_serve_tasks.paypal_ipn: IPN validated, enqueuing process-member-worker' ) # Launch a task to actually create or renew the member. # We'll pass the Paypal params straight through. gapps.enqueue_task('/self-serve/process-member-worker', dict(flask.request.values)) return flask.make_response('', 200)
def process_member_worker(): """Creates or renews a member when payment has been received. """ logging.debug('self_serve.process_member_worker hit') params = gapps.validate_queue_task(flask.request) logging.debug('self_serve.process_member_worker params: %s', params) payer_email = params.get('payer_email', '') payer_id = params.get('payer_id', '') paid_amount = params.get('mc_gross', '') payer_name = '' if params.get('first_name') and params.get('last_name'): payer_name = f"{params.get('first_name')} {params.get('last_name')}" # member_keystring should be considered untrusted. The user could have # removed or altered it before Paypal sent it to us. The rest of the # values came directly from Paypal (fwiw). # This value might also be empty because of an automatic renewal. member_keystring = params.get('invoice', '') # There are two scenarios here: # 1. This is a brand new member. We have their info in NDB and should # now fully create them in the spreadsheet. # 2. This is an automatic renewal payment for an existing member. We # should renew them in the spreadsheet. member_dict = {} candidate_found = False try: member_candidate = MemberCandidate.pop(member_keystring) # Get the member data we stored member_dict = flask.json.loads(member_candidate.member_json) candidate_found = True logging.info('found member candidate') except: logging.info('did not find member candidate') # Add the Paypal info, regardless member_dict[config.SHEETS.member.fields.paypal_name.name] = payer_name member_dict[config.SHEETS.member.fields.paypal_email.name] = payer_email member_dict[config.SHEETS.member.fields.paypal_payer_id.name] = payer_id member_dict[config.SHEETS.member.fields.paid_amount.name] = paid_amount join_or_renew = 'renew' if candidate_found: join_or_renew = gapps.join_or_renew_member_from_dict(member_dict) else: # # Renew an existing member. # We will try to find an existing user by looking up the payer_email # value in either the "Paypal Email" field or the "Email" field. # if not payer_email and not payer_id: logging.warning( 'self_serve.process_member_worker: payer_email and payer_id empty' ) renew_success = False else: renew_success = gapps.renew_member_by_email_or_paypal_id( payer_email, payer_id, member_dict) if not renew_success: # We failed to renew this paying member. # Alert our admins about this. subject = 'ALERT: failed to renew valid payer' body = f''' We received a valid PayPal transaction but were unable to match the \ transaction to a member. In other words, we got someone's money, and it looks \ legit, but we can't figure out who they are in order to actually join or renew \ them. Maybe they're in the spreadsheet under another email address? Here are the transaction values: {pprint.pformat(list(params))} Current URL: {flask.request.path} [This email was sent automatically.] ''' emailer.send_to_admins(subject, body) logging.critical('failed to renew payer') return flask.make_response('', 200) # Enqueue the welcome email if join_or_renew == 'renew': gapps.enqueue_task('/tasks/renew-member-mail', member_dict) logging.info('renewed member') logging.info(member_dict) else: gapps.enqueue_task('/tasks/new-member-mail', member_dict) logging.info('joined member') logging.info(member_dict) return flask.make_response('', 200)
def submit_join(): """Handle submission of the embedded member self-registration form. """ logging.info('self_serve.submit_join') logging.info('headers: %s', list(flask.request.headers.items())) logging.info('values: %s', list(flask.request.values.items())) referrer = flask.request.values.get( _EMBEDDER_VALUE_KEY) or flask.request.referrer or flask.request.origin # Create a dict of the member info. new_member = gapps.member_dict_from_request(flask.request, referrer, 'join') # "Paid" field shouldn't be set by form in self-serve. new_member[config.SHEETS.member.fields.paid.name] = 'N' if flask.request.values.get(_PAYMENT_METHOD_VALUE_KEY) == 'paypal': new_member[config.SHEETS.member.fields.paid.name] = 'paypal' # Write the member info to the member candidate store. # This will be retrieved for processing by process_member_worker() member_candidate = MemberCandidate( member_json=flask.json.dumps(new_member), created=datetime.datetime.now(), expire=datetime.datetime.now() + datetime.timedelta(days=1)) member_candidate_key = member_candidate.store() invoice_id = member_candidate_key.urlsafe().decode('ascii') # If the payment method is "cheque" create the new member directly, # otherwise start the PayPal process. if flask.request.values.get(_PAYMENT_METHOD_VALUE_KEY) == 'cheque': params = {'invoice': invoice_id} gapps.enqueue_task('/self-serve/process-member-worker', params) return 'success' logging.debug('self_serve.submit_join: awaiting PayPal IPN') # We put the key value into the URL so we can retrieve this member # after payment. paypal_url = config.PAYPAL_PAYMENT_URL % (invoice_id, ) # If this is the demo server, then we skip PayPal and just create the user. if config.DEMO: params = { 'payer_email': new_member.get(config.SHEETS.member.fields.email.name), 'payer_id': 'FAKE-ID', 'first_name': new_member.get(config.SHEETS.member.fields.first_name.name), 'last_name': new_member.get(config.SHEETS.member.fields.last_name.name), 'invoice': invoice_id, } gapps.enqueue_task('/self-serve/process-member-worker', params) return 'demo' # Write the URL in the response so it can be shown to the user for follow-up return paypal_url