proofcarryingdata / zupass

Zuzalu Passport
GNU General Public License v3.0
278 stars 72 forks source link

two-way pretix sync #435

Closed ichub closed 1 year ago

robknight commented 1 year ago

I assume that what we need to do here is sync check-ins back to Pretix.

This means calling a Pretix API, providing the secret associated with a position. We will need to keep track of whether a check-in has already been synchronized, and only attempt to synchronize if not so. Pretix supports a nonce field on check-in attempts, to afford some idempotence, so we should prefer the risk of repeating a check-in request to the risk of missing one.

In our app, checking in for a given ticket consumes the ticket, meaning that it cannot be used for further check-ins. A simple implementation would therefore be to add two fields to the devconnect_pretix_tickets table: checkin_synced (boolean) and checkin_nonce (a number for idempotence purposes). checkin_nonce could be generated when the user checks in using our app, and checkin_synced will be set to false by default, and true only once the sync process receives confirmation of a successful response from Pretix.

ichub commented 1 year ago

so there's a 2 by 2 matrix of possibilities on any given sync for any given ticket (position:

two of those situations are trivial - the ones where the check in status on a position is equal on both sides. that leaves us with:

one important thing to note about pretix is that an event admin can check in / un-check-in a position as many times as they want, which our two way sync could be robust to. here's a few situations that could make it so that the check in status is different between the two systems, and how I think the sync should work in those cases:

situation one

ticket created on pretix, and issued to a user via pcdpass. the user gets checked in using the pcdpass app by event staff, causing the ticket's status to be changed to 'checked in'. nobody has touched that ticket on the pretix end. during the next pretix sync, the ticket should be set to 'checked in' on pretix.

situation two

ticket created on pretix, and issued to a user via pcdpass. the user gets checked in using the pcdpass app by event staff, causing the ticket's status to be changed to 'checked in'. prior to a sync, somebody has also marked the ticket as checked in on the pretix side. the sync should cause no changes to the ticket's check in status on either pcdpass or pretix, and should complete successfully.

situation three

ticket created on pretix, and issued to a user via pcdpass. an event admin checks the user in on pretix. the ticket has not been checked in on pcdpass. during the next sync, the ticket on the pcdpass end should change to be checked in.

situation four

ticket created on pretix, and issued to a user via pcdpass. this ticked is checked in by event staff, and its status is synced to pretix, causing the ticket to be 'synced' on both ends. some time later, the ticket is 'unchecked in' via pretix. during the next sync, the pcdpass instance of that ticket should also be unchecked in.

situation five

ticket created on pretix, and issued to a user via pcdpass. this ticked is checked in by event staff, and its status is synced to pretix, causing the ticket to be 'synced' on both ends. some time later, the ticket is 'unchecked in' on the pcdpass end, perhaps using some admin tool. during the next sync, the pretix instance of that ticket should also be unchecked in.

I am less sure about this situation being necessary than the other situations, open to hearing your thoughts.

proposal

what we could do is store the last time the ticket's check in status was changed, whether by checking in via the pcdpass app or by a sync from pretix. if it is possible to calculate the most recent time a check-in was changed on the pretix end, one simple syncing algorithm could be to change both the pcdpass ticket and the pretix ticket's checkin status to be the checkin status of the most recently edited copy of the ticket, whether that is the pcdpass side or the pretix side.

robknight commented 1 year ago

The only problem is that Pretix doesn't tell us specifically when a checkin was deleted - we can only notice that the checkins[] array is empty.

I think this breaks down into three possible states from the perspective of PCDPass:

1) Not checked in 2) Checked in on PCDPass but not synced to Pretix 3) Checked in and synced

There are four possible events that can change this state: 1) The user checks in using PCDPass, moving us from "Not checked in" to "Checked in but not synced" 2) The sync job pushes the local checkin to Pretix, moving us to "Checked in and synced" 3) The sync job fetches from Pretix and gets an empty checkins array, which moves us back to "Not checked in" (unless we are in "Checked in but not synced" and have a pending check-in to tell Pretix about) 4) The sync job fetches from Pretix and gets a non-empty checkins array, which moves us to "Checked in and synced" (if we're not there already)

stateDiagram-v2
    [*] --> NotCheckedIn

    NotCheckedIn --> CheckedInNotSynced: UserChecksIn
    CheckedInNotSynced --> CheckedInSynced: SyncToPretix
    CheckedInSynced --> NotCheckedIn: ReceivedEmptyCheckinsFromPretix
    NotCheckedIn --> CheckedInSynced: ReceivedCheckinsFromPretix

To put it another way: if Pretix says position is checked in, update our state to that state. If Pretix says it's not checked in, but we previously have synced a check-in, reset to NotCheckedIn. If Pretix says it's not checked in, but we have a pending sync, run the sync to update Pretix.

robknight commented 1 year ago

One open question: Pretix has a concept of "checkin lists", where the same ticket might be used to check in multiple times, to different lists. I think this is meant to cover sub-events, and in practice we handle this by having separate tickets for each one.

We might want to restrict our sync process to pay attention only to check-ins on the default checkin list, or validate that only the default checkin list is being used.

robknight commented 1 year ago

We also refer to the "checker" field as "checkerEmail" as part of ITicketData. When a ticket is checked in via Pretix, we do not have an email address for the agent who checked the ticket in.