Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

A C-Based Billing System for Prepaid Cards with Administrator Controls

Notes May 14 2

A console‑based billing system menages prepaid cards, tracks usage sessions, and supports administrator operations including rate configuration. The application stores card records in a singly linked list and administrator accounts in a dynamic array. All data is loaded from text files at startup and written backupon exit.

The system provides the following top‑level menu:

1. Add card
2. Query card
3. Delete card
4. Start session (log in)
5. End session (log out)
6. Top‑up
7. Refund
8. Statistics & summary
9. Administrator management
10. Billing policy configuration
0. Exit

Administrator operations (add, remove, set privileges) require authentication. Two privilege levels allow viewing statistics (level 1) or deleting cards and modifying rates (level 2).

Billing policy fields:

  • Minimum billing unit (minutes)
  • Charge per unit
  • Rate type (0: standard, 1: night flat fee, 2: day flat fee)
  • Night/day flat fees

Data persistence Card records are stored in cards.dat with fields separated by ##:

cardId##password##status##creationTime##expireTime##totalSpent##lastUsed##usageCount##balance##deletedFlag

Administrators are stored in admins.dat similarly:

name##password##privilege##deletedFlag

Header file billing.h

#ifndef BILLING_H
#define BILLING_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>

typedef struct Card {
    char id[20];
    char pwd[9];
    int online;               // 0 off, 1 on
    time_t created;
    time_t expires;
    double total_charge;
    double credit;
    time_t last_active;
    int sessions;
    time_t login_time;
    int removed;
    struct Card* next;
} Card;

typedef struct Admin {
    char id[9];
    char pwd[9];
    int level;                // 0 none, 1 view stats, 2 delete & config
    int deleted;
} Admin;

typedef struct AdminArray {
    Admin* data;
    size_t count;
    size_t cap;
} AdminArray;

typedef struct {
    int unit_min;
    double unit_price;
    int type;                 // 0 normal, 1 night, 2 day
    double night_rate;
    double day_rate;
} RatePolicy;

extern Card* cardList;
extern AdminArray admins;
extern RatePolicy rates;

void init_card_list(void);
void init_admin_list(void);
void load_cards(const char* fname);
void load_admins(const char* fname);
void save_cards(const char* fname);
void save_admins(const char* fname);
void release_all(void);

void fmt_time(time_t t, char* buf, size_t len);
time_t parse_time(const char* s);

int  new_card(void);
void find_card(void);
void revoke_card(void);
void start_use(void);
void stop_use(void);
void deposit(void);
void withdraw(void);
void summary(void);
void admin_panel(void);
void rate_panel(void);

void show_main_menu(void);
void show_admin_menu(void);
void show_rate_menu(void);

#endif

Implementation billing.c (excerpts, error handling omitted for brevity)

#include "billing.h"

Card* cardList = NULL;
AdminArray admins = {NULL,0,0};
RatePolicy rates = {1,1.0,0,50.0,200.0};

void init_card_list() {
    cardList = (Card*)calloc(1,sizeof(Card));
    if (!cardList) exit(1);
    cardList->next = NULL;
}

void init_admin_list() {
    admins.cap = 4;
    admins.data = (Admin*)malloc(admins.cap * sizeof(Admin));
    if (!admins.data) exit(1);
    admins.count = 0;
}

void load_cards(const char* fname) {
    FILE* fp = fopen(fname, "r");
    assert(fp);
    char buf[128];
    Card* tail = cardList;
    while (tail->next) tail = tail->next; // find end
    while (fgets(buf, sizeof(buf), fp)) {
        Card* n = (Card*)calloc(1, sizeof(Card));
        char *tok = strtok(buf, "##\n");
        strncpy(n->id, tok, sizeof(n->id)-1);
        strncpy(n->pwd, strtok(NULL, "##\n"), sizeof(n->pwd)-1);
        n->online = atoi(strtok(NULL, "##\n"));
        n->created = parse_time(strtok(NULL, "##\n"));
        n->expires = parse_time(strtok(NULL, "##\n"));
        n->total_charge = atof(strtok(NULL, "##\n"));
        n->last_active = parse_time(strtok(NULL, "##\n"));
        n->sessions = atoi(strtok(NULL, "##\n"));
        n->credit = atof(strtok(NULL, "##\n"));
        n->removed = atoi(strtok(NULL, "##\n"));
        tail->next = n;
        tail = n;
    }
    fclose(fp);
}

int new_card() {
    Card* node = (Card*)calloc(1, sizeof(Card));
    if (!node) return 1;
    char id[20], pwd[9];
    int ok = 0;
    do {
        printf("Enter card ID (8-18 chars): ");
        scanf("%19s", id);
        if (strlen(id) < 8 || strlen(id) > 18)
            printf("Invalid length.\n");
        else ok = 1;
    } while (!ok);
    strncpy(node->id, id, sizeof(node->id)-1);
    do {
        ok = 0;
        printf("Enter password (1-8 chars): ");
        scanf("%8s", pwd);
        if (strlen(pwd) > 8) printf("Too long.\n");
        else ok = 1;
    } while (!ok);
    strncpy(node->pwd, pwd, sizeof(node->pwd)-1);
    printf("Opening balance: ");
    scanf("%lf", &node->credit);
    node->created = node->last_active = time(NULL);
    node->expires = node->created + 360*24*3600; // 360 days
    // append to end
    Card* tail = cardList;
    while (tail->next) tail = tail->next;
    tail->next = node;
    node->next = NULL;
    printf("Card %s added.\n", node->id);
    return 0;
}

