Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement organizer onboarding process | Check if Organizer has unpaid invoice #430

Open
wants to merge 66 commits into
base: development
Choose a base branch
from

Conversation

lcduong
Copy link
Contributor

@lcduong lcduong commented Nov 11, 2024

This PR partly resolves issue #379 Implement organizer onboarding process

This PR implement:

  • Add a condition in checking step when public Event: Not allow Event to be published if Organizer has unpaid invoice (which status is pending or expired).

image

Summary by Sourcery

Implement a comprehensive billing system for organizers, including invoice management, payment processing with Stripe, and automated billing tasks. Prevent event publication if the organizer has unpaid invoices. Introduce a billing settings form for organizers to manage their billing information. Enhance the deployment with Celery beat schedules for billing tasks.

New Features:

  • Implement a billing system that prevents event publication if the organizer has unpaid invoices, with statuses pending or expired.
  • Introduce a monthly billing collection task that processes billing for events on the first day of each month.
  • Add a feature to update billing invoice information after successful payment, including marking invoices as paid and disabling reminders.
  • Create a retry mechanism for failed payments, allowing for automatic retries based on a schedule.
  • Develop a billing settings form for organizers to manage their billing information, including contact details and tax ID.
  • Integrate Stripe for payment processing, including creating and confirming payment intents, and handling payment methods.

Enhancements:

  • Enhance the organizer form to include billing settings, allowing organizers to manage their billing information directly.
  • Add a new navigation item for billing settings in the organizer's settings menu.

Build:

  • Add a new Celery taskbeat command to the Docker deployment script to handle scheduled tasks.

Deployment:

  • Configure Celery beat schedules for various billing-related tasks, including monthly billing collection, invoice notifications, and payment retries.

Tests:

  • Add a testing view for billing invoice tasks to facilitate testing of billing-related functionalities.

odkhang and others added 27 commits November 5, 2024 10:11
Copy link

sourcery-ai bot commented Nov 11, 2024

Reviewer's Guide by Sourcery

This PR implements an organizer billing system with invoice generation and payment processing through Stripe. The key implementation includes monthly billing collection, invoice generation, payment processing, and a condition to prevent event publication if there are unpaid invoices.

Sequence diagram for event publication check with unpaid invoices

sequenceDiagram
    actor Organizer
    participant System
    participant BillingInvoice
    participant Event

    Organizer->>System: Request to publish event
    System->>BillingInvoice: Check for unpaid invoices
    BillingInvoice-->>System: Return unpaid invoice status
    alt Unpaid invoices exist
        System-->>Organizer: Event cannot be published due to unpaid invoices
    else No unpaid invoices
        System->>Event: Publish event
        Event-->>Organizer: Event published successfully
    end
Loading

ER diagram for billing and organizer relationship

erDiagram
    ORGANIZER {
        int id
        string name
    }
    BILLING_INVOICE {
        int id
        int organizer_id
        int event_id
        string status
        decimal amount
        string currency
        decimal ticket_fee
        date monthly_bill
    }
    ORGANIZER_BILLING_MODEL {
        int id
        int organizer_id
        string primary_contact_name
        string primary_contact_email
        string company_or_organization_name
        string address_line_1
        string address_line_2
        string city
        string zip_code
        string country
        string preferred_language
        string tax_id
        string stripe_customer_id
        string stripe_payment_method_id
    }
    ORGANIZER ||--o{ BILLING_INVOICE : has
    ORGANIZER ||--o{ ORGANIZER_BILLING_MODEL : has
Loading

Class diagram for billing system

classDiagram
    class Organizer {
        +has_unpaid_invoice() bool
    }
    class BillingInvoice {
        +organizer: Organizer
        +event: Event
        +status: String
        +amount: Decimal
        +currency: String
        +ticket_fee: Decimal
        +payment_method: String
        +paid_datetime: DateTime
        +monthly_bill: Date
        +reminder_schedule: List
        +reminder_enabled: bool
    }
    class OrganizerBillingModel {
        +organizer: Organizer
        +primary_contact_name: String
        +primary_contact_email: String
        +company_or_organization_name: String
        +address_line_1: String
        +address_line_2: String
        +city: String
        +zip_code: String
        +country: String
        +preferred_language: String
        +tax_id: String
        +stripe_customer_id: String
        +stripe_payment_method_id: String
    }
    Organizer --> BillingInvoice
    Organizer --> OrganizerBillingModel
Loading

File-Level Changes

Change Details Files
Implemented billing models and database schema
  • Created BillingInvoice model for storing invoice information
  • Added OrganizerBillingModel for storing billing and payment settings
  • Added database migrations for new models
  • Added fields for tracking payment status, reminders, and Stripe integration
src/pretix/base/models/billing.py
src/pretix/base/models/organizer.py
src/pretix/base/migrations/0003_create_billing_invoice_table.py
src/pretix/base/migrations/0003_alter_cachedcombinedticket_id_alter_cachedticket_id_and_more.py
Added Stripe payment integration
  • Implemented Stripe API utilities for payment processing
  • Added webhook handling for Stripe events
  • Created setup intent and payment intent handling
  • Added client-side Stripe integration with card management UI
src/pretix/helpers/stripe_utils.py
src/pretix/api/views/stripe.py
src/pretix/static/billing/js/billing.js
src/pretix/static/billing/css/billing.css
Implemented automated billing tasks
  • Added monthly billing collection task
  • Created invoice notification system
  • Implemented automatic payment retry mechanism
  • Added billing status warning checks
  • Configured Celery beat schedule for automated tasks
src/pretix/eventyay_common/tasks.py
src/pretix/settings.py
deployment/docker/supervisord/pretixbeat.conf
Added billing settings UI
  • Created billing settings form and view
  • Implemented payment method management interface
  • Added billing information display
  • Integrated with Stripe Elements for secure card input
src/pretix/control/templates/pretixcontrol/organizers/billing.html
src/pretix/control/forms/organizer_forms/organizer_form.py
src/pretix/control/views/organizer_views/organizer_view.py
Implemented invoice generation system
  • Created PDF invoice generation functionality
  • Added invoice template with styling
  • Implemented invoice preview functionality
  • Added invoice email notification system
src/pretix/eventyay_common/billing_invoice.py
src/pretix/eventyay_common/views/billing.py

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time. You can also use
    this command to specify where the summary should be inserted.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@odkhang odkhang marked this pull request as ready for review November 14, 2024 06:56
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @lcduong - I've reviewed your changes - here's some feedback:

Overall Comments:

  • Consider adding comprehensive unit tests for the billing and payment processing logic to ensure reliability
  • The monthly_billing_collect task could be broken down into smaller, more focused functions to improve maintainability
Here's what I looked at during the review
  • 🟢 General issues: all looks good
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@@ -180,3 +198,417 @@ def get_header_token(user_id):
"Content-Type": "application/json",
}
return headers


@shared_task(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider refactoring the monthly billing collection logic to reduce nesting and centralize error handling.

The monthly billing collection has some unnecessary complexity that can be simplified:

  1. Extract the duplicated retry logic into a decorator:
def with_retries(max_retries=5, delay=60):
    def decorator(func):
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                logger.error(f"Error in {func.__name__}: {e}")
                if current_retry < max_retries:
                    time.sleep(delay)
                    return wrapper(*args, **kwargs, current_retry=current_retry+1)
                logger.error(f"Max retries exceeded for {func.__name__}")
        return wrapper
    return decorator
  1. Flatten the nested loops with early returns:
@shared_task
@with_retries()
def monthly_billing_collect(self):
    today = datetime.today()
    first_day = today.replace(day=1) 
    last_month = (first_day - relativedelta(months=1)).date()

    gs = GlobalSettingsObject()
    ticket_rate = gs.settings.get("ticket_fee_percentage", 2.5)

    for event in Event.objects.select_related('organizer'):
        if _should_skip_billing(event, last_month):
            continue

        total_amount = calculate_total_amount_on_monthly(event, last_month)
        tickets_fee = calculate_ticket_fee(total_amount, ticket_rate)

        _create_billing_invoice(
            event, total_amount, tickets_fee, 
            last_month, today
        )

def _should_skip_billing(event, last_month):
    if BillingInvoice.objects.filter(
        event=event, monthly_bill=last_month
    ).exists():
        return True

    if not event.orders.filter(
        status=Order.STATUS_PAID,
        datetime__range=[
            last_month,
            last_month + relativedelta(months=1, day=1) - relativedelta(days=1)
        ]
    ).exists():
        return True

    return False

This simplifies the code by:

  • Centralizing retry logic in a reusable decorator
  • Removing nested exception handling
  • Flattening nested loops with helper functions
  • Making the main flow more linear and easier to follow

The core billing logic remains intact but is now more maintainable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants