Merchants - Claim restaurant

Description: When a Merchant registers, they may claim a restaurant (Entity). They may also claim a restaurant after they have registered (for example, if they own 2 restaurants).

The verification flow is identical in both cases - we need to verify that the Merchant in fact owns this Entity.

This can be proven by (a) a phone call to Google Places number listed, (b) sending postmail to the Google Places main address listed, or (c) proof of address uploads, such as a utility bill.

Objective: Associate an Entity with a Merchant which can pay for clicks which accrue with that Entity's Offers

Complexity: Backend Medium Frontend Medium

Base branch: claim

Dependencies: These Requirements partially depend on Merchants - Restaurant<Detail>

User flow / UIs

9c6585da-3680-43c2-8347-2678eb108d8e

→ .ai file can be found here: Illustrator UI files – Google Drive (or ask @Alex Petralia )

The Merchant Validation flow (ie. modals) can appear in three locations:

[ 1. Post-registration ]

Right after they submit, but rather before they see this page:

6dda767d-cd54-47ea-bd34-be29ee465363

The modals cannot appear before they hit Register because then there is no Merchant object to make a MerchantClaimRequest.

Therefore, a modal should pop up right above or before this "Thank you!" page encouraging the Merchant to "Verify now" if they like to.

[ 2. Waitlist page ]

If the Merchant has ownership_approved=False, they will be shown a Waitlist page. On this page, they have the option of claiming a new restaurant, which looks exactly the same as on the Merchant Restaurant<List> page.

6ae43d3c-5712-4856-ab10-a416764977f5

[ 3. Restaurant<List> page ]

On a Restaurant<List> page, a Merchant is shown their claimed restaurants plus a Search component where they can claim another restaurant.

54c69b6a-0b79-43fa-87f0-980fe5bade60

Data model

Any request to claim a business automatically creates a MerchantClaimRequest. A MerchantClaimRequest is a many-to-many table between Merchant & Entity - that is, a claim which a Merchant makes for a given Entity.

