ℹ️ | Re-posted from stripe.dev |
---|
While working remotely and traveling from place to place, I noticed a recurring pattern.
Every time you arrive in a new city, you rent an Airbnb or check-in to a hotel. Then you sign up for a gym and a co-working space.
Payments for these services are all done electronically. But when it comes to access, you need a plastic card, a fob, or a physical key.
This creates friction for everyone involved. You have to go pick it up, carry it around, hope you don’t lose it, and return it before leaving.
From the business side, they need to pay someone to work the front desk to issue, track, and collect these access devices.
This made me wonder, what if access was fully digital?
Digital wallet passes
Luckily, there is a better way that is gaining popularity.
Instead of cards and keys, we can issue digital passes with Google Wallet and Apple Wallet and use an NFC reader to gate the entrance.
These digital passes are like certificates that are scannable with NFC. They can be issued and revoked electronically without human intervention (no front desk needed).
Since everyone already has a smart phone or watch with Apple Wallet & Google Wallet, there’s nothing extra to install or carry.
Integration
Both Apple and Google have public Wallet APIs for issuing passes, but for smaller companies it's easier to use an intermediary like PassNinja.
PassNinja acts as an abstraction layer on top of Apple & Google, so you only have to integrate one API.
Then you can place an Apple/Google compatible NFC reader near the doorway. We'll use the DotOrigin VTAP100.
Building it
As an example, we'll build a simple gym membership system.
Full Source Code
Members can purchase a Stripe Subscription and a digital pass will be issued to them. The member can then use their phone (or watch) to access the doorway.
There will be two codebases:
- Website: A public facing website that accepts payments via Stripe Checkout and issues the digital passes.
- Gate: A private access control system that runs on Linux near the entrance. It will verify the status of the membership by contacting the Stripe Subscription API, and unlock the door for active members.
Architecture
Hardware
Here's what you'll need to build the solution:
Requirements
- Computer: A Raspberry PI Zero 2W or equivalent. For communicating between NFC Reader and Stripe API over WiFi.
- NFC Reader: VTAP100, an Apple / Google Wallet compatible NFC reader.
- Electronic Door Strike/Bolt: To unlock and lock the door.
- Relay: A 3.3V Relay or equivalent. To control locking and unlocking the door strike from the Raspberry PI.
- PassNinja Account: To handle issuing of Google & Apple Wallet Passes.
Wiring diagram
The wiring will look like this:
PassNinja setup
To set up PassNinja:
-
Create an account.
- Visit https://www.passninja.com
- Click "Get Started"
-
Create a pass template
- Log in to PassNinja: https://www.passninja.com/login
- Click on "Dashboard" in the upper right corner
- Click on "New Pass Template" button and create a template for Google & Apple
Setup the NFC Reader:
Follow these instructions https://www.passninja.com/tutorials/hardware/how-to-configure-a-dot-origin-vtap100-nfc-reader
Purchase flow
The purchase part is on a public website that handles the checkout and issuing of passes.
We’ll have an endpoint /subscribe to create the Stripe Checkout session and redirect the user to pay:
// in web/src/index.ts
// create a Stripe API client
const stripe = new Stripe('<your stripe key>')
// when user visits /subscribe, a Stripe checkout session is created and they're redirected to pay
app.post('/subscribe', async (c) => {
// this is where the user is redirected after payment`
const success_url = new URL('/success?session_id={CHECKOUT_SESSION_ID}', '<your domain>').toString()
// create a checkout session`
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
success_url,
line_items: [
{ price: '<your price id>', quantity: 1 }
]
})
// redirect the user to pay
return c.redirect(session.url)
})
When the user finishes paying, Stripe Checkout will redirect them to /success, and the digital pass can then be issued:
// in web/src/index.ts
// create the Pass Ninja API client
const passNinja = new PassNinjaClient('<account id>', '<api key>')
// when user visits /success (after checkout completes), issue the digital pass
app.get('/success', async (c) => {
// get checkout session_id`
const session_id = c.req.query('session_id')
// get the Stripe checkout session
const session = await stripe.checkout.sessions.retrieve(session_id)
// get the Stripe subscription
const subscription = await stripe.subscriptions.retrieve(session.subscription)
// ensure the subscription status is active`
if (subscription?.status !== 'active') throw new Error('Subscription was not successful')
// we've confirmed it's paid, so issue a new pass`
const pass = await passNinja.pass.create(
PASSNINJA_PASS_TYPE,
{
name: session.customer_details.name,
email: session.customer_details.email,
// *important*: save the subscription_id inside the pass
// this is the value the NFC reader sends during a scan
"nfc-message": session.subscription
}
)
// redirect the user to add the pass to their wallet
return c.redirect(pass.url)
})
Door access control
The door access logic can run on any computer, but we'll use a Raspberry PI Zero 2W which is an inexpensive option (~$15 USD) and has the ability to control a relay.
The sequence looks like this:
The NFC reader acts as a virtual serial port, and each time a user’s phone or watch is placed near it, a new line is sent over the serial port.
Linux typically maps virtual serial ports to /dev/ttyACM0
. To access it from Node.js, we'll use the npm package serialport
.
// in gate/src/index.ts`
import { SerialPort } from ‘serialport’`
// create a serial port client`
const port = new SerialPort({ path: '/dev/ttyACM0', baudRate: 9600 })
// use ReadlineParser, so that we receive a full lines
const reader = port.pipe(new ReadlineParser({ delimiter: '\r\n' }))
// a new line is sent whenever a pass is near the NFC reader
reader.on('data', async (data) => {
// verify pass here
})
The data sent comes from the nfc-message field of the pass, which in our case is the Stripe Subscription ID (starts with sub_
).
We can use that ID to verify that the subscription status is active
:
// in gate/src/index.ts
reader.on('data', async (subscription_id) => {
// retrieve the subscription record`
const subscription = await stripe.subscriptions.retrieve(subscription_id)
// check if subscription is active`
if (subscription?.status === 'active') {
// flash LEDs green, play sound, and trigger relay to unlock the door
success(port)
console.log(`Access allowed. id=${subscription_id}`)
} else {
// flash LEDs red and play sound
error(port)
console.error(`Access denied. id=${subscription_id}, status=${subscription?.status}`)
}
})
The success logic will then open the door bolt by triggering the relay:
// in gate/src/index.ts
// setup a connection to the relay on GPIO #8
const relay = new Gpio(8)
// open the door bolt for 5 seconds`
function success(port) {
// turn relay on to unlock the door bolt
relay.high()
// in 5 seconds, turn relay off.
// this causes the door bolt to lock.
setTimeout(() => relay.low(), 5_000)
}
Conclusion
Using digital passes makes physical gating much simpler.
It allows merchants to sell and grant access completely digitally, and users don't have to deal with picking up, carrying, replacing, sharing and returning cards and keys.
It’s as easy as issuing a pass after payment, and adding an NFC reader to the entrance to verify the status of payment.
Special thanks to Bill Scott at DotOrigin for sharing his knowledge on this topic.
Top comments (7)
This is super cool!
This is really cool! I have a few curiosities:
What happens if the Raspberry Pi or Stripe API goes down—does the system lock everyone out?
Is the subscription ID sent raw over NFC, or is it encrypted/signed in some way?
How quickly does access get revoked if someone cancels their subscription?
Could this setup scale to multiple doors/locations off the same subscription?
Very cool project though, and I'd love to see this adapted in the real world <3
Thanks Ivy,
Both of these should be quite rare. I think it would be more likely that a power failure during a storm would impact. So downtime should be similar to any electronic lock.
It's encrypted, otherwise an malicious user would be able to change it.
The Stripe API is called every time before opening the door.
There are usually 2 ways to cancel the subscription (up to user/app), one is to cancel immediately (even if the customer paid for a longer period), the other is to deactivate the subscription once the time their paid for is up.
Yes, absolutely.
You could also build a more advanced back end, and cache the subscription status in your backend. For example, maybe the sauna room has a lock that is only available to the "Pro plan" and is unavailable between 6pm and 6am.. the sky is the limit :D
Thanks for the quick reply and answers! Really awesome idea/project, well done :>
Thank you! and most welcome :D
Really impressive, Josh
I love how you combined hardware and payments so seamlessly,
It’s not something you see every day.
Have you thought about extending this setup beyond doors, such as for event check-ins or membership perks?
Thanks Roshan, glad you liked it :)
Yes, that's a good point, this would work quite well for event check-ins too
It can be integrated with the turnstiles they have at large venues.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.