def reserve_place(): data = request.get_json(force=True) validate(data, post_reserve_place_schema) if dao_has_reserved_place(data['name'], data['email'], data['eventdate_id']): return jsonify( {"error": f"{data['name']} has already reserved a place"}) reserved_place = ReservedPlace(eventdate_id=data['eventdate_id'], name=data['name'], email=data['email']) current_app.logger.info("Reserved place %r", reserved_place.serialize()) dao_create_reserve_place(reserved_place) reserved_place_json = reserved_place.serialize() message = f"{reserved_place_json['name']},<br>"\ f"Thank you for your reservation of {reserved_place_json['event_title']} "\ f"on {reserved_place_json['nice_event_date']}" message_html = get_email_html(BASIC, message=message) send_email(data['email'], f"Reserved place for: {reserved_place_json['event_title']}", message_html) return jsonify(reserved_place_json)
def it_triggers_429_when_next_email_provider_hits_limit( self, mocker, db_session, sample_email_provider): mocker.patch('app.comms.email.dao_get_todays_email_count_for_provider', return_value=30) create_email_provider(name='Next email provider', daily_limit=30) with pytest.raises(expected_exception=InvalidRequest): send_email('*****@*****.**', 'test subject', 'test message')
def it_doesnt_send_email_if_disabled(self, mocker, mock_config_disabled): mock_logger = mocker.patch('app.comms.email.current_app.logger.info') send_email('*****@*****.**', 'test subject', 'test message') assert mock_logger.call_args == call( "Emails disabled, unset EMAIL_DISABLED env var to re-enable")
def it_logs_the_email_if_no_email_config_and_sets_real_email_in_live(self, app, mocker, mock_config_live): mock_logger = mocker.patch('app.comms.email.current_app.logger.info') send_email('*****@*****.**', 'test subject', 'test message') assert mock_logger.call_args == call( "Email not configured, email would have sent: {'to': '*****@*****.**', 'html': 'test message'," " 'from': '*****@*****.**', 'subject': 'test subject'}")
def it_triggers_429_when_minute_limit_reached(self, mocker, db_session): create_email_provider(minute_limit=5) mocker.patch( 'app.comms.email.dao_get_last_minute_email_count_for_provider', return_value=6) with pytest.raises(expected_exception=InvalidRequest): send_email('*****@*****.**', 'test subject', 'test message')
def it_triggers_429_when_hourly_limit_reached(self, mocker, db_session, sample_email_provider): mocker.patch( 'app.comms.email.dao_get_past_hour_email_count_for_provider', return_value=30) with pytest.raises(expected_exception=InvalidRequest): send_email('*****@*****.**', 'test subject', 'test message')
def it_logs_the_email_if_no_email_config_and_sets_real_email_in_live( self, app, db_session, mocker, mock_config_live): mock_logger = mocker.patch('app.comms.email.current_app.logger.info') send_email('*****@*****.**', 'test subject', 'test message') assert mock_logger.call_args == call( "No email providers configured, email would have sent: {'to': '*****@*****.**', " "'from_email': '*****@*****.**', 'from_name': 'New Acropolis', 'subject': 'test subject', " "'message': 'test message'}")
def it_sends_email_to_provider_with_correct_auth(self, mocker, db_session, sample_email_provider, auth_type, expected_header): sample_email_provider.auth_type = auth_type with requests_mock.mock() as r: r.post(sample_email_provider.api_url, text='OK') send_email('*****@*****.**', 'test subject', 'test message') assert expected_header in r.last_request.headers.keys()
def it_sends_email_to_test_email_if_email_restricted( self, mocker, db_session, mock_config_restricted, email, expected_email): mock_logger = mocker.patch('app.comms.email.current_app.logger.info') send_email(email, 'test subject', 'test message') assert mock_logger.call_args == call( "No email providers configured, email would have sent: {'to': '" + expected_email + "', " "'from_email': '*****@*****.**', 'from_name': 'New Acropolis', 'subject': 'test subject', " "'message': 'test message'}")
def it_sends_email_to_provider(self, mocker, db_session, sample_email_provider): with requests_mock.mock() as r: r.post(sample_email_provider.api_url, text='OK') send_email('*****@*****.**', 'test subject', 'test message') data = get_email_data(sample_email_provider.data_map, '*****@*****.**', 'test subject', 'test message', '*****@*****.**', 'Test') assert r.last_request.text == json.dumps(data)
def send_emails(email_id): members_not_sent_to = dao_get_members_not_sent_to(email_id) if current_app.config['ENVIRONMENT'] != 'live': limit = 3 current_app.logger.info('Task send_emails received %s, sending %d emails', email_id, limit or len(members_not_sent_to)) email = dao_get_email_by_id(email_id) for index, (member_id, email_to) in enumerate(members_not_sent_to): if limit and index > limit - 1: break subject = email.get_subject() message = None if email.email_type == EVENT: message = get_email_html(email.email_type, event_id=email.event_id, details=email.details, extra_txt=email.extra_txt, member_id=member_id) email_status_code = send_email(email_to, subject, message) dao_add_member_sent_to_email(email_id, member_id, status_code=email_status_code)
def unsubscribe_member(unsubcode): member = _get_member_from_unsubcode(unsubcode) dao_update_member(member.id, active=False) send_ga_event(f"Unsubscribed {member.id}", "members", "unsubscribe", f"{member.id}") basic_html = get_email_html( email_type=BASIC, title='Unsubscribe', message= "{}, you have successfully unsubscribed from New Acropolis events and magazines" .format(member.name)) send_email(member.email, 'New Acropolis unsubscription', basic_html) return jsonify({'message': '{} unsubscribed'.format(member.name)})
def it_sends_the_email_using_next_available_provider_hourly_limit( self, mocker, db_session): mocker.patch( 'app.comms.email.dao_get_past_hour_email_count_for_provider', mock_get_past_hour_email_count_for_provider_over_first_limit) create_email_provider(name='First email provider', daily_limit=0, hourly_limit=30, pos=0) create_email_provider(name='Another email provider', daily_limit=0, hourly_limit=30, pos=3) next_available_email_provider = create_email_provider( name='Next available email provider', hourly_limit=30, pos=5, available=True) with requests_mock.mock() as r: r.post(next_available_email_provider.api_url, text='OK') resp = send_email('*****@*****.**', 'test subject', 'test message') assert resp == (200, next_available_email_provider.id)
def subscribe_member(): data = request.get_json(force=True) current_app.logger.info('Subscribe member: {}'.format(data)) validate(data, post_subscribe_member_schema) member = dao_get_member_by_email(data.get('email')) if member: return jsonify( {'error': 'member already subscribed: {}'.format(member.email)}), 400 member = Member(name=data['name'], email=data['email'], marketing_id=data['marketing_id'], active=True) dao_create_member(member) send_ga_event(f"Subscribed {member.id}", "members", "subscribe", f"{member.id}") basic_html = get_email_html( email_type=BASIC, title='Subscription', message= "Thank you{} for subscribing to New Acropolis events and magazines". format(' {}'.format(data.get('name', '')) if 'name' in data else ''), member_id=member.id) response = send_email(data['email'], 'New Acropolis subscription', basic_html) return jsonify(member.serialize())
def send_emails(email_id): members_not_sent_to = dao_get_members_not_sent_to(email_id) if current_app.config.get('EMAIL_RESTRICT'): limit = 1 elif current_app.config.get('ENVIRONMENT') == 'live': email_provider = get_email_provider() limit = email_provider.limit else: limit = current_app.config.get('EMAIL_LIMIT') current_app.logger.info( 'Task send_emails received %s, sending %d emails', str(email_id), len(members_not_sent_to) if len(members_not_sent_to) < limit else limit) email = dao_get_email_by_id(email_id) try: for index, (member_id, email_to) in enumerate(members_not_sent_to): if limit and index > limit - 1 or email.email_state != APPROVED: current_app.logger.info("Email stopped - {}".format( "not approved" if email.email_state != APPROVED else f"limit reached: {limit}")) break subject = email.get_subject() message = None if email.email_type == EVENT: message = get_email_html(email.email_type, event_id=email.event_id, details=email.details, extra_txt=email.extra_txt, member_id=member_id) elif email.email_type == MAGAZINE: message = get_email_html(MAGAZINE, magazine_id=email.magazine_id, member_id=member_id) email_status_code, email_provider_id = send_email( email_to, subject, message) dao_add_member_sent_to_email(email_id, member_id, status_code=email_status_code, email_provider_id=email_provider_id) send_ga_event( f"Sent {email.email_type} email, {subject} - {str(email.id)}", "email", "send success" if email_status_code in [200, 201, 202] else "send failed", f"{subject} - {email.id}") except InvalidRequest as e: if e.status_code == 429: current_app.logger.error("Email limit reached: %r", e.message) if "Minute" in e.message: send_periodic_emails.apply_async(countdown=60) raise
def it_sends_the_email_with_override(self, mocker, db_session, sample_email_provider): mocker.patch('app.comms.email.dao_get_todays_email_count_for_provider', return_value=30) with requests_mock.mock() as r: r.post(sample_email_provider.api_url, text='OK') resp = send_email('*****@*****.**', 'test subject', 'test message', override=True) assert resp == (200, sample_email_provider.id)
def it_sends_email_to_provider_with_smtp(self, mocker, db_session, sample_email_provider): sample_email_provider.smtp_server = "http://smtp_server.com" sample_email_provider.smtp_user = "******" sample_email_provider.smtp_password = "******" mock_smtp = mocker.patch('app.comms.email.send_smtp_email') send_email('*****@*****.**', 'test subject', 'test message') assert mock_smtp.called assert mock_smtp.call_args == call('*****@*****.**', 'test subject', 'test message', from_name='New Acropolis', smtp_info={ 'SMTP_SERVER': 'http://smtp_server.com', 'SMTP_USER': '******', 'SMTP_PASS': '******' })
def it_sends_the_email_using_next_provider_with_override( self, mocker, db_session, sample_email_provider): mocker.patch( 'app.comms.email.dao_get_todays_email_count_for_provider', mock_get_todays_email_count_for_provider_over_first_limit) next_email_provider = create_email_provider(name='Next email provider', daily_limit=30) with requests_mock.mock() as r: r.post(next_email_provider.api_url, text='OK') resp = send_email('*****@*****.**', 'test subject', 'test message', override=True) assert resp == (200, next_email_provider.id)
def send_emails(email_id): members_not_sent_to = dao_get_members_not_sent_to(email_id) current_app.logger.info('Task send_emails received %s, sending %d emails', email_id, len(members_not_sent_to)) email = dao_get_email_by_id(email_id) for member_id, email_to in members_not_sent_to: subject = email.get_subject() message = None if email.email_type == EVENT: message = get_email_html( email.email_type, event_id=email.event_id, details=email.details, extra_txt=email.extra_txt) email_status_code = send_email(email_to, subject, message) dao_add_member_sent_to_email(email_id, member_id, status_code=email_status_code)
def update_email(email_id): data = request.get_json(force=True) validate(data, post_update_email_schema) if data['email_type'] == EVENT: try: event = dao_get_event_by_id(data.get('event_id')) except NoResultFound: raise InvalidRequest('event not found: {}'.format(data.get('event_id')), 400) email_data = {} for k in data.keys(): if hasattr(Email, k): email_data[k] = data[k] current_app.logger.info('Update email: {}'.format(email_data)) res = dao_update_email(email_id, **email_data) if res: email = dao_get_email_by_id(email_id) response = None emails_to = [user.email for user in dao_get_users()] if data.get('email_state') == READY: subject = None if data['email_type'] == EVENT: event = dao_get_event_by_id(data.get('event_id')) subject = 'Please review {}'.format(event.title) # send email to admin users and ask them to log in in order to approve the email review_part = '<div>Please review this email: {}/emails/{}</div>'.format( current_app.config['FRONTEND_ADMIN_URL'], str(email.id)) event_html = get_email_html(**data) response = send_email(emails_to, subject, review_part + event_html) elif data.get('email_state') == REJECTED: if email.task_id: revoke_task(email.task_id) message = '<div>Please correct this email <a href="{}">{}</a></div>'.format( '{}/emails/{}'.format(current_app.config['FRONTEND_ADMIN_URL'], str(email.id)), email.get_subject()) message += '<div>Reason: {}</div>'.format(data.get('reject_reason')) response = send_email(emails_to, '{} email needs to be corrected'.format(event.title), message) elif data.get('email_state') == APPROVED: # send the email later in order to allow it to be rejected later = datetime.utcnow() + timedelta(seconds=current_app.config['EMAIL_DELAY']) if later < email.send_starts_at: later = email.send_starts_at + timedelta(hours=9) result = email_tasks.send_emails.apply_async(((str(email_id)),), eta=later) dao_update_email(email_id, task_id=result.id) current_app.logger.info('Task: send_email: %d, %r at %r', email_id, result.id, later) review_part = '<div>Email will be sent at {}, log in to reject: {}/emails/{}</div>'.format( later, current_app.config['FRONTEND_ADMIN_URL'], str(email.id)) event_html = get_email_html(**data) response = send_email( emails_to, "{} has been approved".format(email.get_subject()), review_part + event_html) email_json = email.serialize() if response: email_json['email_status_code'] = response return jsonify(email_json), 200 raise InvalidRequest('{} did not update email'.format(email_id), 400)
def update_event(event_id): data = request.get_json(force=True) current_app.logger.info('Update event: {}'.format(data)) validate(data, post_update_event_schema) try: event = dao_get_event_by_id(event_id) except NoResultFound: raise InvalidRequest('event not found: {}'.format(event_id), 400) errs = [] event_dates = [] event_data = {} if data.get('event_state') == REJECTED: new_rejects = [ r for r in data.get('reject_reasons') if not r.get('id') ] if not new_rejects: raise InvalidRequest('rejected event requires new reject reason', 400) elif data.get('event_state') == APPROVED: if data.get('reject_reasons'): rejects = [ r for r in data.get('reject_reasons') if not r.get('resolved') ] if rejects: raise InvalidRequest( 'approved event should not have any reject reasons', 400) data_event_dates = data.get('event_dates') if data_event_dates: serialized_event_dates = event.serialize_event_dates() data_event_dates__dates = [e['event_date'] for e in data_event_dates] serialized_event_dates__dates = [ e['event_datetime'] for e in serialized_event_dates ] diff_add = set(data_event_dates__dates).difference( serialized_event_dates__dates) intersect = set(data_event_dates__dates).intersection( serialized_event_dates__dates) dates_to_add = [ e for e in data_event_dates if e['event_date'] in diff_add ] dates_to_update = [ e for e in data_event_dates if e['event_date'] in intersect ] for _date in dates_to_add: speakers = [] for s in _date.get('speakers', []): speaker = dao_get_speaker_by_id(s['speaker_id']) speakers.append(speaker) e = EventDate(event_id=event_id, event_datetime=_date['event_date'], end_time=_date.get('end_time'), speakers=speakers) current_app.logger.info('Adding event date: {}'.format( _date['event_date'])) dao_create_event_date(e) if _date['event_date'] not in [ _e.event_datetime for _e in event_dates ]: event_dates.append(e) for _date in sorted(dates_to_update, key=lambda k: k['event_date']): speakers = [] for s in _date['speakers']: speaker = dao_get_speaker_by_id(s['speaker_id']) speakers.append(speaker) db_event_date = [ e for e in event.event_dates if e.event_datetime.strftime( '%Y-%m-%d %H:%M') == _date['event_date'] ][0] db_event_date.speakers = speakers if _date['event_date'] not in [ _e.event_datetime for _e in event_dates ]: event_dates.append(db_event_date) if data.get('reject_reasons'): for reject_reason in data.get('reject_reasons'): if reject_reason.get('id'): reject_data = { 'reason': reject_reason['reason'], 'resolved': reject_reason.get('resolved') or False } dao_update_reject_reason(reject_reason.get('id'), **reject_data) else: rr = RejectReason(event_id=event_id, reason=reject_reason['reason'], resolved=reject_reason.get('resolved') or False, created_by=reject_reason.get('created_by')) dao_create_reject_reason(rr) event_data = {} for k in data.keys(): if hasattr(Event, k) and k not in ['reject_reasons']: event_data[k] = data[k] if event_dates: event_data['event_dates'] = event_dates elif data_event_dates == []: error = 'event needs to have a date' raise InvalidRequest('{} needs an event date'.format(event_id), 400) if event_data.get('fee'): update_data = { 'fee': event_data.get('fee'), 'conc_fee': event_data.get('conc_fee'), 'multi_day_fee': event_data.get('multi_day_fee') or 0, 'multi_day_conc_fee': event_data.get('multi_day_conc_fee') or 0, 'event_type_id': event_data.get('event_type_id'), } db_data = { 'fee': event.fee, 'conc_fee': event.conc_fee, 'multi_day_fee': event.multi_day_fee, 'multi_day_conc_fee': event.multi_day_conc_fee, 'event_type_id': str(event.event_type.id), } if update_data != db_data: event_type = dao_get_event_type_by_id( event_data.get('event_type_id')) p = PayPal() try: event_data['booking_code'] = p.create_update_paypal_button( event_id, event_data.get('title'), event_data.get('fee'), event_data.get('conc_fee'), event_data.get('multi_day_fee'), event_data.get('multi_day_conc_fee'), True if event_type.event_type == 'Talk' else False, booking_code=event_data.get('booking_code')) except PaypalException as e: current_app.logger.error(e) errs.append(str(e)) res = dao_update_event(event_id, **event_data) if res: image_data = data.get('image_data') image_filename = data.get('image_filename') storage = Storage(current_app.config['STORAGE']) if image_data: event_year = str(event.event_dates[0].event_datetime).split('-')[0] target_image_filename = '{}/{}'.format(event_year, str(event_id)) storage.upload_blob_from_base64string(image_filename, target_image_filename, image_data) unix_time = time.time() image_filename = '{}?{}'.format(target_image_filename, unix_time) elif image_filename: image_filename_without_cache_buster = image_filename.split('?')[0] if not storage.blob_exists(image_filename_without_cache_buster): raise InvalidRequest( '{} does not exist'.format( image_filename_without_cache_buster), 400) event.image_filename = image_filename dao_update_event(event.id, image_filename=image_filename) json_event = event.serialize() json_event['errors'] = errs if data.get('event_state') == READY: emails_to = [admin.email for admin in dao_get_admin_users()] message = 'Please review this event for publishing <a href="{}">{}</a>'.format( '{}/events/{}'.format(current_app.config['FRONTEND_ADMIN_URL'], event_id), event.title) send_email(emails_to, '{} is ready for review'.format(event.title), message) elif data.get('event_state') == REJECTED: emails_to = [user.email for user in dao_get_users()] message = '<div>Please correct this event <a href="{}">{}</a></div>'.format( '{}/events/{}'.format(current_app.config['FRONTEND_ADMIN_URL'], event_id), event.title) message += '<ol>' for reject_reason in [ rr for rr in json_event.get('reject_reasons') if not rr.get('resolved') ]: message += '<li>{}</li>'.format(reject_reason['reason']) message += '</ol>' send_email(emails_to, '{} event needs to be corrected'.format(event.title), message) return jsonify(json_event), 200 raise InvalidRequest('{} did not update event'.format(event_id), 400)
def paypal_ipn(params=None, allow_emails=True, replace_order=False): message = '' bypass_verify = False if not params: params = request.form.to_dict(flat=False) else: bypass_verify = True current_app.logger.info('IPN params: %r', params) def get_data(params): data = {} for key in params.keys(): if isinstance(params[key], list): data[key] = params[key][0] else: data[key] = params[key] return data if bypass_verify or (current_app.config['TEST_VERIFY'] and current_app.config['ENVIRONMENT'] != 'live'): v_response = 'VERIFIED' current_app.logger.info('Test paypal verify') else: VERIFY_URL = current_app.config['PAYPAL_VERIFY_URL'] params['cmd'] = '_notify-validate' headers = { 'content-type': 'application/x-www-form-urlencoded', 'user-agent': 'Python-IPN-Verification-Script' } current_app.logger.info("params: %r", params) # debug r = requests.post(VERIFY_URL, params=params, headers=headers, verify=True) r.raise_for_status() v_response = r.text if v_response == 'VERIFIED': current_app.logger.info('VERIFIED: %s', params['txn_id']) # Check return message and take action as needed if v_response == 'VERIFIED': status = product_message = delivery_message = error_message = '' diff = 0.0 data = get_data(params) order_data, tickets, events, products, delivery_zones, errors = parse_ipn( data, replace_order) if 'payment already made' in (','.join(errors)): current_app.logger.info("Transaction payment already made %r", data['txn_id']) return "Duplicate transaction %s" % {data['txn_id']} order_data['params'] = json.dumps(params) if 'is_donation' in order_data.keys( ) and order_data['is_donation'] == "Donation": order_data['is_donation'] = True order_data['linked_txn_id'] = None order = Order(**order_data) dao_create_record(order) if order_data['payment_status'] != 'Completed': err_msg = f"Payment not Completed: {order_data['payment_status']}" errors = [err_msg] else: message = f"<p>Thank you for your order ({order.id})</p>" if order_data['txn_type'] == 'web_accept' and order_data[ 'linked_txn_id']: linked_order = dao_get_order_with_txn_id( order_data['linked_txn_id']) diff = linked_order.delivery_balance - Decimal( order_data['payment_total']) if diff == 0: status = statuses.DELIVERY_EXTRA_PAID elif diff > 0: status = statuses.DELIVERY_EXTRA current_app.logger.warning( 'Delivery balance not paid in full') elif diff < 0: status = statuses.DELIVERY_REFUND current_app.logger.warning('Delivery balance overpaid') dao_update_record(Order, linked_order.id, delivery_status=status, payment_total=linked_order.payment_total + Decimal(order_data['payment_total']), delivery_balance=abs(diff)) order_data['delivery_status'] = status order_data['delivery_zone'] = linked_order.delivery_zone order_data['delivery_balance'] = abs(diff) _payment_total = _get_nice_cost(order.payment_total) _diff = _get_nice_cost(diff) if status == statuses.DELIVERY_EXTRA_PAID: message += f"<div>Outstanding payment for order ({order_data['linked_txn_id']}) of £" \ f"{_payment_total} for delivery to {order_data['delivery_zone']} has been paid.</div>" elif status == statuses.DELIVERY_EXTRA: message += f"<div>Outstanding payment for order ({order_data['linked_txn_id']}) of £" \ f"{_payment_total} for delivery to {order_data['delivery_zone']} has been " \ f"partially paid.</div><div>Not enough delivery paid, £{_diff} due.</div>" elif status == statuses.DELIVERY_REFUND: message += f"<p>You have overpaid for delivery on order ({order_data['linked_txn_id']}) " \ f"by £{_diff}, please send a message to website admin if there is " \ "no refund within 5 working days.</p>" else: if products: for product in products: if product['type'] == BOOK: book_to_order = BookToOrder( book_id=product['book_id'], order_id=order.id, quantity=product['quantity']) dao_create_book_to_order(book_to_order) product_message += ( f'<tr><td>{product["title"]}</td><td> x {product["quantity"]}</td>' f'<td> = {_get_nice_cost(product["price"] * product["quantity"])}</td></tr>' ) product_message = f'<table>{product_message}</table>' address_delivery_zone = None if 'address_country_code' not in order_data: delivery_message = "No address supplied. " status = "missing_address" else: address_delivery_zone = get_delivery_zone( order_data['address_country_code']) admin_message = "" total_cost = 0 for dz in delivery_zones: _d = [ _dz for _dz in DELIVERY_ZONES if _dz['name'] == dz ] if _d: d = _d[0] total_cost += d['price'] _price = _get_nice_cost(d['price']) admin_message += f"<tr><td>{d['name']}</td><td>{_price}</td></tr>" else: errors.append(f'Delivery zone: {dz} not found') admin_message += f"<tr><td>{dz}</td><td>Not found</td></tr>" _total_cost = _get_nice_cost(total_cost) _price = _get_nice_cost(address_delivery_zone['price']) diff = total_cost - address_delivery_zone['price'] if diff != 0: admin_message = f"<p>Order delivery zones: <table>{admin_message}" \ f"</table>Total: £{_total_cost}</p>" admin_message += "<p>Expected delivery zone: " \ f"{address_delivery_zone['name']} - £{_price}</p>" order_data['delivery_balance'] = _get_nice_cost( diff) if diff > 0: status = "refund" delivery_message = f"Refund of £{order_data['delivery_balance']} " \ "due as wrong delivery fee paid" elif diff < 0: _diff = _get_nice_cost(diff) status = "extra" delivery_message = "{}, £{} due. ".format( "No delivery fee paid" if total_cost == 0 else "Not enough delivery paid", order_data['delivery_balance']) admin_message = f"Transaction ID: {order.txn_id}<br>Order ID: {order.id}" \ f"<br>{delivery_message}.{admin_message}" for user in dao_get_admin_users(): send_smtp_email(user.email, f'New Acropolis {status}', admin_message) else: status = statuses.DELIVERY_PAID dao_update_record( Order, order.id, delivery_status=status, delivery_zone=address_delivery_zone['name'] if address_delivery_zone else None, delivery_balance=str(abs(diff))) if delivery_message: order_data['delivery_status'] = status if status == 'refund': delivery_message = f"<p>{delivery_message}, please send a message " \ "to website admin if there is no refund within 5 working days.</p>" else: order_data['delivery_zone'] = address_delivery_zone['name']\ if address_delivery_zone else None else: product_message = ( f'{product_message}<br><div>Delivery to: {order_data["address_street"]},' f'{order_data["address_city"]}, ' f'{order_data["address_postal_code"]}, {order_data["address_country"]}</div>' ) if tickets: for i, _ticket in enumerate(tickets): _ticket['order_id'] = order.id ticket = Ticket(**_ticket) dao_create_record(ticket) tickets[i]['ticket_id'] = ticket.id tickets[i]['title'] = ticket.event.title storage = Storage(current_app.config['STORAGE']) for ticket in tickets: link_to_post = '{}{}'.format( current_app.config['API_BASE_URL'], url_for('.use_ticket', ticket_id=ticket['ticket_id'])) img = pyqrcode.create(link_to_post) buffer = io.BytesIO() img.png(buffer, scale=2) img_b64 = base64.b64encode(buffer.getvalue()) target_image_filename = '{}/{}'.format( 'qr_codes', str(ticket['ticket_id'])) storage.upload_blob_from_base64string( 'qr.code', target_image_filename, img_b64) message += '<div><span><img src="{}/{}"></span>'.format( current_app.config['IMAGES_URL'], target_image_filename) event_date = dao_get_event_date_by_id( ticket['eventdate_id']) minutes = ':%M' if event_date.event_datetime.minute > 0 else '' message += "<div>{} on {}</div></div>".format( ticket['title'], event_date.event_datetime.strftime( '%-d %b at %-I{}%p'.format(minutes))) if event_date.event.remote_access: message += f"<br><div>Meeting id: {event_date.event.remote_access}" if event_date.event.remote_pw: message += f", Password: {event_date.event.remote_pw}" message += "</div>" message += f"<div><a href='https://zoom.us/j/{event_date.event.remote_access}'>"\ "Join zoom event</a></div>" if errors: error_message = '' for error in errors: error_message += f"<div>{error}</div>" order.errors.append(OrderError(error=error)) error_message = f"<p>Errors in order: {error_message}</p>" if status in [ statuses.DELIVERY_EXTRA, statuses.DELIVERY_MISSING_ADDRESS, statuses.DELIVERY_NOT_PAID ]: _delivery_zone_balance = '' if 'delivery_balance' in order_data: _delivery_balance = _get_nice_cost( order_data['delivery_balance']) _delivery_zone_balance = f"/{order_data['delivery_zone']}/{_delivery_balance}"\ if order_data['delivery_zone'] else '' delivery_message = ( f"<p>{delivery_message}Please " f"<a href='{current_app.config['FRONTEND_URL']}/order/{order_data['delivery_status']}/" f"{order_data['txn_id']}{_delivery_zone_balance}'>complete</a> " "your order.</p>") if allow_emails: send_email( order.email_address, 'New Acropolis Order', message + product_message + delivery_message + error_message) else: if v_response == 'INVALID': current_app.logger.info('INVALID %r', params['txn_id']) else: current_app.logger.info('UNKNOWN response %r', params['txn_id']) data = get_data(params) data[ 'txn_id'] = f"XX-{v_response}_{int(datetime.utcnow().timestamp())}-{data['txn_id']}" order_data, tickets, events, products, delivery_zones, errors = parse_ipn( data) order_data['params'] = json.dumps(params) order = Order(**order_data) order.errors.append(OrderError(error=f"{v_response} verification")) dao_create_record(order) return 'Paypal IPN'
def paypal_ipn(): VERIFY_URL = current_app.config['PAYPAL_VERIFY_URL'] params = request.form.to_dict(flat=False) current_app.logger.info('IPN params: %r', params) params['cmd'] = '_notify-validate' headers = { 'content-type': 'application/x-www-form-urlencoded', 'user-agent': 'Python-IPN-Verification-Script' } r = requests.post(VERIFY_URL, params=params, headers=headers, verify=True) r.raise_for_status() # Check return message and take action as needed if r.text == 'VERIFIED': current_app.logger.info('VERIFIED: %s', params['txn_id']) data = {} for key in params.keys(): if isinstance(params[key], list): data[key] = params[key][0] else: data[key] = params[key] order_data, tickets, events = parse_ipn(data) if not order_data: return 'Paypal IPN no order created' order_data['params'] = json.dumps(params) order = Order(**order_data) dao_create_record(order) for i, _ticket in enumerate(tickets): _ticket['order_id'] = order.id ticket = Ticket(**_ticket) dao_create_record(ticket) tickets[i]['ticket_id'] = ticket.id storage = Storage(current_app.config['STORAGE']) message = "<p>Thank you for your order:<p>" for i, event in enumerate(events): link_to_post = '{}{}'.format( current_app.config['API_BASE_URL'], url_for('.use_ticket', ticket_id=tickets[i]['ticket_id'])) img = pyqrcode.create(link_to_post) buffer = io.BytesIO() img.png(buffer, scale=2) img_b64 = base64.b64encode(buffer.getvalue()) target_image_filename = '{}{}'.format('qr_codes', str(tickets[i]['ticket_id'])) storage.upload_blob_from_base64string('qr.code', target_image_filename, img_b64) message += '<div><span><img src="{}/{}"></span>'.format( current_app.config['IMAGES_URL'], target_image_filename) event_date = dao_get_event_date_by_id(tickets[i]['eventdate_id']) minutes = ':%M' if event_date.event_datetime.minute > 0 else '' message += "<span>{} on {}</span></div>".format( event.title, event_date.event_datetime.strftime( '%-d %b at %-I{}%p'.format(minutes))) send_email(order.email_address, 'New Acropolis Event Tickets', message) elif r.text == 'INVALID': current_app.logger.info('INVALID %r', params['txn_id']) else: current_app.logger.info('UNKNOWN response %r', params['txn_id']) return 'Paypal IPN'