SocialPass / socialpass

Hosting the next generation of events
https://registry.socialpass.io
Other
1 stars 0 forks source link

Designing New Checkout Flow #316

Closed crypto-rizzo closed 2 years ago

crypto-rizzo commented 2 years ago

Theme

V1 will revolve around overhauling our core business model. More specifically, we will pivot from charging event organizers to monetizing the ticket checkout experience.

The ideal flow will support a mix-and-match variety of these 4 ticket options: free, fiat, crypto, and asset ownership.

What is the situation

This will require a comprehensive rethinking to our current approach, starting with the database schema and finishing with the end-user experience. Components impacted will include

Key questions include

What is your opinion

I think we need to do a really good job of splitting up research and assignments here, whilst still working closely together. A few working calls this sprint seems like a good idea.

On the checkout experience, I'd say my priorities are in this order

  1. Simplicity
  2. Robustness
  3. Flexibility
hubertokf commented 2 years ago

Talking about the Database schema, I'm thinking about having three extra Models to deal with the checkout.

In addition, the Event Model must contain the payment method that will be accepted.

So we can define this use case: A User wants to buy two tickets from XPTO Event. This Event must be paid using asset ownership. Then:

This is an Example of flow that explain how the "Buy Tickets" should occur.

Other Model fields could be added to the Models to store info and validate the checkout status.

Here is a diagram that represents the relation between the Models image

I think this method covers all possibilities of interaction between checkout, payment and tickets. I'm not sure about simplicity, but this is how a checkout solution is built on the market. With regard to robustness and flexibility, it will certainly meet the demands.

We could discuss any thoughts you all have and adapt whatever is needed.

crypto-rizzo commented 2 years ago

Thanks for this @hubertokf. I think this flow is pretty close to what we're going to need, although I think there may be a few caveats.

The major problem I see with this exact approach is that payment_method is stored as a field on Event, which limits us to only one type of payment_method for an event. Additionally, our current ticket approach is pretty limited with only type of general admission ticket.

I think both of these problems can be solved here with supporting multiple ticket tiers.

Here's my suggestions based on review and some additional reading over the weekend:

  1. TicketTier: Defines each tier of tickets for an event. Holds information on capacity, limits, quantity sold, and supported payment method.
  2. CheckoutSession: Defines a one-time, time-limited checkout session. This session contains the shopping cart items and other information required for checkout.
  3. CartItem: Defines a ticket selection. This ticket selection is tied to TicketTier, and stored via a reference to CheckoutSession.
  4. Order: Defines a completed checkout session. This contains attendee information.
  5. OrderTransaction: Defines payment information for a specific Order. (NOTE: I think we can probably forgo this separate model, and instead put these fields on the Order table itself.)
  6. Ticket: Same as before, but now tied to a specific Order.

Attached below is schema and related image. SocialPass V1-3

CREATE TABLE "root_event" (
  "id" bigint NOT NULL,
  "created" timestamp NOT NULL,
  "modified" timestamp NOT NULL,
  "public_id" uuid NOT NULL,
  "title" "character varying(255)" NOT NULL,
  "description" text,
  "start_date" timestamp,
  "timezone" "character varying(30)",
  "initial_place" "character varying(1024)",
  "team_id" bigint NOT NULL,
  "user_id" bigint,
  "cover_image" "character varying(100)",
  "end_date" timestamp,
  "organizer" "character varying(255)",
  "visibility" "character varying(50)" NOT NULL,
  "is_featured" boolean NOT NULL,
  "publication_date" timestamp,
  "state" "character varying(50)" NOT NULL,
  "address_1" "character varying(255)",
  "address_2" "character varying(255)",
  "city" "character varying(255)",
  "country" "character varying(2)",
  "lat" "numeric(9, 6)",
  "long" "numeric(9, 6)",
  "postal_code" "character varying(12)",
  "region" "character varying(4)",
  "localized_address_display" "character varying(1024)",
  "show_team_image" boolean NOT NULL,
  "show_ticket_count" boolean NOT NULL
);

CREATE TABLE "root_ticket_tier" (
  "id" bigint NOT NULL,
  "created" timestamp NOT NULL,
  "modified" timestamp NOT NULL,
  "public_id" uuid NOT NULL,
  "event_id" bigint NOT NULL,
  "ticket_type" varchar,
  "payment_type" varchar,
  "price" integer NOT NULL,
  "capacity" integer NOT NULL,
  "quantity_sold" integer NOT NULL,
  "max_per_person" integer NOT NULL
);

