Pay by the second

Pay-as-you-go feature in real time for Skillr

Pay-as-you-go feature in real time for Skillr

John Doe

Credit Card

Mike Brown

Credit Card

Lisa Jones

Credit Card

Tom White

Credit Card

Alice Green

Credit Card

Chris Black

Credit Card

Ann Grey

Credit Card

Sam Hill

Credit Card

Kate Bell

Credit Card

Jack Wood

Credit Card

Emma Fox

Credit Card

Matt Stone

Credit Card

John Doe

Credit Card

Mike Brown

Credit Card

Lisa Jones

Credit Card

Tom White

Credit Card

Alice Green

Credit Card

Chris Black

Credit Card

Ann Grey

Credit Card

Sam Hill

Credit Card

Kate Bell

Credit Card

Jack Wood

Credit Card

Emma Fox

Credit Card

Matt Stone

Credit Card

Application where users can connect to professionals and pay them by the second

Skillr

Skillr

Skillr

This feature makes us stand out from the rest of the competition. Compared to the rest, the common problem for users was that every company would pre-authorize a charge for 30-60 minutes just to start, and oftentimes, even when some users had the money for it, you always end up losing money because your unused time does not get refunded back to you. With our feature, we charge the user down to the second they use it, and allow low income users to use the application by not pre-authorizing large amounts, but instead, by 1 minute increments.

Flow Chart

Ideation

Set Up

Stack Assumptions

·

·

WebSocket for real-time communication.

·

·

Stripe for per-second billing using pre-authorization + incremental charging.

·

·

Redis for tracking call state/timer/cache.

·

·

PostgreSQL for persisting call sessions & payment logs.

Main Flow Summary

·

·

User joins a video call.

·

·

Pre-authorize a small amount (e.g., $1 for a few minutes).

·

·

WebSocket server tracks session time.

·

·

Backend updates Redis + charges via Stripe when usage reaches threshold.

·

·

All data is persisted to Postgres.

🧾 Sample Code (Node.js/Express + WebSocket)

const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const Stripe = require('stripe');
const Redis = require('ioredis');
const { Pool } = require('pg');

const stripe = Stripe('sk_test_...');
const redis = new Redis();
const pool = new Pool(); // connect to Postgres

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

wss.on('connection', ws => {
  let interval;
  let sessionId;

  ws.on('message', async message => {
    const data = JSON.parse(message);
    if (data.type === 'start_session') {
      sessionId = data.sessionId;
      await redis.set(`session:${sessionId}:seconds`, 0);

      interval = setInterval(async () => {
        const seconds = await redis.incr(`session:${sessionId}:seconds`);
        const userId = data.userId;

        // Charge every 60 seconds (example: $1.00)
        if (seconds % 60 === 0) {
          const customerId = await redis.get(`user:${userId}:stripeCustomerId`);
          const paymentIntentId = await redis.get(`session:${sessionId}:intent`);

          try {
            await stripe.paymentIntents.update(paymentIntentId, {
              amount: (seconds / 60) * 100, // $1 per minute
            });
          } catch (err) {
            console.error('Stripe charge error:', err);
            ws.send(JSON.stringify({ type: 'error', message: 'Payment failed' }));
          }
        }

        ws.send(JSON.stringify({ type: 'tick', seconds }));
      }, 1000);
    }

    if (data.type === 'end_session') {
      clearInterval(interval);
      const secondsUsed = await redis.get(`session:${sessionId}:seconds`);

      // Final charge
      const totalAmount = Math.ceil(secondsUsed / 60) * 100;
      const paymentIntentId = await redis.get(`session:${sessionId}:intent`);
      await stripe.paymentIntents.capture(paymentIntentId);

      // Save session to DB
      await pool.query('INSERT INTO calls(user_id, session_id, duration) VALUES($1, $2, $3)', [
        data.userId, sessionId, secondsUsed
      ]);

      ws.send(JSON.stringify({ type: 'summary', duration: secondsUsed }));
      ws.close();
    }
  });
});

server.listen(3000, () => console.log('Server running on http://localhost:3000'));
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const Stripe = require('stripe');
const Redis = require('ioredis');
const { Pool } = require('pg');

const stripe = Stripe('sk_test_...');
const redis = new Redis();
const pool = new Pool(); // connect to Postgres

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

wss.on('connection', ws => {
  let interval;
  let sessionId;

  ws.on('message', async message => {
    const data = JSON.parse(message);
    if (data.type === 'start_session') {
      sessionId = data.sessionId;
      await redis.set(`session:${sessionId}:seconds`, 0);

      interval = setInterval(async () => {
        const seconds = await redis.incr(`session:${sessionId}:seconds`);
        const userId = data.userId;

        // Charge every 60 seconds (example: $1.00)
        if (seconds % 60 === 0) {
          const customerId = await redis.get(`user:${userId}:stripeCustomerId`);
          const paymentIntentId = await redis.get(`session:${sessionId}:intent`);

          try {
            await stripe.paymentIntents.update(paymentIntentId, {
              amount: (seconds / 60) * 100, // $1 per minute
            });
          } catch (err) {
            console.error('Stripe charge error:', err);
            ws.send(JSON.stringify({ type: 'error', message: 'Payment failed' }));
          }
        }

        ws.send(JSON.stringify({ type: 'tick', seconds }));
      }, 1000);
    }

    if (data.type === 'end_session') {
      clearInterval(interval);
      const secondsUsed = await redis.get(`session:${sessionId}:seconds`);

      // Final charge
      const totalAmount = Math.ceil(secondsUsed / 60) * 100;
      const paymentIntentId = await redis.get(`session:${sessionId}:intent`);
      await stripe.paymentIntents.capture(paymentIntentId);

      // Save session to DB
      await pool.query('INSERT INTO calls(user_id, session_id, duration) VALUES($1, $2, $3)', [
        data.userId, sessionId, secondsUsed
      ]);

      ws.send(JSON.stringify({ type: 'summary', duration: secondsUsed }));
      ws.close();
    }
  });
});

server.listen(3000, () => console.log('Server running on http://localhost:3000'));

🔐 Stripe Pre-Auth (When Call Starts)

app.post('/create-session', async (req, res) => {
  const { userId } = req.body;

  const customer = await stripe.customers.retrieve('cus_123');
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 100, // $1 pre-auth
    currency: 'usd',
    customer: customer.id,
    capture_method: 'manual',
  });

  const sessionId = `sess_${Date.now()}`;
  await redis.set(`session:${sessionId}:intent`, paymentIntent.id);
  await redis.set(`user:${userId}:stripeCustomerId`, customer.id);

  res.send({ sessionId, clientSecret: paymentIntent.client_secret });
});
app.post('/create-session', async (req, res) => {
  const { userId } = req.body;

  const customer = await stripe.customers.retrieve('cus_123');
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 100, // $1 pre-auth
    currency: 'usd',
    customer: customer.id,
    capture_method: 'manual',
  });

  const sessionId = `sess_${Date.now()}`;
  await redis.set(`session:${sessionId}:intent`, paymentIntent.id);
  await redis.set(`user:${userId}:stripeCustomerId`, customer.id);

  res.send({ sessionId, clientSecret: paymentIntent.client_secret });
});

📌 Database Table (PostgreSQL)

CREATE TABLE calls (
  id SERIAL PRIMARY KEY,
  user_id TEXT NOT NULL,
  session_id TEXT UNIQUE NOT NULL,
  duration INTEGER NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE calls (
  id SERIAL PRIMARY KEY,
  user_id TEXT NOT NULL,
  session_id TEXT UNIQUE NOT NULL,
  duration INTEGER NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);