class Entity(BaseModel):
PENDING = "PENDING"
ALREADY_CLAIMED = "ALREADY_CLAIMED"
CLAIMABLE = "CLAIMABLE"
...
merchant = models.OneToOneField('Merchant')
merchant_claim_request = models.ManyToManyField("Mercant", through="MerchantClaimRequest")
@property
def claimable_status(self, request):
"""Returns if this Entity is claimable or not from the perspective
of a particular Merchant
Note that claimable only means the restaurant is _able to be claimed_ -
that is, a MerchantClaimRequest can be submitted by this Merchant.
"""
if self.merchant:
return ALREADY_CLAIMED
else:
if request.user.merchant_admin == self.merchant_admin_request.merchant_admin:
# If we have issued an outstanding claim already
return PENDING
else:
# Even if another Merchant has an outstanding request, it is still "claimable" from this merchant's perspective
return CLAIMABLE
class MerchantClaimRequest(BaseModel):
"""Tracks all requests by merchants to claim their Entity, using
information Google Places to verify their identity. Requests
which do not yet have `approved` data are still pending verification.
Requests which have `verdict=False` have been denied."""
PHONE = "PHONE"
POSTMAIL = "POSTMAIL"
PROOF_OF_ADDRESS = "PROOF_OF_ADDRESS"
VERIFY_METHODS = (PHONE, POSTMAIL, PROOF_OF_ADDRESS)
merchant = models.ForeignKeyField('Merchant', null=False)
entity = models.ForeignKeyField('Entity', null=False, related="merchant_claim_request)
verify_method = models.CharField(max_length=40, blank=False, choices=(VERIFY_METHODS))
verification_phrase = models.CharField(max_length=200, blank=False) # Example: "Gourmay-rabbit"
staff_comment = models.TextField(blank=True) # Example: Called owner on 2019-04-02, correctly said verification_word of "rabbit"
upload_proof = models.FileField(null=True)
approver = models.OneToOneField('User', null=True) # Example: connects to the Gourmay staff Admin account who made the verdict
approval_date = models.DatetimeField(null=True)
verdict = models.BooleanField(null=True) # Approved / Denied claim

Business logic

When a Merchant is ready to start the verification process for a new restaurant (Entity), they will receive a "Would you like to verify now?" modal.

This will generate a user flow (in the UI) - as mapped in the diagram above - that ultimately creates a MerchantClaimRequest.

The UI will query two backend APIs:

Once a MerchantClaimRequest is created, Gourmay Staff will contact the merchant (via the verification method specified).

Finally, once the merchant is verified (or not), the Gourmay staff user will manually set the MerchantClaimRequest verdict to True or False.

This automatically via a trigger will remove the Merchant from the ownership_approved waitlist (if they were or on it) AND set the Entity.merchant FK to this Merchant (thereby establishing the relationship).

APIs

/api/restaurants/<entity_id>/

User role: Merchant

Method: GET

Testable APIs:

Business logic:

merchant_claim_request is resolved by checking if there is at least one NULL MerchantClaimRequest.verdict for this (Merchant, Entity) tuple

Example response:

GET /restaurants/<entity_id> 200 / {
...,
"claim_status": "PENDING",
"merchant_claim_request": [
{
"creation_date_utc": "2019-04-15T01:12:01.553890Z"
}
]
}

/api/restaurants/<entity_id>/submit-claim/

User role: Merchant

Method: POST

Payload

{
"verify_method": "PHONE" // also: ("POSTMAIL", "PROOF_OF_ADDRESS")
"upload_proof": "base64file" // Base64 file
}

Business logic:

Example response:

POST /restaurants/<entity_id>/submit-claim/ 200 / {
"verfication_phrase": "Gourmay elephant", // ONLY if verify_method=PHONE
"status": "1",
}

Only return a verification_phraseif verify_method=PHONE.

To generate a verification phrase (create-verification-phrase()), we can use the formula: "Gourmay + random animal" (GitHub link).

CMS

MerchantClaimRequests model

The Django Admin should have the following fields readable (R) and modifiable (M):

merchant_admin__name (R)
entity__name (R)
entity__place__data__phone (R)
entity__place__data__address (R)
verification_phrase (M)
verify_method (M)
verdict (M)

The field verification_phrase will be auto-populated by the function create-verification-phrase() (detailed above in the Business Logic).

The field verify_method will be populated based on user input

The field verdict is NULL until a Gourmay staff user sets it to True or False

We should be able to delete MerchantClaimRequests in case they are erroneous or spam (ie. they are not on_delete=PROTECTED somehow).

Entity model

Merchant.ownership_approved should be set to True when a MerchaintClaimRequest.verdict is set to True

Entity.merchant should be set to the verified Merchant when a MerchaintClaimRequest.verdict is set to True

Both these fields should be visible in the Django Admin (R+W)

Email templates

To Gourmay

  1. New MerchantClaimRequest

    1. "Reminder to verify, then approve/disapprove this user"

  2. Nightly email: MerchantClaimRequest.objects.filter(isNull('verdict'))

    1. "Reminder: you have <5> new merchants to verify!"

To MerchantAdmin

  1. New MerchantClaimRequest

    1. "We received your request to verify your business. We'll be reaching out to your shortly!"

  2. MerchantClaimRequest approved

    1. "All set! You can go ahead and add your first offer on this page."

  3. MerchantClaimRequest denied

    1. "Sorry, we couldn't verify your business. You can try again, or contact us at 555-555-5555"

Unit tests

Task outline

  1. @Alex Petralia to check requirements with @André Machado & @Pavlo Hrydzhuk

    1. @Alex Petralia to breakdown feature into Asana tasks & assign out

    2. @Alex Petralia to design Merchant Waitlist page

    3. @Alex Petralia to design Merchant "Claim a Restaurant" components

  2. @André Machado to update data model

  3. @André Machado to update Django Admin panel

  4. @André Machado to build APIs

    1. @Andre to implement business logic & triggers (ie. post-save logic)

    2. @Alex Petralia to create SendGrid email templates

    3. @André Machado to create endpoints

  5. @Pavlo Hrydzhuk to build UI layouts

  6. @Pavlo Hrydzhuk to connect layouts to APIs

  7. @Alex Petralia to QA claim feature