CREATE TABLE "root_checkout_session" (
  "id" bigint NOT NULL,
  "created" timestamp NOT NULL,
  "modified" timestamp NOT NULL,
  "public_id" uuid NOT NULL,
  "event_id" bigint NOT NULL,
  "expiration" datetime NOT NULL
);

CREATE TABLE "root_cart_item" (
  "id" bigint NOT NULL,
  "created" timestamp NOT NULL,
  "modified" timestamp NOT NULL,
  "public_id" uuid NOT NULL,
  "ticket_tier_id" bigint NOT NULL,
  "checkout_session_id" bigint NOT NULL,
  "quantity" integer NOT NULL
);

CREATE TABLE "root_order" (
  "id" bigint NOT NULL,
  "created" timestamp NOT NULL,
  "modified" timestamp NOT NULL,
  "public_id" uuid NOT NULL,
  "checkout_session_id" bigint NOT NULL,
  "name" varchar NOT NULL,
  "email" varchar NOT NULL,
  "cost" integer NOT NULL
);

CREATE TABLE "root_order_transaction" (
  "id" bigint NOT NULL,
  "created" timestamp NOT NULL,
  "modified" timestamp NOT NULL,
  "public_id" uuid NOT NULL,
  "order_id" bigint NOT NULL,
  "status" varchar,
  "data" json
);

CREATE TABLE "root_ticket" (
  "id" bigint NOT NULL,
  "created" timestamp NOT NULL,
  "modified" timestamp NOT NULL,
  "public_id" uuid NOT NULL,
  "order_id" bigint NOT NULL
);

ALTER TABLE "root_event" ADD FOREIGN KEY ("id") REFERENCES "root_ticket_tier" ("event_id");

ALTER TABLE "root_ticket_tier" ADD FOREIGN KEY ("id") REFERENCES "root_cart_item" ("ticket_tier_id");

ALTER TABLE "root_cart_item" ADD FOREIGN KEY ("checkout_session_id") REFERENCES "root_checkout_session" ("id");

ALTER TABLE "root_checkout_session" ADD FOREIGN KEY ("id") REFERENCES "root_order" ("checkout_session_id");

ALTER TABLE "root_order" ADD FOREIGN KEY ("id") REFERENCES "root_ticket" ("order_id");

ALTER TABLE "root_order" ADD FOREIGN KEY ("id") REFERENCES "root_order_transaction" ("order_id");

ALTER TABLE "root_event" ADD FOREIGN KEY ("id") REFERENCES "root_checkout_session" ("public_id");

What do you think @nftylabs/bix @halfmoonui?

hubertokf commented 2 years ago

Got your idea! I have a few questions that I want to ask you:

  1. TicketTier:
    • Each ticket_type could have more than one payment_type?
    • Each event could have more than one ticket_tier? If so, for each payment_type, we will only have a pre defined quantity of tickets to be sold? I ask this because it's attached to the capacity.
  2. CheckoutSession and Order: Taking a look into this two models, they are very close. What is the difference between these two models? Could it be one where a completed status could be defined by another field on the model? Why is it linked with the Event?
  3. OrderTransaction: One order could have more than one transaction? If so, we can't remove this model.

Taking a look on this models, here is my suggestion:

crypto-rizzo commented 2 years ago

To answer your questions directly @hubertokf :

  1. TicketTier

    • With initial design, only one payment_type per ticket_tier
    • Yes, events can have more than one ticket_tier.
  2. Agree on this point. Like you said, I think we just add a 'completed' field to the CheckoutSession, and forgo the Order model.

  3. I think this is more of a question for the checkout flow itself. Should the checkout flow for attendees support MULTIPLE payment methods in one go? (i.e., I buy 2 tickets with asset ownership and 3 tickets with fiat)? For simplicity sake, my initial idea was to restrict ticket selection to tickets with the same payment method. (If you select asset ownership, you cannot select any tickets that require fiat). However, if we want to support multiple payment methods, then like you said, an Order can potentially have more than one transaction.

I think there a few big questions remaining below

hubertokf commented 2 years ago

I can't answer the first two questions. But the last one, we could handle using the time-limited checkout session as you mentioned before. Once it is created, it will subtract the amount from the Ticket Tier. And return it if the time expires.

It is kind of tricky because we have the quantity sold stored on the Ticket Tier Model, but we could handle doing (quantity sold + open checkouts * quantity) on the go and storing the quantity sold once the checkout is finished.

halfmoonui commented 2 years ago

At @hubertokf's first model diagram: Can't we just merge Checkout model into the CheckoutEvent and store the user ID (foreign key) on there instead? Or is it because you're thinking about each Checkout object potentially having more than one CheckoutEvent?

At @crypto-rizzo's model diagram: Again, I think we can merge some of the models - Order, OrderTransaction, and CheckoutSession are good candidates depending on how we decide to design the flow.

