/** Found the valid prize group definition recursively. */
  private PrizeGroup determinePrizeGroup(Merchant merchant) throws ApplicationException {
    if (Merchant.SUPER_MERCHANT_ID == merchant.getId()) {
      return null;
    }
    PrizeGroup prizeGroup = merchant.getPrizeGroup();
    if (prizeGroup == null) {
      throw new ApplicationException(
          SystemException.CODE_MERCHANT_UNALLOWED_PAY,
          "No any prize group definition found of Merchant("
              + merchant
              + "), System will simply reject this payout request.");
    }
    if (PrizeGroup.ALLOWTYPE_USEPARENT == prizeGroup.getAllowType()) {
      prizeGroup = this.determinePrizeGroup(merchant.getParentMerchant());
    } else {
      if (logger.isDebugEnabled()) {
        logger.debug(
            "Use the payout-limit setting(max="
                + prizeGroup.getMaxPayoutAmount()
                + ",min="
                + prizeGroup.getMinPayoutAmount()
                + ") of merhcant("
                + merchant
                + ") to verify payout limit.");
      }

      if (!prizeGroup.isPayoutAllowed()) {
        throw new ApplicationException(
            SystemException.CODE_MERCHANT_UNALLOWED_PAY,
            "Merchant(" + merchant + ") doesn't allow payout.");
      }
    }

    return prizeGroup;
  }
 /**
  * If the payout-limit of current merchant is 'follow parent', TE should query its parent merchant
  * till find a parent merchant whose payout-limit isn't 'follow parent'.
  *
  * @return the PrizeGroup which will be applied at final.
  */
 private PrizeGroup determinePrizeGroup(Context respCtx, Operator operator)
     throws ApplicationException {
   PrizeGroup prizeGroup = operator.getPrizeGroup();
   if (prizeGroup == null) {
     throw new ApplicationException(
         SystemException.CODE_MERCHANT_UNALLOWED_PAY,
         "No any prize group definition found of Operator("
             + operator
             + "), System will simply reject this payout request.");
   }
   if (PrizeGroup.ALLOWTYPE_USEPARENT == prizeGroup.getAllowType()) {
     // lookup prize group definition of operator's parent merchant
     Merchant leafMerchant =
         this.getMerchantDao().findById(Merchant.class, respCtx.getTransaction().getMerchantId());
     if (leafMerchant == null) {
       throw new ApplicationException(
           SystemException.CODE_NO_MERCHANT,
           "can NOT find merhcant by id=" + respCtx.getTransaction().getMerchantId());
     }
     prizeGroup = this.determinePrizeGroup(leafMerchant);
   } else {
     if (logger.isDebugEnabled()) {
       logger.debug(
           "Use the payout-limit setting(max="
               + prizeGroup.getMaxPayoutAmount()
               + ",min="
               + prizeGroup.getMinPayoutAmount()
               + ") of Operator("
               + operator
               + ") to verify payout limit.");
     }
   }
   return prizeGroup;
 }
 @Override
 public void allowPayout(
     Context respCtx,
     Game game,
     PayoutLevelAllowRequest[] levelAllowRequests,
     BigDecimal actualPayout)
     throws ApplicationException {
   // verify whether the game has been allocated
   MerchantCommission comm =
       this.getMerchantCommissionDao()
           .getByMerchantAndGame(respCtx.getTransaction().getMerchantId(), game.getId());
   if (comm == null || !comm.isAllowPayout()) {
     throw new SystemException(
         SystemException.CODE_OPERATOR_PAYOUT_NOPRIVILEDGE,
         "operator(id="
             + respCtx.getOperatorId()
             + ") has no priviledge to payout ticket of game '"
             + game.getId()
             + "', allocate the game to its merchant(id="
             + respCtx.getTransaction().getMerchantId()
             + ") first.");
   }
   GameType gameType = GameType.fromType(game.getType());
   // only need to check the prize group constraints of operator
   PrizeGroup prizeGroup = respCtx.getOperator().getPrizeGroup();
   if (!prizeGroup.isPayoutAllowed()) {
     throw new ApplicationException(
         SystemException.CODE_MERCHANT_UNALLOWED_PAY,
         "Operator(" + respCtx.getOperator() + ") doesn't allow payout.");
   }
   // ** both conditions must be satisfied
   // by payout limit
   if (new BigDecimal("0").compareTo(actualPayout) != 0) {
     this.verifyPayoutByLimit(respCtx, actualPayout, respCtx.getOperator());
   }
   // for odds and fix-prize game, no need to check prize level.
   if (!gameType.isFixedPrize()) {
     // by prize level
     if (levelAllowRequests != null) {
       for (PayoutLevelAllowRequest levelAllowRequest : levelAllowRequests) {
         this.verifyPayoutByLevel(
             respCtx,
             respCtx.getOperator(),
             levelAllowRequest.getRequestedPrizeLevels(),
             levelAllowRequest.getGameType(),
             levelAllowRequest.getPrizeGroupType());
       }
     }
   }
 }
 /**
  * When cash out, a merchant can only cashout a given max amount.
  *
  * @param respCtx The context of cashout transaction.
  * @param actualCashout The actual amount of cashout.
  * @throws ApplicationException when encounter any business exception.
  */
 protected void allowDailyCashout(Context respCtx, BigDecimal actualCashout)
     throws ApplicationException {
   PrizeGroup cashoutGroup = respCtx.getOperator().getCashoutGroup();
   if (cashoutGroup == null) {
     throw new ApplicationException(
         SystemException.CODE_MERCHANT_UNALLOWED_CASHOUT,
         "Operator("
             + respCtx.getOperator()
             + ") can't perform cashout, no any cashout "
             + "group definition found.");
   }
   if (logger.isDebugEnabled()) {
     logger.debug(
         "Before cash out[dailyCashoutLevel: "
             + respCtx.getOperator().getDailyCashoutLevel()
             + ", dailyCashoutLimit: "
             + cashoutGroup.getDailyCashoutLimit()
             + ", cashoutAmount: "
             + actualCashout
             + "] of Operator( "
             + respCtx.getOperator()
             + ").");
   }
   if (actualCashout
           .add(respCtx.getOperator().getDailyCashoutLevel())
           .compareTo(cashoutGroup.getDailyCashoutLimit())
       > 0) {
     throw new ApplicationException(
         SystemException.CODE_EXCEED_ALLOWED_MERCHANT_DAILY_CASHOUT_LIMIT,
         "Cash out amount("
             + actualCashout
             + ") + current cashout level("
             + respCtx.getOperator().getDailyCashoutLevel()
             + ") has exceeded allowed limit("
             + cashoutGroup.getDailyCashoutLimit()
             + ") of Operator("
             + respCtx.getOperator()
             + ").");
   }
 }
 /**
  * When cash out, system check the cashout-limit of the merchant.
  *
  * @param respCtx The context of cashout transaction.
  * @param actualCashout The actual amount of cashout.
  * @throws ApplicationException when encounter any business exception.
  */
 protected void allowCashoutByLimit(Context respCtx, BigDecimal actualCashout)
     throws ApplicationException {
   PrizeGroup cashoutGroup = respCtx.getOperator().getCashoutGroup();
   if (cashoutGroup == null) {
     throw new ApplicationException(
         SystemException.CODE_MERCHANT_UNALLOWED_CASHOUT,
         "Operator("
             + respCtx.getOperator()
             + ") can't perform cashout, no any cashout group definition found.");
   }
   if (cashoutGroup.getMaxPayoutAmount() != null
       && cashoutGroup.getMaxPayoutAmount().compareTo(new BigDecimal("0")) >= 0) {
     if (actualCashout.compareTo(cashoutGroup.getMaxPayoutAmount()) > 0) {
       throw new ApplicationException(
           SystemException.CODE_MERCHANT_UNALLOWED_CASHOUT_SCOPE,
           "The prize actual amount("
               + actualCashout
               + ") exceed the max "
               + "allowed cashout amount("
               + cashoutGroup.getMaxPayoutAmount()
               + ") of the Operator("
               + respCtx.getOperator()
               + ").");
     }
   }
   if (cashoutGroup.getMinPayoutAmount() != null
       && cashoutGroup.getMinPayoutAmount().compareTo(new BigDecimal("0")) >= 0) {
     if (actualCashout.compareTo(cashoutGroup.getMinPayoutAmount()) < 0) {
       throw new ApplicationException(
           SystemException.CODE_MERCHANT_UNALLOWED_CASHOUT_SCOPE,
           "The prize actual amount("
               + actualCashout
               + ") is less than min cashout amount("
               + cashoutGroup.getMinPayoutAmount()
               + ") of Operator("
               + respCtx.getOperator()
               + ").");
     }
   }
 }
 /**
  * When do payout/validation, system check the payout-limit of the merchant. A merchant maybe
  * follow its parent's payout-limit setting, system should handle this case.
  *
  * @param respCtx The context of current transaction.
  * @param actualPayout The actual amount of payout.
  * @param operator The operator who do payout.
  * @throws ApplicationException when encounter any business exception.
  */
 private void verifyPayoutByLimit(Context respCtx, BigDecimal actualPayout, Operator operator)
     throws ApplicationException {
   PrizeGroup prizeGroup = this.determinePrizeGroup(respCtx, operator);
   if (PrizeGroup.ALLOWTYPE_UNLIMIT == prizeGroup.getAllowType()) {
     if (logger.isDebugEnabled()) {
       logger.debug("the prize group definition(" + prizeGroup.getId() + ") is no limited");
     }
     return;
   }
   if (prizeGroup.getMaxPayoutAmount() != null
       && prizeGroup.getMaxPayoutAmount().compareTo(new BigDecimal("0")) >= 0) {
     if (actualPayout.compareTo(prizeGroup.getMaxPayoutAmount()) > 0) {
       throw new ApplicationException(
           SystemException.CODE_EXCEED_MAX_PAYOUT,
           "The prize actual amount("
               + actualPayout
               + ") exceed the max "
               + "allowed payout amount("
               + prizeGroup.getMaxPayoutAmount()
               + ").");
     }
   }
   if (prizeGroup.getMinPayoutAmount() != null
       && prizeGroup.getMinPayoutAmount().compareTo(new BigDecimal("0")) >= 0) {
     if (actualPayout.compareTo(prizeGroup.getMinPayoutAmount()) < 0) {
       throw new ApplicationException(
           SystemException.CODE_EXCEED_MAX_PAYOUT,
           "The prize actual amount("
               + actualPayout
               + ") is less than min "
               + "payout amount("
               + prizeGroup.getMinPayoutAmount()
               + ").");
     }
   }
 }