Orishare
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 grants content_id, then sends content.completed with purchase_verified: true using 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 the content.completed event definition to minimum_trust: "merchant_asserted" and client_allowed: false (PATCH /v1/event_definitions/{id}), and enforce a schema with required purchase_id and content_id.
  • Once per content. The per_content limit counts per customer and content_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.reverse matches content.completed on event.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. match finds the awards made by rules for content.purchased events of this customer whose purchase_id equals the refund's. It matches purchase_id, not content_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 negative reverse entry linked to it for what is still held; what the customer already spent is recorded as unrecoverable, unless the currency allows a negative balance, in which case the full amount is taken. Add fraction or amount for 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.purchased and content.refunded must be server-only and merchant_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.

On this page