testing 101

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-06-30 19:31:45 +08:00
parent 89acdfc239
commit 1372f4e975
62 changed files with 6696 additions and 281 deletions
+10 -3
View File
@@ -15,13 +15,20 @@
const nodemailer = require('nodemailer');
const { emailTemplates } = require('../data/email_body.data')
const port = Number(process.env.SMTP_PORT);
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
host: process.env.SMTP_HOST,
port,
secure: port === 465,
requireTLS: port === 587,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
tls: {
rejectUnauthorized: false,
},
});
const sendEmail = async ({ to, type, data = {} }) => {
@@ -39,7 +46,7 @@ const sendEmail = async ({ to, type, data = {} }) => {
{
from: {
name: "STARR System",
address: "do-not-reply@philproperties.com",
address: process.env.EMAIL_FROM,
},
to,
subject,
+120
View File
@@ -0,0 +1,120 @@
/***********************************************************************************************************************************************************************
* File Name: payment.service.js
* Type of Program: Service
* Description: Provider-agnostic payment orchestration.
* - Loads payment policy per plan (promo rules, refund policy, allowed providers)
* - Evaluates promo codes server-side (type: flat | percent)
* - Calculates refund eligibility window (unit: minutes | hours | days)
* - Delegates create/capture/refund to the provider registry
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Jun. 29, 2026
***********************************************************************************************************************************************************************/
'use strict';
const registry = require('../providers/registry');
const mdl_PaymentPolicies = require('../models/tiers/payment_policies.mdl');
const mdl_Payments = require('../models/tiers/payments.mdl');
// ─── Defaults ─────────────────────────────────────────────────────────────────
const DEFAULT_REFUND_POLICY = {
allowed: true,
window_value: 5,
window_unit: 'minutes',
reason_required: false,
};
const UNIT_MS = { minutes: 60_000, hours: 3_600_000, days: 86_400_000 };
// ─── Policy ───────────────────────────────────────────────────────────────────
async function getPolicyForPlan(plan_id) {
return mdl_PaymentPolicies.findOne({ where: { plan_id } });
}
// ─── Refund window ────────────────────────────────────────────────────────────
function getRefundWindowMs(policy) {
const rp = policy?.refund_policy ?? DEFAULT_REFUND_POLICY;
const { window_value = 5, window_unit = 'minutes' } = rp;
return Number(window_value) * (UNIT_MS[window_unit] ?? 60_000);
}
function isRefundAllowed(policy) {
return policy?.refund_policy?.allowed ?? DEFAULT_REFUND_POLICY.allowed;
}
// ─── Promo evaluation ─────────────────────────────────────────────────────────
// effectivePrice — pass the localized price when charging in a non-base currency
// so discounts are computed against the actual amount being charged.
async function evaluatePromo(policy, plan, rawCode, effectivePrice = null) {
const code = rawCode?.trim?.().toUpperCase?.() ?? null;
if (!code) return { valid: false, reason: 'No promo code provided.' };
const rules = policy?.promo_rules ?? [];
const rule = rules.find((r) => r.code?.toUpperCase() === code);
if (!rule) return { valid: false, reason: 'Invalid promo code.' };
if (rule.expires_at && new Date(rule.expires_at) < new Date())
return { valid: false, reason: 'Promo code has expired.' };
// Count how many completed payments used this code for this plan
if (rule.max_uses != null) {
const uses = await mdl_Payments.count({
where: { promo_code: code, plan_id: plan.plan_id },
});
if (uses >= Number(rule.max_uses))
return { valid: false, reason: 'Promo code has reached its usage limit.' };
}
const subtotal = effectivePrice !== null ? Number(effectivePrice) : Number(plan.price);
if (rule.min_amount != null && subtotal < Number(rule.min_amount))
return { valid: false, reason: `This promo code requires a minimum purchase of ${rule.min_amount}.` };
let discount;
if (rule.type === 'flat') {
discount = Math.min(Number(rule.value), subtotal);
} else if (rule.type === 'percent') {
const pct = Math.min(Number(rule.value), 100);
const raw = (subtotal * pct) / 100;
discount = rule.max_discount != null ? Math.min(raw, Number(rule.max_discount)) : raw;
discount = Math.min(discount, subtotal);
} else {
return { valid: false, reason: 'Unsupported promo type.' };
}
return {
valid: true,
code,
type: rule.type,
value: rule.value,
discount: Number(discount.toFixed(2)),
reason: null,
};
}
// ─── Provider delegation ──────────────────────────────────────────────────────
function createOrder(provider, opts) {
return registry.get(provider).createOrder(opts);
}
function captureOrder(provider, orderId) {
return registry.get(provider).captureOrder(orderId);
}
function refundCapture(provider, captureId, amount, currency) {
return registry.get(provider).refundCapture(captureId, amount, currency);
}
module.exports = {
getPolicyForPlan,
getRefundWindowMs,
isRefundAllowed,
evaluatePromo,
createOrder,
captureOrder,
refundCapture,
};
+4 -4
View File
@@ -10,8 +10,8 @@
// S3_REGION – garage
// S3_ACCESS_KEY
// S3_SECRET_KEY
// S3_BUCKET – philproperties
// S3_PUBLIC_URL – https://garage.philproperties.com
// S3_BUCKET – your-bucket-name
// S3_PUBLIC_URL – https://cdn.yourdomain.com
const { S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand, HeadBucketCommand } = require("@aws-sdk/client-s3");
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
@@ -44,7 +44,7 @@ const s3Public = new S3Client({
forcePathStyle: true,
});
const DEFAULT_BUCKET = process.env.S3_BUCKET || "philproperties";
const DEFAULT_BUCKET = process.env.S3_BUCKET;
const PUBLIC_URL = (process.env.S3_PUBLIC_URL || "").replace(/\/$/, "");
// ─── Key prefix map ───────────────────────────────────────────────────────────
@@ -84,7 +84,7 @@ function buildKey(originalname, ownerType) {
// Builds the public URL for a stored object.
// Garage path-style: {S3_PUBLIC_URL}/{bucket}/{key}
// e.g. https://garage.philproperties.com/philproperties/images/uuid.jpg
// e.g. https://cdn.yourdomain.com/your-bucket/images/uuid.jpg
function buildPublicUrl(key, bucket = DEFAULT_BUCKET) {
return `${PUBLIC_URL}/${bucket}/${key}`;
}