The way this is being planned, I do worry that we would have to rely on database transactions quite a bit, and that can cause performance issues. Would love to hear both of your thoughts on this because I'm certainly not an expert on this.

To try and answer the first two big questions:

Should the end-user checkout flow support multiple payment methods in one order?

I don't fully understand this question. Is it whether or not we give users multiple payment options in the order and they pick one? Or is it that users would be able to use more than one payment method to pay for one order (let's say I pay once using my card, and then using my NFT)? If it's the latter, then I don't think this is a good idea. While this would undoubtedly make our product super flexible in terms of functionality, practically speaking, it would be very very hard to design a good UX that accommodates this type of flow.

Should a ticket tier support multiple payment methods?

I think the answer to this would depend on the first question and what we decide to do there, unless I'm misunderstanding something.

halfmoonui commented 2 years ago

Converting @hubertokf's diagrams into Markdown/Mermaid:

classDiagram
    class Checkout {
        +int id
        +uuid public_id
        +datetime created
        +datetime modified
        +int user
    }

    class CheckoutEvent {
        +int id
        +uuid public_id
        +int checkout
        +int event
        +int quantity
        +datetime created
        +datetime modified
        +int user
    }

    Checkout <|-- CheckoutEvent

    class Transaction {
        +int id
        +uuid public_id
        +int checkout
        +varchar type
        +json data
        +float value
        +varchar status
        +datetime created
        +datetime modified
    }

    Checkout <|-- Transaction
halfmoonui commented 2 years ago

After thinking about this issue, I don't think I can come up with UML diagrams that would be significantly better/different from the ones already posted here. However, I would like to put more emphasis on the UX aspect of this whole issue. As in it doesn't really matter to the customer how we make our database work, but we really need to nail down the product design for this flow to make it as easy and smooth as possible.

crypto-rizzo commented 2 years ago

I've went ahead and created a new UML diagram based on the feedback received.

Most notably, there are now three separate Transaction models - fiat_tx, blockchain_tx, and asset_ownership_tx.

The idea here is that each of these flows has vastly different information required for checkout. Additionally, since we plan to constrain UX based on the payment method, it makes sense to split up this data on the backend.

Additionally, Order and OrderTransaction have had their fields split into these Transaction models, as well as the CheckoutSession model.

Below is schema and diagram again.

SocialPass V1-7

Table "root_event" {
  "id" bigint [not null]
  "created" timestamp [not null]
  "modified" timestamp [not null]
  "public_id" uuid [not null]
  "title" "character varying(255)" [not null]
  "description" text
  "start_date" timestamp
  "timezone" "character varying(30)"
  "initial_place" "character varying(1024)"
  "team_id" bigint [not null]
  "user_id" bigint
  "cover_image" "character varying(100)"
  "end_date" timestamp
  "organizer" "character varying(255)"
  "visibility" "character varying(50)" [not null]
  "is_featured" boolean [not null]
  "publication_date" timestamp
  "state" "character varying(50)" [not null]
  "address_1" "character varying(255)"
  "address_2" "character varying(255)"
  "city" "character varying(255)"
  "country" "character varying(2)"
  "lat" "numeric(9, 6)"
  "long" "numeric(9, 6)"
  "postal_code" "character varying(12)"
  "region" "character varying(4)"
  "localized_address_display" "character varying(1024)"
  "show_team_image" boolean [not null]
  "show_ticket_count" boolean [not null]
}

Table "root_ticket_tier" {
  "id" bigint [not null]
  "created" timestamp [not null]
  "modified" timestamp [not null]
  "public_id" uuid [not null]
  "event_id" bigint [not null]
  "ticket_type" varchar
  "payment_type" varchar
  "price" integer [not null]
  "capacity" integer [not null]
  "quantity_sold" integer [not null]
  "max_per_person" integer [not null]
}

Table "root_checkout_session" {
  "id" bigint [not null]
  "created" timestamp [not null]
  "modified" timestamp [not null]
  "public_id" uuid [not null]
  "event_id" bigint [not null]
  "expiration" datetime [not null]
  "name" varchar [not null]
  "email" varchar [not null]
  "cost" integer [not null]
  "status" varchar
  "fiat_tx" bigint [null]
  "blockchain_tx" bigint [null]
  "asset_ownership_tx" bigint [null]
}

Table "root_checkout_item" {
  "id" bigint [not null]
  "created" timestamp [not null]
  "modified" timestamp [not null]
  "public_id" uuid [not null]
  "ticket_tier_id" bigint [not null]
  "checkout_session_id" bigint [not null]
  "quantity" integer [not null]
}

