@hyperscale0/hsx 1.0.258 → 1.0.284
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -4
- package/README.md +33 -4
- package/SECURITY.md +10 -21
- package/bases/cards.hsx +38 -0
- package/bases/collections.hsx +51 -0
- package/bases/escrow.hsx +23 -0
- package/bases/financing.hsx +36 -0
- package/bases/insurance.hsx +22 -0
- package/bases/lending.hsx +73 -0
- package/bases/marketplace.hsx +38 -0
- package/bases/money.hsx +12 -0
- package/bases/reporting.hsx +40 -0
- package/bases/savings.hsx +20 -0
- package/bases/wallet.hsx +23 -0
- package/dist/src/bases-bundle.d.ts +3 -0
- package/dist/src/bases-bundle.d.ts.map +1 -0
- package/dist/src/bases-bundle.js +14 -0
- package/dist/src/bases-bundle.js.map +1 -0
- package/dist/src/bases.d.ts +10 -0
- package/dist/src/bases.d.ts.map +1 -0
- package/dist/src/bases.js +2 -0
- package/dist/src/bases.js.map +1 -0
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +108 -14
- package/dist/src/compile.js.map +1 -1
- package/dist/src/examples-bundle.js +3 -3
- package/dist/src/examples-bundle.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +7 -6
- package/dist/src/std-bundle.js.map +1 -1
- package/docs/README.md +40 -31
- package/examples/library.hsx +7 -1
- package/examples/rental-deposit.hsx +5 -12
- package/examples/serviced.hsx +7 -1
- package/package.json +13 -7
- package/src/bases-bundle.ts +15 -0
- package/src/bases.ts +9 -0
- package/src/compile.ts +138 -19
- package/src/examples-bundle.ts +3 -3
- package/src/std-bundle.ts +7 -6
- package/std/booking.hsx +8 -3
- package/std/collections.hsx +1 -1
- package/std/escrow.hsx +16 -29
- package/std/financing.hsx +21 -48
- package/std/money.hsx +6 -8
- package/std/purchase.hsx +89 -0
- package/std/reporting.hsx +1 -11
package/std/booking.hsx
CHANGED
|
@@ -7,7 +7,8 @@ header booking
|
|
|
7
7
|
// Expose create, pay_deposit and pay_balance for the booking path, and
|
|
8
8
|
// cancel_early, cancel_middle and cancel_late for the customer. Create captures
|
|
9
9
|
// subject departure, price, deposit and balance, and refuses a deposit and balance
|
|
10
|
-
// that do not add up to the price.
|
|
10
|
+
// that do not add up to the price. Both installments must be positive. Creation
|
|
11
|
+
// and deposit payment close at the balance cutoff. The balance is payable only after the deposit
|
|
11
12
|
// and before balance_due ahead of departure, so a cancelled booking never offers
|
|
12
13
|
// it. cancel_early runs until early_until before departure and returns everything
|
|
13
14
|
// held. cancel_middle runs from then until late_from; cancel_late runs from then
|
|
@@ -22,6 +23,7 @@ header booking
|
|
|
22
23
|
// composed counts.
|
|
23
24
|
|
|
24
25
|
instrument reservation(customer: party = party(person), operator: party = programOperator, early_until: duration = 30d, late_from: duration = 15d, balance_due: duration = 15d, middle_forfeit: percent = 0%, late_forfeit: percent = 0%) {
|
|
26
|
+
constraints { late_from: less_than(early_until) }
|
|
25
27
|
revisioned: true
|
|
26
28
|
summary: "Take a deposit and a balance for a dated booking, with cancellation windows before departure."
|
|
27
29
|
fields {
|
|
@@ -38,11 +40,14 @@ instrument reservation(customer: party = party(person), operator: party = progra
|
|
|
38
40
|
action create {
|
|
39
41
|
subject { departure: date, price: money, deposit: money, balance: money }
|
|
40
42
|
actor: { party: customer }
|
|
41
|
-
requires self.
|
|
43
|
+
requires self.balanceAt > self.now
|
|
44
|
+
requires self.departure > self.balanceAt
|
|
45
|
+
requires self.deposit > "0"
|
|
46
|
+
requires self.balance > "0"
|
|
42
47
|
requires self.split == self.price
|
|
43
48
|
}
|
|
44
49
|
action pay_deposit {
|
|
45
|
-
from: reserved, to: deposit_paid, actor: { party: customer }
|
|
50
|
+
from: reserved, to: deposit_paid, actor: { party: customer }, deadline: { at: self.balanceAt }
|
|
46
51
|
moves self.deposit from customer to self.held economics { purpose: principal, sourceParty: customer }
|
|
47
52
|
}
|
|
48
53
|
action pay_balance {
|
package/std/collections.hsx
CHANGED
|
@@ -30,7 +30,7 @@ instrument case(on: ref<financing.installments>[], agency: party, capital: party
|
|
|
30
30
|
requires unique "overdue_referral" on [self.slice]
|
|
31
31
|
}
|
|
32
32
|
action assign { from: opened, to: assigned, actor: { party: capital } }
|
|
33
|
-
action recover {
|
|
33
|
+
action recover { from: assigned, to: recovered, actor: { party: agency }, requires self.payment.plan == self.plan, invoke: [{ reference: self.payment, action: pay, input: {} }, { reference: self.id, action: pay_fee, input: {} }] }
|
|
34
34
|
action pay_fee { allowZero: true, from: recovered, to: recovered, actor: { parent: current() }, moves self.agencyFee from self.plan.capital to agency economics { recipient: agency, company: { recipient: capital, company: { purpose: internal, sourceParty: capital }, participant: { purpose: earning, sourceParty: capital } }, participant: { recipient: capital, company: { purpose: participant_payout, sourceParty: capital }, participant: { purpose: pass_through, sourceParty: capital } } } }
|
|
35
35
|
action recall { from: [opened, assigned], to: recalled, actor: { party: capital } }
|
|
36
36
|
action return_uncollected { from: assigned, to: returned_uncollected, actor: { party: agency } }
|
package/std/escrow.hsx
CHANGED
|
@@ -8,30 +8,17 @@ header escrow
|
|
|
8
8
|
// subject.price; acceptance pays payee less the configured fee, not payer.
|
|
9
9
|
// Expose dispute, verify_return and refund separately for the return path.
|
|
10
10
|
// Payer disputes delivered goods; payee verifies return evidence from disputed.
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// principal to the payer. Only unwind returns the principal to capital and
|
|
15
|
-
// cancels the plan; after that, refund returns the rest. Before delivery the
|
|
16
|
-
// Product operator runs the plan's unwind_undelivered instead, which invokes
|
|
17
|
-
// unwind_financed here to return the rest to the payer and close the hold.
|
|
18
|
-
// For a paid plan, the plan's unwind_paid runs unwind_financed from funded after
|
|
19
|
-
// deliverBy, and return_hold or unwind_paid runs it from return_verified,
|
|
20
|
-
// depending on the plan's returned_goods.
|
|
21
|
-
// Only an installments plan can run confirm and unwind_financed. In a program
|
|
22
|
-
// without financing neither action exists, so every hold reaches funded through
|
|
23
|
-
// fund, which sets fundedAt. The plan's confirm leaves fundedAt unset, so
|
|
24
|
-
// refund_undelivered never comes due on a financed hold and no schedule offers
|
|
25
|
-
// it. Confirm sets deliverBy, deliver_within after confirmation, and the plan's
|
|
26
|
-
// unwind_paid waits for it so the payee keeps the whole delivery window.
|
|
11
|
+
// Refunds return the held balance to the payer. A composition can supply funding
|
|
12
|
+
// controllers and the states in which they block direct refunds. Only those
|
|
13
|
+
// controllers may confirm outside funding or finish its coordinated refund.
|
|
27
14
|
// Expose refund_undelivered to let the payer take the whole held price back once
|
|
28
15
|
// deliver_within has passed since funding without delivery. It is a payer action
|
|
29
16
|
// that becomes available at that time; nothing runs it automatically.
|
|
30
17
|
// The Product operator settles any disputed hold: settle_release pays the payee
|
|
31
18
|
// as acceptance would, and settle_return moves it to return_verified. Without a
|
|
32
|
-
//
|
|
33
|
-
// to the payer.
|
|
34
|
-
//
|
|
19
|
+
// active funding controller, settle_return also runs settle_refund to return
|
|
20
|
+
// the whole balance to the payer. Otherwise the controller coordinates the
|
|
21
|
+
// return before the operator's settle_refund returns the rest.
|
|
35
22
|
// Acceptance expiry enters disputed, never released. Neither verify_return nor
|
|
36
23
|
// refund becomes public merely because dispute is exposed. Clock actions remain
|
|
37
24
|
// internal. Expose cancel for a pending hold, including a checkout-funded hold;
|
|
@@ -42,12 +29,12 @@ header escrow
|
|
|
42
29
|
// transfer; accept expands the configured fee and tax into moves. Run hsx cost
|
|
43
30
|
// on the composed program for instruction counts. The executor supplies prices.
|
|
44
31
|
|
|
45
|
-
instrument hold(payer: party = party(person), payee: party = programOperator, accept_within: duration = 48h, deliver_within: duration = 14d, fee: fee = { seller: 0%, tax: 0% }, dispute: policy = { refund_after: return_verified }) {
|
|
32
|
+
instrument hold(payer: party = party(person), payee: party = programOperator, accept_within: duration = 48h, deliver_within: duration = 14d, fee: fee = { seller: 0%, tax: 0% }, dispute: policy = { refund_after: return_verified }, funding: policy = { controllers: [], reference: "funds", blocking_states: [active] }) {
|
|
46
33
|
revisioned: true
|
|
47
34
|
summary: "Hold the subject price through delivery, acceptance and verified return."
|
|
48
35
|
fields {
|
|
49
36
|
payer: account of payer, payee: account of payee
|
|
50
|
-
held: account of self, fundedAt: date?, deliverBy: date?, deliveredAt: date?,
|
|
37
|
+
held: account of self, fundedAt: date?, deliverBy: date?, deliveredAt: date?, blockingFunding: integer?
|
|
51
38
|
price: money = subject.price
|
|
52
39
|
}
|
|
53
40
|
lifecycle { states: [pending, funded, delivered, disputed, return_verified, released, refunded, cancelled], initial: pending }
|
|
@@ -61,7 +48,7 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
|
|
|
61
48
|
moves self.price from payer to self.held economics { purpose: principal, sourceParty: payer }
|
|
62
49
|
}
|
|
63
50
|
action confirm {
|
|
64
|
-
from: pending, to: funded, actor: { parent:
|
|
51
|
+
from: pending, to: funded, actor: { parent: funding.controllers }
|
|
65
52
|
requires self.held.balance == self.price
|
|
66
53
|
calculate: [{ target: deliverBy, op: shift, date: { field: self.now }, milliseconds: { literal: deliver_within }, direction: after }]
|
|
67
54
|
}
|
|
@@ -95,27 +82,27 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
|
|
|
95
82
|
action settle_return {
|
|
96
83
|
from: disputed, to: return_verified, actor: { party: programOperator }
|
|
97
84
|
input { ruling: text }
|
|
98
|
-
calculate: [{ target:
|
|
99
|
-
invoke: [{ reference: self.id, action: settle_refund, guard: { kind: compare, left: { field: self.
|
|
85
|
+
calculate: [{ target: blockingFunding, op: aggregate, selection: { instrument: funding.controllers, reference: funding.reference, anchor: self.id, states: funding.blocking_states, limit: 1 }, measure: count }]
|
|
86
|
+
invoke: [{ reference: self.id, action: settle_refund, guard: { kind: compare, left: { field: self.blockingFunding }, operator: "==", right: { literal: 0 } }, input: {} }]
|
|
100
87
|
}
|
|
101
88
|
action settle_refund { allowZero: true,
|
|
102
89
|
from: return_verified, to: refunded, actor: { party: programOperator }
|
|
103
|
-
requires count of { instrument:
|
|
90
|
+
requires count of { instrument: funding.controllers, reference: funding.reference, anchor: self.id, states: funding.blocking_states, limit: 1 } == 0
|
|
104
91
|
moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
|
105
92
|
}
|
|
106
93
|
action refund {
|
|
107
94
|
from: [funded, dispute.refund_after], to: refunded, actor: { party: payee }
|
|
108
|
-
requires count of { instrument:
|
|
95
|
+
requires count of { instrument: funding.controllers, reference: funding.reference, anchor: self.id, states: funding.blocking_states, limit: 1 } == 0
|
|
109
96
|
moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
|
110
97
|
}
|
|
111
98
|
action refund_undelivered {
|
|
112
99
|
from: funded, to: refunded, actor: { party: payer }
|
|
113
100
|
due: { at: self.fundedAt, offset: deliver_within }
|
|
114
|
-
requires count of { instrument:
|
|
101
|
+
requires count of { instrument: funding.controllers, reference: funding.reference, anchor: self.id, states: funding.blocking_states, limit: 1 } == 0
|
|
115
102
|
moves self.price from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
|
116
103
|
}
|
|
117
|
-
action
|
|
118
|
-
from: [funded, return_verified], to: refunded, actor: { parent:
|
|
104
|
+
action finish_refund { allowZero: true,
|
|
105
|
+
from: [funded, return_verified], to: refunded, actor: { parent: funding.controllers }
|
|
119
106
|
moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
|
120
107
|
}
|
|
121
108
|
action cancel { allowZero: true,
|
package/std/financing.hsx
CHANGED
|
@@ -10,13 +10,9 @@ header financing
|
|
|
10
10
|
// flat_total reads it as one charge on principal for the whole term (murabaha,
|
|
11
11
|
// fixed-fee pay-later). 12,000 at 18% over 12 months costs 13,201.95 amortizing
|
|
12
12
|
// and 14,160 flat_total.
|
|
13
|
-
// Expose create and sign on installments
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// Disburse still collects any missing down payment, activates slices, and confirms
|
|
17
|
-
// the fully funded hold. Existing escrow credit counts once toward the down payment.
|
|
18
|
-
// Disburse first credits an active marketplace.reservation on the same hold,
|
|
19
|
-
// so list the plan in its converters; the deposit then counts as escrow credit.
|
|
13
|
+
// Expose create and sign on installments. A funding composition owns checkout,
|
|
14
|
+
// destination funding and returns when disburse_to is funds. The repayment plan
|
|
15
|
+
// owns principal, profit, limits and debt cancellation.
|
|
20
16
|
// disburse_to: seller pays principal to the seller party at activation (pay later
|
|
21
17
|
// with the merchant paid on day one). Capital may refund any paid payment to
|
|
22
18
|
// the borrower. Payments stay refundable while the plan is active. The
|
|
@@ -70,9 +66,9 @@ header financing
|
|
|
70
66
|
// settle returned goods too.
|
|
71
67
|
// Borrower- and seller-directed disbursement exclude collect_down_payment. A down_payment
|
|
72
68
|
// there reduces principal but does not collect cash. Price is subject metadata,
|
|
73
|
-
// not the borrower limit. The
|
|
74
|
-
//
|
|
75
|
-
//
|
|
69
|
+
// not the borrower limit. The funding composition checks its payer and price.
|
|
70
|
+
// Cancelling a plan cancels its debt agreement. Its funding composition owns
|
|
71
|
+
// returning any cash collected before activation.
|
|
76
72
|
// Approve borrower and portfolio limits before disbursement. Create builds the
|
|
77
73
|
// dated slice records from future, strictly increasing dates; disbursement
|
|
78
74
|
// refuses once a slice is due. Payments need a separate payment record and pay action.
|
|
@@ -93,12 +89,12 @@ header financing
|
|
|
93
89
|
// bounded invocations across slices and assessments. Month and charge limits affect
|
|
94
90
|
// the bound. Run hsx cost on the selected policies; profit is not a platform tariff.
|
|
95
91
|
|
|
96
|
-
instrument installments(months: integer(1, 366), max_history: integer(2, 366) = 366, pricing: enum(amortizing, flat_total), profit_rate: percent, charge_limit: integer(1, 3) = 3, disburse_to: enum(funds, borrower, seller) = funds, seller: party?, profit_earned: enum(on_payment, by_schedule, at_disbursement) = on_payment, apply: enum(fines_profit_principal, principal_profit, pro_rata) = fines_profit_principal, payoff_rebate: percent = 100%, write_off_after: duration = 90d, max_extension: duration = 90d, max_amendments: integer(0, 366) = 2, waiver_limit: percent = 100%, amendment_expiry: duration = 7d, payoff_quote_expiry: duration = 1h, refund_within: duration = 30d, returned_goods: enum(keep_profit, refund_all) = keep_profit, allow_overdue: enum(allowed, blocked) = allowed, assessed_fines: enum(carry, require_waiver) = carry, down_payment: percent = 0%, funds: ref
|
|
92
|
+
instrument installments(months: integer(1, 366), max_history: integer(2, 366) = 366, pricing: enum(amortizing, flat_total), profit_rate: percent, charge_limit: integer(1, 3) = 3, disburse_to: enum(funds, borrower, seller) = funds, seller: party?, profit_earned: enum(on_payment, by_schedule, at_disbursement) = on_payment, apply: enum(fines_profit_principal, principal_profit, pro_rata) = fines_profit_principal, payoff_rebate: percent = 100%, write_off_after: duration = 90d, max_extension: duration = 90d, max_amendments: integer(0, 366) = 2, waiver_limit: percent = 100%, amendment_expiry: duration = 7d, payoff_quote_expiry: duration = 1h, refund_within: duration = 30d, returned_goods: enum(keep_profit, refund_all) = keep_profit, allow_overdue: enum(allowed, blocked) = allowed, assessed_fines: enum(carry, require_waiver) = carry, down_payment: percent = 0%, funds: ref?, borrower: party = party(person), capital: party = programOperator, share: percent = 0%, limits: ref<financing.limits> = object(financing.limits), portfolio: ref<financing.portfolio_limit> = object(financing.portfolio_limit)) {
|
|
97
93
|
dependencies {
|
|
98
94
|
funds {
|
|
99
95
|
selector: disburse_to, is: funds
|
|
100
96
|
message: "Attachment `{attachment}` disburses into a hold, but `funds` is not bound."
|
|
101
|
-
fix: "Bind `funds:
|
|
97
|
+
fix: "Bind `funds: checkout` to a funding composition. Choose `disburse_to: seller` to pay a seller on activation, or `disburse_to: borrower` only if the borrower should receive the money directly."
|
|
102
98
|
}
|
|
103
99
|
seller {
|
|
104
100
|
selector: disburse_to, is: seller
|
|
@@ -113,7 +109,7 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
|
|
|
113
109
|
funds: ref<funds>?, borrower: account of borrower, settlementIdentity: text, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>
|
|
114
110
|
months: integer = months, dates: list(date, months), historyLimit: integer = max_history, paymentLimit: integer = subtract(max_history, 1)
|
|
115
111
|
allocatedPayable: account(capital, cash, "allocatedProfitPayable"), capital: account(capital, cash, "capital"), profitIncome: account(capital, cash, "profitIncome"), loss: account(capital, claim, "loss")
|
|
116
|
-
|
|
112
|
+
principalOutstanding: money?, principalRepaid: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money?, refundWindow: date?, refundUntil: date?
|
|
117
113
|
openSlices: integer?, openCharges: money?, openCosts: money?, payoffRefunds: integer?
|
|
118
114
|
price: money = subject.price
|
|
119
115
|
downPayment: money = rate(self.price, down_payment)
|
|
@@ -129,7 +125,7 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
|
|
|
129
125
|
actor: { party: borrower }
|
|
130
126
|
requires self.borrowerLimit.borrower == self.borrower
|
|
131
127
|
when pricing is flat_total { calculate: [{ target: flatProfit, op: rate, base: { field: self.principal }, bps: { literal: profit_rate }, rounding: floor }] }
|
|
132
|
-
when disburse_to is funds {
|
|
128
|
+
when disburse_to is funds { invoke: [{ reference: self.funds, action: check, input: { plan: { field: self.id } } }] }
|
|
133
129
|
invoke: [{ range: { count: { literal: months }, maximum: months, bind: position }, instrument: slice, action: create, input: { plan: { field: self.id }, borrowerLimit: { field: self.borrowerLimit }, portfolioLimit: { field: self.portfolioLimit }, position: { field: position } } }, { selection: { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: months, where: { first: { literal: 0 } } }, action: check_order, input: {} }]
|
|
134
130
|
}
|
|
135
131
|
action sign {
|
|
@@ -140,32 +136,16 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
|
|
|
140
136
|
from: signed, to: signed, actor: { party: borrower }
|
|
141
137
|
when disburse_to is borrower { requires true == false }
|
|
142
138
|
when disburse_to is seller { requires true == false }
|
|
143
|
-
when disburse_to is funds {
|
|
144
|
-
requires self.funds in [pending]
|
|
145
|
-
calculate: [{ target: downPaymentCredit, op: minimum, values: [{ field: self.funds.held.balance }, { field: self.downPayment }] }, { target: downPaymentRemaining, op: subtract, base: { field: self.downPayment }, subtract: [{ field: self.downPaymentCredit }] }]
|
|
146
|
-
moves self.downPaymentRemaining from self.borrower to self.funds.held economics { purpose: principal, sourceParty: borrower }
|
|
147
|
-
}
|
|
139
|
+
when disburse_to is funds { invoke: [{ reference: self.funds, action: collect, input: { plan: { field: self.id } } }] }
|
|
148
140
|
}
|
|
149
141
|
action disburse { allowZero: true,
|
|
150
142
|
from: signed, to: active, actor: { party: capital }
|
|
151
143
|
requires self.borrowerLimit in [approved]
|
|
152
144
|
requires self.portfolioLimit in [approved]
|
|
153
|
-
when disburse_to is funds {
|
|
154
|
-
requires self.funds in [pending]
|
|
155
|
-
invoke: [{ selection: { instrument: all(marketplace.reservation), reference: "funds", anchor: self.funds, states: [active], limit: 1 }, action: credit, input: { funds: { field: self.funds }, contribution: { field: self.downPayment } } }, { reference: self.id, action: fund_down_payment, input: {} }]
|
|
156
|
-
}
|
|
145
|
+
when disburse_to is funds { invoke: [{ reference: self.funds, action: disburse, input: { plan: { field: self.id } } }] }
|
|
157
146
|
when disburse_to is borrower { moves self.principal from self.capital to self.borrower economics { purpose: principal, sourceParty: capital } }
|
|
158
147
|
when disburse_to is seller { moves self.principal from self.capital to seller economics { purpose: principal, sourceParty: capital } }
|
|
159
148
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: 366, order: [position] }, action: activate, input: {} }]
|
|
160
|
-
when disburse_to is funds { invoke: [{ reference: self.funds, action: confirm, input: {} }] }
|
|
161
|
-
}
|
|
162
|
-
action fund_down_payment { allowZero: true,
|
|
163
|
-
from: active, to: active, actor: { parent: current() }
|
|
164
|
-
when disburse_to is funds {
|
|
165
|
-
calculate: [{ target: downPaymentCredit, op: minimum, values: [{ field: self.funds.held.balance }, { field: self.downPayment }] }, { target: downPaymentRemaining, op: subtract, base: { field: self.downPayment }, subtract: [{ field: self.downPaymentCredit }] }]
|
|
166
|
-
moves self.downPaymentRemaining from self.funds.payer to self.funds.held economics { purpose: principal, sourceParty: borrower }
|
|
167
|
-
moves self.principal from self.capital to self.funds.held economics { purpose: principal, sourceParty: capital }
|
|
168
|
-
}
|
|
169
149
|
}
|
|
170
150
|
action complete {
|
|
171
151
|
from: active, to: paid
|
|
@@ -201,7 +181,7 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
|
|
|
201
181
|
set: { refundUntil: { field: self.refundWindow } }
|
|
202
182
|
due: { at: input.overdueSlice.lossEligibleAt }
|
|
203
183
|
requires input.overdueSlice.plan == self.id; requires input.overdueSlice.principalReceivable.balance > "0"
|
|
204
|
-
when disburse_to is funds {
|
|
184
|
+
when disburse_to is funds { invoke: [{ reference: self.funds, action: write_off, input: { plan: { field: self.id } } }] }
|
|
205
185
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: write_off, input: {} }, { instrument: settlement, action: create, input: { plan: { field: self.id }, identity: { field: self.settlementIdentity }, principal: { literal: "0" }, profit: { literal: "0" }, loss: { field: self.lossRecorded }, kind: { literal: "loss" } } }]
|
|
206
186
|
}
|
|
207
187
|
action record_payment {
|
|
@@ -226,7 +206,6 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
|
|
|
226
206
|
]
|
|
227
207
|
set: { refundUntil: { field: self.refundWindow } }
|
|
228
208
|
when disburse_to is borrower { requires true == false }
|
|
229
|
-
when disburse_to is funds { requires self.funds in [return_verified] }
|
|
230
209
|
when returned_goods is refund_all {
|
|
231
210
|
requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
|
|
232
211
|
requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
|
|
@@ -234,7 +213,7 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
|
|
|
234
213
|
}
|
|
235
214
|
requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed], limit: 366 } == 0
|
|
236
215
|
requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed], limit: 366 } == 0
|
|
237
|
-
when disburse_to is funds {
|
|
216
|
+
when disburse_to is funds { invoke: [{ reference: self.funds, action: refund, input: { plan: { field: self.id }, principal: { field: self.principalOutstanding } } }] }
|
|
238
217
|
when disburse_to is seller {
|
|
239
218
|
moves self.principalOutstanding from seller to self.capital economics { purpose: principal, sourceParty: seller }
|
|
240
219
|
moves self.principalRepaid from seller to self.borrower economics { purpose: principal, sourceParty: seller }
|
|
@@ -247,14 +226,12 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
|
|
|
247
226
|
set: { refundUntil: { field: self.refundWindow } }
|
|
248
227
|
when disburse_to is borrower { requires true == false }
|
|
249
228
|
when disburse_to is seller { requires true == false }
|
|
250
|
-
when disburse_to is funds { requires self.funds in [funded] }
|
|
251
229
|
requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
|
|
252
230
|
requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
|
|
253
231
|
requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
|
|
254
|
-
when disburse_to is funds { moves self.principal from self.funds.held to self.capital economics { purpose: principal, sourceParty: capital } }
|
|
255
232
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: cancel, input: {} }]
|
|
256
233
|
invoke: [{ selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [proposed, assessed], limit: 366 }, action: cancel_undelivered, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [proposed, assessed], limit: 366 }, action: cancel_undelivered, input: {} }]
|
|
257
|
-
when disburse_to is funds { invoke: [{ reference: self.funds, action:
|
|
234
|
+
when disburse_to is funds { invoke: [{ reference: self.funds, action: cancel_undelivered, input: { plan: { field: self.id } } }] }
|
|
258
235
|
}
|
|
259
236
|
action return_hold {
|
|
260
237
|
from: paid, to: paid, actor: { party: programOperator }
|
|
@@ -263,24 +240,21 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
|
|
|
263
240
|
when disburse_to is seller { requires true == false }
|
|
264
241
|
when returned_goods is refund_all { requires true == false }
|
|
265
242
|
when disburse_to is funds {
|
|
266
|
-
|
|
267
|
-
invoke: [{ reference: self.funds, action: unwind_financed, input: {} }]
|
|
243
|
+
invoke: [{ reference: self.funds, action: return_paid, input: { plan: { field: self.id } } }]
|
|
268
244
|
}
|
|
269
245
|
}
|
|
270
|
-
action unwind_paid {
|
|
246
|
+
action unwind_paid {
|
|
271
247
|
from: paid, to: cancelled, actor: { party: programOperator }
|
|
272
248
|
when disburse_to is borrower { requires true == false }
|
|
273
249
|
when disburse_to is seller { requires true == false }
|
|
274
|
-
when disburse_to is funds {
|
|
275
|
-
when returned_goods is keep_profit { requires self.funds in [funded] }
|
|
276
|
-
when returned_goods is refund_all { requires self.funds in [funded, return_verified] }
|
|
277
|
-
requires self.funds.deliverBy < self.now
|
|
278
|
-
}
|
|
279
250
|
invoke: [{ selection: { instrument: settlement, reference: "plan", anchor: self.id, states: [recorded], limit: max_history }, action: unwind, input: {} }, { selection: { instrument: profit_piece, reference: "plan", anchor: self.id, states: [paid], limit: max_history, where: { payment.status: { literal: "refunded" } } }, action: refund, input: {} }, { selection: { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: max_history }, action: unwind, input: {} }, { reference: self.id, action: unwind_payoff_profit, input: {} }]
|
|
280
251
|
invoke: [{ selection: { instrument: all(financing.late_charge.receipt), reference: "plan", anchor: self.id, states: [paid], limit: max_history }, action: refund, input: {} }, { selection: { instrument: all(financing.late_charge.cost.receipt), reference: "plan", anchor: self.id, states: [paid], limit: max_history }, action: refund, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: max_history }, action: unwind_payoff, input: {} }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [collected], limit: max_history }, action: unwind_payoff, input: {} }]
|
|
281
252
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due, paid], limit: 366, order: [position] }, action: cancel, input: {} }]
|
|
282
253
|
invoke: [{ selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [proposed, assessed, collected], limit: max_history }, action: cancel_undelivered, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [proposed, assessed, collected], limit: max_history }, action: cancel_undelivered, input: {} }]
|
|
283
|
-
when disburse_to is funds {
|
|
254
|
+
when disburse_to is funds {
|
|
255
|
+
when returned_goods is keep_profit { invoke: [{ reference: self.funds, action: unwind_paid_undelivered, input: { plan: { field: self.id } } }] }
|
|
256
|
+
when returned_goods is refund_all { invoke: [{ reference: self.funds, action: unwind_paid, input: { plan: { field: self.id } } }] }
|
|
257
|
+
}
|
|
284
258
|
}
|
|
285
259
|
action unwind_payoff_profit {
|
|
286
260
|
from: cancelled, to: cancelled, actor: { parent: current() }
|
|
@@ -593,7 +567,6 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
|
|
|
593
567
|
lifecycle { states: [pending, paid, refunded], initial: pending }
|
|
594
568
|
action create { input { plan: ref<parent>, amount: money, paymentIdentity: text } requires self.plan in [active]; requires unique "payment_identity" on [self.plan, self.paymentIdentity] }
|
|
595
569
|
action pay {
|
|
596
|
-
expansionLimit: 8192
|
|
597
570
|
from: pending, to: paid, actor: { party: borrower }
|
|
598
571
|
calculate: [{ target: outstandingPrincipal, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: "principalReceivable.balance" } }]
|
|
599
572
|
when profit_earned is on_payment { calculate: [{ target: outstandingProfit, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: "profitReceivable.balance" } }] }
|
package/std/money.hsx
CHANGED
|
@@ -147,24 +147,22 @@ instrument swap(first: party, second: party, first_amount: money, second_amount:
|
|
|
147
147
|
action expire_both { from: both_ready, to: cancelled, actor: clock, due: { at: self.expiresAt }, moves void self.firstReceipt; moves void self.secondReceipt }
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
instrument payout(payer: party, payee: party, adapter: text,
|
|
151
|
-
summary: "
|
|
152
|
-
fields { amount: money = amount
|
|
150
|
+
instrument payout(payer: party, payee: party, adapter: text, amount: money = runtime) {
|
|
151
|
+
summary: "Pay the recipient when the provider confirms settlement."
|
|
152
|
+
fields { amount: money = amount }
|
|
153
153
|
lifecycle { states: [pending, instructed, settled, rejected], initial: pending }
|
|
154
154
|
action create {}
|
|
155
155
|
action instruct {
|
|
156
156
|
from: pending, to: instructed, actor: { party: payer }
|
|
157
|
-
moves reserve self.amount from payer to payee
|
|
157
|
+
moves reserve self.amount from payer to payee boundary adapter
|
|
158
158
|
}
|
|
159
159
|
action confirm {
|
|
160
160
|
from: instructed, to: settled
|
|
161
|
-
|
|
162
|
-
moves post self.receipt
|
|
161
|
+
moves post instruct
|
|
163
162
|
}
|
|
164
163
|
action reject {
|
|
165
164
|
from: instructed, to: rejected
|
|
166
|
-
|
|
167
|
-
moves void self.receipt
|
|
165
|
+
moves void instruct
|
|
168
166
|
}
|
|
169
167
|
}
|
|
170
168
|
|
package/std/purchase.hsx
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
header purchase
|
|
2
|
+
|
|
3
|
+
// A financed purchase connects a repayment plan, a delivery hold and optional
|
|
4
|
+
// marketplace reservations. The hold blocks refunds while checkout is active.
|
|
5
|
+
// Debt cancellation returns outstanding capital before the buyer's own cash.
|
|
6
|
+
instrument checkout(plans: ref<financing.installments>[], funds: ref<escrow.hold>, borrower: party, capital: party = programOperator) {
|
|
7
|
+
summary: "Fund a purchase and return lender capital before refunding the buyer."
|
|
8
|
+
fields {
|
|
9
|
+
funds: ref<funds>, borrower: account of borrower
|
|
10
|
+
downPaymentCredit: money?, downPaymentRemaining: money?
|
|
11
|
+
}
|
|
12
|
+
lifecycle { states: [pending, active, closed], initial: pending }
|
|
13
|
+
action create { actor: { party: borrower } }
|
|
14
|
+
action check {
|
|
15
|
+
from: pending, to: pending, actor: { parent: plans }
|
|
16
|
+
input { plan: ref<plans> }
|
|
17
|
+
requires input.plan.funds == self.id
|
|
18
|
+
requires input.plan.borrower == self.borrower
|
|
19
|
+
requires input.plan.borrower == self.funds.payer
|
|
20
|
+
requires input.plan.price == self.funds.price
|
|
21
|
+
}
|
|
22
|
+
action collect { allowZero: true,
|
|
23
|
+
from: pending, to: pending, actor: { parent: plans }
|
|
24
|
+
input { plan: ref<plans> }
|
|
25
|
+
requires input.plan.funds == self.id
|
|
26
|
+
requires self.funds in [pending]
|
|
27
|
+
calculate: [{ target: downPaymentCredit, op: minimum, values: [{ field: self.funds.held.balance }, { field: input.plan.downPayment }] }, { target: downPaymentRemaining, op: subtract, base: { field: input.plan.downPayment }, subtract: [{ field: self.downPaymentCredit }] }]
|
|
28
|
+
moves self.downPaymentRemaining from self.borrower to self.funds.held economics { purpose: principal, sourceParty: borrower }
|
|
29
|
+
}
|
|
30
|
+
action disburse {
|
|
31
|
+
from: pending, to: active, actor: { parent: plans }
|
|
32
|
+
input { plan: ref<plans> }
|
|
33
|
+
requires input.plan.funds == self.id
|
|
34
|
+
requires self.funds in [pending]
|
|
35
|
+
invoke: [{ selection: { instrument: all(marketplace.reservation), reference: "funds", anchor: self.funds, states: [active], limit: 1 }, action: credit, input: { funds: { field: self.funds }, contribution: { field: input.plan.downPayment } } }, { reference: self.id, action: fund, input: { plan: { field: input.plan } } }]
|
|
36
|
+
}
|
|
37
|
+
action fund { allowZero: true,
|
|
38
|
+
from: active, to: active, actor: { parent: current() }
|
|
39
|
+
input { plan: ref<plans> }
|
|
40
|
+
calculate: [{ target: downPaymentCredit, op: minimum, values: [{ field: self.funds.held.balance }, { field: input.plan.downPayment }] }, { target: downPaymentRemaining, op: subtract, base: { field: input.plan.downPayment }, subtract: [{ field: self.downPaymentCredit }] }]
|
|
41
|
+
moves self.downPaymentRemaining from self.borrower to self.funds.held economics { purpose: principal, sourceParty: borrower }
|
|
42
|
+
moves input.plan.principal from input.plan.capital to self.funds.held economics { purpose: principal, sourceParty: capital }
|
|
43
|
+
invoke: [{ reference: self.funds, action: confirm, input: {} }]
|
|
44
|
+
}
|
|
45
|
+
action write_off {
|
|
46
|
+
from: active, to: active, actor: { parent: plans }
|
|
47
|
+
input { plan: ref<plans> }
|
|
48
|
+
requires input.plan.funds == self.id
|
|
49
|
+
requires self.funds in [released]
|
|
50
|
+
}
|
|
51
|
+
action refund { allowZero: true,
|
|
52
|
+
from: active, to: closed, actor: { parent: plans }
|
|
53
|
+
input { plan: ref<plans>, principal: money }
|
|
54
|
+
requires input.plan.funds == self.id
|
|
55
|
+
requires self.funds in [return_verified]
|
|
56
|
+
moves input.principal from self.funds.held to input.plan.capital economics { purpose: principal, sourceParty: capital }
|
|
57
|
+
}
|
|
58
|
+
action cancel_undelivered { allowZero: true,
|
|
59
|
+
from: active, to: closed, actor: { parent: plans }
|
|
60
|
+
input { plan: ref<plans> }
|
|
61
|
+
requires input.plan.funds == self.id
|
|
62
|
+
requires self.funds in [funded]
|
|
63
|
+
moves input.plan.principal from self.funds.held to input.plan.capital economics { purpose: principal, sourceParty: capital }
|
|
64
|
+
invoke: [{ reference: self.funds, action: finish_refund, input: {} }]
|
|
65
|
+
}
|
|
66
|
+
action return_paid {
|
|
67
|
+
from: active, to: closed, actor: { parent: plans }
|
|
68
|
+
input { plan: ref<plans> }
|
|
69
|
+
requires input.plan.funds == self.id
|
|
70
|
+
requires self.funds in [return_verified]
|
|
71
|
+
invoke: [{ reference: self.funds, action: finish_refund, input: {} }]
|
|
72
|
+
}
|
|
73
|
+
action unwind_paid_undelivered {
|
|
74
|
+
from: active, to: closed, actor: { parent: plans }
|
|
75
|
+
input { plan: ref<plans> }
|
|
76
|
+
requires input.plan.funds == self.id
|
|
77
|
+
requires self.funds in [funded]
|
|
78
|
+
requires self.funds.deliverBy < self.now
|
|
79
|
+
invoke: [{ reference: self.funds, action: finish_refund, input: {} }]
|
|
80
|
+
}
|
|
81
|
+
action unwind_paid {
|
|
82
|
+
from: active, to: closed, actor: { parent: plans }
|
|
83
|
+
input { plan: ref<plans> }
|
|
84
|
+
requires input.plan.funds == self.id
|
|
85
|
+
requires self.funds in [funded, return_verified]
|
|
86
|
+
requires self.funds.deliverBy < self.now
|
|
87
|
+
invoke: [{ reference: self.funds, action: finish_refund, input: {} }]
|
|
88
|
+
}
|
|
89
|
+
}
|
package/std/reporting.hsx
CHANGED
|
@@ -12,7 +12,7 @@ header reporting
|
|
|
12
12
|
// That zero instruction count does not price report capture, rendering, storage or
|
|
13
13
|
// release. The host owns those charges and their execution.
|
|
14
14
|
|
|
15
|
-
instrument portfolio(on: ref<financing.installments>[], default_days: integer(1, 3650) = 90, retention_years: integer(1, 100) = 10, aging_first_days: integer(1, 3650) = 30, aging_second_days: integer(1, 3650) = 60, aging_third_days: integer(1, 3650) = 90, ratio_scale: integer(1, 1000000) = 10000, ratio_rounding: enum(floor, halfUp) = floor
|
|
15
|
+
instrument portfolio(on: ref<financing.installments>[], default_days: integer(1, 3650) = 90, retention_years: integer(1, 100) = 10, aging_first_days: integer(1, 3650) = 30, aging_second_days: integer(1, 3650) = 60, aging_third_days: integer(1, 3650) = 90, ratio_scale: integer(1, 1000000) = 10000, ratio_rounding: enum(floor, halfUp) = floor) {
|
|
16
16
|
summary: "Declared financing reports evaluated over frozen Product facts."
|
|
17
17
|
fields {}
|
|
18
18
|
lifecycle { states: [declared], initial: declared }
|
|
@@ -333,11 +333,6 @@ instrument portfolio(on: ref<financing.installments>[], default_days: integer(1,
|
|
|
333
333
|
{ left: "scheduleCount", right: "capturedScheduleCount" }
|
|
334
334
|
],
|
|
335
335
|
unavailableFacts: [ ],
|
|
336
|
-
lockTimeoutMs: lock_wait_ms,
|
|
337
|
-
captureTimeoutMs: capture_ms,
|
|
338
|
-
maxRows: max_rows,
|
|
339
|
-
maxJoinRows: max_join_rows,
|
|
340
|
-
maxBytes: max_bytes
|
|
341
336
|
},
|
|
342
337
|
output: {
|
|
343
338
|
profile: "internal.v1",
|
|
@@ -679,11 +674,6 @@ instrument portfolio(on: ref<financing.installments>[], default_days: integer(1,
|
|
|
679
674
|
{ left: "scheduleCount", right: "capturedScheduleCount" }
|
|
680
675
|
],
|
|
681
676
|
unavailableFacts: [ ],
|
|
682
|
-
lockTimeoutMs: lock_wait_ms,
|
|
683
|
-
captureTimeoutMs: capture_ms,
|
|
684
|
-
maxRows: max_rows,
|
|
685
|
-
maxJoinRows: max_join_rows,
|
|
686
|
-
maxBytes: max_bytes
|
|
687
677
|
},
|
|
688
678
|
output: {
|
|
689
679
|
profile: "internal.v1",
|