Bonaventure OgetoBy Bonaventure Ogeto|

Build Paystack Subscriptions in Spring Boot

To build Paystack subscriptions in Spring Boot, create a plan, initialize a transaction with the plan code, handle subscription webhooks to update a JPA entity, use @PreAuthorize for subscription-gated endpoints, and provide cancellation via the Paystack API.

Subscription Entity

@Entity
@Table(name = "subscriptions")
public class Subscription {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;

    @Column(unique = true)
    private String subscriptionCode;
    private String planCode;
    private String emailToken;
    private String status; // active, past_due, cancelled
    private LocalDateTime nextPaymentDate;

    // Getters and setters
}

Subscribe Endpoint

@PostMapping("/subscribe")
public ResponseEntity<?> subscribe(@RequestBody Map<String, String> payload) {
    String planCode = payload.get("plan_code");
    String email = getCurrentUser().getEmail();

    Map<String, Object> body = Map.of("email", email, "plan", planCode);
    // Call Paystack initialize with plan parameter
    // Return authorization_url
}

Webhook Handlers

private void handleSubscriptionCreate(JsonNode data) {
    String email = data.get("customer").get("email").asText();
    User user = userRepository.findByEmail(email);
    if (user == null) return;

    Subscription sub = subscriptionRepository.findByUser(user);
    if (sub == null) {
        sub = new Subscription();
        sub.setUser(user);
    }
    sub.setSubscriptionCode(data.get("subscription_code").asText());
    sub.setPlanCode(data.get("plan").get("plan_code").asText());
    sub.setEmailToken(data.has("email_token") ? data.get("email_token").asText() : "");
    sub.setStatus("active");
    subscriptionRepository.save(sub);
}

Subscription Security

@PreAuthorize("@subscriptionChecker.hasActive(authentication)")
@GetMapping("/premium")
public ResponseEntity<?> premiumContent() {
    return ResponseEntity.ok(Map.of("content", "Premium data"));
}

@Component("subscriptionChecker")
public class SubscriptionChecker {
    @Autowired private SubscriptionRepository subscriptionRepository;

    public boolean hasActive(Authentication auth) {
        User user = (User) auth.getPrincipal();
        Subscription sub = subscriptionRepository.findByUser(user);
        return sub != null && "active".equals(sub.getStatus());
    }
}

Ship Payments Faster

Key Takeaways

  • Paystack handles recurring billing. Create plans and subscribe customers.
  • Use a JPA @Entity for subscription tracking with status, subscriptionCode, and emailToken.
  • Webhook events drive state changes. Handle subscription.create and invoice.payment_failed.
  • Spring Security @PreAuthorize checks subscription status on premium endpoints.
  • Store emailToken from the webhook for cancellation.
  • Use @Async for webhook processing to return 200 quickly.

Frequently Asked Questions

Can I use Spring Security for subscription access control?
Yes. Use @PreAuthorize with a custom SpEL expression that checks the subscription status. This is clean and integrates with Spring Security.
How do I handle plan upgrades?
Cancel the current subscription and create a new one. Paystack does not support direct plan changes.
What if a renewal fails?
Handle the invoice.payment_failed webhook and update the status to past_due. Notify the customer to update their payment method.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon