Exemple #1
0
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)
Exemple #2
0
 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')
Exemple #3
0
    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")
Exemple #4
0
    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'}")
Exemple #5
0
    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')
Exemple #6
0
    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')
Exemple #7
0
    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'}")
Exemple #8
0
    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()
Exemple #9
0
    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'}")
Exemple #10
0
    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)
Exemple #11
0
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)
Exemple #12
0
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)})
Exemple #13
0
    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)
Exemple #14
0
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())
Exemple #15
0
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
Exemple #16
0
    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)
Exemple #17
0
    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': '******'
                                           })
Exemple #18
0
    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)
Exemple #19
0
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)
Exemple #20
0
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)
Exemple #21
0
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)
Exemple #22
0
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 &pound;" \
                        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 &pound;" \
                        f"{_payment_total} for delivery to {order_data['delivery_zone']} has been " \
                        f"partially paid.</div><div>Not enough delivery paid, &pound;{_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 &pound;{_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: &pound;{_total_cost}</p>"

                            admin_message += "<p>Expected delivery zone: " \
                                f"{address_delivery_zone['name']} - &pound;{_price}</p>"

                            order_data['delivery_balance'] = _get_nice_cost(
                                diff)
                            if diff > 0:
                                status = "refund"
                                delivery_message = f"Refund of &pound;{order_data['delivery_balance']} " \
                                    "due as wrong delivery fee paid"
                            elif diff < 0:
                                _diff = _get_nice_cost(diff)

                                status = "extra"
                                delivery_message = "{}, &pound;{} 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'
Exemple #23
0
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'