Service Library - subscription

This document provides a complete reference of the custom code library for the subscription service. It includes all library functions, edge functions with their REST endpoints, templates, and assets.

Edge Functions

Edge functions are custom HTTP endpoint handlers that run outside the standard Business API pipeline. Each edge function is paired with an Edge Controller that defines its REST endpoint.

onPaymentDone.js

Edge Controller:

const { updateSubscriptionById } = require("dbLayer");

module.exports = async (request) => {
  const subscription = request.subscription;
  if (subscription && subscription.id) {
    await updateSubscriptionById(subscription.id, {
      activatedAt: new Date().toISOString()
    });
  }
  return { status: 200, message: "Subscription activated successfully" };
};

onPricingConfigCreated.js

Edge Controller:

const { fetchRemoteListByMQuery } = require("serviceCommon");
const axios = require("axios");

// HTML Email Template
const getEmailHtml = (data) => {
  return `<h2>Exciting News, ${data.userFullname}!</h2>
<p>We're thrilled to announce a new premium plan option for AI Fitness & Nutrition Coach!</p>

<h3>New Plan Details:</h3>
<ul>
  <li><strong>Price:</strong> ${(data.pricingAmount / 100).toFixed(2)} ${data.pricingCurrency.toUpperCase()}</li>
  <li><strong>Description:</strong> ${data.pricingDescription || 'Premium Subscription'}</li>
</ul>

<p>With this premium subscription, you'll unlock:</p>
<ul>
  <li><strong>Personalized Training Programs</strong> — Evidence-based workout routines tailored to your goals</li>
  <li><strong>Custom Nutrition Plans</strong> — Calorie and macro targets with regional meal suggestions</li>
  <li><strong>AI Fitness Coach Chatbot</strong> — Get real-time guidance, exercise substitutions, and food alternatives</li>
  <li><strong>Dynamic Program Adjustments</strong> — Your plans evolve as you progress</li>
  <li><strong>Weight Tracking & Optimization</strong> — Report your weight and let AI optimize your plan</li>
</ul>

<p>Ready to take your fitness journey to the next level? Log in to your account and upgrade today!</p>

<p>Best regards,<br/>The AI Fitness Coach Team</p>`;
};

module.exports = async (request) => {
  try {
    // Get pricing config data from the Kafka event
    const pricingConfig = request.pricingConfig;
    
    if (!pricingConfig) {
      console.log("No pricing config data in event");
      return { status: 200, message: "No pricing config data" };
    }

    console.log(`New pricing config created: ${pricingConfig.id}`);

    // Fetch all users from auth service via Elasticsearch
    // Using empty query to get all users, with high limit
    const users = await fetchRemoteListByMQuery(
      "User",
      {},  // Empty query = all users
      0,   // from
      1000, // size - adjust based on your user count
      [{ createdAt: { order: "desc" } }]
    );

    console.log(`Found ${users.length} users to notify`);

    if (!users || users.length === 0) {
      return { status: 200, message: "No users found to notify" };
    }

    // Get notification service URL from environment
    const notificationServiceUrl = process.env.NOTIFICATION_SERVICE_URL || 
                                   "http://notification-service:3000";

    // Send email to each user
    const emailPromises = users.map(async (user) => {
      try {
        const emailHtml = getEmailHtml({
          userFullname: user.fullname,
          pricingAmount: pricingConfig.amount,
          pricingCurrency: pricingConfig.currency,
          pricingDescription: pricingConfig.description
        });

        const notificationPayload = {
          types: ["email"],
          template: "NONE",  // Use direct body instead of template
          to: user.email,
          isStored: false,
          title: "New Premium Plan Available!",
          body: emailHtml,
          metadata: {
            userFullname: user.fullname,
            pricingAmount: pricingConfig.amount,
            pricingCurrency: pricingConfig.currency,
            configId: pricingConfig.id
          }
        };

        await axios.post(
          `${notificationServiceUrl}/notifications`,
          notificationPayload
        );

        console.log(`Email sent to ${user.email}`);
        return { success: true, email: user.email };
      } catch (error) {
        console.error(`Failed to send email to ${user.email}:`, error.message);
        return { success: false, email: user.email, error: error.message };
      }
    });

    const results = await Promise.allSettled(emailPromises);
    
    const successCount = results.filter(r => r.status === 'fulfilled' && r.value.success).length;
    const failCount = results.length - successCount;

    console.log(`Email broadcast complete: ${successCount} successful, ${failCount} failed`);

    return {
      status: 200,
      message: `Pricing config notification sent to ${successCount} users (${failCount} failed)`,
      totalUsers: users.length,
      successCount,
      failCount
    };
  } catch (error) {
    console.error("Error in onPricingConfigCreated:", error);
    return {
      status: 500,
      message: "Error processing pricing config notification",
      error: error.message
    };
  }
};

Edge Controllers Summary

Function Name Method Path Login Required
onPricingConfigCreated GET /pricing-config-created No
onPaymentDone GET `` No

This document was generated from the service library configuration and should be kept in sync with design changes.