@hyperscale0/hsx 1.0.125 → 1.0.128
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/README.md +2 -2
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +62 -14
- package/dist/src/compile.js.map +1 -1
- package/dist/src/examples-bundle.js +8 -8
- package/dist/src/examples-bundle.js.map +1 -1
- package/dist/src/headers.d.ts +1 -1
- package/dist/src/headers.d.ts.map +1 -1
- package/dist/src/headers.js +1 -0
- package/dist/src/headers.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 +74 -47
- package/examples/collections.hsx +1 -1
- package/examples/financing.hsx +1 -1
- package/examples/insurance.hsx +4 -4
- package/examples/lending.hsx +1 -1
- package/examples/library.hsx +1 -1
- package/examples/rental-deposit.hsx +1 -1
- package/examples/reporting.hsx +1 -1
- package/examples/serviced.hsx +1 -1
- package/package.json +4 -4
- package/src/compile.ts +80 -20
- package/src/examples-bundle.ts +8 -8
- package/src/headers.ts +1 -0
- package/src/std-bundle.ts +7 -6
- package/std/booking.hsx +75 -0
- package/std/escrow.hsx +24 -7
- package/std/financing.hsx +83 -26
- package/std/insurance.hsx +9 -5
- package/std/marketplace.hsx +3 -3
- package/std/money.hsx +3 -3
- package/std/wallet.hsx +17 -6
package/std/booking.hsx
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
header booking
|
|
2
|
+
|
|
3
|
+
// Economics: Deposit and balance payments and every refund are principal. Completion
|
|
4
|
+
// and any forfeit are earnings for the company, or pass the customer's cash through
|
|
5
|
+
// to a bound outside operator.
|
|
6
|
+
|
|
7
|
+
// Expose create, pay_deposit and pay_balance for the booking path, and
|
|
8
|
+
// cancel_early, cancel_middle and cancel_late for the customer. Create captures
|
|
9
|
+
// subject departure, price, deposit and balance, and refuses a deposit and balance
|
|
10
|
+
// that do not add up to the price. The balance is payable only after the deposit
|
|
11
|
+
// and before balance_due ahead of departure, so a cancelled booking never offers
|
|
12
|
+
// it. cancel_early runs until early_until before departure and returns everything
|
|
13
|
+
// held. cancel_middle runs from then until late_from; cancel_late runs from then
|
|
14
|
+
// until departure, even when the clock's complete runs late. They keep
|
|
15
|
+
// middle_forfeit or late_forfeit of the held money and return the rest. Both
|
|
16
|
+
// forfeits default to 0%, so nothing is kept unless the author sets them. At
|
|
17
|
+
// departure the clock completes a paid booking and pays operator, and lapses an
|
|
18
|
+
// unpaid one on cancel_late's terms, so nothing stays held after departure.
|
|
19
|
+
|
|
20
|
+
// Cost: create declares one owned account. Each payment declares one transfer;
|
|
21
|
+
// cancellation and completion move the held balance. Run hsx cost for the
|
|
22
|
+
// composed counts.
|
|
23
|
+
|
|
24
|
+
instrument reservation(customer: party = party(person), operator: party = programOperator, early_until: duration = 30d, late_from: duration = 15d, balance_due: duration = 14d, middle_forfeit: percent = 0%, late_forfeit: percent = 0%) {
|
|
25
|
+
revisioned: true
|
|
26
|
+
summary: "Take a deposit and a balance for a dated booking, with cancellation windows before departure."
|
|
27
|
+
fields {
|
|
28
|
+
customer: account of customer, operator: account of operator, held: account of self
|
|
29
|
+
departure: date, price: money = subject.price
|
|
30
|
+
deposit: money = subject.deposit, balance: money = subject.balance
|
|
31
|
+
split: money = sum(self.deposit, self.balance)
|
|
32
|
+
balanceAt: date = before(self.departure, balance_due)
|
|
33
|
+
earlyAt: date = before(self.departure, early_until)
|
|
34
|
+
lateAt: date = before(self.departure, late_from)
|
|
35
|
+
refund: money?, forfeit: money?
|
|
36
|
+
}
|
|
37
|
+
lifecycle { states: [reserved, deposit_paid, paid, completed, cancelled], initial: reserved }
|
|
38
|
+
action create {
|
|
39
|
+
subject { departure: date, price: money, deposit: money, balance: money }
|
|
40
|
+
actor: { party: customer }
|
|
41
|
+
requires self.departure > self.now
|
|
42
|
+
requires self.split == self.price
|
|
43
|
+
}
|
|
44
|
+
action pay_deposit {
|
|
45
|
+
from: reserved, to: deposit_paid, actor: { party: customer }
|
|
46
|
+
moves self.deposit from customer to self.held economics { purpose: principal, sourceParty: customer }
|
|
47
|
+
}
|
|
48
|
+
action pay_balance {
|
|
49
|
+
from: deposit_paid, to: paid, actor: { party: customer }, deadline: { at: self.balanceAt }
|
|
50
|
+
moves self.balance from customer to self.held economics { purpose: principal, sourceParty: customer }
|
|
51
|
+
}
|
|
52
|
+
action complete {
|
|
53
|
+
from: paid, to: completed, actor: clock, due: { at: self.departure }
|
|
54
|
+
moves self.held.balance from self.held to operator economics { recipient: operator, company: { purpose: earning, sourceParty: customer }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
55
|
+
}
|
|
56
|
+
action cancel_early { allowZero: true,
|
|
57
|
+
from: [reserved, deposit_paid, paid], to: cancelled, actor: { party: customer }, deadline: { at: self.earlyAt }
|
|
58
|
+
moves self.held.balance from self.held to customer economics { purpose: principal, sourceParty: programOperator }
|
|
59
|
+
}
|
|
60
|
+
action cancel_middle { allowZero: true,
|
|
61
|
+
from: [reserved, deposit_paid, paid], to: cancelled, actor: { party: customer }, due: { at: self.earlyAt }, deadline: { at: self.lateAt }
|
|
62
|
+
calculate: [{ target: forfeit, op: rate, base: { field: self.held.balance }, bps: { literal: middle_forfeit }, rounding: floor }, { target: refund, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.forfeit }] }]
|
|
63
|
+
moves self.refund from self.held to customer economics { purpose: principal, sourceParty: programOperator }; moves self.forfeit from self.held to operator economics { recipient: operator, company: { purpose: earning, sourceParty: customer }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
64
|
+
}
|
|
65
|
+
action cancel_late { allowZero: true,
|
|
66
|
+
from: [reserved, deposit_paid, paid], to: cancelled, actor: { party: customer }, due: { at: self.lateAt }, deadline: { at: self.departure }
|
|
67
|
+
calculate: [{ target: forfeit, op: rate, base: { field: self.held.balance }, bps: { literal: late_forfeit }, rounding: floor }, { target: refund, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.forfeit }] }]
|
|
68
|
+
moves self.refund from self.held to customer economics { purpose: principal, sourceParty: programOperator }; moves self.forfeit from self.held to operator economics { recipient: operator, company: { purpose: earning, sourceParty: customer }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
69
|
+
}
|
|
70
|
+
action lapse { allowZero: true,
|
|
71
|
+
from: [reserved, deposit_paid], to: cancelled, actor: clock, due: { at: self.departure }
|
|
72
|
+
calculate: [{ target: forfeit, op: rate, base: { field: self.held.balance }, bps: { literal: late_forfeit }, rounding: floor }, { target: refund, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.forfeit }] }]
|
|
73
|
+
moves self.refund from self.held to customer economics { purpose: principal, sourceParty: programOperator }; moves self.forfeit from self.held to operator economics { recipient: operator, company: { purpose: earning, sourceParty: customer }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
74
|
+
}
|
|
75
|
+
}
|
package/std/escrow.hsx
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
header escrow
|
|
2
2
|
|
|
3
3
|
// Economics: Funding and unspent returns are principal. Acceptance realizes earnings for
|
|
4
|
-
// the company
|
|
4
|
+
// the company; the payer's cash passes through to a bound outside seller, less the
|
|
5
|
+
// fee the company earns. Tax is pass-through.
|
|
5
6
|
|
|
6
7
|
// Expose create, fund, deliver and accept for the purchase path. Create captures
|
|
7
8
|
// subject.price; acceptance pays payee less the configured fee, not payer.
|
|
8
9
|
// Expose dispute, verify_return and refund separately for the return path.
|
|
9
|
-
// Payer disputes delivered goods; payee verifies return evidence from disputed
|
|
10
|
-
//
|
|
10
|
+
// Payer disputes delivered goods; payee verifies return evidence from disputed.
|
|
11
|
+
// Payee refunds the whole held balance before delivery, or from return_verified
|
|
12
|
+
// by default after a dispute. While a signed, active or paid financing plan funds
|
|
13
|
+
// the hold, both refunds refuse: the plan's unwind returns the lender's principal
|
|
14
|
+
// first, then refund returns the rest. Unwind needs return_verified, so a
|
|
15
|
+
// financed hold cannot be refunded before delivery.
|
|
16
|
+
// Expose refund_undelivered to let the payer take the whole held price back once
|
|
17
|
+
// deliver_within has passed since funding without delivery. It is a payer action
|
|
18
|
+
// that becomes available at that time; nothing runs it automatically.
|
|
11
19
|
// Acceptance expiry enters disputed, never released. Neither verify_return nor
|
|
12
20
|
// refund becomes public merely because dispute is exposed. Clock actions remain
|
|
13
21
|
// internal. Expose cancel for a pending hold, including a checkout-funded hold;
|
|
@@ -18,12 +26,12 @@ header escrow
|
|
|
18
26
|
// transfer; accept expands the configured fee and tax into moves. Run hsx cost
|
|
19
27
|
// on the composed program for instruction counts. The executor supplies prices.
|
|
20
28
|
|
|
21
|
-
instrument hold(payer: party = party(person), payee: party = programOperator, accept_within: duration = 48h, fee: fee = { seller: 0%, tax: 0% }, dispute: policy = { refund_after: return_verified }) {
|
|
29
|
+
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 }) {
|
|
22
30
|
revisioned: true
|
|
23
31
|
summary: "Hold the subject price through delivery, acceptance and verified return."
|
|
24
32
|
fields {
|
|
25
33
|
payer: account of payer, payee: account of payee
|
|
26
|
-
held: account of self, deliveredAt: date?
|
|
34
|
+
held: account of self, fundedAt: date?, deliveredAt: date?
|
|
27
35
|
price: money = subject.price
|
|
28
36
|
}
|
|
29
37
|
lifecycle { states: [pending, funded, delivered, disputed, return_verified, released, refunded, cancelled], initial: pending }
|
|
@@ -34,11 +42,13 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
|
|
|
34
42
|
action fund {
|
|
35
43
|
from: pending, to: funded, actor: { party: payer }
|
|
36
44
|
subject { price: money }
|
|
45
|
+
set: { fundedAt: { field: self.now } }
|
|
37
46
|
moves self.price from payer to self.held economics { purpose: principal, sourceParty: payer }
|
|
38
47
|
}
|
|
39
48
|
action confirm {
|
|
40
49
|
from: pending, to: funded
|
|
41
50
|
requires self.held.balance == self.price
|
|
51
|
+
set: { fundedAt: { field: self.now } }
|
|
42
52
|
}
|
|
43
53
|
action deliver {
|
|
44
54
|
from: funded, to: delivered, actor: { party: payee }
|
|
@@ -48,7 +58,7 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
|
|
|
48
58
|
from: delivered, to: released, actor: { party: payer }
|
|
49
59
|
input { acceptanceReference: text }
|
|
50
60
|
deadline: { at: self.deliveredAt, offset: accept_within }
|
|
51
|
-
moves self.price from self.held to payee fee fee economics { payee: { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose:
|
|
61
|
+
moves self.price from self.held to payee fee fee economics { payee: { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose: pass_through, sourceParty: programOperator } }, fee: { purpose: earning, sourceParty: payer }, tax: { purpose: pass_through, sourceParty: programOperator } }
|
|
52
62
|
}
|
|
53
63
|
action expire_acceptance {
|
|
54
64
|
from: delivered, to: disputed, actor: clock
|
|
@@ -63,7 +73,14 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
|
|
|
63
73
|
input { returnEvidence: text }
|
|
64
74
|
}
|
|
65
75
|
action refund {
|
|
66
|
-
from: dispute.refund_after, to: refunded, actor: { party: payee }
|
|
76
|
+
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
|
|
78
|
+
moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
|
79
|
+
}
|
|
80
|
+
action refund_undelivered {
|
|
81
|
+
from: funded, to: refunded, actor: { party: payer }
|
|
82
|
+
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
|
|
67
84
|
moves self.price from self.held to payer economics { purpose: principal, sourceParty: programOperator }
|
|
68
85
|
}
|
|
69
86
|
action cancel { allowZero: true,
|
package/std/financing.hsx
CHANGED
|
@@ -2,9 +2,14 @@ header financing
|
|
|
2
2
|
|
|
3
3
|
// Economics: Principal advances and repayments are excluded from earnings. Claims, waivers,
|
|
4
4
|
// write-offs and allocations are internal. Paid profit and company charges are
|
|
5
|
-
// earnings; receipt refunds reverse captured cash.
|
|
6
|
-
//
|
|
5
|
+
// earnings; receipt refunds reverse captured cash. Borrower cash paid to outside
|
|
6
|
+
// capital or recovery recipients passes through, whether held first or paid direct.
|
|
7
7
|
|
|
8
|
+
// Pricing is required. amortizing reads profit_rate as an annual rate on the
|
|
9
|
+
// declining balance: equal monthly instalments, the last absorbing rounding.
|
|
10
|
+
// flat_total reads it as one charge on principal for the whole term (murabaha,
|
|
11
|
+
// fixed-fee pay-later). 12,000 at 18% over 12 months costs 13,201.95 amortizing
|
|
12
|
+
// and 14,160 flat_total.
|
|
8
13
|
// Expose create and sign on installments, then collect_down_payment as checkout
|
|
9
14
|
// for disburse_to: funds. Checkout requires a signed plan and pending escrow;
|
|
10
15
|
// it tops up only the missing down payment, without advancing lender principal.
|
|
@@ -12,13 +17,22 @@ header financing
|
|
|
12
17
|
// the fully funded hold. Existing escrow credit counts once toward the down payment.
|
|
13
18
|
// Disburse first credits an active marketplace.reservation on the same hold,
|
|
14
19
|
// so list the plan in its converters; the deposit then counts as escrow credit.
|
|
15
|
-
//
|
|
20
|
+
// disburse_to: seller pays principal to the seller party at activation (pay later
|
|
21
|
+
// with the merchant paid on day one). For a return, capital refunds each paid
|
|
22
|
+
// payment to the borrower, then runs unwind: it takes principal back to capital and
|
|
23
|
+
// cancels open slices with their claims. Seller mode debits the seller; funds mode
|
|
24
|
+
// needs the hold in return_verified and debits the hold, after which the payee's
|
|
25
|
+
// escrow refund returns only the down payment. Escrow refund refuses while a live
|
|
26
|
+
// plan funds the hold. Unwind needs no assessed or collected late charges; waive
|
|
27
|
+
// them first.
|
|
28
|
+
// Borrower- and seller-directed disbursement exclude collect_down_payment. A down_payment
|
|
16
29
|
// there reduces principal but does not collect cash. Price is subject metadata,
|
|
17
30
|
// not the borrower limit. The escrow payer and price must match the plan.
|
|
18
31
|
// Expose escrow cancel to return checkout cash if financing is abandoned; plan
|
|
19
32
|
// void alone cancels the agreement and does not refund its separate escrow.
|
|
20
33
|
// Approve borrower and portfolio limits before disbursement. Create builds the
|
|
21
|
-
// dated slice records
|
|
34
|
+
// dated slice records from future, strictly increasing dates; disbursement
|
|
35
|
+
// refuses once a slice is due. Payments need a separate payment record and pay action.
|
|
22
36
|
// Expose parent payoff separately and create a current, unexpired payoff_quote.
|
|
23
37
|
// Expose late_charge.create as propose_assessment before the clock can assess;
|
|
24
38
|
// provide matching plan, slice and borrower. Fine is fixed money, not a percentage.
|
|
@@ -33,12 +47,17 @@ header financing
|
|
|
33
47
|
// bounded invocations across slices and assessments. Month and charge limits affect
|
|
34
48
|
// the bound. Run hsx cost on the selected policies; profit is not a platform tariff.
|
|
35
49
|
|
|
36
|
-
instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3,
|
|
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)) {
|
|
37
51
|
dependencies {
|
|
38
52
|
funds {
|
|
39
53
|
selector: disburse_to, is: funds
|
|
40
54
|
message: "Attachment `{attachment}` disburses into a hold, but `funds` is not bound."
|
|
41
|
-
fix: "Bind `funds: sale` to an escrow attachment. Choose `disburse_to: borrower` only if the borrower should receive the money directly."
|
|
55
|
+
fix: "Bind `funds: sale` to an escrow attachment. Choose `disburse_to: seller` to pay a seller on activation, or `disburse_to: borrower` only if the borrower should receive the money directly."
|
|
56
|
+
}
|
|
57
|
+
seller {
|
|
58
|
+
selector: disburse_to, is: seller
|
|
59
|
+
message: "Attachment `{attachment}` pays a seller, but `seller` is not bound."
|
|
60
|
+
fix: "Bind `seller: merchant` to the declared business that receives the principal."
|
|
42
61
|
}
|
|
43
62
|
}
|
|
44
63
|
revisioned: true
|
|
@@ -52,8 +71,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
52
71
|
price: money = subject.price
|
|
53
72
|
downPayment: money = rate(self.price, down_payment)
|
|
54
73
|
principal: money = subtract(self.price, self.downPayment)
|
|
55
|
-
|
|
56
|
-
total: money = sum(self.principal, self.profit)
|
|
74
|
+
flatProfit: money?
|
|
57
75
|
}
|
|
58
76
|
lifecycle { states: [quoted, signed, active, paid, written_off, cancelled], initial: quoted }
|
|
59
77
|
invariants count of { instrument: all(financing.installments), reference: "borrowerLimit", anchor: self.borrowerLimit, states: [active], limit: 366 } <= self.borrowerLimit.activePlans
|
|
@@ -62,8 +80,9 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
62
80
|
action create {
|
|
63
81
|
subject { price: money }
|
|
64
82
|
requires self.borrowerLimit.borrower == self.borrower
|
|
83
|
+
when pricing is flat_total { calculate: [{ target: flatProfit, op: rate, base: { field: self.principal }, bps: { literal: profit_rate }, rounding: floor }] }
|
|
65
84
|
when disburse_to is funds { requires self.borrower == self.funds.payer; requires self.price == self.funds.price }
|
|
66
|
-
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 } } }]
|
|
85
|
+
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: {} }]
|
|
67
86
|
}
|
|
68
87
|
action sign {
|
|
69
88
|
from: quoted, to: signed, actor: { party: borrower }, input { signedOffer: text, affordabilityReference: text }
|
|
@@ -72,6 +91,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
72
91
|
action collect_down_payment { allowZero: true,
|
|
73
92
|
from: signed, to: signed, actor: { party: borrower }
|
|
74
93
|
when disburse_to is borrower { requires true == false }
|
|
94
|
+
when disburse_to is seller { requires true == false }
|
|
75
95
|
when disburse_to is funds {
|
|
76
96
|
requires self.funds in [pending]
|
|
77
97
|
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 }] }]
|
|
@@ -87,6 +107,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
87
107
|
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: {} }]
|
|
88
108
|
}
|
|
89
109
|
when disburse_to is borrower { moves self.principal from self.capital to self.borrower economics { purpose: principal, sourceParty: capital } }
|
|
110
|
+
when disburse_to is seller { moves self.principal from self.capital to seller economics { purpose: principal, sourceParty: capital } }
|
|
90
111
|
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: 366, order: [position] }, action: activate, input: {} }]
|
|
91
112
|
when disburse_to is funds { invoke: [{ reference: self.funds, action: confirm, input: {} }] }
|
|
92
113
|
}
|
|
@@ -136,6 +157,17 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
136
157
|
}
|
|
137
158
|
action cancel { from: quoted, to: cancelled }
|
|
138
159
|
action void { from: signed, to: cancelled, actor: { party: borrower } }
|
|
160
|
+
action unwind { allowZero: true,
|
|
161
|
+
from: active, to: cancelled, actor: { party: capital }
|
|
162
|
+
when disburse_to is borrower { requires true == false }
|
|
163
|
+
when disburse_to is funds { requires self.funds in [return_verified] }
|
|
164
|
+
requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
|
|
165
|
+
requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == 0
|
|
166
|
+
requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == 0
|
|
167
|
+
when disburse_to is funds { moves self.principal from self.funds.held to self.capital economics { purpose: principal, sourceParty: capital } }
|
|
168
|
+
when disburse_to is seller { moves self.principal from seller to self.capital economics { purpose: principal, sourceParty: seller } }
|
|
169
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: cancel, input: {} }]
|
|
170
|
+
}
|
|
139
171
|
records {
|
|
140
172
|
write_off_request: {
|
|
141
173
|
fields { plan: ref<parent>, overdueSlice: ref<slice> }
|
|
@@ -251,31 +283,48 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
251
283
|
revisioned: true
|
|
252
284
|
fields {
|
|
253
285
|
plan: ref<parent>, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>, position: integer(1, months)
|
|
254
|
-
originalDueAt: date = at(self.plan.dates, self.position), dueAt: date?, nextDueAt: date?, previousDueAt: date?, previousPosition: integer
|
|
255
|
-
principalBase: money = divide(self.plan.principal, months)
|
|
256
|
-
principalAllocated: money = multiply(self.principalBase, months)
|
|
257
|
-
principalRemainder: money = subtract(self.plan.principal, self.principalAllocated)
|
|
258
|
-
profitBase: money = divide(self.plan.profit, months)
|
|
259
|
-
profitAllocated: money = multiply(self.profitBase, months)
|
|
260
|
-
profitRemainder: money = subtract(self.plan.profit, self.profitAllocated)
|
|
286
|
+
originalDueAt: date = at(self.plan.dates, self.position), dueAt: date?, nextDueAt: date?, previousDueAt: date?, prior: integer = subtract(self.position, 1), previousPosition: integer = sum(self.prior, self.first), previousDate: date = at(self.plan.dates, self.previousPosition), extensionLimit: date = after(self.originalDueAt, max_extension), lossEligibleAt: date?
|
|
261
287
|
first: integer = divide(1, self.position)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
principal: money
|
|
265
|
-
profit: money = sum(self.profitBase, self.profitAdjustment)
|
|
288
|
+
principalBase: money?, principalAllocated: money?, principalRemainder: money?, principalAdjustment: money?
|
|
289
|
+
profitBase: money?, profitAllocated: money?, profitRemainder: money?, profitAdjustment: money?
|
|
290
|
+
principal: money, profit: money, instalment: money
|
|
266
291
|
principalReceivable: account(self, claim), profitReceivable: account(self, claim), profitEarned: account(self, claim)
|
|
267
292
|
payoffIdentity: text?, payoffRebate: money?, payoffProfitTotal: money?, payoffProfit: money?
|
|
268
|
-
paymentBudget: money?, principalPayment: money?, principalBasePayment: money?, principalRatio: money?, profitPayment: money?, profitBudget: money?, collectibleProfit: money?, collectibleTotal: money?, ratioDenominator: integer = sum(10000,
|
|
293
|
+
paymentBudget: money?, principalPayment: money?, principalBasePayment: money?, principalRatio: money?, profitPayment: money?, profitBudget: money?, collectibleProfit: money?, collectibleTotal: money?, ratioDenominator: integer = sum(10000, profit_rate)
|
|
269
294
|
debt: account(borrower, claim, contra, "debt"), loss: account(capital, claim, "loss")
|
|
270
295
|
}
|
|
271
|
-
lifecycle { states: [draft, pending, due, paid, written_off], initial: draft }
|
|
296
|
+
lifecycle { states: [draft, pending, due, paid, written_off, cancelled], initial: draft }
|
|
272
297
|
action create {
|
|
273
298
|
actor: { parent: parent }
|
|
274
299
|
input { plan: ref<parent>, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>, position: integer(1, months) }
|
|
275
300
|
set: { dueAt: { field: self.originalDueAt } }
|
|
301
|
+
when pricing is flat_total {
|
|
302
|
+
calculate: [
|
|
303
|
+
{ target: principalBase, op: divide, amount: { field: self.plan.principal }, divisor: { literal: months }, rounding: floor }
|
|
304
|
+
{ target: principalAllocated, op: multiply, amount: { field: self.principalBase }, units: { literal: months } }
|
|
305
|
+
{ target: principalRemainder, op: subtract, base: { field: self.plan.principal }, subtract: [{ field: self.principalAllocated }] }
|
|
306
|
+
{ target: principalAdjustment, op: multiply, amount: { field: self.principalRemainder }, units: { field: self.first } }
|
|
307
|
+
{ target: principal, op: sum, values: [{ field: self.principalBase }, { field: self.principalAdjustment }] }
|
|
308
|
+
{ target: profitBase, op: divide, amount: { field: self.plan.flatProfit }, divisor: { literal: months }, rounding: floor }
|
|
309
|
+
{ target: profitAllocated, op: multiply, amount: { field: self.profitBase }, units: { literal: months } }
|
|
310
|
+
{ target: profitRemainder, op: subtract, base: { field: self.plan.flatProfit }, subtract: [{ field: self.profitAllocated }] }
|
|
311
|
+
{ target: profitAdjustment, op: multiply, amount: { field: self.profitRemainder }, units: { field: self.first } }
|
|
312
|
+
{ target: profit, op: sum, values: [{ field: self.profitBase }, { field: self.profitAdjustment }] }
|
|
313
|
+
{ target: instalment, op: sum, values: [{ field: self.principal }, { field: self.profit }] }
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
when pricing is amortizing {
|
|
317
|
+
calculate: [
|
|
318
|
+
{ target: principal, op: annuity, amount: { field: self.plan.principal }, rate: { literal: profit_rate }, periods: { literal: months }, position: { field: self.position }, part: principal }
|
|
319
|
+
{ target: profit, op: annuity, amount: { field: self.plan.principal }, rate: { literal: profit_rate }, periods: { literal: months }, position: { field: self.position }, part: interest }
|
|
320
|
+
{ target: instalment, op: sum, values: [{ field: self.principal }, { field: self.profit }] }
|
|
321
|
+
]
|
|
322
|
+
}
|
|
276
323
|
invoke: [{ reference: self.id, action: refresh_loss_date, input: {} }]
|
|
277
324
|
requires self.plan in [quoted]; requires unique "signed_position" on [self.plan, self.position]; requires self.borrowerLimit == self.plan.borrowerLimit; requires self.portfolioLimit == self.plan.portfolioLimit
|
|
325
|
+
requires self.originalDueAt > self.now; requires self.originalDueAt >= self.previousDate
|
|
278
326
|
}
|
|
327
|
+
action check_order { from: draft, to: draft, actor: { parent: parent }, requires self.originalDueAt > self.previousDate }
|
|
279
328
|
action quote_pending {
|
|
280
329
|
from: pending, to: pending, actor: { parent: payoff_quote }, input { quote: ref<payoff_quote> }
|
|
281
330
|
requires input.quote.plan == self.plan
|
|
@@ -310,6 +359,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
310
359
|
}
|
|
311
360
|
action activate { allowZero: true,
|
|
312
361
|
from: draft, to: pending, actor: { parent: parent }
|
|
362
|
+
requires self.dueAt > self.now
|
|
313
363
|
moves self.principal from self.debt to self.principalReceivable economics { purpose: internal, sourceParty: borrower }
|
|
314
364
|
moves self.profit from self.debt to self.profitReceivable economics { purpose: internal, sourceParty: borrower }
|
|
315
365
|
when profit_earned is at_disbursement { moves self.profit from self.profitReceivable to self.profitEarned economics { purpose: internal, sourceParty: programOperator } }
|
|
@@ -356,7 +406,10 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
356
406
|
calculate: [
|
|
357
407
|
{ target: collectibleTotal, op: sum, values: [{ field: self.principalReceivable.balance }, { field: self.collectibleProfit }] }
|
|
358
408
|
{ target: paymentBudget, op: minimum, values: [{ field: input.payment.held.balance }, { field: self.collectibleTotal }] }
|
|
359
|
-
|
|
409
|
+
]
|
|
410
|
+
when pricing is flat_total { calculate: [{ target: principalRatio, op: ratio, amount: { field: self.paymentBudget }, numerator: { literal: 10000 }, denominator: { field: self.ratioDenominator }, rounding: floor }] }
|
|
411
|
+
when pricing is amortizing { calculate: [{ target: principalRatio, op: ratio, amount: { field: self.paymentBudget }, numerator: { field: self.principal }, denominator: { field: self.instalment }, rounding: floor }] }
|
|
412
|
+
calculate: [
|
|
360
413
|
{ target: principalBasePayment, op: minimum, values: [{ field: self.principalRatio }, { field: self.principalReceivable.balance }] }
|
|
361
414
|
{ target: profitBudget, op: subtract, base: { field: self.paymentBudget }, subtract: [{ field: self.principalBasePayment }] }
|
|
362
415
|
{ target: profitPayment, op: minimum, values: [{ field: self.profitBudget }, { field: self.collectibleProfit }] }
|
|
@@ -387,6 +440,10 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
387
440
|
from: [pending, due], to: written_off, actor: { parent: parent }
|
|
388
441
|
moves self.principalReceivable.balance from self.principalReceivable to self.loss economics { purpose: internal, sourceParty: programOperator }; moves self.profitReceivable.balance from self.profitReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }; moves self.profitEarned.balance from self.profitEarned to self.loss economics { purpose: internal, sourceParty: programOperator }
|
|
389
442
|
}
|
|
443
|
+
action cancel { allowZero: true,
|
|
444
|
+
from: [pending, due], to: cancelled, actor: { parent: parent }
|
|
445
|
+
moves self.principalReceivable.balance from self.principalReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }; moves self.profitReceivable.balance from self.profitReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }; moves self.profitEarned.balance from self.profitEarned to self.debt economics { purpose: internal, sourceParty: programOperator }
|
|
446
|
+
}
|
|
390
447
|
}
|
|
391
448
|
recognition: {
|
|
392
449
|
fields { slice: ref<slice>, dueAt: date = after(self.slice.dueAt, 0) }
|
|
@@ -467,7 +524,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
467
524
|
actor: { parent: slice }
|
|
468
525
|
input { slice: ref<slice>, payment: ref<payment>, limit: money }
|
|
469
526
|
requires unique "profit_receipt" on [self.slice, self.payment]
|
|
470
|
-
moves self.amount from self.payment.held to self.slice.plan.profitIncome capture "cashReceipt" economics { recipient: capital, company: { purpose: earning, sourceParty: borrower }, participant: { purpose:
|
|
527
|
+
moves self.amount from self.payment.held to self.slice.plan.profitIncome capture "cashReceipt" economics { recipient: capital, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
471
528
|
when profit_earned is on_payment { moves self.amount from self.slice.profitReceivable to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
472
529
|
when profit_earned is by_schedule { moves self.amount from self.slice.profitEarned to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
473
530
|
when profit_earned is at_disbursement { moves self.amount from self.slice.profitEarned to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
@@ -555,7 +612,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
555
612
|
receipt: {
|
|
556
613
|
fields { cashReceipt: text?, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
|
|
557
614
|
lifecycle { states: [paid, refunded], initial: paid }
|
|
558
|
-
action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "fine_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to fines_to capture "cashReceipt" economics { recipient: fines_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose:
|
|
615
|
+
action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "fine_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to fines_to capture "cashReceipt" economics { recipient: fines_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
559
616
|
action refund { allowZero: true, from: paid, to: refunded, actor: { parent: parent }, moves self.amount from fines_to to self.payment.plan.borrower economics { recipient: fines_to, company: { purpose: earning, sourceParty: fines_to, reversalOf: self.cashReceipt }, participant: unclassified }; moves self.amount from self.assessment.debt to self.assessment.receivable economics { purpose: internal, sourceParty: borrower } }
|
|
560
617
|
}
|
|
561
618
|
|
|
@@ -622,7 +679,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
622
679
|
receipt: {
|
|
623
680
|
fields { cashReceipt: text?, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
|
|
624
681
|
lifecycle { states: [paid, refunded], initial: paid }
|
|
625
|
-
action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "cost_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to costs_to capture "cashReceipt" economics { recipient: costs_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose:
|
|
682
|
+
action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "cost_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to costs_to capture "cashReceipt" economics { recipient: costs_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
626
683
|
action refund { allowZero: true, from: paid, to: refunded, actor: { parent: parent }, moves self.amount from costs_to to self.payment.plan.borrower economics { recipient: costs_to, company: { purpose: earning, sourceParty: costs_to, reversalOf: self.cashReceipt }, participant: unclassified }; moves self.amount from self.assessment.debt to self.assessment.receivable economics { purpose: internal, sourceParty: borrower } }
|
|
627
684
|
}
|
|
628
685
|
}
|
package/std/insurance.hsx
CHANGED
|
@@ -4,7 +4,11 @@ header insurance
|
|
|
4
4
|
// Outside broker commissions are pass-through. Adapter-owned premium and claim
|
|
5
5
|
// accounts lack an authored source party, so their moves remain unclassified.
|
|
6
6
|
|
|
7
|
-
// Expose cover.create, activate and cancel separately.
|
|
7
|
+
// Expose cover.create, activate and cancel separately. The adapter names a
|
|
8
|
+
// registered boundary adapter (conformance_boundary in sandbox). covers is an
|
|
9
|
+
// optional link to a sibling attachment; the attached object is already the
|
|
10
|
+
// insured thing. claim_limit is the sum insured: approved and paid claims on one
|
|
11
|
+
// cover never total more. Activation binds adapter;
|
|
8
12
|
// each slice needs a separately created unique period and an active cover to collect.
|
|
9
13
|
// Collection debits holder for insurerPremium to the insurer and commission to
|
|
10
14
|
// broker as two moves. Broker defaults to programOperator; bind a declared business
|
|
@@ -23,9 +27,9 @@ header insurance
|
|
|
23
27
|
// reserves once; pay posts once and expiry voids once. Cover cancellation adds
|
|
24
28
|
// bounded slice invocations. Run hsx cost for composed counts, not provider prices.
|
|
25
29
|
|
|
26
|
-
instrument cover(holder: party, adapter: text, commission: percent = 0%, broker: party = programOperator, covers: ref
|
|
30
|
+
instrument cover(holder: party, adapter: text, commission: percent = 0%, broker: party = programOperator, covers: ref?, premium: money = runtime, claim_limit: money = runtime) {
|
|
27
31
|
summary: "Recurring cover bound to an outside insurer across an ADL boundary."
|
|
28
|
-
fields { covers: ref<covers
|
|
32
|
+
fields { covers: ref<covers>?, holder: account of holder, insurer: account(adapter(adapter), cash, "premium"), expiresAt: date, claimLimit: money = claim_limit }
|
|
29
33
|
lifecycle { states: [pending, active, cancelled, expired], initial: pending }
|
|
30
34
|
action create {}
|
|
31
35
|
action activate {
|
|
@@ -41,13 +45,12 @@ instrument cover(holder: party, adapter: text, commission: percent = 0%, broker:
|
|
|
41
45
|
slice: {
|
|
42
46
|
summary: "A whole premium period, collected once and refundable only before it starts."
|
|
43
47
|
fields {
|
|
44
|
-
cover: ref<parent>,
|
|
48
|
+
cover: ref<parent>, premium: money = premium, startsAt: date, endsAt: date
|
|
45
49
|
commission: money = rate(self.premium, commission)
|
|
46
50
|
insurerPremium: money = subtract(self.premium, self.commission), commissionReceipt: text?
|
|
47
51
|
}
|
|
48
52
|
lifecycle { states: [pending, paid, active, expired, refunded], initial: pending }
|
|
49
53
|
action create {
|
|
50
|
-
requires self.covers == self.cover.covers
|
|
51
54
|
requires self.startsAt < self.endsAt
|
|
52
55
|
requires unique "premium_period" on [self.cover, self.startsAt, self.endsAt]
|
|
53
56
|
}
|
|
@@ -76,6 +79,7 @@ instrument claim(cover: ref<insurance.cover>, inspector: party) {
|
|
|
76
79
|
summary: "A claim decision reserves one claim payment before settlement."
|
|
77
80
|
fields { cover: ref<cover>, amount: money, expiresAt: date, evidence: text, reservation: text? }
|
|
78
81
|
lifecycle { states: [submitted, approved, paid, denied, expired], initial: submitted }
|
|
82
|
+
invariants sum "amount" of { instrument: current(), reference: "cover", anchor: self.cover, states: [approved, paid], limit: 366 } <= self.cover.claimLimit
|
|
79
83
|
action create { requires self.cover in [active] }
|
|
80
84
|
action approve {
|
|
81
85
|
from: submitted, to: approved
|
package/std/marketplace.hsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
header marketplace
|
|
2
2
|
|
|
3
3
|
// Economics: Deposit funding, escrow credit and returns are principal. Retention is
|
|
4
|
-
// earning for the company or
|
|
4
|
+
// earning for the company or passes the buyer's cash through to a bound outside recipient.
|
|
5
5
|
|
|
6
6
|
// Listing, order and reservation are separate agreements. Expose their create
|
|
7
7
|
// and decision actions individually. Order fulfillment does not move escrow cash.
|
|
@@ -100,7 +100,7 @@ instrument reservation(listing: ref<marketplace.listing>, funds: ref<escrow.hold
|
|
|
100
100
|
from: active, to: cancelled, actor: { party: buyer }, input { reason: text }
|
|
101
101
|
calculate: [{ target: refund, op: rate, base: { field: self.held.balance }, bps: { literal: cancellation_refund }, rounding: floor }, { target: retained, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.refund }] }]
|
|
102
102
|
set: { reason: { field: input.reason }, dispositionAt: { field: self.now } }
|
|
103
|
-
moves self.refund from self.held to self.buyer economics { purpose: principal, sourceParty: programOperator }; moves self.retained from self.held to retained_to economics { recipient: retained_to, company: { purpose: earning, sourceParty: buyer }, participant: { purpose:
|
|
103
|
+
moves self.refund from self.held to self.buyer economics { purpose: principal, sourceParty: programOperator }; moves self.retained from self.held to retained_to economics { recipient: retained_to, company: { purpose: earning, sourceParty: buyer }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
104
104
|
}
|
|
105
105
|
action seller_cancel {
|
|
106
106
|
from: active, to: cancelled, actor: { party: seller }, input { reason: text }
|
|
@@ -117,7 +117,7 @@ instrument reservation(listing: ref<marketplace.listing>, funds: ref<escrow.hold
|
|
|
117
117
|
from: active, to: expired, actor: clock, due: { at: self.expiresAt }
|
|
118
118
|
calculate: [{ target: refund, op: rate, base: { field: self.held.balance }, bps: { literal: expiry_refund }, rounding: floor }, { target: retained, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.refund }] }]
|
|
119
119
|
set: { dispositionAt: { field: self.now } }
|
|
120
|
-
moves self.refund from self.held to self.buyer economics { purpose: principal, sourceParty: programOperator }; moves self.retained from self.held to retained_to economics { recipient: retained_to, company: { purpose: earning, sourceParty: buyer }, participant: { purpose:
|
|
120
|
+
moves self.refund from self.held to self.buyer economics { purpose: principal, sourceParty: programOperator }; moves self.retained from self.held to retained_to economics { recipient: retained_to, company: { purpose: earning, sourceParty: buyer }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
121
121
|
}
|
|
122
122
|
action expire_proposal { from: proposed, to: expired, actor: clock, due: { at: self.proposalExpiresAt } }
|
|
123
123
|
}
|
package/std/money.hsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
header money
|
|
2
2
|
|
|
3
3
|
// Economics: Held funding and returns are principal. Release to the company realizes
|
|
4
|
-
// earnings; release to another participant
|
|
4
|
+
// earnings; release to another participant passes the payer's cash through. Generic transfers, splits,
|
|
5
5
|
// schedules, swaps, boundary payouts and metered fees retain no assumed purpose.
|
|
6
6
|
|
|
7
7
|
// Expose each attachment's create and caller actions explicitly. A transfer or
|
|
@@ -48,7 +48,7 @@ instrument hold(payer: party, payee: party, amount: money = runtime) {
|
|
|
48
48
|
}
|
|
49
49
|
action release {
|
|
50
50
|
from: funded, to: released, actor: { party: payer }
|
|
51
|
-
moves self.amount from self.held to payee economics { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose:
|
|
51
|
+
moves self.amount from self.held to payee economics { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
52
52
|
}
|
|
53
53
|
action refund {
|
|
54
54
|
from: funded, to: refunded, actor: { party: payee }
|
|
@@ -107,7 +107,7 @@ instrument pool(payer: party, payee: party, target: money, closes: date) {
|
|
|
107
107
|
action pay {
|
|
108
108
|
from: open, to: paid, actor: { party: payee }
|
|
109
109
|
requires self.held.balance == self.target
|
|
110
|
-
moves self.target from self.held to payee economics { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose:
|
|
110
|
+
moves self.target from self.held to payee economics { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose: pass_through, sourceParty: programOperator } }
|
|
111
111
|
}
|
|
112
112
|
action fail {
|
|
113
113
|
from: open, to: failed, actor: clock, due: { at: self.closesAt }
|
package/std/wallet.hsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
header wallet
|
|
2
2
|
|
|
3
|
-
// Economics: Topups and withdrawals are excluded prepaid credit
|
|
3
|
+
// Economics: Topups and withdrawals are excluded prepaid credit; a top-up fee is
|
|
4
|
+
// Company earning. Spending remains
|
|
4
5
|
// unclassified because the bound payee alone does not state the purchase purpose.
|
|
5
6
|
|
|
6
7
|
// Expose balance.create, activate, topup and withdraw as needed. Holder moves
|
|
@@ -12,20 +13,30 @@ header wallet
|
|
|
12
13
|
// Existing reservations may still post after the wallet freezes. There is no
|
|
13
14
|
// daily aggregate limit, partial spend or card-network settlement in this header.
|
|
14
15
|
|
|
15
|
-
//
|
|
16
|
-
//
|
|
16
|
+
// Top-up fee: topup_fee takes a floored percentage of each top-up for the Company;
|
|
17
|
+
// the wallet receives the rest. At 1%, a 100.00 top-up credits 99.00 and earns 1.00.
|
|
18
|
+
// The default 0% moves nothing to the Company.
|
|
19
|
+
|
|
20
|
+
// Cost: balance.create declares one owned account; topup declares two transfers
|
|
21
|
+
// and withdraw one. Spend reserve, pay and cancel each declare one transfer
|
|
17
22
|
// instruction. These are counts, not the executor tariff.
|
|
18
23
|
|
|
19
|
-
instrument balance(holder: party, spend_limit: money = 100000 SAR) {
|
|
24
|
+
instrument balance(holder: party, spend_limit: money = 100000 SAR, topup_fee: percent = 0%) {
|
|
20
25
|
summary: "A holder's reusable balance with an immutable per-spend ceiling."
|
|
21
|
-
fields { holder: account of holder, held: account of self, spendLimit: money = spend_limit }
|
|
26
|
+
fields { holder: account of holder, held: account of self, spendLimit: money = spend_limit, topupFee: money?, topupCredit: money? }
|
|
22
27
|
lifecycle { states: [pending, active, frozen, closed], initial: pending }
|
|
23
28
|
action create { }
|
|
24
29
|
action activate { from: pending, to: active, actor: { party: holder } }
|
|
25
30
|
action topup {
|
|
31
|
+
allowZero: true, requires input.amount > "0"
|
|
26
32
|
from: active, to: active, actor: { party: holder }
|
|
27
33
|
input { amount: money }
|
|
28
|
-
|
|
34
|
+
calculate: [
|
|
35
|
+
{ target: topupFee, op: rate, base: { field: input.amount }, bps: { literal: topup_fee }, rounding: floor }
|
|
36
|
+
{ target: topupCredit, op: subtract, base: { field: input.amount }, subtract: [{ field: self.topupFee }] }
|
|
37
|
+
]
|
|
38
|
+
moves self.topupCredit from holder to self.held economics { purpose: prepaid_credit, sourceParty: holder }
|
|
39
|
+
moves self.topupFee from holder to programOperator economics { purpose: earning, sourceParty: holder }
|
|
29
40
|
}
|
|
30
41
|
action withdraw {
|
|
31
42
|
from: active, to: active, actor: { party: holder }
|