cd ../projects

Project 12 // _ultratecheffect

UltraTechEffect

Posting a job is free. The bill arrives one applicant at a time — which means the card has to still work months after anyone typed it in.

client

private client — confirm before publishing

timeline

4 months · Mar 2022 → Jul 2022 · client-owned, no longer running

role

full-stack developer · client work

scope

pay-per-applicant billingjob search & geo queriesemployer portalcoaching marketplaceAWS infrastructure & deploy

## the-problem

Job boards charge for the listing, which is the one thing that costs nothing. An employer pays up front and finds out afterwards whether anybody applied. This one inverted it: post as many jobs as you like for free, and pay per applicant received — the price attaches to the outcome instead of the shelf space.

That inversion moves the hard part into the payment layer. At the moment of billing there is no employer present: a candidate on the other side of the world hits apply, and a card that was typed in weeks ago has to be charged with nobody there to confirm it. Card-on-file, off-session, no UI to fall back on.

The second product made it a two-sided problem in a different sense. The same site also sells career coaching, which is an ordinary attended checkout — one price, one card, one buyer watching the spinner. Two payment shapes, opposite in almost every respect, on one account model.

## the-approach

  • split the card journey in two: a Stripe SetupIntent at post-job time authorises the card without taking a cent, and the payment method id is stored on the employer alongside their Stripe customer id
  • put the charge where the value is — the apply handler creates an off-session, auto-confirming PaymentIntent against that saved card, so the invoice is generated by the applicant rather than by a calendar
  • kept the coaching side conventional: an attended PaymentIntent, its client secret returned to the browser, confirmed against Stripe Elements with the buyer watching
  • stored each job's address as a GeoJSON point at save time, so "jobs near here" is a radius query rather than a string match on a city name
  • layered the facets on top — employment type, seniority and salary band — over the same query the map and the list both read
  • gave employers a portal built from their own records: posts, applications and views bucketed by day over a rolling week
  • transformed uploads on the way to S3 — full-size and a 400px thumbnail written in one pass, so no resize job runs later
  • provisioned the whole box in Terraform — VPC, subnet, gateway, security group, EC2 — with cloud-init installing nginx, certbot, Node and pm2, and a self-hosted Actions runner doing the redeploy

// the two halves of the card — authorised with a buyer, charged without one

// Post-job. The employer is present, and nothing is charged:
// a SetupIntent just proves the card is real and stores it.
export const saveCard = async (req, res) => {
const user = await User.findById(req.userid);
const intent = await stripe.setupIntents.create({
customer: user.employerProfile.payment.customerId,
payment_method_types: ["card"],
});
res.send(intent.client_secret); // confirmed in the browser
};
// Weeks later. A candidate hits apply — the employer isn't here,
// there's no page to challenge on, so it has to go through
// unattended or not at all.
export const applyToJob = async (req, res) => {
const job = await Job.findById(req.params.id);
job.appointments.push({ userData, schedule, resume, appliedBy });
await job.save();
res.send("Applied");
if (job.isAdminJob) return; // house listings bill nobody
const employer = await User.findById(job.postedBy);
chargeCustomer(
19,
employer.employerProfile.payment.customerId,
employer.employerProfile.payment.activeCard,
);
};
const chargeCustomer = (amount, customer, payment_method) =>
stripe.paymentIntents.create({
amount: amount * 100, currency: "usd",
customer, payment_method,
off_session: true, confirm: true, // nobody to prompt
});

## the-architecture

// architecture — the card is authorised on one path and charged on another

client

employer

post · portal

  • posts free, saves a card
  • weekly posts and applications
client

candidate

search · apply

  • radius search plus facets
  • profile, résumé, apply
client

admin console

jobs · coaches · pricing

  • users, bookings, blog
  • package tiers
backend

Express API

on a custom Next server

  • jobs, users, coaches, packages
  • JWT, optional on public reads
backend

billing split

setup vs charge

  • SetupIntent when posting
  • off-session charge on apply
data

MongoDB

jobs · users · coaches

  • jobs as GeoJSON points
  • applications on the job record
data

media

AWS S3

  • résumés and avatars
  • full-size + 400px thumbnail
external

Stripe

card on file

  • customer + payment method stored
  • attended checkout for coaching
external

email

SendGrid

  • resets and enterprise enquiries
external

the box itself

Terraform · EC2

  • VPC, subnet, gateway, SG
  • cloud-init: nginx, certbot, pm2
external

deploy

self-hosted runner

  • push to main
  • pm2 stop, build, restart
clientbackenddataexternal

## the-results

$0

to post; the bill starts at the first applicant

2

payment shapes, one account model

1 push

from main to a Terraform-provisioned box

  • billing follows applicants, not listings — an employer with no response pays nothing, and the charge is raised by the apply handler itself
  • one account model carries both an unattended card-on-file charge and an ordinary attended checkout
  • "near me" is a real geo query: points in, radius out, with the facets applied over the same result set
  • the environment is reproducible — the VPC and instance are Terraform, the box's setup is cloud-init, and a push to main redeploys it
  • what I'd change, and it's the same flaw three times over: that unattended charge is fire-and-forget. It isn't awaited, its rejection is only logged, no record of it is written against the application, and it carries no idempotency key — so a card that fails off-session (which is the normal outcome when the issuer wants 3-D Secure) loses the money silently, and a retried apply request bills twice. It belongs in a charges collection written in the same step as the application, keyed on the application id, with a Stripe webhook reconciling the outcome and a retry path that emails the employer to re-authorise.
  • and the amount is hardcoded rather than read from the employer's package, so the tier the pricing page sells isn't the tier that bills
nextreactexpressmongodbtypescriptawspayments

// want something like this built for you?

start-a-project