@hyperscale0/hsx 1.0.128 → 1.0.130
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/dist/src/cli.js +1 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +96 -10
- 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/parse.js +4 -1
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.js +5 -5
- package/dist/src/std-bundle.js.map +1 -1
- package/docs/README.md +45 -20
- package/examples/cards.hsx +4 -0
- package/examples/collections.hsx +4 -0
- package/examples/cost-table.json +80 -56
- package/examples/insurance.hsx +2 -1
- package/package.json +4 -4
- package/src/cli.ts +1 -1
- package/src/compile.ts +146 -10
- package/src/examples-bundle.ts +3 -3
- package/src/parse.ts +4 -1
- package/src/std-bundle.ts +5 -5
- package/std/booking.hsx +1 -1
- package/std/cards.hsx +5 -3
- package/std/escrow.hsx +18 -9
- package/std/financing.hsx +81 -16
- package/std/wallet.hsx +3 -3
package/std/booking.hsx
CHANGED
|
@@ -21,7 +21,7 @@ header booking
|
|
|
21
21
|
// cancellation and completion move the held balance. Run hsx cost for the
|
|
22
22
|
// composed counts.
|
|
23
23
|
|
|
24
|
-
instrument reservation(customer: party = party(person), operator: party = programOperator, early_until: duration = 30d, late_from: duration = 15d, balance_due: duration =
|
|
24
|
+
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%) {
|
|
25
25
|
revisioned: true
|
|
26
26
|
summary: "Take a deposit and a balance for a dated booking, with cancellation windows before departure."
|
|
27
27
|
fields {
|
package/std/cards.hsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
header cards
|
|
2
2
|
|
|
3
3
|
// Activate holder and card before creating and approving an authorization.
|
|
4
|
+
// Only the Product operator activates, suspends or resumes a cardholder; the
|
|
5
|
+
// holder freezes, unfreezes and cancels their own card.
|
|
4
6
|
// Expose authorization.create, approve and capture; expose decline and reverse
|
|
5
7
|
// separately if callers need those paths. Issuer declines only from pending;
|
|
6
8
|
// merchant captures or reverses an approved reservation. Expiry voids approved
|
|
@@ -21,9 +23,9 @@ instrument cardholder(holder: party) {
|
|
|
21
23
|
fields { holder: account of holder }
|
|
22
24
|
lifecycle { states: [pending, active, suspended, closed], initial: pending }
|
|
23
25
|
action create {}
|
|
24
|
-
action activate { from: pending, to: active }
|
|
25
|
-
action suspend { from: active, to: suspended }
|
|
26
|
-
action resume { from: suspended, to: active }
|
|
26
|
+
action activate { from: pending, to: active, actor: { party: programOperator } }
|
|
27
|
+
action suspend { from: active, to: suspended, actor: { party: programOperator } }
|
|
28
|
+
action resume { from: suspended, to: active, actor: { party: programOperator } }
|
|
27
29
|
action close { from: [active, suspended], to: closed }
|
|
28
30
|
}
|
|
29
31
|
|
package/std/escrow.hsx
CHANGED
|
@@ -9,10 +9,17 @@ header escrow
|
|
|
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
11
|
// Payee refunds the whole held balance before delivery, or from return_verified
|
|
12
|
-
// by default after a dispute. While a
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
12
|
+
// by default after a dispute. While a financing plan funds the hold in any state
|
|
13
|
+
// but cancelled, both refunds refuse, so a written-off plan never hands lender
|
|
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
|
+
// After a paid plan's refund window, the plan's return_hold runs it from funded
|
|
19
|
+
// or return_verified to return the whole balance to the payer.
|
|
20
|
+
// Only an installments plan can run confirm and unwind_financed; without
|
|
21
|
+
// financing neither action exists, so no hold reaches funded without fundedAt. The plan's confirm leaves fundedAt unset, so
|
|
22
|
+
// refund_undelivered never comes due on a financed hold and no schedule offers it.
|
|
16
23
|
// Expose refund_undelivered to let the payer take the whole held price back once
|
|
17
24
|
// deliver_within has passed since funding without delivery. It is a payer action
|
|
18
25
|
// that becomes available at that time; nothing runs it automatically.
|
|
@@ -41,14 +48,12 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
|
|
|
41
48
|
}
|
|
42
49
|
action fund {
|
|
43
50
|
from: pending, to: funded, actor: { party: payer }
|
|
44
|
-
subject { price: money }
|
|
45
51
|
set: { fundedAt: { field: self.now } }
|
|
46
52
|
moves self.price from payer to self.held economics { purpose: principal, sourceParty: payer }
|
|
47
53
|
}
|
|
48
54
|
action confirm {
|
|
49
|
-
from: pending, to: funded
|
|
55
|
+
from: pending, to: funded, actor: { parent: all(financing.installments) }
|
|
50
56
|
requires self.held.balance == self.price
|
|
51
|
-
set: { fundedAt: { field: self.now } }
|
|
52
57
|
}
|
|
53
58
|
action deliver {
|
|
54
59
|
from: funded, to: delivered, actor: { party: payee }
|
|
@@ -74,15 +79,19 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
|
|
|
74
79
|
}
|
|
75
80
|
action refund {
|
|
76
81
|
from: [funded, dispute.refund_after], to: refunded, actor: { party: payee }
|
|
77
|
-
requires count of { instrument: all(financing.installments), reference: "funds", anchor: self.id, states: [signed, active, paid], limit: 1 } == 0
|
|
82
|
+
requires count of { instrument: all(financing.installments), reference: "funds", anchor: self.id, states: [quoted, signed, active, paid, written_off], limit: 1 } == 0
|
|
78
83
|
moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
|
79
84
|
}
|
|
80
85
|
action refund_undelivered {
|
|
81
86
|
from: funded, to: refunded, actor: { party: payer }
|
|
82
87
|
due: { at: self.fundedAt, offset: deliver_within }
|
|
83
|
-
requires count of { instrument: all(financing.installments), reference: "funds", anchor: self.id, states: [signed, active, paid], limit: 1 } == 0
|
|
88
|
+
requires count of { instrument: all(financing.installments), reference: "funds", anchor: self.id, states: [quoted, signed, active, paid, written_off], limit: 1 } == 0
|
|
84
89
|
moves self.price from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
|
85
90
|
}
|
|
91
|
+
action unwind_financed { allowZero: true,
|
|
92
|
+
from: [funded, return_verified], to: refunded, actor: { parent: all(financing.installments) }
|
|
93
|
+
moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
|
94
|
+
}
|
|
86
95
|
action cancel { allowZero: true,
|
|
87
96
|
from: pending, to: cancelled, actor: { party: payer }
|
|
88
97
|
moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
package/std/financing.hsx
CHANGED
|
@@ -19,12 +19,34 @@ header financing
|
|
|
19
19
|
// so list the plan in its converters; the deposit then counts as escrow credit.
|
|
20
20
|
// disburse_to: seller pays principal to the seller party at activation (pay later
|
|
21
21
|
// with the merchant paid on day one). For a return, capital refunds each paid
|
|
22
|
-
// payment to the borrower
|
|
23
|
-
//
|
|
22
|
+
// payment to the borrower. Payments stay refundable while the plan is active. The
|
|
23
|
+
// payment that clears the last slice completes the plan when no late charge or
|
|
24
|
+
// recovery cost is outstanding; otherwise expose complete to close it once they
|
|
25
|
+
// are settled or waived. When
|
|
26
|
+
// the plan leaves active (paid, written off or cancelled) it sets refundUntil to
|
|
27
|
+
// refund_within (default 30d) later, and payment, late charge and recovery cost
|
|
28
|
+
// refunds stop at that time. A refund that reopens the plan clears refundUntil, which is set
|
|
29
|
+
// from the calculated refundWindow so a restore can clear it. Once it passes,
|
|
30
|
+
// nothing can move the plan's payments or late charges, so they stop counting as
|
|
31
|
+
// open records. After the refunds, capital runs unwind: it takes principal back to
|
|
32
|
+
// capital and cancels open slices with their claims. Seller mode debits the seller; funds mode
|
|
24
33
|
// needs the hold in return_verified and debits the hold, after which the payee's
|
|
25
|
-
// escrow refund returns only the down payment.
|
|
26
|
-
//
|
|
27
|
-
//
|
|
34
|
+
// escrow refund returns only the down payment. Before delivery, the Product
|
|
35
|
+
// operator runs unwind_undelivered on a funds-mode plan whose hold is still
|
|
36
|
+
// funded: principal goes back to capital and the hold's unwind_financed returns
|
|
37
|
+
// the down payment to the payer and closes the hold as refunded, all in one
|
|
38
|
+
// operation, so deliver is refused afterwards. It also cancels proposed and
|
|
39
|
+
// assessed late charges and recovery costs: goods that never arrived earn no
|
|
40
|
+
// fines. Escrow refund refuses while any plan that is
|
|
41
|
+
// not cancelled funds the hold, written off included, so the borrower never
|
|
42
|
+
// receives lender principal. A funds-mode plan writes off only once its hold
|
|
43
|
+
// has released principal to the seller: principal still held is not a loss, and
|
|
44
|
+
// unwind or unwind_undelivered returns it. Both unwinds need no paid payments and no
|
|
45
|
+
// collected late charges; refund them first. unwind also needs no assessed
|
|
46
|
+
// late charges; waive them first. Once a paid plan's refund window has passed,
|
|
47
|
+
// its payments can no longer be refunded, so the Product operator runs
|
|
48
|
+
// return_hold to return an undelivered or returned hold's whole balance to the
|
|
49
|
+
// borrower, who has repaid the principal.
|
|
28
50
|
// Borrower- and seller-directed disbursement exclude collect_down_payment. A down_payment
|
|
29
51
|
// there reduces principal but does not collect cash. Price is subject metadata,
|
|
30
52
|
// not the borrower limit. The escrow payer and price must match the plan.
|
|
@@ -40,14 +62,15 @@ header financing
|
|
|
40
62
|
// payment does not settle the remaining fine. Recovery cost needs its own record.
|
|
41
63
|
// Profit share credits capital's allocatedProfitPayable, not an outside agency.
|
|
42
64
|
// Credit lines need adapter binding at activation; draws are ledger advances,
|
|
43
|
-
// not evidence of outside funding.
|
|
44
|
-
// need the Product's operation
|
|
65
|
+
// not evidence of outside funding. Only the Product operator approves borrower
|
|
66
|
+
// and portfolio limits; caller-authorized advances need the Product's operation
|
|
67
|
+
// authority. No clock declaration proves retries.
|
|
45
68
|
|
|
46
69
|
// Cost: installments include cash and claim moves, child-account creation and
|
|
47
70
|
// bounded invocations across slices and assessments. Month and charge limits affect
|
|
48
71
|
// the bound. Run hsx cost on the selected policies; profit is not a platform tariff.
|
|
49
72
|
|
|
50
|
-
instrument installments(months: integer(1, 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, allow_overdue: enum(allowed, blocked) = allowed, assessed_fines: enum(carry, require_waiver) = carry, down_payment: percent = 0%, funds: ref<escrow.hold>?, 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)) {
|
|
73
|
+
instrument installments(months: integer(1, 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, allow_overdue: enum(allowed, blocked) = allowed, assessed_fines: enum(carry, require_waiver) = carry, down_payment: percent = 0%, funds: ref<escrow.hold>?, 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)) {
|
|
51
74
|
dependencies {
|
|
52
75
|
funds {
|
|
53
76
|
selector: disburse_to, is: funds
|
|
@@ -67,7 +90,8 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
67
90
|
funds: ref<funds>?, borrower: account of borrower, settlementIdentity: text, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>
|
|
68
91
|
months: integer = months, dates: list(date, months)
|
|
69
92
|
allocatedPayable: account(capital, cash, "allocatedProfitPayable"), capital: account(capital, cash, "capital"), profitIncome: account(capital, cash, "profitIncome"), loss: account(capital, claim, "loss")
|
|
70
|
-
downPaymentRemaining: money?, downPaymentCredit: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money?
|
|
93
|
+
downPaymentRemaining: money?, downPaymentCredit: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money?, refundWindow: date?, refundUntil: date?
|
|
94
|
+
openSlices: integer?, openCharges: money?, openCosts: money?
|
|
71
95
|
price: money = subject.price
|
|
72
96
|
downPayment: money = rate(self.price, down_payment)
|
|
73
97
|
principal: money = subtract(self.price, self.downPayment)
|
|
@@ -121,11 +145,13 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
121
145
|
}
|
|
122
146
|
action complete {
|
|
123
147
|
from: active, to: paid
|
|
148
|
+
calculate: [{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }]
|
|
149
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
124
150
|
requires count of { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 } == 0
|
|
125
151
|
requires sum "receivable.balance" of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == "0"
|
|
126
152
|
requires sum "receivable.balance" of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == "0"
|
|
127
153
|
}
|
|
128
|
-
action restore { from: [active, paid], to: active, actor: { parent: payment } }
|
|
154
|
+
action restore { from: [active, paid], to: active, actor: { parent: payment }, input { refundUntil: date? }, set: { refundUntil: { field: input.refundUntil } } }
|
|
129
155
|
action payoff {
|
|
130
156
|
from: active, to: paid, actor: { party: borrower }
|
|
131
157
|
input { quote: ref<payoff_quote> }
|
|
@@ -133,7 +159,9 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
133
159
|
deadline: { at: input.quote.expiresAt }
|
|
134
160
|
calculate: [
|
|
135
161
|
{ target: "principalReturned", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }
|
|
162
|
+
{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }
|
|
136
163
|
]
|
|
164
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
137
165
|
invoke: [{ selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: payoff, input: {} }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: payoff, input: {} }]
|
|
138
166
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: payoff, input: { identity: { field: self.settlementIdentity } } }, { reference: self.id, action: record_payoff, input: {} }]
|
|
139
167
|
}
|
|
@@ -145,20 +173,30 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
145
173
|
action write_off {
|
|
146
174
|
from: active, to: written_off, actor: { parent: write_off_request }
|
|
147
175
|
input { overdueSlice: ref<slice> }
|
|
148
|
-
calculate: [{ target: "lossRecorded", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }]
|
|
176
|
+
calculate: [{ target: "lossRecorded", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }, { target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }]
|
|
177
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
149
178
|
due: { at: input.overdueSlice.lossEligibleAt }
|
|
150
179
|
requires input.overdueSlice.plan == self.id; requires input.overdueSlice.principalReceivable.balance > "0"
|
|
180
|
+
when disburse_to is funds { requires self.funds in [released] }
|
|
151
181
|
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" } } }]
|
|
152
182
|
}
|
|
153
183
|
action record_payment {
|
|
154
184
|
from: active, to: active, actor: { parent: payment }
|
|
155
185
|
input { identity: text, principal: money, profit: money }
|
|
186
|
+
calculate: [
|
|
187
|
+
{ target: openSlices, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: count }
|
|
188
|
+
{ target: openCharges, op: aggregate, selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
|
|
189
|
+
{ target: openCosts, op: aggregate, selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
|
|
190
|
+
]
|
|
156
191
|
invoke: [{ instrument: settlement, action: create, input: { plan: { field: self.id }, identity: { field: input.identity }, principal: { field: input.principal }, profit: { field: input.profit }, loss: { literal: "0" }, kind: { literal: "cash" } } }]
|
|
192
|
+
invoke: [{ selection: { instrument: current(), reference: "borrowerLimit", anchor: self.borrowerLimit, states: [active], limit: 1, where: { id: { field: self.id }, openSlices: { literal: 0 }, openCharges: { literal: "0" }, openCosts: { literal: "0" } } }, action: complete, input: {} }]
|
|
157
193
|
}
|
|
158
194
|
action cancel { from: quoted, to: cancelled }
|
|
159
195
|
action void { from: signed, to: cancelled, actor: { party: borrower } }
|
|
160
196
|
action unwind { allowZero: true,
|
|
161
197
|
from: active, to: cancelled, actor: { party: capital }
|
|
198
|
+
calculate: [{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }]
|
|
199
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
162
200
|
when disburse_to is borrower { requires true == false }
|
|
163
201
|
when disburse_to is funds { requires self.funds in [return_verified] }
|
|
164
202
|
requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
|
|
@@ -168,6 +206,31 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
168
206
|
when disburse_to is seller { moves self.principal from seller to self.capital economics { purpose: principal, sourceParty: seller } }
|
|
169
207
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: cancel, input: {} }]
|
|
170
208
|
}
|
|
209
|
+
action unwind_undelivered { allowZero: true,
|
|
210
|
+
from: active, to: cancelled, actor: { party: programOperator }
|
|
211
|
+
calculate: [{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }]
|
|
212
|
+
set: { refundUntil: { field: self.refundWindow } }
|
|
213
|
+
when disburse_to is borrower { requires true == false }
|
|
214
|
+
when disburse_to is seller { requires true == false }
|
|
215
|
+
when disburse_to is funds { requires self.funds in [funded] }
|
|
216
|
+
requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
|
|
217
|
+
requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
|
|
218
|
+
requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
|
|
219
|
+
when disburse_to is funds { moves self.principal from self.funds.held to self.capital economics { purpose: principal, sourceParty: capital } }
|
|
220
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: cancel, input: {} }]
|
|
221
|
+
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: {} }]
|
|
222
|
+
when disburse_to is funds { invoke: [{ reference: self.funds, action: unwind_financed, input: {} }] }
|
|
223
|
+
}
|
|
224
|
+
action return_hold {
|
|
225
|
+
from: paid, to: paid, actor: { party: programOperator }
|
|
226
|
+
requires self.refundUntil < self.now
|
|
227
|
+
when disburse_to is borrower { requires true == false }
|
|
228
|
+
when disburse_to is seller { requires true == false }
|
|
229
|
+
when disburse_to is funds {
|
|
230
|
+
requires self.funds in [funded, return_verified]
|
|
231
|
+
invoke: [{ reference: self.funds, action: unwind_financed, input: {} }]
|
|
232
|
+
}
|
|
233
|
+
}
|
|
171
234
|
records {
|
|
172
235
|
write_off_request: {
|
|
173
236
|
fields { plan: ref<parent>, overdueSlice: ref<slice> }
|
|
@@ -491,7 +554,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
|
|
|
491
554
|
invoke: [{ reference: self.plan, action: record_payment, input: { identity: { field: self.paymentIdentity }, principal: { field: self.principalReturned }, profit: { field: self.profitReturned } } }]
|
|
492
555
|
}
|
|
493
556
|
action refund {
|
|
494
|
-
from: paid, to: refunded, actor: { party: capital }
|
|
557
|
+
from: paid, to: refunded, actor: { party: capital }, deadline: { at: self.plan.refundUntil }
|
|
495
558
|
requires unique "settlement_consumption" on [self.plan, self.paymentIdentity]; requires self.plan in [active, paid]
|
|
496
559
|
requires count of { instrument: settlement, reference: "plan", anchor: self.plan, states: [recorded], limit: 366, where: { identity: { field: self.paymentIdentity } } } == 1
|
|
497
560
|
invoke: [{ selection: { instrument: settlement, reference: "plan", anchor: self.plan, states: [recorded], limit: 366, where: { identity: { field: self.paymentIdentity } } }, action: refund, input: {} }]
|
|
@@ -576,6 +639,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
576
639
|
}
|
|
577
640
|
action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == "0" }
|
|
578
641
|
action cancel { from: proposed, to: waived }
|
|
642
|
+
action cancel_undelivered { allowZero: true, from: [proposed, assessed], to: waived, actor: { parent: on }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
579
643
|
action waive_for_amendment {
|
|
580
644
|
from: [assessed, collected], to: assessed, actor: { parent: child(on, amendment) }, input { amendment: ref<on.amendment> }
|
|
581
645
|
requires input.amendment.plan == self.plan; requires input.amendment in [applied]
|
|
@@ -583,7 +647,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
583
647
|
}
|
|
584
648
|
action waive { allowZero: true, from: [assessed, collected], to: waived, actor: { parent: waiver_request }, input { reason: text, evidence: text }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
585
649
|
action refund {
|
|
586
|
-
from: [assessed, collected], to: assessed, actor: { party: fines_to }, input { payment: ref<on.payment> }
|
|
650
|
+
from: [assessed, collected], to: assessed, actor: { party: fines_to }, input { payment: ref<on.payment> }, deadline: { at: self.plan.refundUntil }
|
|
587
651
|
requires input.payment in [paid, refunded]
|
|
588
652
|
requires input.payment.plan == self.plan
|
|
589
653
|
invoke: [{ selection: { instrument: receipt, reference: "payment", anchor: input.payment, states: [paid], limit: 1, where: { assessment: { field: self.id } } }, action: refund, input: {} }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
|
|
@@ -643,6 +707,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
643
707
|
}
|
|
644
708
|
action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == "0" }
|
|
645
709
|
action cancel { from: proposed, to: waived }
|
|
710
|
+
action cancel_undelivered { allowZero: true, from: [proposed, assessed], to: waived, actor: { parent: on }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
646
711
|
action waive_for_amendment {
|
|
647
712
|
from: [assessed, collected], to: assessed, actor: { parent: child(on, amendment) }, input { amendment: ref<on.amendment> }
|
|
648
713
|
requires input.amendment.plan == self.plan; requires input.amendment in [applied]
|
|
@@ -650,7 +715,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
650
715
|
}
|
|
651
716
|
action waive { allowZero: true, from: [assessed, collected], to: waived, actor: { parent: waiver_request }, input { reason: text, evidence: text }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
652
717
|
action refund {
|
|
653
|
-
from: [assessed, collected], to: assessed, actor: { party: costs_to }, input { payment: ref<on.payment> }
|
|
718
|
+
from: [assessed, collected], to: assessed, actor: { party: costs_to }, input { payment: ref<on.payment> }, deadline: { at: self.plan.refundUntil }
|
|
654
719
|
requires input.payment in [paid, refunded]
|
|
655
720
|
requires input.payment.plan == self.plan
|
|
656
721
|
invoke: [{ selection: { instrument: receipt, reference: "payment", anchor: input.payment, states: [paid], limit: 1, where: { assessment: { field: self.id } } }, action: refund, input: {} }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
|
|
@@ -694,7 +759,7 @@ instrument limits(per_borrower: money, borrower: party = party(person), active_p
|
|
|
694
759
|
fields { borrower: account of borrower, limit: money = per_borrower, activePlans: integer = active_plans }
|
|
695
760
|
lifecycle { states: [pending, approved], initial: pending }
|
|
696
761
|
action create { requires unique "borrower_limit" on [self.borrower] }
|
|
697
|
-
action approve { from: pending, to: approved }
|
|
762
|
+
action approve { from: pending, to: approved, actor: { party: programOperator } }
|
|
698
763
|
}
|
|
699
764
|
|
|
700
765
|
instrument portfolio_limit(limit: money) {
|
|
@@ -704,7 +769,7 @@ instrument portfolio_limit(limit: money) {
|
|
|
704
769
|
fields { key: text = "portfolio", limit: money = limit }
|
|
705
770
|
lifecycle { states: [pending, approved], initial: pending }
|
|
706
771
|
action create { requires unique "portfolio_limit" on [self.key] }
|
|
707
|
-
action approve { from: pending, to: approved }
|
|
772
|
+
action approve { from: pending, to: approved, actor: { party: programOperator } }
|
|
708
773
|
}
|
|
709
774
|
|
|
710
775
|
instrument credit_line(borrower: party = programOperator, adapter: text, limit: money, expires: date) {
|
package/std/wallet.hsx
CHANGED
|
@@ -6,7 +6,7 @@ header wallet
|
|
|
6
6
|
|
|
7
7
|
// Expose balance.create, activate, topup and withdraw as needed. Holder moves
|
|
8
8
|
// cash only while active. Freeze blocks new topups, withdrawals and spend reserves;
|
|
9
|
-
// close requires no held cash.
|
|
9
|
+
// close requires no held cash. Only the Product operator freezes or unfreezes.
|
|
10
10
|
// A separate spend record checks its amount against spendLimit at creation.
|
|
11
11
|
// Expose reserve, pay and cancel for the bound payee. Reserve requires an active
|
|
12
12
|
// wallet before expiresAt; pay posts the whole reservation, cancel/expiry void it.
|
|
@@ -43,8 +43,8 @@ instrument balance(holder: party, spend_limit: money = 100000 SAR, topup_fee: pe
|
|
|
43
43
|
input { amount: money }
|
|
44
44
|
moves input.amount from self.held to holder economics { purpose: prepaid_credit, sourceParty: programOperator }
|
|
45
45
|
}
|
|
46
|
-
action freeze { from: active, to: frozen, }
|
|
47
|
-
action unfreeze { from: frozen, to: active, }
|
|
46
|
+
action freeze { from: active, to: frozen, actor: { party: programOperator } }
|
|
47
|
+
action unfreeze { from: frozen, to: active, actor: { party: programOperator } }
|
|
48
48
|
action close { from: [pending, active], to: closed, requires self.held.balance == "0" }
|
|
49
49
|
}
|
|
50
50
|
|