@hyperscale0/hsx 2.1.0 → 2.2.0
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 +36 -7
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +39 -5
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +0 -1
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +15 -1
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts +3 -3
- package/dist/src/cost.d.ts.map +1 -1
- package/dist/src/cost.js +22 -12
- package/dist/src/cost.js.map +1 -1
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +10 -14
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +20 -84
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/typecheck.d.ts.map +1 -1
- package/dist/src/typecheck.js +171 -39
- package/dist/src/typecheck.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/README.md +8 -2
- package/docs/guide/01-first-program.md +1 -1
- package/docs/guide/06-schedules.md +5 -1
- package/docs/guide/08-writing-a-module.md +48 -0
- package/docs/llms-full.txt +1928 -421
- package/docs/llms.txt +2 -3
- package/docs/piece-plans.md +120 -0
- package/docs/reference/cli.md +6 -5
- package/docs/reference/diagnostics.md +9 -9
- package/docs/reference/grammar.md +2 -3
- package/docs/reference/std/advance.md +94 -17
- package/docs/reference/std/cancellable_booking.md +138 -17
- package/docs/reference/std/captured_payment.md +96 -24
- package/docs/reference/std/conditional_disbursement.md +82 -14
- package/docs/reference/std/credit_facility.md +89 -16
- package/docs/reference/std/held_payment.md +155 -55
- package/docs/reference/std/instant_transfer.md +79 -12
- package/docs/reference/std/metered.md +71 -9
- package/docs/reference/std/pooled_split.md +76 -7
- package/docs/reference/std/premium_forward.md +100 -20
- package/docs/reference/std/reconciled_payout.md +84 -14
- package/docs/reference/std/rotating_pool.md +112 -24
- package/docs/reference/std/scheduled.md +117 -32
- package/docs/reference/std/security_deposit.md +119 -27
- package/docs/reference/std/settlement_batch.md +105 -24
- package/docs/reference/std/swap.md +113 -26
- package/docs/reference/std/threshold_pool.md +120 -29
- package/docs/reference/std/weighted_distribution.md +117 -21
- package/docs/reference/types.md +15 -15
- package/docs/reference/udl-output.md +6 -6
- package/examples/01-first-program/README.md +1 -1
- package/examples/{02-imports-and-archetypes → 02-imports-and-modules}/README.md +1 -1
- package/examples/{02-imports-and-archetypes → 02-imports-and-modules}/photo-booth.hsx +1 -1
- package/examples/04-complete-product/README.md +1 -1
- package/examples/05-authored-instrument/README.md +5 -0
- package/examples/05-authored-instrument/payment.hsx +37 -0
- package/examples/05-watch-club/watch-club.hsx +0 -1
- package/examples/README.md +1 -2
- package/examples/advance/advance.udl +29 -6
- package/examples/cancellable_booking/cancellable_booking.udl +19 -0
- package/examples/captured_payment/captured_payment.hsx +0 -4
- package/examples/captured_payment/captured_payment.udl +6 -0
- package/examples/conditional_disbursement/conditional_disbursement.hsx +0 -2
- package/examples/conditional_disbursement/conditional_disbursement.udl +4 -0
- package/examples/cost-table.json +136 -8
- package/examples/credit_facility/credit_facility.hsx +0 -3
- package/examples/credit_facility/credit_facility.udl +17 -1
- package/examples/held_payment/held_payment.udl +16 -0
- package/examples/instant_transfer/instant_transfer.udl +6 -0
- package/examples/metered/metered.udl +5 -1
- package/examples/pooled_split/pooled_split.udl +4 -0
- package/examples/premium_forward/premium_forward.udl +4 -0
- package/examples/reconciled_payout/reconciled_payout.udl +7 -0
- package/examples/rotating_pool/rotating_pool.udl +8 -0
- package/examples/scheduled/scheduled.udl +18 -3
- package/examples/security_deposit/security_deposit.udl +13 -0
- package/examples/settlement_batch/settlement_batch.udl +4 -0
- package/examples/swap/swap.udl +10 -0
- package/examples/threshold_pool/threshold_pool.udl +11 -0
- package/examples/weighted_distribution/weighted_distribution.udl +9 -0
- package/package.json +10 -10
- package/skills/hsx/SKILL.md +2 -36
- package/src/cli.ts +41 -5
- package/src/compile.ts +14 -6
- package/src/cost.ts +13 -16
- package/src/parse.ts +15 -10
- package/src/std-bundle.ts +21 -88
- package/src/typecheck.ts +207 -34
- package/src/version.ts +1 -1
- package/std/SEMANTICS.md +33 -128
- package/std/money_flows/advance.hsx +39 -26
- package/std/money_flows/cancellable_booking.hsx +275 -8
- package/std/money_flows/captured_payment.hsx +1 -9
- package/std/money_flows/conditional_disbursement.hsx +0 -6
- package/std/money_flows/credit_facility.hsx +0 -9
- package/std/money_flows/held_payment.hsx +39 -1
- package/std/money_flows/index.hsx +2 -1
- package/std/money_flows/metered.hsx +1 -5
- package/std/money_flows/scheduled.hsx +29 -119
- package/std/money_flows/threshold_pool.hsx +40 -5
- package/std/money_flows/weighted_distribution.hsx +47 -5
- package/docs/reference/std/recurring_collection.md +0 -25
- package/examples/recurring_collection/README.md +0 -3
- package/examples/recurring_collection/recurring_collection.hsx +0 -21
- package/examples/recurring_collection/recurring_collection.udl +0 -1135
- package/std/money_flows/recurring_collection.hsx +0 -72
|
@@ -24,6 +24,9 @@ module std.money_flows.advance
|
|
|
24
24
|
// - `count`: Optional number of scheduled installment repayments.
|
|
25
25
|
// - `every`: Optional recurrence duration between installments (e.g. `"P30D"`).
|
|
26
26
|
// - `first_due`: Optional date for the first installment repayment.
|
|
27
|
+
// - `repayment_source`: Optional party repaying when distinct from the capital recipient.
|
|
28
|
+
// - `profit_to`: Optional recipient of profit, separated from principal repayments.
|
|
29
|
+
// - `dated`: Require an explicit signed date for each repayment instead of duration offsets.
|
|
27
30
|
// - `against`: Optional reference to a hold instrument whose release will be carved to repay the advance.
|
|
28
31
|
//
|
|
29
32
|
// ### Decision ports
|
|
@@ -54,6 +57,9 @@ export instrument advance<C>(
|
|
|
54
57
|
every: optional<text>,
|
|
55
58
|
first_due: optional<date>,
|
|
56
59
|
against: optional<ref>,
|
|
60
|
+
repayment_source: optional<party>,
|
|
61
|
+
profit_to: optional<party>,
|
|
62
|
+
dated: optional<boolean>,
|
|
57
63
|
) {
|
|
58
64
|
let(amount_path): concat("fields.", amount);
|
|
59
65
|
let(currency_path): "fields.currency";
|
|
@@ -66,6 +72,8 @@ export instrument advance<C>(
|
|
|
66
72
|
parties {
|
|
67
73
|
payer: funder;
|
|
68
74
|
beneficiary: to;
|
|
75
|
+
when(repayment_source) { repayment: repayment_source; }
|
|
76
|
+
when(profit_to) { profit: profit_to; }
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
when(against) {
|
|
@@ -131,6 +139,8 @@ export instrument advance<C>(
|
|
|
131
139
|
}
|
|
132
140
|
|
|
133
141
|
when_not(against) {
|
|
142
|
+
let(repayment_role): if_eq(repayment_source, false, "beneficiary", "repayment");
|
|
143
|
+
when(fee) { computes derived: { field: feeAmount; source_field: amount; rule: { kind: percentage_of; bps: basis_points(fee); }; rounding: floor; }; }
|
|
134
144
|
let(repayment_fields): names("repayment", count, "Amount");
|
|
135
145
|
let(first_target): if_eq(count, 1, "repaid", "repaying_1");
|
|
136
146
|
let(states_key): "states";
|
|
@@ -144,6 +154,7 @@ export instrument advance<C>(
|
|
|
144
154
|
}
|
|
145
155
|
fee_amount {
|
|
146
156
|
type: money<C>;
|
|
157
|
+
when(fee) { optional: true; }
|
|
147
158
|
description: concat(fee, " of ", amount, ", the funder's discount repaid on top of the advance");
|
|
148
159
|
}
|
|
149
160
|
repayable_amount {
|
|
@@ -154,16 +165,13 @@ export instrument advance<C>(
|
|
|
154
165
|
type: date;
|
|
155
166
|
description: concat("Due date of the first repayment; repayment k falls ", every, " after its predecessor");
|
|
156
167
|
}
|
|
157
|
-
|
|
158
|
-
type: money<C>;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
let(index): add(repayment, 1);
|
|
163
|
-
repayment[index]Amount {
|
|
164
|
-
type: money<C>;
|
|
165
|
-
description: concat("Repayment ", index, " of ", count, ": about 33.33% of repayableAmount, collected on its own stored-date anchor");
|
|
168
|
+
for i in count {
|
|
169
|
+
repayment[i]Amount { type: money<C>; description: "Stored total for repayment {i}"; }
|
|
170
|
+
when(profit_to) {
|
|
171
|
+
repayment[i]Principal { type: money<C>; description: "Principal returned to the funder by repayment {i}"; }
|
|
172
|
+
repayment[i]Profit { type: money<C>; description: "Profit paid to the named recipient by repayment {i}"; }
|
|
166
173
|
}
|
|
174
|
+
when(dated) { repayment[i]DueAt { type: date; description: "Signed calendar due date for repayment {i}"; } }
|
|
167
175
|
}
|
|
168
176
|
}
|
|
169
177
|
lifecycle {
|
|
@@ -187,8 +195,15 @@ export instrument advance<C>(
|
|
|
187
195
|
piece_fields: [amount, feeAmount];
|
|
188
196
|
total_field: repayableAmount;
|
|
189
197
|
}];
|
|
198
|
+
when(profit_to) {
|
|
199
|
+
partitions: { total_field: amount; piece_fields: names("repayment", count, "Principal"); };
|
|
200
|
+
partitions: { total_field: feeAmount; piece_fields: names("repayment", count, "Profit"); };
|
|
201
|
+
for i in count {
|
|
202
|
+
partitions: { total_field: concat("repayment", i, "Amount"); piece_fields: [concat("repayment", i, "Principal"), concat("repayment", i, "Profit")]; };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
190
205
|
action create {
|
|
191
|
-
agent_description: "Open the advance row before any money moves. The caller supplies the {amount}, the
|
|
206
|
+
agent_description: "Open the advance row before any money moves. The caller supplies the {amount}, the repayable total, and repayment dates. A declared fee derives the fee amount from principal. The repayment fields must partition the repayable total exactly. Nothing is disbursed here.";
|
|
192
207
|
summary: concat("Create a ", words(instrument));
|
|
193
208
|
steps: [];
|
|
194
209
|
moves: [];
|
|
@@ -199,22 +214,20 @@ export instrument advance<C>(
|
|
|
199
214
|
moves: [{ amount: amount; from: payer; to: beneficiary; key: transfer; operation: create; }];
|
|
200
215
|
steps: [];
|
|
201
216
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
};
|
|
217
|
-
moves: [{ amount: repayment_field; from: beneficiary; to: payer; key: transfer; operation: create; }];
|
|
217
|
+
for i in count {
|
|
218
|
+
let(repayment_field): concat("repayment", i, "Amount");
|
|
219
|
+
action collect_repayment_[i] {
|
|
220
|
+
agent_description: "Collect repayment {i} once on its stored due anchor, returning principal to the funder and any separately routed profit to its recipient.";
|
|
221
|
+
summary: "Collect repayment {i} of {count}";
|
|
222
|
+
when(dated) { due: { field: concat("repayment", i, "DueAt"); }; }
|
|
223
|
+
when_not(dated) { due: { field: first_due_field; offset: scale_duration(every, sub(i, 1)); }; }
|
|
224
|
+
when_not(profit_to) { moves: [{ amount: repayment_field; from: repayment_role; to: payer; key: transfer; }]; }
|
|
225
|
+
when(profit_to) {
|
|
226
|
+
moves: [
|
|
227
|
+
{ amount: concat("repayment", i, "Principal"); from: repayment_role; to: payer; key: principal; },
|
|
228
|
+
{ amount: concat("repayment", i, "Profit"); from: repayment_role; to: profit; key: profit; }
|
|
229
|
+
];
|
|
230
|
+
}
|
|
218
231
|
steps: [];
|
|
219
232
|
}
|
|
220
233
|
}
|
|
@@ -45,16 +45,38 @@ module std.money_flows.cancellable_booking
|
|
|
45
45
|
// offer_life: "PT30M"
|
|
46
46
|
// }
|
|
47
47
|
// ```
|
|
48
|
+
// ### Finite cancellation bands
|
|
49
|
+
// Supply cancel_bands to select deposit/balance custody instead of quoted cancellation.
|
|
50
|
+
// Each band has name, refund (full, balance or none), starts_at after the first band,
|
|
51
|
+
// and supplier_bps when money is retained. The unpaid_band index selects a retained
|
|
52
|
+
// deposit policy. Omit it to return the deposit; a full-refund band also returns it. price_value and supplier_cost fix the price; deposit_bps derives
|
|
53
|
+
// the deposit. balance_before and confirmation_window are positive fixed durations
|
|
54
|
+
// relative to departure. tax_bps applies to margin after the supplier share.
|
|
55
|
+
// authority_type and supplier_role bind release to a consumed confirmation decision.
|
|
56
|
+
// acceptance optionally supplies an aggregate gate. All actions are private until
|
|
57
|
+
// expose selects them. Ordered band starts and deadlines prevent a late sweep from
|
|
58
|
+
// extending an earlier band's cancellation rights. Full timeout refund returns price.
|
|
59
|
+
// Seven derivations suffice for three bands; larger finite policies use up to the
|
|
60
|
+
// UDL bound of 64 derived amounts. shift_date(date, duration, "before") subtracts a
|
|
61
|
+
// fixed duration; omitting the direction adds it. Invalid dates or durations refuse.
|
|
62
|
+
//
|
|
48
63
|
export instrument cancellable_booking<C>(
|
|
49
|
-
guest: party
|
|
50
|
-
host: party
|
|
51
|
-
amount: money<C
|
|
52
|
-
starts_at: date
|
|
53
|
-
late_penalty_bps: integer
|
|
54
|
-
late_within: text
|
|
55
|
-
early_penalty_bps: integer
|
|
56
|
-
offer_life: text
|
|
64
|
+
guest: optional<party>,
|
|
65
|
+
host: optional<party>,
|
|
66
|
+
amount: optional<money<C>>,
|
|
67
|
+
starts_at: optional<date>,
|
|
68
|
+
late_penalty_bps: optional<integer>,
|
|
69
|
+
late_within: optional<text>,
|
|
70
|
+
early_penalty_bps: optional<integer>,
|
|
71
|
+
offer_life: optional<text>,
|
|
72
|
+
cancel_bands: optional<list<block>>,
|
|
73
|
+
price_value: optional<text>, supplier_cost: optional<text>, departure: optional<text>,
|
|
74
|
+
balance_before: optional<text>, confirmation_window: optional<text>, unpaid_band: optional<integer>,
|
|
75
|
+
acceptance: optional<block>, deposit_bps: optional<integer>, tax_bps: optional<integer>,
|
|
76
|
+
authority_type: optional<text>, supplier_role: optional<text>,
|
|
77
|
+
|
|
57
78
|
) {
|
|
79
|
+
when_not(cancel_bands) {
|
|
58
80
|
let(guest_account_field): camel(concat(guest, "_account_id"));
|
|
59
81
|
let(host_account_field): camel(concat(host, "_account_id"));
|
|
60
82
|
let(reference_filter_key): "x-hyperscale-reference-filter";
|
|
@@ -130,4 +152,249 @@ export instrument cancellable_booking<C>(
|
|
|
130
152
|
moves: [{ key: penalty; operation: internal_transfer.create; bind: { amount: { from: instance; path: refs.cancellationPenaltyAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", host_account_field); }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.bookingHoldingAccountId; }; }; capture: { penaltyTransferId: transferId; }; }];
|
|
131
153
|
steps: [];
|
|
132
154
|
}
|
|
155
|
+
}
|
|
156
|
+
when(cancel_bands) {
|
|
157
|
+
let(band_count): len(cancel_bands);
|
|
158
|
+
for band in cancel_bands {
|
|
159
|
+
let(refund): get(band, "refund");
|
|
160
|
+
let(not_full): if_eq(refund, "full", false, true);
|
|
161
|
+
let(not_balance): if_eq(refund, "balance", false, true);
|
|
162
|
+
let(not_none): if_eq(refund, "none", false, true);
|
|
163
|
+
when(not_full) { when(not_balance) { when(not_none) {
|
|
164
|
+
unsupported { code: HSX1407; message: "Unknown cancellation refund policy"; fix: "Choose full, balance or none"; }
|
|
165
|
+
} } }
|
|
166
|
+
}
|
|
167
|
+
let(deposit_states): names("deposit_", len(cancel_bands), "");
|
|
168
|
+
let(full_states): names("full_", len(cancel_bands), "");
|
|
169
|
+
fields {
|
|
170
|
+
bookingKey { type: text; min_length: 1; }
|
|
171
|
+
signedTermsReference { type: text; min_length: 1; }
|
|
172
|
+
currency { type: text; const: C; }
|
|
173
|
+
customerAccountId: account<C>;
|
|
174
|
+
supplierAccountId: account<C>;
|
|
175
|
+
operatorAccountId: account<C>;
|
|
176
|
+
taxAccountId: account<C>;
|
|
177
|
+
authorityId { type: ref<authority_type>; optional: true; }
|
|
178
|
+
supplierRole { type: text; const: supplier_role; }
|
|
179
|
+
travellerCount { type: integer; minimum: 1; maximum: 20; }
|
|
180
|
+
departureAt { type: date; const: departure; }
|
|
181
|
+
balanceDueAt { type: date; const: shift_date(departure, balance_before, "before"); }
|
|
182
|
+
confirmationDeadline { type: date; const: shift_date(departure, confirmation_window); }
|
|
183
|
+
price { type: money<C>; const: price_value; }
|
|
184
|
+
supplierCost { type: money<C>; const: supplier_cost; }
|
|
185
|
+
depositAmount { type: money<C>; optional: true; }
|
|
186
|
+
balanceAmount: money<C>;
|
|
187
|
+
margin: money<C>;
|
|
188
|
+
operatorNet: money<C>;
|
|
189
|
+
marginVat { type: money<C>; optional: true; }
|
|
190
|
+
for i in len(cancel_bands) {
|
|
191
|
+
let(band): at(cancel_bands, i);
|
|
192
|
+
let(refund_mode): get(band, "refund");
|
|
193
|
+
let(full_refund): if_eq(refund_mode, "full", true, false);
|
|
194
|
+
let(balance_refund): if_eq(refund_mode, "balance", true, false);
|
|
195
|
+
let(retains): if_eq(refund_mode, "full", false, true);
|
|
196
|
+
let(later): if_eq(i, 1, false, true);
|
|
197
|
+
when(later) { band[i]Start { type: date; const: get(band, "starts_at"); } }
|
|
198
|
+
|
|
199
|
+
when(retains) {
|
|
200
|
+
band[i]Supplier { type: money<C>; optional: true; }
|
|
201
|
+
let(portions): if_eq(refund_mode, "balance", ["deposit"], ["deposit", "full"]);
|
|
202
|
+
for paid in portions {
|
|
203
|
+
let(is_full): if_eq(paid, "full", true, false);
|
|
204
|
+
let(portion): if_eq(refund_mode, "balance", "deposit", paid);
|
|
205
|
+
let(margin_field): concat("band", i, title(portion), "Margin");
|
|
206
|
+
let(net_field): concat("band", i, title(portion), "Net");
|
|
207
|
+
let(vat_field): concat("band", i, title(portion), "Vat");
|
|
208
|
+
[margin_field]: money<C>;
|
|
209
|
+
[net_field]: money<C>;
|
|
210
|
+
[vat_field] { type: money<C>; optional: true; }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
parties { payer: customerAccountId; beneficiary: supplierAccountId; fee: operatorAccountId; tax: taxAccountId; }
|
|
216
|
+
distinct_parties: true;
|
|
217
|
+
computes derived: { field: depositAmount; source_field: price; rule: { kind: percentage_of; bps: deposit_bps; }; rounding: floor; };
|
|
218
|
+
computes derived: { field: marginVat; source_field: margin; rule: { kind: percentage_of; bps: tax_bps; }; rounding: floor; };
|
|
219
|
+
for i in len(cancel_bands) {
|
|
220
|
+
let(band): at(cancel_bands, i);
|
|
221
|
+
let(refund_mode): get(band, "refund");
|
|
222
|
+
let(balance_refund): if_eq(refund_mode, "balance", true, false);
|
|
223
|
+
when(balance_refund) {
|
|
224
|
+
partitions: { total_field: price; piece_fields: [balanceAmount, concat("band", i, "Supplier"), concat("band", i, "DepositNet"), concat("band", i, "DepositVat")]; };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
partitions: { total_field: price; piece_fields: [supplierCost, operatorNet, marginVat]; };
|
|
228
|
+
partitions: { total_field: price; piece_fields: [depositAmount, balanceAmount]; };
|
|
229
|
+
partitions: { total_field: price; piece_fields: [supplierCost, margin]; };
|
|
230
|
+
partitions: { total_field: margin; piece_fields: [operatorNet, marginVat]; };
|
|
231
|
+
date order: { before_field: balanceDueAt; after_field: departureAt; operator: "<"; };
|
|
232
|
+
date order: { before_field: departureAt; after_field: confirmationDeadline; operator: "<"; };
|
|
233
|
+
for i in len(cancel_bands) {
|
|
234
|
+
let(band): at(cancel_bands, i);
|
|
235
|
+
let(refund_mode): get(band, "refund");
|
|
236
|
+
let(full_refund): if_eq(refund_mode, "full", true, false);
|
|
237
|
+
let(balance_refund): if_eq(refund_mode, "balance", true, false);
|
|
238
|
+
let(retains): if_eq(refund_mode, "full", false, true);
|
|
239
|
+
let(later): if_eq(i, 1, false, true);
|
|
240
|
+
let(band_start): concat("band", i, "Start");
|
|
241
|
+
when(later) {
|
|
242
|
+
date order: { before_field: band_start; after_field: departureAt; operator: "<"; };
|
|
243
|
+
let(after_second): if_eq(i, 2, false, true);
|
|
244
|
+
when(after_second) { date order: { before_field: concat("band", sub(i, 1), "Start"); after_field: band_start; operator: "<"; }; }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
when(retains) {
|
|
248
|
+
let(supplier_field): concat("band", i, "Supplier");
|
|
249
|
+
computes derived: { field: supplier_field; source_field: supplierCost; rule: { kind: percentage_of; bps: get(band, "supplier_bps"); }; rounding: floor; };
|
|
250
|
+
let(portions): if_eq(refund_mode, "balance", ["deposit"], ["deposit", "full"]);
|
|
251
|
+
for paid in portions {
|
|
252
|
+
let(is_full): if_eq(paid, "full", true, false);
|
|
253
|
+
let(portion): if_eq(refund_mode, "balance", "deposit", paid);
|
|
254
|
+
let(margin_field): concat("band", i, title(portion), "Margin");
|
|
255
|
+
let(net_field): concat("band", i, title(portion), "Net");
|
|
256
|
+
let(vat_field): concat("band", i, title(portion), "Vat");
|
|
257
|
+
let(retained): if_eq(paid, "deposit", "depositAmount", if_eq(refund_mode, "balance", "depositAmount", "price"));
|
|
258
|
+
computes derived: { field: vat_field; source_field: margin_field; rule: { kind: percentage_of; bps: tax_bps; }; rounding: floor; };
|
|
259
|
+
partitions: { total_field: retained; piece_fields: [supplier_field, net_field, vat_field]; };
|
|
260
|
+
partitions: { total_field: retained; piece_fields: [supplier_field, margin_field]; };
|
|
261
|
+
partitions: { total_field: margin_field; piece_fields: [net_field, vat_field]; };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
agent_description: "Hold deposit and balance in one booking account. Apply a finite dated cancellation policy and release the supplier, operator and tax partition only after the named authority confirms. Refund the entire price when confirmation times out.";
|
|
266
|
+
lifecycle {
|
|
267
|
+
states created awaiting_confirmation confirmed released refunded canceled expired;
|
|
268
|
+
let(states_key): "states";
|
|
269
|
+
[states_key]: concat_lists(deposit_states, full_states);
|
|
270
|
+
initial created;
|
|
271
|
+
on deposit: created -> deposit_1;
|
|
272
|
+
on expire: created -> expired;
|
|
273
|
+
for i in len(cancel_bands) {
|
|
274
|
+
let(band): at(cancel_bands, i);
|
|
275
|
+
let(refund_mode): get(band, "refund");
|
|
276
|
+
let(full_refund): if_eq(refund_mode, "full", true, false);
|
|
277
|
+
let(balance_refund): if_eq(refund_mode, "balance", true, false);
|
|
278
|
+
let(retains): if_eq(refund_mode, "full", false, true);
|
|
279
|
+
let(band_name): get(band, "name");
|
|
280
|
+
let(deposit_state): concat("deposit_", i);
|
|
281
|
+
let(full_state): concat("full_", i);
|
|
282
|
+
let(balance_action): concat("balance_", band_name);
|
|
283
|
+
let(cancel_deposit): concat("cancel_deposit_", band_name);
|
|
284
|
+
let(cancel_full): concat("cancel_full_", band_name);
|
|
285
|
+
on [balance_action]: deposit_state -> full_state;
|
|
286
|
+
on [cancel_deposit]: deposit_state -> canceled;
|
|
287
|
+
on [cancel_full]: full_state -> canceled;
|
|
288
|
+
let(later): if_eq(i, 1, false, true);
|
|
289
|
+
when(later) {
|
|
290
|
+
let(previous_deposit): names("deposit_", sub(i, 1), "");
|
|
291
|
+
let(previous_full): names("full_", sub(i, 1), "");
|
|
292
|
+
let(open_deposit): concat("open_deposit_", band_name);
|
|
293
|
+
let(open_full): concat("open_full_", band_name);
|
|
294
|
+
on [open_deposit]: previous_deposit -> deposit_state;
|
|
295
|
+
on [open_full]: previous_full -> full_state;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
on cancel_unpaid: deposit_states -> canceled;
|
|
299
|
+
on await_confirmation: full_states -> awaiting_confirmation;
|
|
300
|
+
on confirm: awaiting_confirmation -> confirmed;
|
|
301
|
+
on release: confirmed -> released;
|
|
302
|
+
on refund_unconfirmed: full_states | awaiting_confirmation -> refunded;
|
|
303
|
+
}
|
|
304
|
+
caller_parked_states: { confirmed: "Release pays the confirmed supplier and frozen partition."; };
|
|
305
|
+
action create {
|
|
306
|
+
public: none;
|
|
307
|
+
agent_description: "Freeze the booking terms and provision its holding account.";
|
|
308
|
+
unique: { namespace: instrument; by_fields: [bookingKey]; };
|
|
309
|
+
deadline: { field: balanceDueAt; };
|
|
310
|
+
steps: { operation: "account.escrow.provision"; bind: { currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; "owner.id": { from: instance; path: productId; }; "owner.type": { from: const; value: product; }; role: { from: const; value: product_escrow; }; }; capture: { heldAccountId: accountId; }; };
|
|
311
|
+
}
|
|
312
|
+
action deposit {
|
|
313
|
+
public: none;
|
|
314
|
+
agent_description: "Collect the derived deposit after the acceptance requirements pass.";
|
|
315
|
+
when(acceptance) { requires aggregate: acceptance; }
|
|
316
|
+
deadline: { field: balanceDueAt; }; port: { allowedParties: [payer]; };
|
|
317
|
+
moves: { key: deposit; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "depositAmount"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "fields.customerAccountId"; }; destinationAccountId: { from: instance; path: "refs.heldAccountId"; }; }; };
|
|
318
|
+
steps: [];
|
|
319
|
+
}
|
|
320
|
+
action expire { public: none; agent_description: "Close an unfunded booking."; due: { field: balanceDueAt; }; requires_drained: { path: refs.heldAccountId; }; steps: []; }
|
|
321
|
+
for i in len(cancel_bands) {
|
|
322
|
+
let(band): at(cancel_bands, i);
|
|
323
|
+
let(refund_mode): get(band, "refund");
|
|
324
|
+
let(full_refund): if_eq(refund_mode, "full", true, false);
|
|
325
|
+
let(balance_refund): if_eq(refund_mode, "balance", true, false);
|
|
326
|
+
let(retains): if_eq(refund_mode, "full", false, true);
|
|
327
|
+
let(band_name): get(band, "name");
|
|
328
|
+
let(balance_action): concat("balance_", band_name);
|
|
329
|
+
let(next_index): add(i, 1);
|
|
330
|
+
let(next_field): concat("band", next_index, "Start");
|
|
331
|
+
let(band_end): if_eq(i, band_count, "departureAt", next_field);
|
|
332
|
+
let(later): if_eq(i, 1, false, true);
|
|
333
|
+
action [balance_action] {
|
|
334
|
+
public: none; agent_description: "Collect the remaining balance once before its deadline.";
|
|
335
|
+
deadline: { field: balanceDueAt; }; port: { allowedParties: [payer]; };
|
|
336
|
+
moves: { key: balance; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "balanceAmount"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "fields.customerAccountId"; }; destinationAccountId: { from: instance; path: "refs.heldAccountId"; }; }; };
|
|
337
|
+
steps: [];
|
|
338
|
+
}
|
|
339
|
+
for paid in ["deposit", "full"] {
|
|
340
|
+
let(is_full): if_eq(paid, "full", true, false);
|
|
341
|
+
when(later) {
|
|
342
|
+
let(open_action): concat("open_", paid, "_", band_name);
|
|
343
|
+
action [open_action] { public: none; agent_description: "Open the next dated cancellation band."; due: { field: concat("band", i, "Start"); }; steps: []; }
|
|
344
|
+
}
|
|
345
|
+
let(portion): if_eq(refund_mode, "balance", "deposit", paid);
|
|
346
|
+
let(cancel_action): concat("cancel_", paid, "_", band_name);
|
|
347
|
+
action [cancel_action] {
|
|
348
|
+
public: none; agent_description: "Settle this paid amount under the active cancellation band.";
|
|
349
|
+
deadline: { field: band_end; }; port: { allowedParties: [payer]; };
|
|
350
|
+
when(full_refund) { moves: { key: refund; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", if_eq(paid, "deposit", "depositAmount", "price")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.customerAccountId"; }; }; }; }
|
|
351
|
+
when(retains) {
|
|
352
|
+
when(is_full) { when(balance_refund) { moves: { key: refund; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "balanceAmount"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.customerAccountId"; }; }; }; } }
|
|
353
|
+
moves: { key: supplier; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", concat("band", i, "Supplier")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.supplierAccountId"; }; }; };
|
|
354
|
+
moves: { key: operator; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", concat("band", i, title(portion), "Net")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.operatorAccountId"; }; }; };
|
|
355
|
+
moves: { key: vat; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", concat("band", i, title(portion), "Vat")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.taxAccountId"; }; }; };
|
|
356
|
+
}
|
|
357
|
+
steps: [];
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
action cancel_unpaid {
|
|
362
|
+
public: none; agent_description: "Settle the deposit under the declared unpaid-balance band and end the remaining obligation.";
|
|
363
|
+
due: { field: balanceDueAt; };
|
|
364
|
+
when_not(unpaid_band) { moves: { key: refund; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: "fields.depositAmount"; }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.customerAccountId"; }; }; }; }
|
|
365
|
+
when(unpaid_band) {
|
|
366
|
+
let(unpaid_policy): at(cancel_bands, unpaid_band);
|
|
367
|
+
let(unpaid_full_refund): if_eq(get(unpaid_policy, "refund"), "full", true, false);
|
|
368
|
+
when(unpaid_full_refund) { moves: { key: refund; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: "fields.depositAmount"; }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.customerAccountId"; }; }; }; }
|
|
369
|
+
when_not(unpaid_full_refund) {
|
|
370
|
+
moves: { key: supplier; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", concat("band", unpaid_band, "Supplier")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.supplierAccountId"; }; }; };
|
|
371
|
+
moves: { key: operator; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", concat("band", unpaid_band, "DepositNet")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.operatorAccountId"; }; }; };
|
|
372
|
+
moves: { key: vat; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", concat("band", unpaid_band, "DepositVat")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.taxAccountId"; }; }; };
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
steps: [];
|
|
376
|
+
}
|
|
377
|
+
action await_confirmation { public: none; agent_description: "Open confirmation at the booked departure."; due: { field: departureAt; }; steps: []; }
|
|
378
|
+
action confirm {
|
|
379
|
+
public: none; agent_description: "Consume the named supplier's approved decision for this booking before timeout.";
|
|
380
|
+
deadline: { field: confirmationDeadline; };
|
|
381
|
+
input: { type: object; additionalProperties: false; properties: { authorityId: { type: string; pattern: concat("^", prefix(authority_type), "_(sandbox|live)_[a-z0-9]{8,64}$"); }; }; required: [authorityId]; };
|
|
382
|
+
updates: [authorityId];
|
|
383
|
+
requires refs: { field: authorityId; statuses: [approved]; match: { instrumentInstanceId: fields.requestInstanceId; "fields.supplierAccountId": fields.decidingActorAccountId; "fields.supplierRole": fields.decidingRole; }; attests: { instrument: fields.requestInstrumentId; party: decider; action: fields.requestAction; digest: fields.inputDigest; role: fields.decidingRole; expires_at: fields.expiresAt; consume: consume; }; };
|
|
384
|
+
steps: [];
|
|
385
|
+
}
|
|
386
|
+
action release {
|
|
387
|
+
public: none; agent_description: "Pay supplier cost, operator net and VAT after confirmation.";
|
|
388
|
+
moves: { key: supplier; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "supplierCost"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.supplierAccountId"; }; }; };
|
|
389
|
+
moves: { key: operator; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "operatorNet"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.operatorAccountId"; }; }; };
|
|
390
|
+
moves: { key: vat; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "marginVat"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.taxAccountId"; }; }; };
|
|
391
|
+
steps: [];
|
|
392
|
+
}
|
|
393
|
+
action refund_unconfirmed {
|
|
394
|
+
public: none; agent_description: "Return the entire booking price when supplier confirmation times out.";
|
|
395
|
+
due: { field: confirmationDeadline; };
|
|
396
|
+
moves: { key: refund; operation: "internal_transfer.create"; bind: { amount: { from: instance; path: concat("fields.", "price"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: "refs.heldAccountId"; }; destinationAccountId: { from: instance; path: "fields.customerAccountId"; }; }; };
|
|
397
|
+
steps: [];
|
|
398
|
+
}
|
|
399
|
+
}
|
|
133
400
|
}
|
|
@@ -21,10 +21,6 @@ module std.money_flows.captured_payment
|
|
|
21
21
|
// - `reserve_until`: Expiration date for the authorization hold.
|
|
22
22
|
// - `correction`: Condition port allowing post-settlement amount corrections.
|
|
23
23
|
// - `external_reversal`: Condition port allowing external processor chargebacks/reversals.
|
|
24
|
-
// - `capture_mode`: Text specifying capture behavior (`"slice"` or `"full"`).
|
|
25
|
-
// - `correction_mode`: Text specifying correction behavior.
|
|
26
|
-
// - `negative_position`: Text policy when correction creates an overdraft.
|
|
27
|
-
// - `timeout`: Timeout duration string for pending authorizations.
|
|
28
24
|
// - `derived_amount`: Optional block declaring percentage fee calculation.
|
|
29
25
|
//
|
|
30
26
|
// ### Decision ports
|
|
@@ -44,10 +40,6 @@ module std.money_flows.captured_payment
|
|
|
44
40
|
// reserve_until: reserveUntil
|
|
45
41
|
// correction: port correct_capture
|
|
46
42
|
// external_reversal: port reverse_capture within P14D
|
|
47
|
-
// capture_mode: partial_then_full
|
|
48
|
-
// correction_mode: full_only
|
|
49
|
-
// negative_position: reject
|
|
50
|
-
// timeout: reject
|
|
51
43
|
// }
|
|
52
44
|
// port correct_capture { allowed: [payee] }
|
|
53
45
|
// port reverse_capture {
|
|
@@ -55,7 +47,7 @@ module std.money_flows.captured_payment
|
|
|
55
47
|
// shape: { externalReference: text }
|
|
56
48
|
// }
|
|
57
49
|
// ```
|
|
58
|
-
export instrument captured_payment<C>(payer: party, payee: party, amount: money<C>, reserve_until: date, correction: condition, external_reversal: condition,
|
|
50
|
+
export instrument captured_payment<C>(payer: party, payee: party, amount: money<C>, reserve_until: date, correction: condition, external_reversal: condition, derived_amount: optional<block>) {
|
|
59
51
|
let(correction_name): correction;
|
|
60
52
|
let(reversal_name): external_reversal;
|
|
61
53
|
let(reversal_window): external_reversal_within;
|
|
@@ -20,8 +20,6 @@ module std.money_flows.conditional_disbursement
|
|
|
20
20
|
// - `cap`: Maximum total amount that can be disbursed across all child approvals in minor units of currency `C`.
|
|
21
21
|
// - `amount`: Binding name for child approval amount values.
|
|
22
22
|
// - `decision`: Port conditioning approval, requiring evidence reference.
|
|
23
|
-
// - `reopen_policy`: Policy for reopening closed disbursements (`refuse`).
|
|
24
|
-
// - `recovery_policy`: Policy for recovering overpayments (`separate_transfer`).
|
|
25
23
|
//
|
|
26
24
|
// ### Decision ports
|
|
27
25
|
// - `decision`: External port providing decision evidence required to approve child disbursement amounts.
|
|
@@ -38,8 +36,6 @@ module std.money_flows.conditional_disbursement
|
|
|
38
36
|
// cap: policyLimit: money(SAR)
|
|
39
37
|
// amount: approvedAmount: money(SAR)
|
|
40
38
|
// decision: port approve_claim
|
|
41
|
-
// reopen_policy: refuse
|
|
42
|
-
// recovery_policy: separate_transfer
|
|
43
39
|
// }
|
|
44
40
|
// port approve_claim {
|
|
45
41
|
// allowed: [source]
|
|
@@ -52,8 +48,6 @@ export instrument conditional_disbursement<C>(
|
|
|
52
48
|
cap: money<C>,
|
|
53
49
|
amount: money<C>,
|
|
54
50
|
decision: condition,
|
|
55
|
-
reopen_policy: text,
|
|
56
|
-
recovery_policy: text,
|
|
57
51
|
) {
|
|
58
52
|
let(child_id): concat(instrument, "_approved_amount");
|
|
59
53
|
let(child_amount): "amount";
|
|
@@ -20,9 +20,6 @@ module std.money_flows.credit_facility
|
|
|
20
20
|
// - `limit`: Total revolving borrowing limit in minor units of currency `C`.
|
|
21
21
|
// - `expires_at`: Expiration date after which new draws cannot be opened.
|
|
22
22
|
// - `obligation`: Reference to a scheduled obligation instrument handling draw repayments.
|
|
23
|
-
// - `availability_policy`: Facility capacity availability policy (`revolving`).
|
|
24
|
-
// - `expiry_policy`: Policy on facility expiration (`freeze_draws`).
|
|
25
|
-
// - `close_policy`: Policy on facility closure (`no_open_draws`).
|
|
26
23
|
//
|
|
27
24
|
// ### Decision ports
|
|
28
25
|
// None on the facility itself. Mandates and decision ports are declared on the linked `obligation` instrument.
|
|
@@ -52,9 +49,6 @@ module std.money_flows.credit_facility
|
|
|
52
49
|
// limit: facilityLimit: money(SAR)
|
|
53
50
|
// expires_at: expiresAt
|
|
54
51
|
// obligation: repayment.obligation
|
|
55
|
-
// availability_policy: revolving
|
|
56
|
-
// expiry_policy: freeze_draws
|
|
57
|
-
// close_policy: no_open_draws
|
|
58
52
|
// }
|
|
59
53
|
// ```
|
|
60
54
|
export instrument credit_facility<C>(
|
|
@@ -64,9 +58,6 @@ export instrument credit_facility<C>(
|
|
|
64
58
|
limit: money<C>,
|
|
65
59
|
expires_at: date,
|
|
66
60
|
obligation: ref,
|
|
67
|
-
availability_policy: text,
|
|
68
|
-
expiry_policy: text,
|
|
69
|
-
close_policy: text,
|
|
70
61
|
) {
|
|
71
62
|
let(draw_id): concat(instrument, "_draw");
|
|
72
63
|
let(parent_field): camel(concat(instrument, "_id"));
|