Table "root_ticket" {
  "id" bigint [not null]
  "created" timestamp [not null]
  "modified" timestamp [not null]
  "public_id" uuid [not null]
  "checkout_session_id" bigint [not null]
  "ticket_tier_id" bigint [not null]
}

Table "root_fiat_tx" {
  "id" bigint [not null]
  "created" timestamp [not null]
  "modified" timestamp [not null]
  "public_id" uuid [not null]
}

Table "root_blockchain_tx" {
  "id" bigint [not null]
  "created" timestamp [not null]
  "modified" timestamp [not null]
  "public_id" uuid [not null]
}

Table "root_asset_ownership_tx" {
  "id" bigint [not null]
  "created" timestamp [not null]
  "modified" timestamp [not null]
  "public_id" uuid [not null]
}

Ref: "root_ticket_tier"."event_id" < "root_event"."id"

Ref: "root_checkout_item"."ticket_tier_id" < "root_ticket_tier"."id"

Ref: "root_checkout_session"."id" < "root_checkout_item"."checkout_session_id"

Ref: "root_ticket"."checkout_session_id" < "root_checkout_session"."id"

Ref: "root_fiat_tx"."id" < "root_checkout_session"."fiat_tx"

Ref: "root_blockchain_tx"."id" < "root_checkout_session"."blockchain_tx"

Ref: "root_asset_ownership_tx"."id" < "root_checkout_session"."asset_ownership_tx"

Ref: "root_ticket"."ticket_tier_id" < "root_ticket_tier"."id"

Ref: "root_checkout_session"."event_id" < "root_event"."id"

Totally agree on the UX aspect as well. Below is roughly the user flow I was thinking, based on this DB schema.

  1. Upon loading the checkout-app, user is created a one-time, time-limited checkout session (CheckoutSession).
  2. The ticket selection screen should show a list of all available TicketTiers.
  3. User will select desired quantity for each of their desired TicketTier. (NOTE: Users can request tickets from multiple TicketTiers, but ONLY if they use the same payment_type).
  4. These ticket selection(s) are stored in CheckoutItem, with a reference to their CheckoutSession.
  5. User proceeds to checkout. Based on the payment_type of the ticket, generate a corresponding *_tx model to be used in checkout.
  6. User fulfills *_tx model (sign message, pay fiat, or send crypto). Once this *_tx` model has met all the requirements for checkout, proceed to success. Create and deliver tickets to user.
crypto-rizzo commented 2 years ago

@hubertokf on high-level user story (what we are trying to accomplish) @halfmoonui on low-level user story (screens, flow, etc.) @crypto-rizzo on ERD

Deadline 9/23

halfmoonui commented 2 years ago

User Story:

As an event attendee, I want to buy tickets using fiat, crypto, and/or NFT ownership, so that I get to attend the events I want.

Acceptance Criteria:

Let me know if this what you meant by a user story @crypto-rizzo

halfmoonui commented 2 years ago

Also posting the rough wireframes here before moving to the actual UI:

Image

Each of the shaded box would look something like this:

Image

crypto-rizzo commented 2 years ago

@mvpedro @devjoaov ⏫

mvpedro commented 2 years ago

@halfmoonui I like what you did there but have a couple of thoughts that I liked to share:

1- Wouldn't we deliver too much information at the same time to the user by grouping the every ticket tier available to each of the accepted currencies on the same page? Following this approach we could end up having 12 or more options for the user to click on the same screen (4 payment options X 3 ticket tiers). With each of the shaded boxes having at least 4 different infos (ticket tier, amout left, price, quantity) it could mean that prior to ticket selection we could reach (following the same logic on the previous question) close to 48 infos on the screen. Wouldn't it be too cluttered? What I've seen Event companies do is that they split ticket selection from payment selection in two screens (like Eventbrite on the attached screenshot). I think it could be a more seamless process to the user if we had a page with only the ticket tiers and then proceed to the accepted currencies. image image

2- On a more general note, our current solution seems to contemplate events that have different entrance gates but not different individual spots (such as numbered chairs on a theater). Should we adapt our design to also fit that kind of possible client as well? From a product validation standpoint I think we should design a solution that validates the product and not every possible client needs but it could be good to wonder if we should already adapt things so that can be an easier implementation in the future.

crypto-rizzo commented 2 years ago

Newest ERD / Schema https://dbdiagram.io/d/6321f1240911f91ba5ae16a2

![Uploading SocialPass V1-10 (1).png…]()

crypto-rizzo commented 2 years ago

I think discussion is pretty much wrapped here - should we close @hubertokf ?

hubertokf commented 2 years ago

I think so