Concepts
Rule examples 04 and 05
Purchase-gated completion and refund reversal in the canonical v2 rule shape, with the trust floor and reversal semantics stated.
These two examples ship in packages/rule-schema/examples and validate under the current
schema. Both are orishare.rule/v2 with an explicit customer_event trigger and a rule trust
floor written as trust: { minimum }. Which channels may feed a program (allowed_sources)
is not part of a rule; it belongs to the program trust policy.
04: completion requires a purchase
Award 200 points once per content_id when the customer completes content they bought.
{
"schema": "orishare.rule/v2",
"trigger": {
"kind": "customer_event",
"event": "content.completed"
},
"conditions": {
"all": [
{
"path": "event.properties.purchase_verified",
"op": "eq",
"value": true
},
{
"path": "event.properties.purchase_id",
"op": "exists"
},
{
"path": "event.properties.content_id",
"op": "exists"
}
]
},
"limits": [
{
"id": "per_content",
"scope": ["customer", "event.properties.content_id"],
"max": 1
}
],
"actions": [
{
"type": "points.award",
"currency": "points",
"amount": 200,
"reason": "Completed"
}
],
"priority": 100,
"trust": {
"minimum": "merchant_asserted"
}
}- Proof of purchase. Your server looks up
purchase_id, checks that it belongs to this customer and grantscontent_id, then sendscontent.completedwithpurchase_verified: trueusing the secret key. A flag a browser sends is not proof, and a count of earlier purchases does not prove ownership of this content. - Trust. The rule requires
merchant_asserted. Also set thecontent.completedevent definition tominimum_trust: "merchant_asserted"andclient_allowed: false(PATCH /v1/event_definitions/{id}), and enforce a schema with requiredpurchase_idandcontent_id. - Once per content. The
per_contentlimit counts per customer andcontent_id. - Reversal. A later refund of the purchase does not reverse this completion award;
example 05 reverses only the purchase award. To take the completion award back too, add a
second refund rule whose
points.reversematchescontent.completedonevent.properties.purchase_id.
05: a refund reverses the purchase award
Take back the points awarded for one purchase when that purchase is refunded.
{
"schema": "orishare.rule/v2",
"trigger": {
"kind": "customer_event",
"event": "content.refunded"
},
"conditions": {
"all": [
{
"path": "event.properties.purchase_id",
"op": "exists"
}
]
},
"actions": [
{
"type": "points.reverse",
"currency": "points",
"match": {
"event": "content.purchased",
"on": "event.properties.purchase_id"
},
"reason": "Refund"
}
],
"priority": 100,
"trust": {
"minimum": "merchant_asserted"
}
}- Which award.
matchfinds the awards made by rules forcontent.purchasedevents of this customer whosepurchase_idequals the refund's. It matchespurchase_id, notcontent_id: two purchases of the same content are two facts. - Reversal semantics. A pending award (still inside
confirm_after) is voided. A confirmed award gets a negativereverseentry linked to it for what is still held; what the customer already spent is recorded asunrecoverable, unless the currency allows a negative balance, in which case the full amount is taken. Addfractionoramountfor a partial refund. Across all refunds an award is never reversed for more than it paid. - Retries. One refund event touches each award once, so resending the same refund event id reverses nothing more. A refund with no matching award changes nothing.
- Trust. Both
content.purchasedandcontent.refundedmust be server-only andmerchant_asserted; the rule floor repeats that. - Retention. Awards are found through retained events. Once the purchase event ages out of
retention, the award can no longer be reversed by rule; native programs correct by permanent
contribution identity instead (
program.reverse_contribution).
The earlier v1 forms of both examples are kept unchanged as test fixtures and still validate as
orishare.rule/v1.