Skip to main content

Overview

The Payment Service is a shared API developed by the Job Manager team that enables both Job Applicants and Companies to subscribe to premium features.
This is a shared service used by both Job Manager and Job Applicant subsystems.

Service Responsibility

Simplex Level (6.1.1)

Payment API Development

The Job Manager team develops a centralized Payment API that:
  • Accepts subscription requests from both subsystems
  • Integrates with third-party payment providers
  • Processes credit card payments
  • Returns payment confirmation

Supported Payment Providers

  • Stripe
  • PayPal
Recommended Provider
  • Industry-standard security
  • PCI DSS compliant
  • Comprehensive API
  • Webhook support
  • Test mode available

Transaction Recording

Medium Level (6.1.2)

All payment transactions must be recorded in the database
Required Transaction Data:
  • Applicant/Company email
  • Transaction timestamp
  • Transaction status
  • Payment amount
  • Payment method
  • Transaction ID (from provider)

Transaction Status

SUCCESS

Payment completed successfully

PENDING

Payment processing

FAILED

Payment failed or declined

REFUNDED

Payment refunded to user

CANCELLED

User cancelled payment

API Endpoints

Subscribe to Premium

POST /api/payments/subscribe
Content-Type: application/json
Authorization: Bearer {jwt_token}

{
  "userType": "COMPANY" | "APPLICANT",
  "userId": "uuid",
  "email": "company@example.com",
  "plan": "MONTHLY",
  "paymentMethod": "STRIPE" | "PAYPAL",
  "returnUrl": "https://app.devision.com/subscription/success",
  "cancelUrl": "https://app.devision.com/subscription/cancel"
}
Response:
{
  "transactionId": "txn_abc123",
  "status": "PENDING",
  "checkoutUrl": "https://checkout.stripe.com/...",
  "expiresAt": "2026-01-10T10:30:00Z"
}

Verify Payment

GET /api/payments/verify/{transactionId}
Authorization: Bearer {jwt_token}

Response:
{
  "transactionId": "txn_abc123",
  "status": "SUCCESS",
  "email": "company@example.com",
  "amount": 30.00,
  "currency": "USD",
  "timestamp": "2026-01-10T10:25:00Z",
  "subscriptionStartDate": "2026-01-10",
  "subscriptionEndDate": "2026-02-10"
}

Payment Webhook

POST /api/payments/webhook/stripe
Content-Type: application/json
Stripe-Signature: {signature}

{
  "type": "payment_intent.succeeded",
  "data": {
    "object": {
      "id": "pi_abc123",
      "amount": 3000,
      "currency": "usd",
      "customer": "cus_xyz789",
      "metadata": {
        "userId": "uuid",
        "userType": "COMPANY"
      }
    }
  }
}

Payment Flow

Subscription Process

1

Initiate Subscription

User clicks “Subscribe to Premium” in their subsystem
2

Create Payment Intent

Subsystem calls Payment API to create payment intent
3

Redirect to Checkout

User redirected to Stripe/PayPal checkout page
4

Complete Payment

User enters payment details and confirms
5

Webhook Notification

Payment provider sends webhook to Payment API
6

Update Database

Payment API records transaction and updates subscription status
7

Notify Subsystem

Payment API notifies requesting subsystem of success
8

Activate Premium

Subsystem activates premium features for user

Security Requirements

PCI DSS Compliance

Never store credit card details directly. Use payment provider’s tokenization.
Security Measures:
  • All payment data transmitted over HTTPS
  • Use payment provider’s hosted checkout
  • Implement webhook signature verification
  • Store only tokenized payment methods
  • Log all payment attempts for audit

Webhook Verification

@PostMapping("/webhook/stripe")
public ResponseEntity<?> handleStripeWebhook(
    @RequestBody String payload,
    @RequestHeader("Stripe-Signature") String signature
) {
    try {
        Event event = Webhook.constructEvent(
            payload,
            signature,
            webhookSecret
        );

        // Process verified event
        processPaymentEvent(event);

        return ResponseEntity.ok().build();
    } catch (SignatureVerificationException e) {
        // Invalid signature - reject
        return ResponseEntity.status(400).build();
    }
}

Database Schema

CREATE TABLE payment_transactions (
    id UUID PRIMARY KEY,
    user_type VARCHAR(20) NOT NULL, -- COMPANY or APPLICANT
    user_id UUID NOT NULL,
    user_email VARCHAR(255) NOT NULL,

    amount DECIMAL(10,2) NOT NULL,
    currency VARCHAR(3) DEFAULT 'USD',

    status VARCHAR(20) NOT NULL,
    payment_method VARCHAR(50) NOT NULL,
    payment_provider VARCHAR(50) NOT NULL,

    provider_transaction_id VARCHAR(255) UNIQUE,
    provider_customer_id VARCHAR(255),

    subscription_start_date DATE,
    subscription_end_date DATE,

    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW(),

    metadata JSONB,

    INDEX idx_user (user_id, user_type),
    INDEX idx_email (user_email),
    INDEX idx_status (status),
    INDEX idx_created (created_at DESC)
);

CREATE TABLE payment_webhooks (
    id UUID PRIMARY KEY,
    provider VARCHAR(50) NOT NULL,
    event_type VARCHAR(100) NOT NULL,
    event_id VARCHAR(255) UNIQUE,
    payload JSONB NOT NULL,
    processed BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT NOW()
);

Error Handling

Common Error Scenarios

Status Code: 402 Payment Required
{
  "error": "PAYMENT_DECLINED",
  "message": "Your payment method was declined",
  "code": "card_declined"
}
Status Code: 402 Payment Required
{
  "error": "INSUFFICIENT_FUNDS",
  "message": "Insufficient funds in account",
  "code": "insufficient_funds"
}
Status Code: 504 Gateway Timeout
{
  "error": "PAYMENT_TIMEOUT",
  "message": "Payment provider did not respond in time",
  "transactionId": "txn_abc123",
  "retry": true
}
Status Code: 409 Conflict
{
  "error": "ALREADY_SUBSCRIBED",
  "message": "User already has an active subscription",
  "expiresAt": "2026-02-10"
}

Testing

Test Mode Configuration

  • Stripe Test Cards
  • PayPal Sandbox
Success: 4242 4242 4242 4242
Declined: 4000 0000 0000 0002
Insufficient Funds: 4000 0000 0000 9995
Processing Error: 4000 0000 0000 0119

Expiry: Any future date
CVV: Any 3 digits
ZIP: Any 5 digits

Test Checklist

Integration Examples

Job Manager Integration

// Company subscribes to premium
async function subscribeToPremium(companyId: string) {
  const response = await fetch('/api/payments/subscribe', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify({
      userType: 'COMPANY',
      userId: companyId,
      email: company.email,
      plan: 'MONTHLY',
      paymentMethod: 'STRIPE'
    })
  });

  const { checkoutUrl } = await response.json();
  window.location.href = checkoutUrl;
}

Job Applicant Integration

// Applicant subscribes to premium
async function subscribeToJobAlerts(applicantId: string) {
  const response = await fetch('/api/payments/subscribe', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify({
      userType: 'APPLICANT',
      userId: applicantId,
      email: applicant.email,
      plan: 'MONTHLY',
      paymentMethod: 'STRIPE'
    })
  });

  const { checkoutUrl } = await response.json();
  window.location.href = checkoutUrl;
}