diff --git a/app/api/helpers/order.py b/app/api/helpers/order.py index 1f73da3035..4b0d3015fb 100644 --- a/app/api/helpers/order.py +++ b/app/api/helpers/order.py @@ -3,6 +3,7 @@ from flask import render_template +from app.settings import get_settings from app.api.helpers import ticketing from app.api.helpers.db import save_to_db, safe_query_without_soft_deleted_entries, get_count from app.api.helpers.exceptions import UnprocessableEntity, ConflictException @@ -37,9 +38,10 @@ def set_expiry_for_order(order, override=False): :param override: flag to force expiry. :return: """ + order_expiry_time = get_settings()['order_expiry_time'] if order and not order.paid_via and (override or (order.status == 'initializing' and ( order.created_at + - timedelta(minutes=order.event.order_expiry_time)) < datetime.now(timezone.utc))): + timedelta(minutes=order_expiry_time)) < datetime.now(timezone.utc))): order.status = 'expired' delete_related_attendees_for_order(order) save_to_db(order) diff --git a/app/api/schema/events.py b/app/api/schema/events.py index 75acbfd0cd..c6c6969846 100644 --- a/app/api/schema/events.py +++ b/app/api/schema/events.py @@ -125,7 +125,6 @@ def validate_timezone(self, data, original_data): ical_url = fields.Url(dump_only=True) xcal_url = fields.Url(dump_only=True) average_rating = fields.Float(dump_only=True) - order_expiry_time = fields.Integer(allow_none=True, default=10, validate=lambda n: 1 <= n <= 60) refund_policy = fields.String(dump_only=True, default='All sales are final. No refunds shall be issued in any case.') is_stripe_linked = fields.Boolean(dump_only=True, allow_none=True, default=False) diff --git a/app/api/schema/settings.py b/app/api/schema/settings.py index d4e187c3c2..eb97e5acaa 100644 --- a/app/api/schema/settings.py +++ b/app/api/schema/settings.py @@ -27,6 +27,9 @@ class Meta: # Tagline for the application. (Eg. Event Management and Ticketing, Home) tagline = fields.Str(allow_none=True) + # Order Expiry Time + order_expiry_time = fields.Integer(allow_none=False, default=15, validate=lambda n: 1 <= n <= 60) + # Google Analytics analytics_key = fields.Str(allow_none=True) diff --git a/app/factories/event.py b/app/factories/event.py index 393d6093e9..873ff3f830 100644 --- a/app/factories/event.py +++ b/app/factories/event.py @@ -50,6 +50,5 @@ class Meta: event_topic_id = None event_sub_topic_id = None discount_code_id = None - order_expiry_time = 10 refund_policy = 'All sales are final. No refunds shall be issued in any case.' is_stripe_linked = False diff --git a/app/factories/setting.py b/app/factories/setting.py index 0fbe7de89a..3d0b983b6b 100644 --- a/app/factories/setting.py +++ b/app/factories/setting.py @@ -19,6 +19,8 @@ class Meta: secret = common.secret_ # Static domain static_domain = common.url_ + # Order Expiry Time + order_expiry_time = 15 #min # # STORAGE diff --git a/app/models/event.py b/app/models/event.py index 08c0d4e5c3..d9766019fc 100644 --- a/app/models/event.py +++ b/app/models/event.py @@ -114,7 +114,6 @@ class Event(SoftDeletionModel): xcal_url = db.Column(db.String) is_sponsors_enabled = db.Column(db.Boolean, default=False) refund_policy = db.Column(db.String, default='All sales are final. No refunds shall be issued in any case.') - order_expiry_time = db.Column(db.Integer, default=10) is_stripe_linked = db.Column(db.Boolean, default=False) discount_code_id = db.Column(db.Integer, db.ForeignKey( 'discount_codes.id', ondelete='CASCADE')) @@ -231,7 +230,6 @@ def __init__(self, is_sponsors_enabled=None, stripe_authorization=None, tax=None, - order_expiry_time=None, refund_policy='All sales are final. No refunds shall be issued in any case.', is_stripe_linked=False): @@ -298,7 +296,6 @@ def __init__(self, self.is_sponsors_enabled = is_sponsors_enabled self.stripe_authorization = stripe_authorization self.tax = tax - self.order_expiry_time = order_expiry_time self.refund_policy = refund_policy self.is_stripe_linked = is_stripe_linked diff --git a/app/models/setting.py b/app/models/setting.py index 8ec3eab489..bd88c73202 100644 --- a/app/models/setting.py +++ b/app/models/setting.py @@ -30,6 +30,8 @@ class Setting(db.Model): secret = db.Column(db.String) # Static domain static_domain = db.Column(db.String) + # Order Expiry Time in Minutes + order_expiry_time = db.Column(db.Integer, default=15, nullable=False) # # STORAGE @@ -220,7 +222,8 @@ def __init__(self, admin_billing_address=None, admin_billing_city=None, admin_billing_zip=None, - admin_billing_additional_info=None): + admin_billing_additional_info=None, + order_expiry_time=None): self.app_environment = app_environment self.aws_key = aws_key self.aws_secret = aws_secret @@ -302,6 +305,9 @@ def __init__(self, self.admin_billing_zip = admin_billing_zip self.admin_billing_additional_info = admin_billing_additional_info + # Order Expiry Time in Minutes + self.order_expiry_time = order_expiry_time + @hybrid_property def is_paypal_activated(self): if self.paypal_mode == 'sandbox' and self.paypal_sandbox_client and self.paypal_sandbox_secret: diff --git a/docs/api/api_blueprint.apib b/docs/api/api_blueprint.apib index 02ad4f332d..df229e90f6 100644 --- a/docs/api/api_blueprint.apib +++ b/docs/api/api_blueprint.apib @@ -3013,7 +3013,6 @@ These endpoints help you to create the basic event structure. To add other parts | `external-event-url` | - | string | - | | `has-organizer-info` | - | boolean(default: `false`) | - | | `average-rating` | Average rating of event | float | - | -| `order-expiry-time` | Expiry time for orders in minutes | Integer(default: 10) | - | | `refund-policy` | Refund policy | string | - | | `is-stripe-linked` | Shows if the event has a linked stripe account. | boolean(default: `false`) | - | @@ -3250,7 +3249,6 @@ Get a list of events. "bank-details": "example", "cheque-details": "example", "identifier": "b8324ae2", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -3321,8 +3319,7 @@ Create a new event using a `name`, `starts-at`, `ends-at`, `timezone` and an opt "bank-details": "example", "onsite-details": "example", "is-sponsors-enabled": "false", - "has-organizer-info": "false", - "order-expiry-time": "10" + "has-organizer-info": "false" }, "type": "event" } @@ -3546,7 +3543,6 @@ Create a new event using a `name`, `starts-at`, `ends-at`, `timezone` and an opt "bank-details": "example", "cheque-details": "example", "identifier": "b8324ae2", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -3797,7 +3793,6 @@ Get a single event. "bank-details": "example", "cheque-details": "example", "identifier": "b8324ae2", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -3872,7 +3867,7 @@ All other fields are optional. Add attributes you want to modify. "onsite-details": "example1", "is-sponsors-enabled": "false", "has-organizer-info": "false", - "order-expiry-time": "10", + "refund-policy": "All sales are final. No refunds shall be issued in any case." }, "id": "1", @@ -4097,7 +4092,6 @@ All other fields are optional. Add attributes you want to modify. "bank-details": "example1", "cheque-details": "example1", "identifier": "b8324ae2", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -4361,7 +4355,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -4598,7 +4591,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -4828,7 +4820,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -5065,7 +5056,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -5296,7 +5286,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -5524,7 +5513,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -5752,7 +5740,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -5980,7 +5967,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -6207,7 +6193,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "can-pay-by-omise": false, "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" @@ -6436,7 +6421,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -6664,7 +6648,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -6892,7 +6875,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -7120,7 +7102,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -7348,7 +7329,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -7576,7 +7556,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -7804,7 +7783,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -8032,7 +8010,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -8260,7 +8237,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -8488,7 +8464,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -8716,7 +8691,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -8944,7 +8918,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -9172,7 +9145,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -9400,7 +9372,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -9628,7 +9599,6 @@ Get a list of events. "external-event-url": "http://example.com", "identifier": "f4fbf878", "ends-at": "2016-12-14T23:59:59.123456+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -9883,7 +9853,6 @@ Get a list of events. "external-event-url": null, "identifier": "4f27fefe", "ends-at": "2022-05-30T04:00:10+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "false" }, @@ -10138,7 +10107,6 @@ Get a list of events. "external-event-url": null, "identifier": "4f27fefe", "ends-at": "2022-05-30T04:00:10+00:00", - "order-expiry-time": "10", "refund-policy": "All sales are final. No refunds shall be issued in any case.", "is-stripe-linked": "true" }, @@ -21867,6 +21835,7 @@ To update or get any attribute of this data layer, you will need admin access. H | `app-name` | Name of the application (Eg. Event Yay!, Open Event) | string | - | | `tagline` | Tagline (Eg. Event Management and Ticketing, Home) | string | - | | `secret` | App Secret | string | **yes** | +| `order-expiry-time` | Expiry time for orders in minutes | Integer(default: 15) | **yes** | | `is-paypal-activated` | Whether paypal payment is configured or not | boolean | - | | `is-stripe-activated` | Whether stripe payment is configured or not | boolean | - | | `is-omise-activated` | Whether omise payment is configured or not | boolean | - | @@ -22023,6 +21992,7 @@ To update or get any attribute of this data layer, you will need admin access. H "app-name": "Event Yay!", "tagline": "Event Management and Ticketing", "secret": "example1234", + "order-expiry-time": "15", "storage-place": "s3", "aws-key": "example1234", "aws-secret": "example1234", @@ -22175,6 +22145,7 @@ To update or get any attribute of this data layer, you will need admin access. H "app-name": "Event Yay!", "tagline": "Event Management and Ticketing", "secret": "example1234", + "order-expiry-time": "15", "storage-place": "s3", "aws-key": "example1234", "aws-secret": "example1234", diff --git a/migrations/versions/2c7ff9781032_.py b/migrations/versions/2c7ff9781032_.py new file mode 100644 index 0000000000..b8832f0304 --- /dev/null +++ b/migrations/versions/2c7ff9781032_.py @@ -0,0 +1,32 @@ +"""empty message + +Revision ID: 2c7ff9781032 +Revises: b08a4ffff5dd +Create Date: 2019-06-21 04:33:59.021835 + +""" + +from alembic import op +import sqlalchemy as sa +import sqlalchemy_utils + + +# revision identifiers, used by Alembic. +revision = '2c7ff9781032' +down_revision = 'b08a4ffff5dd' + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('events', 'order_expiry_time') + op.drop_column('events_version', 'order_expiry_time') + op.add_column('settings', sa.Column('order_expiry_time', sa.Integer(), default=15, nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('settings', 'order_expiry_time') + op.add_column('events_version', sa.Column('order_expiry_time', sa.INTEGER(), autoincrement=False, nullable=False)) + op.add_column('events', sa.Column('order_expiry_time', sa.INTEGER(), autoincrement=False, nullable=False)) + # ### end Alembic commands ### diff --git a/tests/all/integration/api/helpers/test_order.py b/tests/all/integration/api/helpers/test_order.py index 3ea8426c65..d530a5e7e2 100644 --- a/tests/all/integration/api/helpers/test_order.py +++ b/tests/all/integration/api/helpers/test_order.py @@ -1,6 +1,7 @@ import unittest from datetime import timedelta, datetime, timezone +from app.settings import get_settings from app import current_app as app, db from app.api.helpers.db import save_to_db from app.api.helpers.order import set_expiry_for_order, delete_related_attendees_for_order @@ -22,10 +23,11 @@ def test_should_expire_outdated_order(self): with app.test_request_context(): obj = OrderFactory() + order_expiry_time = get_settings()['order_expiry_time'] event = EventFactoryBasic() obj.event = event obj.created_at = datetime.now(timezone.utc) - timedelta( - minutes=obj.event.order_expiry_time) + minutes=order_expiry_time) set_expiry_for_order(obj) self.assertEqual(obj.status, 'expired')