void start_use() {
    char id[20], pwd[9];
    printf("Card ID: "); scanf("%19s", id);
    printf("Password: "); scanf("%8s", pwd);
    Card* cur = cardList->next;
    while (cur) {
        if (strcmp(cur->id, id)==0 && strcmp(cur->pwd, pwd)==0) {
            if (cur->online) { printf("Already active.\n"); return; }
            if (cur->credit <= 0) { printf("Insufficient balance.\n"); return; }
            cur->login_time = time(NULL);
            cur->online = 1;
            cur->sessions++;
            printf("Session started for %s\n", cur->id);
            return;
        }
        cur = cur->next;
    }
    printf("Card not found or invalid password.\n");
}

void stop_use() {
    char id[20], pwd[9];
    printf("Card ID: "); scanf("%19s", id);
    printf("Password: "); scanf("%8s", pwd);
    Card* cur = cardList->next;
    while (cur) {
        if (strcmp(cur->id, id)==0 && strcmp(cur->pwd, pwd)==0) {
            if (!cur->online) { printf("Already offline.\n"); return; }
            time_t now = time(NULL);
            cur->online = 0;
            cur->last_active = now;
            double elapsed = difftime(now, cur->login_time);
            double cost = 0;
            if (rates.type == 0)
                cost = elapsed / (rates.unit_min * 60.0) * rates.unit_price;
            else if (rates.type == 1)
                cost = rates.night_rate;
            else
                cost = rates.day_rate;
            cur->credit -= cost;
            cur->total_charge += cost;
            printf("Session ended. Charge: %.2f, Balance: %.2f\n", cost, cur->credit);
            return;
        }
        cur = cur->next;
    }
    printf("Card not found.\n");
}

void summary() {
    if (admins.count == 0) { printf("No admins – add one first.\n"); return; }
    char aid[9], apwd[9];
    printf("Admin name: "); scanf("%8s", aid);
    printf("Admin password: "); scanf("%8s", apwd);
    Admin* adm = NULL;
    for (size_t i=0; i<admins.count; i++) {
        if (strcmp(admins.data[i].id, aid)==0 && strcmp(admins.data[i].pwd, apwd)==0) {
            adm = &admins.data[i]; break;
        }
    }
    if (!adm) { printf("Authentication failed.\n"); return; }
    if (adm->level < 1) { printf("Insufficient privilege.\n"); return; }
    Card* cur = cardList->next;
    double total_revenue = 0;
    int total_sessions = 0, total_cards = 0;
    printf("--- Card Report ---\n");
    while (cur) {
        total_revenue += cur->total_charge;
        total_sessions += cur->sessions;
        total_cards++;
        char expire_str[20];
        fmt_time(cur->expires, expire_str, sizeof(expire_str));
        printf("%s  %d  %.2f  %.2f  %d  %s\n",
               cur->id, cur->online, cur->total_charge, cur->credit, cur->sessions, expire_str);
        cur = cur->next;
    }
    printf("Total cards: %d, sessions: %d, revenue: %.2f\n", total_cards, total_sessions, total_revenue);
}

void admin_panel() {
    int opt;
    do {
        show_admin_menu();
        scanf("%d", &opt);
        switch(opt) {
            case 1: /* add admin */ {
                Admin a; memset(&a,0,sizeof(a));
                printf("Name (max 8): "); scanf("%8s", a.id);
                printf("Password: "); scanf("%8s", a.pwd);
                printf("Privilege (0-2): "); scanf("%d", &a.level);
                if (admins.count == admins.cap) {
                    admins.cap *= 2;
                    admins.data = (Admin*)realloc(admins.data, admins.cap * sizeof(Admin));
                    assert(admins.data);
                }
                admins.data[admins.count++] = a;
                printf("Admin added.\n");
            } break;
            case 2: /* remove admin */ {
                char name[9], pwd[9];
                printf("Name: "); scanf("%8s", name);
                printf("Password: "); scanf("%8s", pwd);
                for (size_t i=0; i<admins.count; i++) {
                    if (strcmp(admins.data[i].id, name)==0 && strcmp(admins.data[i].pwd, pwd)==0) {
                        admins.data[i].deleted = 1;
                        printf("Admin marked deleted.\n");
                        return;
                    }
                }
                printf("Not found.\n");
            } break;
            /* ... other cases similar */
        }
    } while (opt != 0);
}
// rate_panel(), save functions etc. follow the same pattern.

The program’s main loop calls show_main_menu, dispatches to the selected operation, and finally writes all lists back to the files before quitting.

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.