Skip to content

Commit

Permalink
Ticketing integrate cybersource -> ticketing (#652)
Browse files Browse the repository at this point in the history
* Add check on event deletion

* add cybersource package

* Capture context generation + local dev setup instructions (#645)

* capture context view

* fix populate

* move capture context generation to checkout view

* Optimize Django ops in cart validation

* Use Q objects in cart validation

* switch out nginx for local-ssl-proxy

---------

Co-authored-by: aviupadhyayula <aupadhy@gmail.com>

* fix target origin url

* Closes #632 (#648)

* This commit resolves #632:

- Add logic to interact with the CyberSource API to validate
transaction data and also confirm the payment.
- Add appropriate error handling for API invocation failures causing
transaction failure.
- Store the transaction data in a new model `TicketTransactionRecord`
for bookkeeping purposes. Each ticket is also associated with an
instance of this class.
- On transaction success, assign the ticket to the user, remove holds
and from cart, and send out confirmation email.

* Address PR comments, query opt, and others

- More judicious use of `select_for_update`: only lock when updating
holder/owner.
- Better prefetching/bulk updating throughout the query logic
- Return HTTP status codes
- Refactor as per PR comments

* Validate the transient token's signature

- I tested the workflow from `initiate_checkout` to `complete_checkout`
and was able to get it working.
- Ironed out a few bugs
- Add the `reconciliation_id` as a field on the transaction record;
could be useful to generate reports. We'll need to figure out what else
to store to interact with their reporting API.

* Make reconciliation_id nullable to support free tickets

* Address nit, refactor ticket count logic to SQL

* merge migrations...

* pipenv lock again

* Pin uwsgi...2.0.25 breaks CI

---------

Co-authored-by: aviupadhyayula <aupadhy@gmail.com>
Co-authored-by: Rohan Moniz <60864468+rm03@users.noreply.github.com>
  • Loading branch information
3 people authored Apr 14, 2024
1 parent 20d250d commit 428dd7e
Show file tree
Hide file tree
Showing 17 changed files with 633 additions and 194 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ htmlcov/
# Test database
db.sqlite3

# Mac
# Misc
.DS_Store
*.pem

# React
node_modules/
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,13 @@ Use `$ yarn test` to run Cypress tests.
### Development

Click `Login` to log in as a test user. The `./manage.py populate` command creates a test user for you with username `bfranklin` and password `test`. Go to `/api/admin` to login to this account.

#### Ticketing

To test ticketing locally, you will need to [install](https://github.com/FiloSottile/mkcert?tab=readme-ov-file#installation) `mkcert`, enter the `frontend` directory, and run the following commands:

- `$ mkcert -install`
- `$ mkcert localhost 127.0.0.1 ::1`
- `$ export DOMAIN=https://localhost:3001 NODE_TLS_REJECT_UNAUTHORIZED=0`

Then, after the frontend is running, run `yarn ssl-proxy` **in a new terminal window** and access the application at [https://localhost:3001](https://localhost:3001).
4 changes: 3 additions & 1 deletion backend/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ django-runtime-options = "*"
social-auth-app-django = "*"
django-redis = "*"
channels-redis = "*"
uwsgi = {version = "*", sys_platform = "== 'linux'"}
uwsgi = {version ="==2.0.24", sys_platform = "== 'linux'"}
uvloop = {version = "*", sys_platform = "== 'linux'"}
uvicorn = {extras = ["standard"], version = "*"}
gunicorn = "*"
Expand All @@ -54,6 +54,8 @@ pandas = "*"
drf-excel = "*"
numpy = "*"
inflection = "*"
cybersource-rest-client-python = "*"
pyjwt = "*"

[requires]
python_version = "3.11"
146 changes: 96 additions & 50 deletions backend/Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions backend/clubs/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
TargetYear,
Testimonial,
Ticket,
TicketTransactionRecord,
Year,
ZoomMeetingVisit,
)
Expand Down Expand Up @@ -454,5 +455,6 @@ class ApplicationSubmissionAdmin(admin.ModelAdmin):
admin.site.register(ZoomMeetingVisit, ZoomMeetingVisitAdmin)
admin.site.register(AdminNote)
admin.site.register(Ticket)
admin.site.register(TicketTransactionRecord)
admin.site.register(Cart)
admin.site.register(ApplicationCycle)
12 changes: 8 additions & 4 deletions backend/clubs/management/commands/populate.py
Original file line number Diff line number Diff line change
Expand Up @@ -773,19 +773,23 @@ def get_image(url):

# Create some unowned tickets
Ticket.objects.bulk_create(
[Ticket(event=e, type="Regular") for _ in range(10)]
[Ticket(event=e, type="Regular", price=10.10) for _ in range(10)]
)

Ticket.objects.bulk_create(
[Ticket(event=e, type="Premium") for _ in range(5)]
[Ticket(event=e, type="Premium", price=100.10) for _ in range(5)]
)

# Create some owned tickets and tickets in cart
for i in range((idx + 1) * 10):
if i % 5:
Ticket.objects.create(event=e, owner=person, type="Regular")
Ticket.objects.create(
event=e, owner=person, type="Regular", price=i
)
else:
c, _ = Cart.objects.get_or_create(owner=person)
c.tickets.add(Ticket.objects.create(event=e, type="Premium"))
c.tickets.add(
Ticket.objects.create(event=e, type="Premium", price=i)
)

self.stdout.write("Finished populating database!")
18 changes: 18 additions & 0 deletions backend/clubs/migrations/0097_ticket_price.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.3 on 2024-04-03 04:32

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("clubs", "0096_merge_20240304_1450"),
]

operations = [
migrations.AddField(
model_name="ticket",
name="price",
field=models.DecimalField(decimal_places=2, default=0, max_digits=5),
preserve_default=False,
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Generated by Django 5.0.3 on 2024-04-14 20:25

import django.db.models.deletion
import phonenumber_field.modelfields
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("clubs", "0097_ticket_price"),
]

operations = [
migrations.CreateModel(
name="TicketTransactionRecord",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"reconciliation_id",
models.CharField(blank=True, max_length=100, null=True),
),
("total_amount", models.DecimalField(decimal_places=2, max_digits=5)),
(
"buyer_phone",
phonenumber_field.modelfields.PhoneNumberField(
blank=True, max_length=128, null=True, region=None
),
),
("buyer_first_name", models.CharField(max_length=100)),
("buyer_last_name", models.CharField(max_length=100)),
(
"buyer_email",
models.EmailField(blank=True, max_length=254, null=True),
),
],
),
migrations.AddField(
model_name="ticket",
name="transaction_record",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="tickets",
to="clubs.tickettransactionrecord",
),
),
]
Loading

0 comments on commit 428dd7e

Please sign in to comment.