{
  "feature": "payment-processing",
  "version": "1.0.0",
  "description": "Process incoming, outgoing, and internal transfer payments with multi-currency support, reference allocation, and automatic reconciliation",
  "category": "payment",
  "tags": [
    "accounting",
    "payments",
    "reconciliation",
    "multi-currency",
    "erp"
  ],
  "fields": [
    {
      "name": "payment_type",
      "type": "select",
      "required": true,
      "options": [
        {
          "value": "receive",
          "label": "Receive"
        },
        {
          "value": "pay",
          "label": "Pay"
        },
        {
          "value": "internal_transfer",
          "label": "Internal Transfer"
        }
      ]
    },
    {
      "name": "party_type",
      "type": "select",
      "required": false,
      "options": [
        {
          "value": "customer",
          "label": "Customer"
        },
        {
          "value": "supplier",
          "label": "Supplier"
        },
        {
          "value": "employee",
          "label": "Employee"
        },
        {
          "value": "shareholder",
          "label": "Shareholder"
        }
      ]
    },
    {
      "name": "party",
      "type": "text",
      "required": false
    },
    {
      "name": "paid_from",
      "type": "text",
      "required": true,
      "validation": [
        {
          "type": "minLength",
          "value": 1,
          "message": "Source account must be specified"
        }
      ]
    },
    {
      "name": "paid_to",
      "type": "text",
      "required": true,
      "validation": [
        {
          "type": "minLength",
          "value": 1,
          "message": "Target account must be specified"
        }
      ]
    },
    {
      "name": "paid_amount",
      "type": "number",
      "required": true,
      "validation": [
        {
          "type": "min",
          "value": 0,
          "message": "Paid amount must be greater than zero"
        }
      ]
    },
    {
      "name": "received_amount",
      "type": "number",
      "required": true,
      "validation": [
        {
          "type": "min",
          "value": 0,
          "message": "Received amount must be greater than zero"
        }
      ]
    },
    {
      "name": "source_exchange_rate",
      "type": "number",
      "required": true,
      "validation": [
        {
          "type": "min",
          "value": 0,
          "message": "Source exchange rate must be greater than zero"
        }
      ]
    },
    {
      "name": "target_exchange_rate",
      "type": "number",
      "required": true,
      "validation": [
        {
          "type": "min",
          "value": 0,
          "message": "Target exchange rate must be greater than zero"
        }
      ]
    },
    {
      "name": "references",
      "type": "json",
      "required": false
    },
    {
      "name": "mode_of_payment",
      "type": "select",
      "required": false,
      "options": [
        {
          "value": "cash",
          "label": "Cash"
        },
        {
          "value": "bank_transfer",
          "label": "Bank Transfer"
        },
        {
          "value": "cheque",
          "label": "Cheque"
        },
        {
          "value": "wire_transfer",
          "label": "Wire Transfer"
        },
        {
          "value": "credit_card",
          "label": "Credit Card"
        },
        {
          "value": "online_payment",
          "label": "Online Payment"
        }
      ]
    },
    {
      "name": "status",
      "type": "select",
      "required": true,
      "options": [
        {
          "value": "draft",
          "label": "Draft"
        },
        {
          "value": "submitted",
          "label": "Submitted"
        },
        {
          "value": "cancelled",
          "label": "Cancelled"
        }
      ]
    }
  ],
  "actors": [
    {
      "id": "accounts_user",
      "name": "Accounts User",
      "type": "human",
      "description": "Creates and submits payment entries"
    },
    {
      "id": "accounting_system",
      "name": "Accounting System",
      "type": "system",
      "description": "Posts GL entries, updates outstanding balances, and reconciles"
    }
  ],
  "states": {
    "field": "status",
    "values": [
      {
        "name": "draft",
        "description": "Payment entry is being prepared",
        "initial": true
      },
      {
        "name": "submitted",
        "description": "Payment posted to the ledger"
      },
      {
        "name": "cancelled",
        "description": "Payment reversed",
        "terminal": true
      }
    ],
    "transitions": [
      {
        "from": "draft",
        "to": "submitted",
        "actor": "accounts_user",
        "description": "Submit payment entry for posting"
      },
      {
        "from": "submitted",
        "to": "cancelled",
        "actor": "accounts_user",
        "description": "Cancel payment and reverse GL entries"
      }
    ]
  },
  "rules": {
    "balanced_entries": {
      "description": "Total debit must equal total credit in the GL entries generated by the payment.\n"
    },
    "allocation_limit": {
      "description": "Allocated amount against a reference cannot exceed that reference outstanding amount.\n"
    },
    "reference_validation": {
      "description": "Referenced documents must be in Submitted status before allocation. Draft or cancelled documents cannot be referenced.\n"
    },
    "exchange_rate_auto": {
      "description": "Exchange rates are auto-fetched for the posting date when source and target currencies differ.\n"
    },
    "party_required": {
      "description": "Party type and party are required for Receive and Pay payment types. Internal Transfers do not require a party.\n"
    },
    "internal_transfer_accounts": {
      "description": "Internal Transfer requires different paid_from and paid_to accounts. Same-account transfers are rejected.\n"
    },
    "unallocated_tracking": {
      "description": "Unallocated amount is tracked when payment exceeds total allocated references. Can be allocated later.\n"
    },
    "cancellation_reversal": {
      "description": "Cancellation reverses all GL entries and restores outstanding on referenced documents.\n"
    }
  },
  "outcomes": {
    "process_payment": {
      "given": [
        "payment entry is in Draft status",
        {
          "field": "paid_amount",
          "operator": "gt",
          "value": 0,
          "description": "Paid amount is positive"
        },
        {
          "field": "paid_from",
          "operator": "exists",
          "description": "Source account is specified"
        },
        {
          "field": "paid_to",
          "operator": "exists",
          "description": "Target account is specified"
        }
      ],
      "then": [
        {
          "action": "transition_state",
          "field": "status",
          "from": "draft",
          "to": "submitted"
        },
        {
          "action": "create_record",
          "type": "gl_entry",
          "target": "gl_entries",
          "description": "Create debit and credit GL entries for the payment"
        },
        {
          "action": "emit_event",
          "event": "payment.submitted",
          "payload": [
            "payment_id",
            "payment_type",
            "party",
            "paid_amount",
            "received_amount"
          ]
        }
      ],
      "result": "Payment is submitted with GL entries posted and referenced document outstanding amounts reduced",
      "transaction": true
    },
    "reconcile_payment": {
      "given": [
        "payment is in Submitted status",
        {
          "field": "references",
          "operator": "exists",
          "description": "At least one reference document is allocated"
        }
      ],
      "then": [
        {
          "action": "set_field",
          "target": "unallocated_amount",
          "description": "Calculated as paid_amount minus sum of all allocated amounts"
        },
        {
          "action": "emit_event",
          "event": "payment.reconciled",
          "payload": [
            "payment_id",
            "references",
            "unallocated_amount"
          ]
        }
      ],
      "result": "Payment is reconciled against referenced invoices with outstanding amounts updated"
    },
    "cancel_payment": {
      "given": [
        "payment is in Submitted status"
      ],
      "then": [
        {
          "action": "transition_state",
          "field": "status",
          "from": "submitted",
          "to": "cancelled"
        },
        {
          "action": "create_record",
          "type": "gl_entry",
          "target": "gl_entries",
          "description": "Reverse all GL entries for this payment"
        },
        {
          "action": "emit_event",
          "event": "payment.cancelled",
          "payload": [
            "payment_id",
            "payment_type",
            "party",
            "paid_amount"
          ]
        }
      ],
      "result": "Payment is cancelled, GL entries reversed, and outstanding on referenced documents restored",
      "transaction": true
    }
  },
  "errors": [
    {
      "code": "PAYMENT_ALLOCATION_EXCEEDED",
      "message": "Allocated amount exceeds outstanding amount on the referenced document.",
      "status": 400
    },
    {
      "code": "PAYMENT_REFERENCE_INVALID",
      "message": "Referenced document does not exist or is not in a submitted state.",
      "status": 400
    },
    {
      "code": "PAYMENT_BALANCE_MISMATCH",
      "message": "Total debit does not equal total credit in the payment GL entries.",
      "status": 500
    }
  ],
  "events": [
    {
      "name": "payment.submitted",
      "description": "Payment entry transitions from Draft to Submitted",
      "payload": [
        "payment_id",
        "payment_type",
        "party",
        "paid_amount",
        "received_amount"
      ]
    },
    {
      "name": "payment.cancelled",
      "description": "Payment entry is cancelled and GL entries reversed",
      "payload": [
        "payment_id",
        "payment_type",
        "party",
        "paid_amount"
      ]
    },
    {
      "name": "payment.reconciled",
      "description": "Payment is reconciled against one or more references",
      "payload": [
        "payment_id",
        "references",
        "unallocated_amount"
      ]
    }
  ],
  "related": [
    {
      "feature": "sales-purchase-invoicing",
      "type": "required",
      "reason": "Payments are allocated against sales and purchase invoices"
    },
    {
      "feature": "general-ledger",
      "type": "required",
      "reason": "Payment submission creates GL entries"
    },
    {
      "feature": "bank-reconciliation",
      "type": "recommended",
      "reason": "Bank statements are matched against payment entries"
    }
  ],
  "extensions": {
    "source": {
      "repo": "https://github.com/frappe/erpnext",
      "project": "ERPNext",
      "tech_stack": "Python, Frappe Framework, MariaDB/PostgreSQL"
    }
  }
}