@hyperscale0/hsx 1.0.0-beta.1 → 1.0.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 +58 -9
- package/LICENSING.md +3 -3
- package/README.md +39 -149
- package/TRADEMARKS.md +4 -4
- package/bin/hsx.ts +2 -0
- package/dist/bin/hsx.js +2 -0
- package/dist/bin/hsx.js.map +1 -1
- package/dist/src/ast.d.ts +135 -10
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/ast.js +24 -0
- package/dist/src/ast.js.map +1 -1
- package/dist/src/cli.d.ts +4 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +172 -16
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +42 -7
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +82 -16
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts +78 -0
- package/dist/src/cost.d.ts.map +1 -0
- package/dist/src/cost.js +369 -0
- package/dist/src/cost.js.map +1 -0
- package/dist/src/diagnostics.d.ts +11 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +420 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/emit.d.ts +34 -0
- package/dist/src/emit.d.ts.map +1 -0
- package/dist/src/emit.js +137 -0
- package/dist/src/emit.js.map +1 -0
- package/dist/src/entry-overrides.d.ts +17 -3
- package/dist/src/entry-overrides.d.ts.map +1 -1
- package/dist/src/entry-overrides.js +180 -35
- package/dist/src/entry-overrides.js.map +1 -1
- package/dist/src/format.d.ts +10 -0
- package/dist/src/format.d.ts.map +1 -0
- package/dist/src/format.js +183 -0
- package/dist/src/format.js.map +1 -0
- package/dist/src/index.d.ts +11 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +10 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/ir.d.ts +66 -0
- package/dist/src/ir.d.ts.map +1 -0
- package/dist/src/ir.js +15 -0
- package/dist/src/ir.js.map +1 -0
- package/dist/src/lex.d.ts +9 -1
- package/dist/src/lex.d.ts.map +1 -1
- package/dist/src/lex.js +50 -4
- package/dist/src/lex.js.map +1 -1
- package/dist/src/limits.d.ts +2 -3
- package/dist/src/limits.d.ts.map +1 -1
- package/dist/src/limits.js +2 -3
- package/dist/src/limits.js.map +1 -1
- package/dist/src/lsp/server.d.ts +32 -0
- package/dist/src/lsp/server.d.ts.map +1 -0
- package/dist/src/lsp/server.js +391 -0
- package/dist/src/lsp/server.js.map +1 -0
- package/dist/src/modules.d.ts +38 -0
- package/dist/src/modules.d.ts.map +1 -0
- package/dist/src/modules.js +368 -0
- package/dist/src/modules.js.map +1 -0
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +590 -17
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts +2 -0
- package/dist/src/std-bundle.d.ts.map +1 -0
- package/dist/src/std-bundle.js +88 -0
- package/dist/src/std-bundle.js.map +1 -0
- package/dist/src/std-library.d.ts +5 -0
- package/dist/src/std-library.d.ts.map +1 -0
- package/dist/src/std-library.js +9 -0
- package/dist/src/std-library.js.map +1 -0
- package/dist/src/typecheck.d.ts +8 -0
- package/dist/src/typecheck.d.ts.map +1 -0
- package/dist/src/typecheck.js +2958 -0
- package/dist/src/typecheck.js.map +1 -0
- package/dist/src/version.d.ts +4 -15
- package/dist/src/version.d.ts.map +1 -1
- package/dist/src/version.js +4 -15
- package/dist/src/version.js.map +1 -1
- package/docs/README.md +26 -0
- package/docs/guide/01-first-program.md +24 -0
- package/docs/guide/02-money.md +25 -0
- package/docs/guide/03-instruments.md +22 -0
- package/docs/guide/04-lifecycles.md +25 -0
- package/docs/guide/05-fees-and-splits.md +45 -0
- package/docs/guide/06-schedules.md +22 -0
- package/docs/guide/07-composition.md +46 -0
- package/docs/guide/08-writing-a-module.md +26 -0
- package/docs/guide/09-cost.md +24 -0
- package/docs/guide/10-diagnostics.md +27 -0
- package/docs/llms-full.txt +1739 -0
- package/docs/llms.txt +45 -0
- package/docs/playground.md +54 -0
- package/docs/reference/cli.md +35 -0
- package/docs/reference/diagnostics.md +619 -0
- package/docs/reference/grammar.md +39 -0
- package/docs/reference/std/advance.md +35 -0
- package/docs/reference/std/cancellable_booking.md +35 -0
- package/docs/reference/std/captured_payment.md +43 -0
- package/docs/reference/std/conditional_disbursement.md +33 -0
- package/docs/reference/std/credit_facility.md +35 -0
- package/docs/reference/std/held_payment.md +64 -0
- package/docs/reference/std/instant_transfer.md +30 -0
- package/docs/reference/std/metered.md +27 -0
- package/docs/reference/std/pooled_split.md +26 -0
- package/docs/reference/std/premium_forward.md +39 -0
- package/docs/reference/std/reconciled_payout.md +33 -0
- package/docs/reference/std/recurring_collection.md +25 -0
- package/docs/reference/std/rotating_pool.md +42 -0
- package/docs/reference/std/scheduled.md +51 -0
- package/docs/reference/std/security_deposit.md +46 -0
- package/docs/reference/std/settlement_batch.md +42 -0
- package/docs/reference/std/swap.md +46 -0
- package/docs/reference/std/threshold_pool.md +47 -0
- package/docs/reference/std/weighted_distribution.md +39 -0
- package/docs/reference/types.md +20 -0
- package/docs/reference/udl-output.md +13 -0
- package/docs/sessions/2026-09-02-two-instruments.hsx +21 -0
- package/docs/sessions/2026-09-02-two-instruments.md +41 -0
- package/examples/01-first-program/README.md +45 -0
- package/examples/01-first-program/tip-jar.hsx +17 -0
- package/examples/02-imports-and-archetypes/README.md +96 -0
- package/examples/02-imports-and-archetypes/photo-booth.hsx +37 -0
- package/examples/03-diagnostics/README.md +68 -0
- package/examples/03-diagnostics/corner-shop-fixed.hsx +22 -0
- package/examples/03-diagnostics/corner-shop.hsx +19 -0
- package/examples/04-complete-product/README.md +84 -0
- package/examples/04-complete-product/study-hall.hsx +67 -0
- package/examples/05-watch-club/README.md +9 -0
- package/examples/05-watch-club/watch-club.hsx +124 -0
- package/examples/README.md +18 -0
- package/examples/advance/README.md +3 -0
- package/examples/advance/advance.hsx +13 -0
- package/examples/advance/advance.udl +339 -0
- package/examples/cancellable_booking/README.md +3 -0
- package/examples/cancellable_booking/cancellable_booking.hsx +14 -0
- package/examples/cancellable_booking/cancellable_booking.udl +391 -0
- package/examples/captured_payment/README.md +3 -0
- package/examples/captured_payment/captured_payment.hsx +21 -0
- package/examples/captured_payment/captured_payment.udl +677 -0
- package/examples/conditional_disbursement/README.md +3 -0
- package/examples/conditional_disbursement/conditional_disbursement.hsx +17 -0
- package/examples/conditional_disbursement/conditional_disbursement.udl +412 -0
- package/examples/cost-table.json +386 -0
- package/examples/credit_facility/README.md +3 -0
- package/examples/credit_facility/credit_facility.hsx +27 -0
- package/examples/credit_facility/credit_facility.udl +1373 -0
- package/examples/held_payment/README.md +3 -0
- package/examples/held_payment/held_payment.hsx +13 -0
- package/examples/held_payment/held_payment.udl +507 -0
- package/examples/instant_transfer/README.md +3 -0
- package/examples/instant_transfer/instant_transfer.hsx +16 -0
- package/examples/instant_transfer/instant_transfer.udl +364 -0
- package/examples/metered/README.md +3 -0
- package/examples/metered/metered.hsx +13 -0
- package/examples/metered/metered.udl +247 -0
- package/examples/pooled_split/README.md +3 -0
- package/examples/pooled_split/pooled_split.hsx +15 -0
- package/examples/pooled_split/pooled_split.udl +425 -0
- package/examples/premium_forward/README.md +3 -0
- package/examples/premium_forward/premium_forward.hsx +19 -0
- package/examples/premium_forward/premium_forward.udl +594 -0
- package/examples/reconciled_payout/README.md +3 -0
- package/examples/reconciled_payout/reconciled_payout.hsx +13 -0
- package/examples/reconciled_payout/reconciled_payout.udl +303 -0
- package/examples/recurring_collection/README.md +3 -0
- package/examples/recurring_collection/recurring_collection.hsx +21 -0
- package/examples/recurring_collection/recurring_collection.udl +1130 -0
- package/examples/rotating_pool/README.md +3 -0
- package/examples/rotating_pool/rotating_pool.hsx +18 -0
- package/examples/rotating_pool/rotating_pool.udl +4352 -0
- package/examples/scheduled/README.md +3 -0
- package/examples/scheduled/scheduled.hsx +12 -0
- package/examples/scheduled/scheduled.udl +335 -0
- package/examples/security_deposit/README.md +3 -0
- package/examples/security_deposit/security_deposit.hsx +21 -0
- package/examples/security_deposit/security_deposit.udl +293 -0
- package/examples/settlement_batch/README.md +3 -0
- package/examples/settlement_batch/settlement_batch.hsx +19 -0
- package/examples/settlement_batch/settlement_batch.udl +753 -0
- package/examples/swap/README.md +3 -0
- package/examples/swap/swap.hsx +19 -0
- package/examples/swap/swap.udl +876 -0
- package/examples/threshold_pool/README.md +3 -0
- package/examples/threshold_pool/threshold_pool.hsx +16 -0
- package/examples/threshold_pool/threshold_pool.udl +667 -0
- package/examples/weighted_distribution/README.md +3 -0
- package/examples/weighted_distribution/weighted_distribution.hsx +20 -0
- package/examples/weighted_distribution/weighted_distribution.udl +337 -0
- package/package.json +27 -6
- package/skills/hsx/SKILL.md +483 -0
- package/src/ast.ts +193 -9
- package/src/cli.ts +191 -20
- package/src/compile.ts +160 -23
- package/src/cost.ts +579 -0
- package/src/diagnostics.ts +442 -0
- package/src/emit.ts +201 -0
- package/src/entry-overrides.ts +263 -48
- package/src/format.ts +261 -0
- package/src/index.ts +34 -4
- package/src/ir.ts +99 -0
- package/src/lex.ts +59 -4
- package/src/limits.ts +2 -3
- package/src/lsp/server.ts +460 -0
- package/src/modules.ts +446 -0
- package/src/parse.ts +655 -21
- package/src/std-bundle.ts +91 -0
- package/src/std-library.ts +12 -0
- package/src/typecheck.ts +3876 -0
- package/src/version.ts +4 -16
- package/std/SEMANTICS.md +130 -0
- package/std/settlements/advance.hsx +177 -0
- package/std/settlements/cancellable_booking.hsx +87 -0
- package/std/settlements/captured_payment.hsx +144 -0
- package/std/settlements/conditional_disbursement.hsx +185 -0
- package/std/settlements/credit_facility.hsx +167 -0
- package/std/settlements/held_payment.hsx +578 -0
- package/std/settlements/index.hsx +3 -0
- package/std/settlements/instant_transfer.hsx +190 -0
- package/std/settlements/metered.hsx +67 -0
- package/std/settlements/pooled_split.hsx +68 -0
- package/std/settlements/premium_forward.hsx +257 -0
- package/std/settlements/reconciled_payout.hsx +77 -0
- package/std/settlements/recurring_collection.hsx +27 -0
- package/std/settlements/rotating_pool.hsx +252 -0
- package/std/settlements/scheduled.hsx +641 -0
- package/std/settlements/security_deposit.hsx +166 -0
- package/std/settlements/settlement_batch.hsx +122 -0
- package/std/settlements/swap.hsx +1114 -0
- package/std/settlements/threshold_pool.hsx +221 -0
- package/std/settlements/weighted_distribution.hsx +141 -0
- package/dist/src/archetypes.d.ts +0 -17
- package/dist/src/archetypes.d.ts.map +0 -1
- package/dist/src/archetypes.js +0 -327
- package/dist/src/archetypes.js.map +0 -1
- package/dist/src/check.d.ts +0 -12
- package/dist/src/check.d.ts.map +0 -1
- package/dist/src/check.js +0 -1910
- package/dist/src/check.js.map +0 -1
- package/dist/src/lower.d.ts +0 -146
- package/dist/src/lower.d.ts.map +0 -1
- package/dist/src/lower.js +0 -4570
- package/dist/src/lower.js.map +0 -1
- package/dist/src/model.d.ts +0 -467
- package/dist/src/model.d.ts.map +0 -1
- package/dist/src/model.js +0 -15
- package/dist/src/model.js.map +0 -1
- package/spec/hsx-ir.schema.json +0 -920
- package/src/archetypes.ts +0 -340
- package/src/check.ts +0 -2938
- package/src/lower.ts +0 -5416
- package/src/model.ts +0 -519
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
module std.settlements.held_payment
|
|
2
|
+
|
|
3
|
+
export instrument held_payment<C>(payer: party, payee: party, amount: money<C>, release: condition, fees: optional<block>, on_cancel: optional<block>, derived_amount: optional<block>, release_to: optional<party>, whole_amount: optional<block>, release_action: optional<text>, whole_fee: optional<money<C>>, reference: optional<text>, upstream: optional<ref>, id_prefix_override: optional<text>) {
|
|
4
|
+
let(release_name): release;
|
|
5
|
+
let(release_actor): at(release_allowed, 1);
|
|
6
|
+
let(release_actor_2): at(release_allowed, 2);
|
|
7
|
+
let(release_multiple): lt(1, len(release_allowed));
|
|
8
|
+
let(release_role): if_eq(release_actor, payer, "payer", "beneficiary");
|
|
9
|
+
let(release_role_2): if_eq(release_actor_2, payer, "payer", "beneficiary");
|
|
10
|
+
let(release_actor_text): if_eq(len(release_allowed), 2, concat(words(release_actor), " or ", words(release_actor_2)), words(release_actor));
|
|
11
|
+
let(payer_account_field): camel(concat(payer, "_account_id"));
|
|
12
|
+
let(payee_account_field): camel(concat(payee, "_account_id"));
|
|
13
|
+
let(release_to_account_field): camel(concat(release_to, "_account_id"));
|
|
14
|
+
let(payer_fee): get(fees, payer);
|
|
15
|
+
let(payee_fee): get(fees, payee);
|
|
16
|
+
let(payer_fee_kind): kind(payer_fee);
|
|
17
|
+
let(payee_fee_kind): kind(payee_fee);
|
|
18
|
+
when_eq(payee_fee_kind, binding) {
|
|
19
|
+
when(on_cancel) {
|
|
20
|
+
unsupported {
|
|
21
|
+
code: HSX1110;
|
|
22
|
+
message: "exact payee fees cannot combine with on_cancel yet";
|
|
23
|
+
fix: "remove on_cancel or use a percentage payee fee";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
let(payer_bps): basis_points(payer_fee);
|
|
28
|
+
let(payee_bps): basis_points(payee_fee);
|
|
29
|
+
let(net_bps): sub(10000, payee_fee);
|
|
30
|
+
let(net_percent): percent_text(net_bps);
|
|
31
|
+
let(cancel_payer): get(on_cancel, payer);
|
|
32
|
+
let(cancel_payee): get(on_cancel, payee);
|
|
33
|
+
let(cancel_payer_bps): basis_points(cancel_payer);
|
|
34
|
+
let(cancel_payee_bps): basis_points(cancel_payee);
|
|
35
|
+
let(cancel_splits): lt(1, len(on_cancel));
|
|
36
|
+
let(piece1_bps): min(net_bps, cancel_payer_bps);
|
|
37
|
+
let(piece3_bps): min(payee_bps, cancel_payee_bps);
|
|
38
|
+
let(piece2_bps): sub(sub(10000, piece1_bps), piece3_bps);
|
|
39
|
+
let(piece1_percent): percent_text(piece1_bps);
|
|
40
|
+
let(piece2_percent): percent_text(piece2_bps);
|
|
41
|
+
let(piece3_percent): percent_text(piece3_bps);
|
|
42
|
+
let(carved_spans): lt(cancel_payee_bps, payee_bps);
|
|
43
|
+
let(derived_field): get(derived_amount, "field");
|
|
44
|
+
let(derived_source): get(derived_amount, "source");
|
|
45
|
+
let(derived_rule): get(derived_amount, "rule");
|
|
46
|
+
let(derived_bearer): get(derived_amount, "bearer");
|
|
47
|
+
agent_description: concat("Reach for ", words(instrument), " when the ", words(payer), " must commit money up front and nobody is paid until ", release, " fires. Funding moves ", amount, " into this settlement's own escrow account, where it stays until a release or an unwind pays it out. A straight-through payment with no custody step is a different shape.");
|
|
48
|
+
when_not(whole_amount) {
|
|
49
|
+
summary: concat("Escrow-held payment from ", words(payer), " to ", words(payee));
|
|
50
|
+
title: sentence(instrument);
|
|
51
|
+
when_not(release_to) { description: concat("Held payment: the ", words(payer), " funds ", amount, " into this settlement's own escrow; ", release_actor_text, " confirms through ", release, " to release"); }
|
|
52
|
+
when(release_to) { description: concat("Held payment: the ", words(payer), " funds ", amount, " into this settlement's own escrow; ", release_actor_text, " confirms through ", release, " to release to the ", words(release_to), ", whose advance the ", words(payee), " repays out of it"); }
|
|
53
|
+
fields {
|
|
54
|
+
let(fee_collection_key): "x-hyperscale-fee-collection-port";
|
|
55
|
+
let(reference_filter_key): "x-hyperscale-reference-filter";
|
|
56
|
+
when(release_to) {
|
|
57
|
+
[release_to_account_field] { type: account<C>; description: concat("The ", words(release_to), " account"); pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }
|
|
58
|
+
}
|
|
59
|
+
platformAccountId {
|
|
60
|
+
type: account<C>;
|
|
61
|
+
description: "The product fee collection account";
|
|
62
|
+
pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$";
|
|
63
|
+
[fee_collection_key]: true;
|
|
64
|
+
[reference_filter_key]: { column: role; values: [customer_balance, product_revenue]; };
|
|
65
|
+
}
|
|
66
|
+
when_not(payee_fee) {
|
|
67
|
+
when_not(cancel_splits) { amount { type: money<C>; description: "The held amount in {C} minor units, funded and paid out whole"; } }
|
|
68
|
+
when(cancel_splits) {
|
|
69
|
+
amount { type: money<C>; description: "The gross held amount in {C} minor units; the piece fields below partition it exactly"; }
|
|
70
|
+
piece1Amount { type: money<C>; description: concat(cancel_payer, " of ", amount, " (carries the integer-division remainder): released to the ", payee, "; on cancellation to the ", payer, ". Computed as floor(", amount, " * ", cancel_payer_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }
|
|
71
|
+
piece2Amount { type: money<C>; description: concat(cancel_payee, " of ", amount, ": released to the ", payee, "; on cancellation to the ", payee, ". Computed as floor(", amount, " * ", cancel_payee_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
when(payee_fee) {
|
|
75
|
+
amount { type: money<C>; description: "The gross held amount in {C} minor units; the piece fields below partition it exactly"; }
|
|
76
|
+
when_not(on_cancel) {
|
|
77
|
+
when_eq(payee_fee_kind, percent) {
|
|
78
|
+
piece1Amount { type: money<C>; description: concat(net_percent, " of ", amount, " (carries the integer-division remainder): released to the ", payee, ". Computed as floor(", amount, " * ", net_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }
|
|
79
|
+
piece2Amount { type: money<C>; description: concat(payee_fee, " of ", amount, ": released to the platform. Computed as floor(", amount, " * ", payee_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; optional: true; }
|
|
80
|
+
}
|
|
81
|
+
when_eq(payee_fee_kind, binding) {
|
|
82
|
+
piece1Amount { type: money<C>; description: concat("The remainder of ", amount, " after the exact ", payee_fee, " fee; released to the ", payee); pattern: "^[1-9][0-9]{0,17}$"; }
|
|
83
|
+
[payee_fee] { type: payee_fee; description: concat("Immutable exact ", payee, " fee in ", C, " minor units; released to the platform"); pattern: "^[1-9][0-9]{0,17}$"; }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
when(on_cancel) {
|
|
87
|
+
piece1Amount { type: money<C>; description: concat(piece1_percent, " of ", amount, " (carries the integer-division remainder): released to the ", payee, "; on cancellation to the ", payer, ". Computed as floor(", amount, " * ", piece1_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }
|
|
88
|
+
when(carved_spans) {
|
|
89
|
+
piece2Amount { type: money<C>; description: concat(piece2_percent, " of ", amount, ": released to the platform; on cancellation to the ", payer, ". Computed as floor(", amount, " * ", piece2_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }
|
|
90
|
+
piece3Amount { type: money<C>; description: concat(piece3_percent, " of ", amount, ": released to the platform; on cancellation to the ", payee, ". Computed as floor(", amount, " * ", piece3_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }
|
|
91
|
+
carvedFeeAmount { type: money<C>; description: "Machine-owned total of the 2 carved platform fee pieces"; pattern: "^[1-9][0-9]{0,17}$"; optional: true; }
|
|
92
|
+
}
|
|
93
|
+
when_not(carved_spans) {
|
|
94
|
+
piece2Amount { type: money<C>; description: concat(piece2_percent, " of ", amount, ": released to the ", payee, "; on cancellation to the ", payee, ". Computed as floor(", amount, " * ", piece2_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }
|
|
95
|
+
piece3Amount { type: money<C>; description: concat(piece3_percent, " of ", amount, ": released to the platform; on cancellation to the ", payee, ". Computed as floor(", amount, " * ", piece3_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; optional: true; }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
when_eq(payer_fee_kind, percent) { serviceFeeAmount { type: money<C>; description: concat("Machine-owned ", payer_fee, " service fee computed from ", amount, "; charged on top at funding"); pattern: "^[1-9][0-9]{0,17}$"; optional: true; } }
|
|
100
|
+
when_eq(payer_fee_kind, binding) { [payer_fee] { type: payer_fee; description: concat("Immutable exact ", payer, " fee in ", C, " minor units; charged on top at funding"); pattern: "^[1-9][0-9]{0,17}$"; } }
|
|
101
|
+
when(release_deadline) {
|
|
102
|
+
when_not(release_to) { [release_deadline] { type: date; description: concat("The date an undecided hold releases to the ", words(payee), " on; ", release, " and cancellation decide only before it"); } }
|
|
103
|
+
when(release_to) { [release_deadline] { type: date; description: concat("The date an undecided hold releases to the ", words(release_to), " on; ", release, " and cancellation decide only before it"); } }
|
|
104
|
+
}
|
|
105
|
+
when(derived_amount) { [derived_field] { type: money<C>; description: "Machine-computed {derived_rule} of {derived_source}; callers never supply it"; pattern: "^[1-9][0-9]{0,17}$"; optional: true; } }
|
|
106
|
+
}
|
|
107
|
+
lifecycle {
|
|
108
|
+
when_not(payee_fee) {
|
|
109
|
+
when_not(cancel_splits) {
|
|
110
|
+
states created funded released;
|
|
111
|
+
when(on_cancel) { states cancelled; }
|
|
112
|
+
states abandoned;
|
|
113
|
+
}
|
|
114
|
+
when(cancel_splits) { states created funding_1 funded releasing_1 released cancelling_1 cancelled abandoned; }
|
|
115
|
+
}
|
|
116
|
+
when(payee_fee) {
|
|
117
|
+
when_not(on_cancel) { states created funding_1 funded releasing_1 released abandoned; }
|
|
118
|
+
when(on_cancel) {
|
|
119
|
+
when_eq(payer_fee_kind, percent) { states created funding_1 funding_2 funding_3 funded releasing_1 releasing_2 released cancelling_1 cancelling_2 cancelled abandoning_1 abandoned abandoning_2; }
|
|
120
|
+
when_eq(payer_fee_kind, none) { states created funding_1 funding_2 funded releasing_1 releasing_2 released cancelling_1 cancelling_2 cancelled abandoning_1 abandoned; }
|
|
121
|
+
when_eq(payer_fee_kind, binding) { states created funding_1 funding_2 funded releasing_1 releasing_2 released cancelling_1 cancelling_2 cancelled abandoning_1 abandoned; }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
initial created;
|
|
125
|
+
when_not(payee_fee) {
|
|
126
|
+
when_not(cancel_splits) {
|
|
127
|
+
on fund_piece_1: created -> funded;
|
|
128
|
+
on [release_name]: funded -> released;
|
|
129
|
+
when(release_deadline) { on release_on_deadline: funded -> released; }
|
|
130
|
+
when(on_cancel) { on cancel: funded -> cancelled; }
|
|
131
|
+
}
|
|
132
|
+
when(cancel_splits) {
|
|
133
|
+
on fund_piece_1: created -> funding_1;
|
|
134
|
+
on fund_piece_2: funding_1 -> funded;
|
|
135
|
+
on [release_name]: funded -> releasing_1;
|
|
136
|
+
on release_piece_2: releasing_1 -> released;
|
|
137
|
+
on cancel: funded -> cancelling_1;
|
|
138
|
+
on refund_piece_2: cancelling_1 -> cancelled;
|
|
139
|
+
on unfund_piece_1: funding_1 -> abandoned;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
when(payee_fee) {
|
|
143
|
+
when_not(on_cancel) {
|
|
144
|
+
on fund_piece_1: created -> funding_1;
|
|
145
|
+
on fund_piece_2: funding_1 -> funded;
|
|
146
|
+
on [release_name]: funded -> releasing_1;
|
|
147
|
+
when(release_deadline) { on release_on_deadline: funded -> releasing_1; }
|
|
148
|
+
on release_piece_2: releasing_1 -> released;
|
|
149
|
+
on unfund_piece_1: funding_1 -> abandoned;
|
|
150
|
+
}
|
|
151
|
+
when(on_cancel) {
|
|
152
|
+
on fund_piece_1: created -> funding_1;
|
|
153
|
+
on fund_piece_2: funding_1 -> funding_2;
|
|
154
|
+
when_eq(payer_fee_kind, percent) {
|
|
155
|
+
on fund_piece_3: funding_2 -> funding_3;
|
|
156
|
+
on collect_service_fee: funding_3 -> funded;
|
|
157
|
+
}
|
|
158
|
+
when_eq(payer_fee_kind, none) { on fund_piece_3: funding_2 -> funded; }
|
|
159
|
+
when_eq(payer_fee_kind, binding) { on fund_piece_3: funding_2 -> funded; }
|
|
160
|
+
on [release_name]: funded -> releasing_1;
|
|
161
|
+
on release_piece_2: releasing_1 -> releasing_2;
|
|
162
|
+
on release_piece_3: releasing_2 -> released;
|
|
163
|
+
on cancel: funded -> cancelling_1;
|
|
164
|
+
on refund_piece_2: cancelling_1 -> cancelling_2;
|
|
165
|
+
on refund_piece_3: cancelling_2 -> cancelled;
|
|
166
|
+
when_eq(payer_fee_kind, percent) {
|
|
167
|
+
on unfund_piece_3: funding_3 -> abandoning_2;
|
|
168
|
+
on unfund_piece_2: funding_2 | abandoning_2 -> abandoning_1;
|
|
169
|
+
}
|
|
170
|
+
when_eq(payer_fee_kind, none) { on unfund_piece_2: funding_2 -> abandoning_1; }
|
|
171
|
+
when_eq(payer_fee_kind, binding) { on unfund_piece_2: funding_2 -> abandoning_1; }
|
|
172
|
+
on unfund_piece_1: funding_1 | abandoning_1 -> abandoned;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
on abandon: created -> abandoned;
|
|
176
|
+
}
|
|
177
|
+
parties {
|
|
178
|
+
payer: payer;
|
|
179
|
+
beneficiary: payee;
|
|
180
|
+
}
|
|
181
|
+
when_eq(payer_fee_kind, percent) {
|
|
182
|
+
computes fees: { amount_field: serviceFeeAmount; base_field: amount; bearer_field: payer_account_field; position: on_top; rule: { kind: bps; bps: payer_bps; }; };
|
|
183
|
+
}
|
|
184
|
+
when_eq(payer_fee_kind, binding) {
|
|
185
|
+
computes fees: { amount_field: camel(payer_fee); base_field: amount; bearer_field: payer_account_field; position: on_top; rule: { kind: exact; field: camel(payer_fee); currency_field: currency; }; };
|
|
186
|
+
}
|
|
187
|
+
when(payee_fee) {
|
|
188
|
+
when(on_cancel) {
|
|
189
|
+
action fund_piece_2 {
|
|
190
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
191
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
192
|
+
let(metadata_phase): "metadata.phase";
|
|
193
|
+
agent_description: concat("Move piece 2 of the split amount from the ", words(payer), " into escrow. Piece 1 must be in escrow first. It debits the ", words(payer), " for real, and the hold is still short of funded until piece 3 lands.");
|
|
194
|
+
summary: "Fund piece 2 of the held amount into escrow";
|
|
195
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }];
|
|
196
|
+
steps: [];
|
|
197
|
+
}
|
|
198
|
+
action fund_piece_3 {
|
|
199
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
200
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
201
|
+
let(metadata_phase): "metadata.phase";
|
|
202
|
+
agent_description: concat("Move piece 3 of the split amount from the ", words(payer), " into escrow. Pieces 1 and 2 must be in escrow first. It debits the ", words(payer), " for real. Where the product charges a percentage payer fee, one more call collects that fee before the hold counts as funded.");
|
|
203
|
+
summary: "Fund piece 3 of the held amount into escrow";
|
|
204
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece3TransferTransferId: transferId; }; }];
|
|
205
|
+
steps: [];
|
|
206
|
+
}
|
|
207
|
+
when_eq(payer_fee_kind, percent) {
|
|
208
|
+
action collect_service_fee {
|
|
209
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
210
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
211
|
+
let(metadata_phase): "metadata.phase";
|
|
212
|
+
agent_description: concat("Charge the ", words(payer), " the percentage service fee on top of the held amount and pay it to the platform account. All three funding pieces must be in escrow first. It debits the ", words(payer), " for real and it is the last step of funding.");
|
|
213
|
+
summary: "Collect the payer-side service fee";
|
|
214
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.serviceFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: collect_service_fee; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { collectServiceFeeTransferTransferId: transferId; }; }];
|
|
215
|
+
steps: [];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
action release_piece_2 {
|
|
219
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
220
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
221
|
+
let(metadata_phase): "metadata.phase";
|
|
222
|
+
agent_description: concat("Pay piece 2 out of escrow once the release decision has run. Its destination follows the product fee split, either the platform account or the ", words(payee), ". Money leaves escrow on this call and does not come back.");
|
|
223
|
+
summary: "Release piece 2 of the held amount";
|
|
224
|
+
when(carved_spans) { let(release_piece_2_destination): "fields.platformAccountId"; }
|
|
225
|
+
when_not(carved_spans) { let(release_piece_2_destination): concat("fields.", payee_account_field); }
|
|
226
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: release_piece_2_destination; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }];
|
|
227
|
+
steps: [];
|
|
228
|
+
}
|
|
229
|
+
action release_piece_3 {
|
|
230
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
231
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
232
|
+
let(metadata_phase): "metadata.phase";
|
|
233
|
+
agent_description: concat("Pay piece 3 out of escrow to the platform account, which closes the release. Piece 2 must be released first. Money leaves escrow on this call and does not come back.");
|
|
234
|
+
summary: "Release piece 3 of the held amount";
|
|
235
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece3TransferTransferId: transferId; }; }];
|
|
236
|
+
steps: [];
|
|
237
|
+
}
|
|
238
|
+
action refund_piece_2 {
|
|
239
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
240
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
241
|
+
let(metadata_phase): "metadata.phase";
|
|
242
|
+
agent_description: concat("Return piece 2 out of escrow once cancel has run. Its destination follows the product fee split, either the ", words(payer), " or the ", words(payee), ". Money leaves escrow on this call and does not come back.");
|
|
243
|
+
summary: "Return piece 2 on cancellation";
|
|
244
|
+
when(carved_spans) { let(refund_piece_2_destination): concat("fields.", payer_account_field); }
|
|
245
|
+
when_not(carved_spans) { let(refund_piece_2_destination): concat("fields.", payee_account_field); }
|
|
246
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refund_piece_2_destination; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: refund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { refundPiece2TransferTransferId: transferId; }; }];
|
|
247
|
+
steps: [];
|
|
248
|
+
}
|
|
249
|
+
action refund_piece_3 {
|
|
250
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
251
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
252
|
+
let(metadata_phase): "metadata.phase";
|
|
253
|
+
agent_description: concat("Return piece 3 out of escrow to the ", words(payee), ", which closes the cancellation. Piece 2 must be returned first. Money leaves escrow on this call and does not come back.");
|
|
254
|
+
summary: "Return piece 3 on cancellation";
|
|
255
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: refund_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { refundPiece3TransferTransferId: transferId; }; }];
|
|
256
|
+
steps: [];
|
|
257
|
+
}
|
|
258
|
+
action unfund_piece_1 {
|
|
259
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
260
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
261
|
+
let(metadata_phase): "metadata.phase";
|
|
262
|
+
agent_description: concat("Return piece 1 from escrow to the ", words(payer), " and abandon the ", words(instrument), ". It runs when only piece 1 was ever funded, or after the later pieces have already gone back. The settlement ends abandoned.");
|
|
263
|
+
summary: "Return piece 1 to the {payer} on abandonment";
|
|
264
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece1TransferTransferId: transferId; }; }];
|
|
265
|
+
steps: [];
|
|
266
|
+
}
|
|
267
|
+
action unfund_piece_2 {
|
|
268
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
269
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
270
|
+
let(metadata_phase): "metadata.phase";
|
|
271
|
+
agent_description: concat("Return piece 2 from escrow to the ", words(payer), " while abandoning a part-funded ", words(instrument), ". Funding must have stopped at piece 2, or piece 3 must already be back. Piece 1 still has to be returned after this.");
|
|
272
|
+
summary: "Return piece 2 to the {payer} on abandonment";
|
|
273
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece2TransferTransferId: transferId; }; }];
|
|
274
|
+
steps: [];
|
|
275
|
+
}
|
|
276
|
+
when_eq(payer_fee_kind, percent) {
|
|
277
|
+
action unfund_piece_3 {
|
|
278
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
279
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
280
|
+
let(metadata_phase): "metadata.phase";
|
|
281
|
+
agent_description: concat("Return piece 3 from escrow to the ", words(payer), " while abandoning a ", words(instrument), " funded as far as piece 3. Pieces 2 and 1 still have to be returned after this, in that order.");
|
|
282
|
+
summary: "Return piece 3 to the {payer} on abandonment";
|
|
283
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece3TransferTransferId: transferId; }; }];
|
|
284
|
+
steps: [];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
when_not(on_cancel) {
|
|
289
|
+
when_eq(payee_fee_kind, percent) {
|
|
290
|
+
computes fees: { amount_field: piece2Amount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: bps; bps: payee_bps; }; };
|
|
291
|
+
partitions: { total_field: amount; piece_fields: [piece1Amount, piece2Amount]; };
|
|
292
|
+
}
|
|
293
|
+
when_eq(payee_fee_kind, binding) {
|
|
294
|
+
computes fees: { amount_field: camel(payee_fee); base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: exact; field: camel(payee_fee); currency_field: currency; }; };
|
|
295
|
+
partitions: { total_field: amount; piece_fields: [piece1Amount, camel(payee_fee)]; };
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
when(on_cancel) {
|
|
299
|
+
when_eq(payee_fee_kind, percent) {
|
|
300
|
+
when(carved_spans) {
|
|
301
|
+
computes fees: { amount_field: carvedFeeAmount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: bps; bps: payee_bps; }; };
|
|
302
|
+
}
|
|
303
|
+
when_not(carved_spans) {
|
|
304
|
+
computes fees: { amount_field: piece3Amount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: bps; bps: payee_bps; }; };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
partitions: { total_field: amount; piece_fields: [piece1Amount, piece2Amount, piece3Amount]; };
|
|
308
|
+
when(carved_spans) { partitions: { total_field: carvedFeeAmount; piece_fields: [piece2Amount, piece3Amount]; }; }
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
when_not(payee_fee) {
|
|
312
|
+
when(cancel_splits) { partitions: { total_field: amount; piece_fields: [piece1Amount, piece2Amount]; }; }
|
|
313
|
+
}
|
|
314
|
+
when(derived_amount) {
|
|
315
|
+
computes derived: { field: derived_field; source_field: derived_source; rounding: floor; rule: { kind: percentage_of; bps: derived_rule; }; };
|
|
316
|
+
}
|
|
317
|
+
action create {
|
|
318
|
+
let(owner_id): "owner.id";
|
|
319
|
+
let(owner_type): "owner.type";
|
|
320
|
+
agent_description: concat("Open the ", words(instrument), " and provision the escrow account that holds the funds. This is the first call, and every funding and release action moves money through that account. It does not move the held amount itself.");
|
|
321
|
+
summary: concat("Create a ", words(instrument), " settlement");
|
|
322
|
+
when_not(derived_amount) { moves: []; }
|
|
323
|
+
when(derived_amount) {
|
|
324
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
325
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
326
|
+
let(metadata_phase): "metadata.phase";
|
|
327
|
+
moves: [{ key: "derived_amount"; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", derived_field); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: create; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", camel(derived_bearer), "AccountId"); }; }; capture: { createDerivedAmountTransferId: transferId; }; }];
|
|
328
|
+
}
|
|
329
|
+
steps: [{ operation: "account.escrow.provision"; bind: { currency: { from: instance; path: fields.currency; }; [owner_id]: { from: instance; path: productId; }; [owner_type]: { from: const; value: product; }; productId: { from: instance; path: productId; }; role: { from: const; value: product_escrow; }; }; capture: { escrowAccountId: accountId; }; }];
|
|
330
|
+
}
|
|
331
|
+
action fund_piece_1 {
|
|
332
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
333
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
334
|
+
let(metadata_phase): "metadata.phase";
|
|
335
|
+
agent_description: concat("Move the ", words(payer), "'s money into this settlement's escrow. Call it after create and before any release. It debits the ", words(payer), " for real, and where the product charges a payer-side fee that fee is taken on top in the same call. Only a later release or unwind moves the money again.");
|
|
336
|
+
summary: "Fund piece 1 of the held amount into escrow";
|
|
337
|
+
when_not(payee_fee) {
|
|
338
|
+
when_not(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1TransferTransferId: transferId; }; }]; }
|
|
339
|
+
when(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1TransferTransferId: transferId; }; }]; }
|
|
340
|
+
}
|
|
341
|
+
when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1TransferTransferId: transferId; }; }]; }
|
|
342
|
+
when_eq(payer_fee_kind, percent) {
|
|
343
|
+
when_not(payee_fee) { moves: [{ key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.serviceFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1ServiceFeeTransferId: transferId; }; }]; }
|
|
344
|
+
when(payee_fee) {
|
|
345
|
+
when_not(on_cancel) { moves: [{ key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.serviceFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1ServiceFeeTransferId: transferId; }; }]; }
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
when_eq(payer_fee_kind, binding) { moves: [{ key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", payer_fee); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1ServiceFeeTransferId: transferId; }; }]; }
|
|
349
|
+
steps: [];
|
|
350
|
+
}
|
|
351
|
+
action [release_name] {
|
|
352
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
353
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
354
|
+
let(metadata_phase): "metadata.phase";
|
|
355
|
+
let(release_capture): camel(concat(release_name, "_transfer_transfer_id"));
|
|
356
|
+
agent_description: concat("Confirm ", release, " and start paying the held money out of escrow. The ", words(instrument), " must be funded first, and the caller must be ", release_actor_text, ". Where the product requires a decision reference, supply it here. Money leaves escrow on this call and does not come back.");
|
|
357
|
+
summary: concat("Confirm through ", release, " and start the release payout");
|
|
358
|
+
when(release_deadline) { deadline { field: release_deadline; }; }
|
|
359
|
+
when(release_fields) {
|
|
360
|
+
input {
|
|
361
|
+
type: object;
|
|
362
|
+
additional_properties: false;
|
|
363
|
+
properties {
|
|
364
|
+
for input_name in keys(release_fields) {
|
|
365
|
+
[input_name] { type: string; description: "Required decision reference retained in the operation receipt"; max_length: 180; min_length: 1; }
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
required: keys(release_fields);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
when_not(release_multiple) { port { allowed_parties: [release_role]; }; }
|
|
372
|
+
when(release_multiple) { port { allowed_parties: [release_role, release_role_2]; }; }
|
|
373
|
+
when_not(payee_fee) {
|
|
374
|
+
when_not(cancel_splits) {
|
|
375
|
+
when_not(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }
|
|
376
|
+
when(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", release_to_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }
|
|
377
|
+
}
|
|
378
|
+
when(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }
|
|
379
|
+
}
|
|
380
|
+
when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }
|
|
381
|
+
steps: [];
|
|
382
|
+
}
|
|
383
|
+
when(release_deadline) {
|
|
384
|
+
action release_on_deadline {
|
|
385
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
386
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
387
|
+
let(metadata_phase): "metadata.phase";
|
|
388
|
+
when_not(release_to) { summary: "Release to the {payee} when {release_deadline} arrives undecided"; }
|
|
389
|
+
when(release_to) { summary: "Release to the {release_to} when {release_deadline} arrives undecided"; }
|
|
390
|
+
due { field: release_deadline; };
|
|
391
|
+
when_not(payee_fee) {
|
|
392
|
+
when_not(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_on_deadline; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseOnDeadlineTransferTransferId: transferId; }; }]; }
|
|
393
|
+
when(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", release_to_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_on_deadline; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseOnDeadlineTransferTransferId: transferId; }; }]; }
|
|
394
|
+
}
|
|
395
|
+
when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_on_deadline; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseOnDeadlineTransferTransferId: transferId; }; }]; }
|
|
396
|
+
steps: [];
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
when(on_cancel) {
|
|
400
|
+
action cancel {
|
|
401
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
402
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
403
|
+
let(metadata_phase): "metadata.phase";
|
|
404
|
+
agent_description: concat("Cancel the ", words(instrument), " and start returning the held money to the ", words(payer), ". It must be funded and not yet released. Cancellation is final, and a cancelled settlement never releases. Where the product sets a release deadline, this call is refused once that date passes.");
|
|
405
|
+
summary: "Cancel the settlement and start the unwind";
|
|
406
|
+
when(release_deadline) { deadline { field: release_deadline; }; }
|
|
407
|
+
when_not(payee_fee) {
|
|
408
|
+
when_not(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: cancel; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelTransferTransferId: transferId; }; }]; }
|
|
409
|
+
when(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: cancel; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelTransferTransferId: transferId; }; }]; }
|
|
410
|
+
}
|
|
411
|
+
when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: cancel; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelTransferTransferId: transferId; }; }]; }
|
|
412
|
+
steps: [];
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
when(payee_fee) {
|
|
416
|
+
when_not(on_cancel) {
|
|
417
|
+
action fund_piece_2 {
|
|
418
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
419
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
420
|
+
let(metadata_phase): "metadata.phase";
|
|
421
|
+
agent_description: concat("Move the carved platform fee piece from the ", words(payer), " into escrow, which completes funding. Piece 1 must be in escrow first. It debits the ", words(payer), " for real, and the whole held amount then sits in escrow.");
|
|
422
|
+
summary: "Fund piece 2 of the held amount into escrow";
|
|
423
|
+
when_eq(payee_fee_kind, percent) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }]; }
|
|
424
|
+
when_eq(payee_fee_kind, binding) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", payee_fee); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }]; }
|
|
425
|
+
steps: [];
|
|
426
|
+
}
|
|
427
|
+
action release_piece_2 {
|
|
428
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
429
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
430
|
+
let(metadata_phase): "metadata.phase";
|
|
431
|
+
agent_description: concat("Pay the carved platform fee out of escrow to the platform account, which closes the release. The release decision must have run first. Money leaves escrow on this call and does not come back.");
|
|
432
|
+
summary: "Release piece 2 of the held amount";
|
|
433
|
+
when_eq(payee_fee_kind, percent) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }]; }
|
|
434
|
+
when_eq(payee_fee_kind, binding) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", payee_fee); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }]; }
|
|
435
|
+
steps: [];
|
|
436
|
+
}
|
|
437
|
+
action unfund_piece_1 {
|
|
438
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
439
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
440
|
+
let(metadata_phase): "metadata.phase";
|
|
441
|
+
agent_description: concat("Return piece 1 from escrow to the ", words(payer), " and abandon the ", words(instrument), ". It runs only while funding stopped after piece 1, before the release. The settlement ends abandoned.");
|
|
442
|
+
summary: "Return piece 1 to the {payer} on abandonment";
|
|
443
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece1TransferTransferId: transferId; }; }];
|
|
444
|
+
steps: [];
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
when_not(payee_fee) {
|
|
449
|
+
when(cancel_splits) {
|
|
450
|
+
action fund_piece_2 {
|
|
451
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
452
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
453
|
+
let(metadata_phase): "metadata.phase";
|
|
454
|
+
agent_description: concat("Move piece 2 of the split amount from the ", words(payer), " into escrow, which completes funding. Piece 1 must be in escrow first. It debits the ", words(payer), " for real, and the whole held amount then sits in escrow.");
|
|
455
|
+
summary: "Fund piece 2 of the held amount into escrow";
|
|
456
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }];
|
|
457
|
+
steps: [];
|
|
458
|
+
}
|
|
459
|
+
action release_piece_2 {
|
|
460
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
461
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
462
|
+
let(metadata_phase): "metadata.phase";
|
|
463
|
+
agent_description: concat("Pay piece 2 out of escrow to the ", words(payee), ", which closes the release. The release decision must have run first. Money leaves escrow on this call and does not come back.");
|
|
464
|
+
summary: "Release piece 2 of the held amount";
|
|
465
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }];
|
|
466
|
+
steps: [];
|
|
467
|
+
}
|
|
468
|
+
action refund_piece_2 {
|
|
469
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
470
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
471
|
+
let(metadata_phase): "metadata.phase";
|
|
472
|
+
agent_description: concat("Return piece 2 out of escrow to the ", words(payee), ", which closes the cancellation. Cancel must have run first. Money leaves escrow on this call and does not come back.");
|
|
473
|
+
summary: "Return piece 2 on cancellation";
|
|
474
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: refund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { refundPiece2TransferTransferId: transferId; }; }];
|
|
475
|
+
steps: [];
|
|
476
|
+
}
|
|
477
|
+
action unfund_piece_1 {
|
|
478
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
479
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
480
|
+
let(metadata_phase): "metadata.phase";
|
|
481
|
+
agent_description: concat("Return piece 1 from escrow to the ", words(payer), " and abandon the ", words(instrument), ". It runs only while funding stopped after piece 1, before the release. The settlement ends abandoned.");
|
|
482
|
+
summary: "Return piece 1 to the {payer} on abandonment";
|
|
483
|
+
moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece1TransferTransferId: transferId; }; }];
|
|
484
|
+
steps: [];
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
action abandon {
|
|
489
|
+
agent_description: concat("Close a ", words(instrument), " that was never funded. The escrow account must be empty, so this refuses once any piece is in it. It moves no money and the settlement ends here with no way back.");
|
|
490
|
+
summary: "Abandon the settlement before any money is held";
|
|
491
|
+
requires drained: { path: refs.escrowAccountId; };
|
|
492
|
+
moves: [];
|
|
493
|
+
steps: [];
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
when(whole_amount) {
|
|
497
|
+
let(fee_collection_key): "x-hyperscale-fee-collection-port";
|
|
498
|
+
let(reference_filter_key): "x-hyperscale-reference-filter";
|
|
499
|
+
let(whole_fee_field): camel(whole_fee);
|
|
500
|
+
title: sentence(instrument);
|
|
501
|
+
id_prefix: if_eq(id_prefix_override, false, prefix(instrument), id_prefix_override);
|
|
502
|
+
summary: concat("Escrow-held payment from ", words(payer), " to ", words(payee));
|
|
503
|
+
description: concat("Held payment: the ", words(payer), " funds ", amount, " into this settlement's own escrow; ", release_actor_text, " confirms through ", release, " to release");
|
|
504
|
+
distinct_parties: true;
|
|
505
|
+
caller_parked_states: { created: "The tenant either funds the hold or abandons it before any money moves."; };
|
|
506
|
+
fields {
|
|
507
|
+
currency { type: text; description: "One currency for the hold and its fee"; const: C; }
|
|
508
|
+
amount { type: money<C>; description: "Amount held in custody, in {C} minor units"; }
|
|
509
|
+
[payee]AccountId { type: account<C>; description: "Party the hold protects and pays on release"; pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }
|
|
510
|
+
[payer]AccountId { type: account<C>; description: "Party whose money is held until the hold resolves"; pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }
|
|
511
|
+
platformAccountId { type: account<C>; description: "Product revenue or customer balance account collecting the custody fee"; pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; [fee_collection_key]: true; [reference_filter_key]: { column: role; values: [customer_balance, product_revenue]; }; }
|
|
512
|
+
when(reference) { reference { type: text; description: "The tenant's own identifier for what is held: invoice number, project code, contract reference"; optional: true; max_length: 180; min_length: 1; } }
|
|
513
|
+
[release_deadline] { type: date; description: "Date the hold releases to the payee on its own; fixed at creation and the cutoff for both the release port and cancellation"; }
|
|
514
|
+
whole_fee { type: money<C>; description: "Custody fee charged on top at funding and never placed in escrow"; }
|
|
515
|
+
when(upstream) { upstream { type: ref<instrument>; description: "Hold this one is carved out of, when a chain of holds passes the same money down"; optional: true; pattern: concat("^", id_prefix_override, "_(sandbox|live)_[a-z0-9]{8,64}$"); } }
|
|
516
|
+
}
|
|
517
|
+
lifecycle {
|
|
518
|
+
states created funded released cancelled abandoned;
|
|
519
|
+
initial created;
|
|
520
|
+
on abandon: created -> abandoned;
|
|
521
|
+
on cancel: funded -> cancelled;
|
|
522
|
+
on fund: created -> funded;
|
|
523
|
+
on [release_action]: funded -> released;
|
|
524
|
+
on release_on_deadline: funded -> released;
|
|
525
|
+
}
|
|
526
|
+
parties { beneficiary: payee_account_field; payer: payer_account_field; }
|
|
527
|
+
required: [payer_account_field, payee_account_field, platformAccountId, amount, whole_fee_field, currency, release_deadline];
|
|
528
|
+
action create {
|
|
529
|
+
let(owner_id): "owner.id";
|
|
530
|
+
let(owner_type): "owner.type";
|
|
531
|
+
agent_description: concat("Open the ", words(instrument), " and provision the escrow account that holds the money. No money moves on this call. ", release_deadline, " is fixed here and never moves again, so one date governs the early release, the cancellation, and the automatic payout.");
|
|
532
|
+
summary: concat("Create a ", words(instrument), " settlement");
|
|
533
|
+
moves: [];
|
|
534
|
+
steps: [{ operation: "account.escrow.provision"; bind: { currency: { from: instance; path: fields.currency; }; [owner_id]: { from: instance; path: productId; }; [owner_type]: { from: const; value: product; }; productId: { from: instance; path: productId; }; role: { from: const; value: product_escrow; }; }; capture: { escrowAccountId: accountId; }; }];
|
|
535
|
+
}
|
|
536
|
+
action fund {
|
|
537
|
+
agent_description: concat("Move ", amount, " from the ", words(payer), " into escrow and charge ", whole_fee_field, " on top to the platform account, as one linked batch. This moves money and the ", words(payer), " pays both. Only a created hold funds. The fee never enters escrow, so it is non-refundable however the hold ends.");
|
|
538
|
+
summary: "Fund the hold and collect the custody fee";
|
|
539
|
+
moves: [
|
|
540
|
+
{ key: principal; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: fund; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundingTransferId: transferId; }; },
|
|
541
|
+
{ key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", whole_fee_field); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: service_fee; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { serviceFeeTransferId: transferId; }; }
|
|
542
|
+
];
|
|
543
|
+
sandbox_failure_point: funding;
|
|
544
|
+
steps: [];
|
|
545
|
+
}
|
|
546
|
+
action [release_action] {
|
|
547
|
+
agent_description: concat("Pay the ", words(payee), " the whole ", amount, " out of escrow ahead of ", release_deadline, ". This moves money and does not reverse. Only a funded hold releases, the caller must act as the ", release_actor_text, ", and the call is refused once ", release_deadline, " passes. The custody fee stays posted.");
|
|
548
|
+
summary: concat("Release the hold to the ", words(payee), " early");
|
|
549
|
+
deadline: { field: release_deadline; };
|
|
550
|
+
port: { allowed_parties: [release_role]; };
|
|
551
|
+
moves: [{ key: release_action; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: release_action; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseTransferId: transferId; }; }];
|
|
552
|
+
sandbox_failure_point: "release";
|
|
553
|
+
steps: [];
|
|
554
|
+
}
|
|
555
|
+
action release_on_deadline {
|
|
556
|
+
summary: concat("Release the hold to the ", words(payee), " on its release date");
|
|
557
|
+
due: { field: release_deadline; };
|
|
558
|
+
event_name: concat(instrument, ".released_on_deadline");
|
|
559
|
+
moves: [{ key: release_action; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: deadline_release; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { deadlineReleaseTransferId: transferId; }; }];
|
|
560
|
+
steps: [];
|
|
561
|
+
}
|
|
562
|
+
action cancel {
|
|
563
|
+
agent_description: concat("Return the whole ", amount, " from escrow to the ", words(payer), " and end the hold. This moves money and does not reverse. Only a funded hold cancels, and only before ", release_deadline, " passes. The custody fee stays posted because the custody it paid for was already provided.");
|
|
564
|
+
summary: "Cancel the hold and return the held amount";
|
|
565
|
+
deadline: { field: release_deadline; };
|
|
566
|
+
event_name: concat(instrument, ".cancelled");
|
|
567
|
+
moves: [{ key: refund; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: cancel_refund; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelRefundTransferId: transferId; }; }];
|
|
568
|
+
steps: [];
|
|
569
|
+
}
|
|
570
|
+
action abandon {
|
|
571
|
+
agent_description: concat("Close a ", words(instrument), " that was opened and never funded. No money has moved and the escrow must be empty, so this is a lifecycle transition only. A funded hold refuses. Release it to the ", words(payee), " or cancel it back to the ", words(payer), " instead.");
|
|
572
|
+
summary: "Abandon the hold before it is funded";
|
|
573
|
+
moves: [];
|
|
574
|
+
requires_drained: { path: refs.escrowAccountId; };
|
|
575
|
+
steps: [];
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|