Plans and durations
Building the investment products your customers buy — every field on a forex plan, which two of them decide what you actually pay out, how durations attach, and the validation that stops a dead plan reaching the site.
A plan is the product. A duration is the term. A customer buys the pair, and the pair decides what leaves your balance sheet at maturity. Build durations first — a plan cannot be saved without at least one.
Durations
Admin → Forex → Plans → Durations, at /admin/forex/duration.
A duration is two fields: a whole number and a timeframe of HOUR, DAY,
WEEK or MONTH. That is all it holds — there is no rate, no label and no
description on the row itself.
The end date is computed when the investment is created, from the moment of creation:
| Timeframe | Adds |
|---|---|
HOUR |
that many hours |
DAY |
that many days |
WEEK |
that many × 7 days |
MONTH |
that many × 30 days |
Not a calendar month. A three-month investment opened on 31 January ends on 31 March, not 30 April. If you advertise calendar months anywhere in your marketing, your customers will eventually notice the gap.
Durations are shared across every plan on the platform. Attaching them is what scopes them — see below.
forex_investment has a database-level cascade to forex_duration. Deleting a
duration used to physically remove every investment on it, principal and
history together, with nothing to settle and nothing to refund.
Deletion is now refused while active investments reference the duration, and a permanent delete is refused while any investment does — settled ones included, because those rows are what customer statements and your accounting are built from. Settle or cancel first; cancelling refunds the principal.
Plans
Admin → Forex → Plans, at /admin/forex/plan. The create form has six
groups.
Plan identity
name is required and is what the tables and settlement logs use. title,
description and image are the customer-facing presentation and are all
optional — but a plan with no image draws a placeholder on the plan cards, so
supply one.
Wallet and currency
Both required, and together they are the most consequential choice on the form. The wallet type list is built from what your install actually supports, and the currency list refreshes from it.
The wallet type dropdown offers ECO as soon as the Ecosystem addon is
installed, because it is the platform's generic wallet-type list. This addon
cannot use it: the fee calculation behind every forex deposit and withdrawal
handles FIAT and SPOT only and refuses anything else with Invalid wallet
type.
A plan denominated in an ECO currency saves, publishes and appears on the site.
Every attempt to fund an account for it then fails with a 400, and the customer
has no way to tell why. Use FIAT or SPOT.
The plan's currency and wallet type must also match the customer's forex
account. An account is bound to one currency and one wallet type the first time
it is funded, and an investment against a plan in a different denomination is
refused with a message naming both. So a customer holding a SPOT USDT forex
account can only invest in SPOT USDT plans. If you want to offer more than
one denomination, understand that a customer must keep a separate account per
currency and that the product only issues one LIVE account per person — in
practice, run one denomination.
Investment limits
minAmount and maxAmount bound what a single investment may be. Both are
optional; a null maximum means unlimited.
They are enforced twice — once by the investment route and once by the fraud
checks, which use the plan's own maxAmount as the ceiling rather than a
hidden literal. A negative or non-numeric amount is rejected outright,
regardless of what the limits say.
Profit configuration
Five fields, and this is where operators go wrong.
Settlement computes the movement as principal × profitPercentage ÷ 100 and
then applies the result:
WIN— the forex account gets principal + profitLOSS— the forex account gets principal − profit, never below zeroDRAW— the forex account gets the principal back
minProfit and maxProfit do not enter that calculation at all. They bound
what you may set profitPercentage to, and they are what the plan card shows
as a range. A plan advertising "5–20%" with a profit percentage of 12 pays
exactly 12% to every investor on it.
This is the point to be honest with yourself about the product. There is no
market behind an investment. A plan with default result WIN and profit
percentage 15 is a promise to hand every investor 15% of their principal at
maturity, out of your own funds, on a schedule you cannot pause once the
investments are open. Model that before you publish.
Per-investment overrides are possible: an admin can set result on an
individual investment at /admin/forex/investment before it settles, and that
value wins over the plan's default. It is the only way to make one investment
differ from another on the same plan.
Duration options
Required. Pick every duration this plan should offer; the picker is fed from your durations list.
The investment route checks the forex_plan_duration join before it does
anything else and refuses with That duration is not offered by this plan.
This is deliberate — because a plan's profit is a flat percentage that does not
scale with time, an unscoped duration list would let a customer take a 30-day
plan's whole return in one hour.
The consequence is that a plan whose durations were never attached is completely dead: it lists, it opens, and every investment on it is refused.
Plan settings
status and trending. Both default to off, and status defaulting to off is
the one that catches people — a plan you have just created is invisible until
you enable it, because the customer-facing plan list only returns plans with
status true. A disabled plan also refuses new investments outright, so
switching an active plan off is a safe way to stop taking money on it.
trending promotes the plan into the Trending tab on /forex/plan and onto
the landing page's featured row. It changes nothing else.
What the validation refuses
Both the create and the update routes run the same checks before writing anything. Each returns a 400 with the reason:
| Refused when | Why it matters |
|---|---|
| Any of the six numeric fields is negative | A negative percentage inverts the payout |
maxAmount is below minAmount |
No amount could ever satisfy both — the plan is dead on arrival |
maxProfit is below minProfit |
The advertised range is inverted |
profitPercentage sits outside minProfit–maxProfit |
Investors are paid the percentage, so the two must agree |
The third and fourth are the ones that used to ship broken plans. Transposing the limits — minimum 5,000, maximum 100 — saved happily and put a plan on the site where every possible amount was refused with Amount must be between 5000 and 100, whatever the customer typed.
Changing a live plan
Editing a plan changes the terms of investments that have not settled yet, because settlement reads the plan at maturity rather than a snapshot taken at purchase.
An investment holds its own principal, duration and end date, but not its
rate. Change profitPercentage from 10 to 25 and every open investment on the
plan settles at 25% — including ones bought weeks ago on the old terms.
If you need to change what a plan pays going forward, create a new plan and disable the old one. The disabled plan stops taking money immediately and its open investments continue to settle on the terms they were sold under.
Changing the currency or wallet type of a plan that already has open investments is worse: those investments were funded from accounts bound to the old denomination, and the mismatch check will refuse anything new while the old ones settle into an account whose currency no longer matches the plan. Do not do it. Make a new plan.
What the customer sees
GET /api/forex/plan returns only enabled plans, and deliberately excludes
defaultProfit, defaultResult and status from the payload — so the outcome
you have configured is never visible to the person buying. It also computes an
invested total per plan, which is what the default popularity sort orders
by.
Customers can filter by trending, search title and description, set a minimum profit, cap the maximum investment, and sort by popularity, profit or minimum investment.
Next: Accounts — where the money the plan consumes has to be sitting first.