[MUSIC PLAYING] JENNIFER PERSON: So why are we
talking about managing payments today? Well, a lot of apps require you
to be able to exchange money, especially because that's
probably the primary way that you are making
profit off of an app. It would be nice if we
could just do it out of the goodness of our hearts. But it is important to be able
to break even or make a profit. So a lot of different
applications require that ability. So for instance,
mobile commerce apps-- there are all sorts
of stores, seem to have their own
applications right now. And there's a rich
on-demand economy. You can get anything delivered
to your door within 24 hours, or ridesharing, et cetera. And there's also
software as a service, like subscription
for billing software, Cloud services, et cetera. We are most focused on
these first two bullets because if you're familiar with
how apps work in the App Store, when it comes to goods
that are digital, that is managed differently. So in this case, we're
talking about tangible goods. Specifically,
we're talking about a hypothetical
online flower store that Susan and I
are going to open. And it is going to be
called Mobile Florist. And Susan's going to tell you a
little bit more about our app. SUSAN GOLDBLATT: Thanks, Jen. Yeah, so we've been building all
this great flower application. I'm very excited about it. The way that this
will work-- it's a four-step process, like
many things in our talk today. And you'll order
flowers via the app. So figure out which flowers
you want and select them. And then, step 2 will be
scheduling a delivery. So when will our person come and
deliver these flowers to you? Then, the third step
is making a payment via the app-- so actually
using your credit card to charge the customer,
that that'll be most of what we talk about during this talk. And then, step 4 will be
getting your flowers delivered. So how do you actually
get the services? So that's how we'll build
out the business model. And with that in mind,
I'll go into talking a little bit about how we'll
be building the application. So there's four steps
to this process. And we'll do an
authentication process. So how does a user log in
and access their information? How do we create
them in Firebase? How do we schedule things? So store the information. And then finally, how are we
going to manage those customer payments? And then, we'll follow
up with how might we handle a refund in
the application-- so just a little bit more
of a use case for refunds. So we know that
managing payments can be kind of intimidating. This is the part where you
actually charge your customers. And so if you do it wrong,
you can do it really wrong. And no one wants that. It erodes user trust. So we're going to talk about
how you can handle payments in a way that's safe and
seamless for your customers. And we'll use two technologies,
Stripe and Firebase. So I'm going to
talk a little bit about why we're using Stripe. First, I'll explain what Stripe
is in case we don't know. It's a payment processor. And so that means
that it handles charging the credit cards. And this can be really
helpful because we don't want to be in the
business of handling credit card information in our app. We want to focus
on flower delivery because that's
what we care about. And so we want to take the
process of actually making payments, and handling credit
card information, and all of the security
concerns-- like what if someone is trying
to use an old card? How do we handle that? And Stripe and other
payment processors do a really good job at this. So for us, it makes
the most sense to use a payment
processor to handle that type of information, and
security, and convenience. So we're going to
use Stripe here. But you definitely don't
have to use Stripe. We really only picked it
because we know the Stripe API, and we're familiar with it. But you can use any payment
processor that you want. So that's why we're
going to use Stripe. And I'll pass it off to
Jenn to explain a little bit about why we're using Firebase. JENNIFER PERSON: Thank you. So there are actually
several reasons that we're going to
be using Firebase because we are using different
products along different steps. So I'm going to break it down
by each step that we're taking. Here are the
Firebase technologies that we're going
to be using today-- Firebase Authentication,
Cloud Firestore, and Cloud Functions
for Firebase. Going back to our
four steps-- yes, this is going to
come up quite a bit-- our first step is going to
be authentication, in order for us to know
who is going to be paying, where we're
sending the flowers, storing that information. So when the user comes
back to the application, they still have all that. We need a way to verify
who our users are. But we know that
auth is very complex, though it is essential. If you've ever rolled your own
auth, you already know this. One option that
people use a lot is federated identity providers,
like Google, or Facebook, or Twitter. And these are really
great options. They definitely eliminate
some of the difficult work of setting up auth. But at the same time, there
are still some challenges. So for instance, what
if your customers don't want to use that account
to sign into your app, or they just don't have one. Or if you want to give them
more than one option, how are you going to make
sure that a user who signs in with Google, and a
user who signs in with Facebook still have the
same type of user, the same type of information? So another problem is, what
if someone accidentally logs in with one provider
one day and another the next? How are you going to
handle those situations? Fortunately, there's
Firebase Auth. So Firebase Auth supports
all these methods of authentication and more-- so email and password, Google,
Facebook, Twitter, GitHub, Anonymous, which is very useful. So for instance,
when a user first goes into our application,
it's not the best experience to right out of the box
make them need to sign in. They aren't invested
yet, and that can really ruin the experience. So allowing them
to explore the app and then log in once
they're ready to schedule can give you a much
better experience. At the same time, you still
want to protect your information with security rules and be able
to identify who your users are. Anonymous Auth allows
you to do that. When a user first
comes into the app, you can log them in anonymously. And then every time they
return to the application, it is still the same user
with the same user ID. And even better, when
it is time for that user then to schedule something,
and they choose the way that they're going to
log in, it actually links automatically
that anonymous user with the email and password
or federated identity provider that they sign in with-- so at least, for a very
seamless experience. And if you're interested in a
UI that looks a little like this one-- you'll actually see
another example of it further in the talk-- this is an example of Firebase
UI, which is an open source project on GitHub that allows
you to build some UI out of the box for different
Firebase products, including Auth. And it really is as
easy to add a provider as it is to add a
single line of code. And I know that gets
thrown around a lot. But in this case, it
really is that simple. So yeah, that's why
we're using Firebase Auth for authentication. Our next step is going
to be scheduling. And we need to store that
scheduling information somewhere, so that our customers
can see their scheduling information. We need to be able
to see all of it, so we can figure out
where to send the flowers. So in this case, we are going
to use Cloud Firestore to store all of our data. So Cloud Firestore is
a database in the Cloud that is built as part
of the Firebase service. It is divided into documents. So if you are familiar
with a NoSQL model-- maybe you've used the real-time
time database before-- this is similar but
has some benefits. So a document has inside
of it key value pairs that we're calling fields. Keys are strings,
and values can be all sorts of different things-- strings, numbers, binary values. They could be Booleans, and even
these objecty-looking things. So you have all sorts
of things that you can put inside of a document. Documents are stored
in collections, which are, of course,
collections of documents. And these documents can point to
other collections of documents. You cannot have a document
inside of a document, but you can have
a sub-collection. So what you end up with is a
structure somewhat like this. So what is the advantage of
storing your data like this, as opposed to just a full
Jason blob like you might get in the real-time database? What this allows you to
do is make shallow queries for one thing. So let's say, I just
want the information that's in that highlighted box. In a NoSQL model sometimes,
if you want to query that, you're going to get all of
the information underneath it. So you really have to design
your database structure, such that you keep it
as shallow as possible. And that means that
it doesn't necessarily match with your application. This allows you to
make a data model that really mirrors your
application and makes it a lot easier to understand. So on the back-end,
Cloud Firebase Store supports security rules. So along with
Firebase Auth, they can help you make sure that
users only access the data that you want them to access. So this is where Auth and Cloud
Firestore work nicely together. Since we can identify our
users by their user ID, we can make sure that
securely, they can only check their own data, and we are
able to look at any scheduling data. So the final technology
when we want to talk about is in terms of managing
customer payments. And this is the bulk
of what you're going to see us working on today. We are going to use Cloud
Functions for Firebase, which are event-driven-- meaning
they react to events that happen in your app. This could be when a user first
slides into Firebase Auth, or writes to a particular
document in Cloud Firestore, writes to a field in
the real-time database, maybe triggers a
conversion analytics event in Google Analytics. There are all sorts of
things that can trigger this. You could even have an
HTTP triggered function, so that you can trigger
it whatever you want to. Then, wherever
these events occur, the Cloud Function
starts to run. And whatever code
that you have put in it, whatever actions you want
to occur are going to happen. Cloud Functions are written
in TypeScript or JavaScript on top of No.js, which means you
have the option to incorporate all sorts of NPM packages. We're looking at 350,000
different packages at your disposal. So you can definitely
customize it. This is not just
for Google products. You can incorporate all of the
different APIs that you need, which we'll see today in
terms of using the Stripe API. So Cloud Functions work inside
a privileged Google environment, which means that
unlike the client code, they aren't going to be
subject to security rules. This is really
beneficial for when you want to make actions
that you don't want to be accessible to the client. You can send things
here and do checks before writing to
places in the database, for instance, that you
wouldn't want your client to be able to do so. And if you are using any sort
of Google Cloud Services, usually setup is a snap. And you don't even
necessarily need to include some of those
configuration steps that you're used to setting up. So now that you know about why
we're using these technologies, let's dig into the good
stuff, the payments. SUSAN GOLDBLATT: Cool. So we are going to do another
four-step process to manage our customer payments. The first step in there is
creating a Stripe customer. So we know through Firebase
Auth that we have a customer. But we need to let Stripe
know because that way, we can actually do
things like add cards, and also charge payments,
and make refunds. So that's step 1. Step 2 is then creating
a payment method. So sometimes, we
say payment method. Sometimes, we say add a card. And sometimes, we say source. It's just a way of
paying for the service. So we'll do that. And then next, we
make the payment, so processing the credit card. Then finally, we'll
handle the refund. So let's go into the first step
of creating a Stripe customer. The way that this
will work is the user will log in and create
a Firebase Auth user. From there, we'll go ahead and
trigger a Cloud Function based on the creation of that user. Then, we'll talk to the Stripe
SDK and wait for the data to come back in
the Cloud Function. Then, with the data back, we'll
write that to Cloud Firestore, so that we can use it later. Let's look at what this is
going to look like in code. So let me walk
through some of this. It's a lot of code
on the screen. So here we have a function
called Create Stripe Customer. The function is
triggered off of an auth. So you can see the functions
that auth that user. And then, on Create--
so that means that when a user is created,
we'll run this function. Then, we get the
user object here. So that contains
information that we'll use like the email
address or other things that we might need. So what we do is just talk
to the Stripe API here. So Stripe.customers.create. So we're saying, hey,
Stripe, make a customer. Here's their email address. Super easy, and then we
just wait for that data to come back. And we use a wait here because
it's TypeScript JavaScript. And if we don't use a wait,
it'll just fall right through, and we won't get the data back. So we have to wait. And we wait for the customer. And then, with that
customer, we store it back to Cloud Firestore. So we do that by saying
admin.firestore.collection. And then, we have a collection
called Stripe Customers. And that collection is
made up of all of our users with the user.uid. So we'll see in Firestore
this collection and then a bunch of documents. And each of those documents
will have data that's related to their Stripe ID. And then, we'll also add
other information there. So let's look at
what this will look like in our actual application. So here you can see, we go
and click the Login button. And this will pop up some
really nice UI from Firebase. And this is all part
of authentication. So we'll go into the actual
Google account to log in. We pick our login account
that we want to use. And we can see here, we've
logged in successfully. If we look at our Data
Viewer, the Stripe customer is added there. And when we click into it,
we have the customer ID that we got back from Stripe. Cool, so I'll pass
it off to Jen to talk about how we're going to
create a Stripe payment method. JENNIFER PERSON: So now that
we have a Stripe customer associated with a
Firebase user, we want to be able to
add a method for them to pay for our services. This involves the
Client Stripe SDK, creating what is called a token. A token is a single-use
payment method. So you give, say, a credit
card or some other source of information to the
Stripe Client SDK, and they give you
back this token that they identify
as the credit card. So it doesn't actually
have specific information about the credit card in it--
which is nice because then, we can write it to Cloud Firestore
without actually storing any of that very sensitive data. At every point in
this, you will see that we don't store anything
that is identifiable in terms of someone being able
to take the credit card information from a user. So we want to store stuff
that even if it leaked wouldn't have much of an impact. So in this case,
we're just going to write that one-time token,
which is naturally going to trigger a Cloud Function. At this point, we're going to
speak to the Stripe Server SDK, and take that token
and our customer ID, and link them
together, which gives us what's called a Source. a Source and a token
are very similar, but a source is something
that you can use over and over because we think that once
someone orders flowers through Mobile Florist,
they're definitely going to want to do so again. Finally, we're going to write
information about the Source into Cloud Firestore,
which enables the client to be able to see that the
card was added successfully and also see some
information about their card. So here's what that Cloud
Function looks like in code. Our function is called
Add Payment Source. And in terms of its
form, we have functions again dot Firestore instead
of in the previous case, you saw off. Here we have a document which
is Stripe customer's user ID, tokens, auto ID. This is where that
token is being written. You'll see that a
couple of these fields are in brackets, which means
that they are wild cards. So whatever it happens
to be in that place will still trigger
this function. It doesn't need to
actually say user ID. In this case, it's
actually going to be the Firebase user's UID. This isn't on write
function, which means it is triggered
whenever there is a write to that
specific location. So that could be
when it is first created, when a change is made,
when something is deleted-- all of that is going to
trigger this function, which could be beneficial and works
for this particular situation. But you'll see that there
are some cases where you don't want that. So to get the data
from Firestore, we're going to call
change.after.data. And this is going to
give us what was just changed in Cloud Firestore. If there is no data because
it was just a delete, we're just going to return
out of this function. We don't need to proceed. Otherwise, we're going to
get the token from that data. And then, we are going to
get the Stripe customer ID from Cloud Firestore. So this is what we stored in
the first function that you saw. And this is going to
come up a lot, where we're going to need
to get that customer ID based on the Firebase user. So we're just going to put
that in its own function, and you'll see get customer ID. And that means we're
going to get the customer ID for this particular
customer, which enables us to do something like this. CallStripe.customers
create source, pass the customer,
end of the token. And then, we're going to
write that information back to the database. Notice, we also have
something that instead of just a random ID
for the document, we're using something
called Response. In the Response, we
have fingerprint, which is a unique
identifier of that source. And that actually just makes
it easy to query this later. We also call merge true, so
that rather than overriding all the information
in the document, it just merges the
fields together. So let's see what this
looks like in action. Here, you'll see
adding a credit card. Feel free to try to use this. It's just a Stripe test card. It's really not
going to do anything. Also, it's expired. So it's really not good. Good luck. So the card has been saved. You'll see it says Success. And after a moment, the UI
updates to show the new card. So here, I can show a
little information about it. And then, in Cloud
Firestore, you can see the kind of
information that comes back. A lot of these are No. But if you wanted to provide
that information as well, you could. OK, so we have our
Stripe customer. We have their payment
method all cued up. Let's move on to
actually making a payment because that's the
part I'm excited about is making some money. SUSAN GOLDBLATT: Awesome. Same, same. So the way that this
will work is a client will write to Cloud Firestore
the charge that they're going to make. So that'll come from the app,
and we'll see that in our video later. But we'll write to
Cloud Firestore. Then after that, we'll
trigger a Cloud Function. So hopefully, this is starting
to sound a little familiar. Then, our Cloud Function
will talk to the Stripe SDK. And we'll wait for
that data to come back. Then, with that
information, we'll return the result
to Cloud Firestore. So before I go into
the code for this, I want to talk a little
bit about Cloud Functions for Firebase Retries. So Cloud Functions for
Firebase are guaranteed to run at least once,
but that means that they could run more than once. And we need our function
to be able to support that. This is really important
when you're making payments because if you run a function
that charges a customer more than once, it could charge
them more than once-- which would be very bad. And users would be writing in. They wouldn't be using our app. So we really want to make sure
that we handle this correctly. So the way that we're
going to handle this is called Idempotency. It's a concept that if you
run something more than once with the same arguments, only
one side effect of it will run. So in this case, we're going
to talk to the Stripe API. And if the Cloud
Function retries, the Stripe API knows
with our Idempotency key that we only want
to charge one time. So we give them the
same Idempotency key, and they won't recharge
the customer every time. So we'll see this in the
code a little bit later. But just wanted to set
the stage for that. So let's look at the code. This function is called
Create Stripe Customer. So it's run off of
functions.Firestore.document. And that's when this is
created, so whenever a document is created in this location. So we're in the collections
write customers. We have our user ID, so
whatever user we're in. And then, we have a
sub-collection charges. And then, the charge
will get an auto ID that is randomly generated
but unique by Firestore. And we get the snap and context. So we can get information
from that as well. So first thing that we
do is get the customer ID since we need that
to talk to Stripe. We'll use our Get Customer
ID function here and pass in the context.params.userID. Next, we get the amount
that they're being charged. So snap.data.amount will
have that information. And then, we create
a charge object, which is just the amount, and
the currency, and the customer. Next, we need to set the source. So what credit card are
they going to be using? Do they want to
use a default card? Or are they using
one that they've put in for the first time? Maybe this is a corporate
account for some reason, and they have a
different credit card. So after that, we need to
get our Idempotency key. So what are we going
to use for that here? Well, it needs to be unique,
and it needs to be not changing, even though if we run
this more than once. So a good example here would
be the auto ID of our document. So even if this function
runs more than once, the auto ID won't change. So it makes a really
good Idempotency key, and so we'll just grab that
from the context.params. Then, we'll talk to Stripe
and pass in our charge that we created earlier with
our currency, and amount, and customer, and also give
it the Idempotency key. Then, we wait for that
data to come back. And then, store it
in Cloud Firestore. You'll notice here that we
wrap this in a try catch. And that's because this
logic is really important. And we want to make
sure any errors are serviced to the user. So we have a function that
says User Facing Message. And then, we put
the error in there, so the user can
understand what's going on with their application. So let's look at what this will
look like in our application. JENNIFER PERSON: Now,
I'd also just like to note very briefly
that there is the risk of the client pressing
the button more than once. Maybe they're just
really eager to pay you. And resolving that is actually
a lot easier than dealing with Idempotency, which
is the button in this case is no longer active until
a response comes back from the Cloud Functions. SUSAN GOLDBLATT: So here we are
write who the flowers are for, click Pay, Payment
Was Successful. We can wait for that
data to come back, and we see the
charge to Puf here. We go and look into our charges. We can see all of this
information back from Stripe here. So we were able to
successfully charge our credit card from Stripe. And we get all this information
back that can be helpful. Now, I'll pass it off to
Jen to talk a little bit about handling refunds because
sometimes, the flowers aren't always the freshest. JENNIFER PERSON:
Right, so we would like to think that
we will never have to refund our customers
because their product is just that great. But quite frankly, it is going
to come up from time to time. So in our case,
we want to set up a system where users can
request a refund if they think that one is deserved. But we want to be able
to have the final say, and what we are
actually going to refund to people because there
are certainly cases where a refund would be a good idea. Maybe they ended up with black
roses instead of red roses. But there are some cases
where it's really not on us. So if they send
flowers to someone, but that person doesn't
actually want them. So we want to be able
to pick and choose which refunds we want to approve. So in order to see how we're
going to pick and choose that, let's look at how we are
going to approve refunds. Starting to look familiar? The client is going to
write a refund request to Cloud Firestore, which is
going to end up in a queue that we can see. If we decide that it is
a reasonable request, we will approve that refund. This approval is
going to trigger a Cloud Function,
which is actually going to do the refunding. So we need a way to distinguish
between moderators-- which right now is just us,
but as our company grows, there could be more of them-- and regular customers,
so that users can't just approve
their own refunds. The way that we're going to
do that is through a concept called Custom Claims. In Firebase Auth,
when you sign in, a user gives you a whole lot
of different information, including the user
ID, their email, what providers they've
signed in with, maybe an image URL, which could
have come from their provider or could be provided by them. And then, they can
upload one, and then you could update the user. There's all sorts of
information that's in there. There's also this little
bit of information on their Auth token,
which are Custom Claims. And these can be any kind of
key value pairs that you want. But do note that they have
a very low limit in size. I think it's like
1,000 bytes, maybe. It's really not meant for those
cases where you're like, hey, it would be great if I could put
it in their whole home address into the user or the
list of their friends. It's just for little
pieces of information that can distinguish one
kind of user from another. So in this case, we're
going to add to our token a claim called moderator, and
we're going to set it to true. So this is how
we're going to see, does this claim exist on a user. Then they have the right to make
changes such as giving refunds. This also enables us to
make a different UI where we can see more information. This allows us to
distinguish between us being able to see every
scheduled delivery and clients just being able to
see their deliveries. So how do we make sure that only
authorized folks can actually become moderators? We don't want someone to just
be able to say, I'm a moderator. Give me that claim. So what we're going
to do is make it so that only existing moderators
can give that privilege. And in this case, you can get
more hierarchical if you want. Maybe you could have
an owner property and only that person
gets to grant privileges to other people. For simplicity's
sake, we are just checking for a single
claim, but remember that you can do sort of
a combination of them. So this is what
the function looks like to give that permission. We are using an HTTP
function, but this one is a little different. It is an onCall function,
which means this call can only be made from a Firebase client,
your Android app or your iOS app, rather than just being
able to make it from anywhere. In this case, passes
data and context. We check the context
because it also passes information about the
user who has made the request. We check their Auth token
and see if they are already a moderator. If they are not, we let them
know that they don't have permission to make this change. This format might
look a little strange if you're used to
doing HTTP calls. But this is just how it's done. So you can return
your fields instead of doing a response.send. If they are a
moderator, we're going to get the email of the user
that they want to promote. So in this case,
if I'm a moderator and I want to promote
someone else-- I want to promote Susan-- I would pass her email. If there's no email,
I'm just going to let them know, just keeping
all my checks in there. Then we have a function
called getUserByEmail, which enables us to get all
the information about the user based on just their email. If there doesn't happen to
be a user with that email, maybe Susan hasn't
signed up yet, again, we're just going to
have another check. Otherwise, we're going to
get the user's UID, which is what is required
to add these claims. So then we set custom user
claims using their user ID and then passing
whatever claims we want. In this case, we're just
doing moderator true. You could do several
claims at once if you want. You can do them over time, and
they'll still all be there. And then we're going
to return a response that user is a moderator. So this is how we're
making sure that only we can approve moderators and only
people with the moderator claim are going to be able
to make refunds. So once a moderator
approves the refund, it's still going to need
to be processed by Stripe. So Susan is going to
show you how to do that. SUSAN GOLDBLATT: Awesome. Cool. So we have this refund request
that the user is written into. And I just wanted to give
you all a little insight into what this might look like. So we have the amount,
the approvedAt, which is what our moderator
would say yes or no to, an ID, closedAt, all
this type of information. I'm just trying to add more
dates to these so that we can have an insight if something
goes wrong with the customer to look back and be like, oh,
it was closed at this time. It was approved at this time. So this is just a little
insight into how we're structuring some of our data. So let's go back to our example
of how the approvals process for refunds get processed. So we write to Cloud Firestore
to request the approval, and then a moderator will
come in and approve it or not. Then that moderator
coming in to approve will trigger a Cloud Function. And then our Cloud Function
will talk to the Stripe SDK, and then we will write that
data back to Cloud Firestore after that. So that's kind of the process. And so now we have
our moderators, so we have a way to
create moderators, and we need to look at the code
to actually talk to Stripe. So let's look at that. Here we have a function
called refundCharge, and so this is based
off a function, that firestore.document--
so a refund request-- and then on the ID. And you can see this
is onUpdate function, so this will run when
there's an update. And we'll get the
change in the context here too for this function. So the first thing
that we want to do is make sure that the
user is a moderator. And if they're not
a moderator, we want to just return an error. And I've included an
attemptedAt here to be like, hey, someone tried
to refund this, and they weren't a moderator. So maybe pay attention to that
if you're looking through it. After that, we want to do a
little bit more error checking here too. So we get our refund that
we're trying to issue, and then we also want to
get a refund object before. So we get our refund that
we're trying to issue by saying, change.after.data. So that would be the data after
the change has been applied. And we can get the data before
the change has been applied by saying, change.before.data. So we want to make
sure two things here. One, that the refund hasn't
already been approved and two, that the previous refund
hasn't been approved. So we want to make
sure you're not trying to re-refund something
that's already been approved. And then the second one is that
there is an approval on it. So make sure that it actually
gets approved by someone. If either of those
are fall through, so if no one if it's
already been approved or if no one approved it, also
set an attemptedAt and return. So don't process this refund. So now with all of that
error checking aside, we're going to go ahead
and talk to Stripe to process the refund. So we call stripe.refunds.create
with our charge and the refund ID. And we wait for that
data to come back. Here, the response can succeed,
and so if it's succeeded, we'll say, great, refund it at
this time and return that back. And we'll let the user know. But if the refund
doesn't succeed, we also need to make sure
that we set that data as well. So we'll say, it was
attempted at this time and send the data back to the
customer and to the moderator so they can look through it. Finally, we wrapped all
of this in a try catch. So just in case there
was an error talking to Stripe for some
reason that wasn't a successful response or
an unsuccessful response, we also want to
keep track of that. So we'll say, hey, you
attempted this at this time, and it didn't work. And then report the error. So we keep track of
all of that in order to just help understand
the process for the refunds and understand where our
users are in the process. So let's look at this
in our application. So here, we have our list
of approvals over there, and we can see approvedAt. So it was approved
by our moderator, and then this all has the data
about when it was refunded, the response that we got
back, stuff like that. So you can see that process
of going through and approving things. So the next step
two, we just covered a bunch of data protection
or these if cases, stuff like that. We also want to make sure, on
top of our moderator, that we want to protect this data. And so as we mentioned
earlier, Cloud Firestore has rules when it's
talking with the client to keep things secure. So let's write some
rules specifically for refunds that can cover
some of our security concerns. So if you're not
familiar with rules, it's a match-based statement. So here we have at the top,
our databases and the database and the documents that
we're trying to match. And then each path in the
database gets kind of-- we can give it its own rules. So this particular statement
is for the refund request collection. And the refund ID
there is a wildcard, so it matches to any of the
refund IDs that you pass in. So these are all
ordered together, and so you'll have more
complex rules as you build out your Firestore database. But this is just, as I said,
the refund request ones. So what we're allowing here
is, allow read and create. So if the user is
logged in and they're the one trying to
create the refund and they're not
including approved that-- so approve that is not in
the request.resource.data, they can read and create. So they can't
approve themselves. So this rule prevents that. Then the next rule,
allow read and write, is so that moderators-- so you
can actually access that custom claim token here. So if
request.auth.token.moderator is true-- sorry, that was a lot of words. But if that's true,
you can read and write. So that allows our moderators
to go in and update the request. So with that, I'll
pass it off to Jenn to talk a little bit
about our fifth topic. JENNIFER PERSON: Yeah. So the first time we were
going to deliver this talk, we actually had a
25 minute window. So I'm super excited that
we have a little more room because we're able to cover
some topics that I think are very important and exciting. And in this case, we're going
to be talking about webhooks. So most payment
processors are going to offer you some kind
of webhook, an ability to trigger an endpoint
when something happens within the payment processor. So in terms of Stripe, this
could be pretty much anything. This could be when a
payment goes through, when a payment is declined,
when a refund happens, when a new customer is created,
when a new card is created. They have a whole list of things
that you can trigger endpoints from. So again, expired credit cards,
failed charges, and stuff. So in terms of making
sure that it's just not some malicious person
triggering that endpoint, Stripe offers you
a signature header that you can use to verify
that this webhook actually came from Stripe and
not somewhere else. So let's take a look at
one example of an endpoint. In this case, we want to do
this whenever a customer makes an update to their charge. So in this case, if it seems
to be approved upfront, but then later it
actually fails, we want to be able to keep
data in sync in Cloud Firestore to reflect that change. So this is a regular
HTTPS request, as opposed to the ones that are only called
from a Firebase client app. So we're going to get the data. This is provided by Stripe. They're going to tell us
some information about what has happened. We're also going to get the
customer ID because that's provided by Stripe. And then we're going
to get an ID, which is the ID of the charge
where that change happened. Now we're going to get the
Firebase user associated with the Stripe customer ID. So you've seen examples of where
we start with a Firebase user and then we get the
Stripe customer by looking in the Cloud Firestore. We can do the reverse as well. So in this case,
we're going to do it using a query of
Firestore Stripe customers where the customer ID
field of that document is equal to the
customer ID field that just came to us from Stripe. This does return an
array of documents even if you set
the limit to one. So in this case, I
get the first document which is going to be
our Firebase user who has that customer ID. And then we're going to get
the reference to that path to their customer path. Similarly, we want to know
which of their charges something happened to. So now that we know
which user it is, we can go into their charges
and get the specific document where the ID of the charge is
equal to the ID that was just provided from Stripe. Now, we are going to
add the information that changed in that charge
to Cloud Firestore. So in this case,
in that path, I'll set all of the data that
just came from Stripe and again merge true. So we have the previous
information about the charge and then any updates like the
fact that maybe it just failed. We also send a
response back to Stripe because they are
expecting to know that something was successful. So you can imagine that
this could be very useful. You could have a specific
endpoint for all sorts of events that happen. So today, we talked to
you about a whole lot. We threw a whole lot at
you at once, including how to create a Stripe customer
and link it to a Firebase user, how to store information
about a straight payment method in Cloud
Firestore without storing the specific information that
makes that card identifiable, how to pay for things by
writing the Cloud Firestore and handling the payments
from the Stripe server API. Now certainly, you can
pay from the client, but the benefit of
doing it this way is that you are then a lot-- it makes it much easier
to function and update Cloud Firestore. [MUSIC PLAYING]