Esempio n. 1
0
    /**
     *  @brief  Processes product sales web service request.
     *  This function shall generate product sales TMI record.
     *  If product sales transaction is paid by TPurse, this function
     *  shall execute business rule to deduct the transaction amount
     *  from the presented patron card. If "TPurse Load" is listed in
     *  the shopping cart, this function shall execute business rule
     *  to add value to the presented patron card.
     *  @param  request Json::Value object containing JSON request message
     *          of the following format,
     *  @code
     *  {
     *      "terminalid":"1234",
     *      "userid":"D101",
     *      "name":"productsalesreversal",
     *      "data":
     *      {
     *          "paymenttype":"cash",
     *          "cardnumber":"0123456789ABCDEF",
     *          "txnseqno":1234,
     *          "saleseqno":1212,
     *          "saletimestamp":"2015-12-16T10:44:55+10:00",
     *          "cartitems":
     *          [
     *              {
     *                  "itemnumber":1,
     *                  "GSTApplicable":false,
     *                  "PLU":"4. myki Child",
     *                  "Price":50,
     *                  "id":46,
     *                  "issuer_id":1,
     *                  "long_desc":"GTS Timetable",
     *                  "short_desc":"GTS Timetable",
     *                  "subtype":"None",
     *                  "type":"ThirdParty"
     *              },
     *              {
     *                  "itemnumber":2,
     *                  "GSTApplicable":false,
     *                  "Price":5000,
     *                  "id":300,
     *                  "issuer_id":1,
     *                  "long_desc":"TPurse Load",
     *                  "short_desc":"TPurse Load",
     *                  "subtype":"None",
     *                  "type":"LLSC"
     *              },
     *              { ... }
     *          ]
     *      }
     *  }
     *  @endcode
     *  where,
     *  @li     @c terminalid is the terminal id of the device that
     *          initiated the transaction,
     *  @li     @c name is "productsales",
     *  @li     @c data.cardnumber is the card serial number used as
     *          payment type or used for TPurse load,
     *  @li     @c data.cartitems is an array of items in shopping cart,
     *  @li     @c data.cartitems[n].quantity is the quantity purchasing,
     *  @li     @c data.cartitems[n] is the product details (as
     *          returned from MYKI_CD_getProducts API).\n
     *          For TPurse Load, product id is 300, product type
     *          is "LLSC" and product price is the TPurse Load amount.
     *  @return The string object representing JSON response message of
     *          the following format,
     *  @code
     *  {
     *      "terminalid":"1234",
     *      "userid":"D101",
     *      "name":"productsalesreversal",
     *      "string_error":"WS_SUCCESS",
     *      "timestamp":"2015-12-16T10:44:55+10:00",
     *      "data":
     *      {
     *          "paymenttype":"cash",
     *          "cardnumber":"0123456789ABCDEF",
     *          "remvalue":5200,
     *          "txnseqno":1234,
     *          "saleseqno":1212,
     *          "saletimestamp":"2015-12-16T10:44:55+10:00",
     *          "revtxnseqno":4321,
     *          "revsaleseqno":2121,
     *          "string_reason":"REASON_NOT_APPLICABLE",
     *          "string_blocking_reason":"BLOCKING_REASON_LOST_STOLEN",
     *          "expiry":"24 Dec 15 12:00 pm",
     *          "amounttotal":7812,
     *          "amountgst":781,
     *          "amountrounding":-2,
     *          "amountrefunded":7810,
     *          "vat_rate":10,
     *          "route_id":1234,
     *          "stop_id":12,
     *          "cartitems":
     *          [
     *              {
     *                  "itemnumber":1,
     *                  "GSTApplicable":false,
     *                  "PLU":"4. myki Child",
     *                  "Price":50,
     *                  "id":46,
     *                  "issuer_id":1,
     *                  "long_desc":"GTS Timetable",
     *                  "short_desc":"GTS Timetable",
     *                  "subtype":"None",
     *                  "type":"ThirdParty"
     *              },
     *              {
     *                  "itemnumber":2,
     *                  "GSTApplicable":false,
     *                  "PLU":"4. myki Child",
     *                  "Price":50,
     *                  "id":46,
     *                  "issuer_id":1,
     *                  "long_desc":"GTS Timetable",
     *                  "short_desc":"GTS Timetable",
     *                  "subtype":"None",
     *                  "type":"ThirdParty"
     *              },
     *              {
     *                  "itemnumber":3,
     *                  "GSTApplicable":false,
     *                  "Price":5000,
     *                  "id":300,
     *                  "issuer_id":1,
     *                  "long_desc":"TPurse Load",
     *                  "short_desc":"TPurse Load",
     *                  "subtype":"None",
     *                  "type":"LLSC"
     *              },
     *              { ... }
     *          ]
     *      }
     *  }
     *  @endcode
     *  where,
     *  @li     @c terminalid is the terminal id of the device that
     *          initiated the transaction,
     *  @li     @c name is "productsales",
     *  @li     @c data.cardnumber is the card serial number used as
     *          payment type or used for TPurse load,
     *  @li     @c data.timestamp is the transaction unix time since epoch,
     *  @li     @c data.vat_rate is the percentage GST.
     *  @li     @c data.cartitems is an array of items in shopping cart.
     *  @li     @c data.cartitems[n].product.itemnumber is the item number.
     *  @li     @c data.cartitems[n].product is the product details.
     */
std::string
processProductSalesReversal( Json::Value &request )
{
    Json::Value             response;
    Json::FastWriter        fw;
    MYKI_BR_ContextData_t  *pDataIn                 = GetCardProcessingThreadContextData();
    MYKI_BR_ContextData_t  *pDataOut                = GetBrContextDataOut();

    Currency_t              amountTotal             = 0;
    Currency_t              amountGST               = 0;
    Currency_t              amountRefunded          = 0;
    Currency_t              amountSurchargeTotal    = 0;
    Currency_t              amountSurchargeGST      = 0;
    Currency_t              amountSurchargeRefunded = 0;
    Currency_t              tpurseTxnAmount         = 0;
    int                     itemIndexTPurseLoad     = -1;
    std::string             paymentType             = getJsonString( request[ "data" ], "paymenttype" );
    std::string             cardNumber              = getJsonString( request[ "data" ], "cardnumber"  );
    int                     txnSeqNo                = getJsonInt(    request[ "data" ], "txnseqno"    );
    int                     routeId                 = getJsonInt(    request[ "data" ], "route_id"    );
    int                     stopId                  = getJsonInt(    request[ "data" ], "stop_id"     );
    U8_t                    vatRate                 = 0;
    bool                    canRefundProduct        = false;
    Json::Value             media;

    CsDbg( APP_DEBUG_FLOW, "processProductSalesReversal" );

    createBaseResponse( request, response );

    if ( MYKI_CD_getGSTPercentage( &vatRate ) == FALSE )
    {
        CsErrx( "processProductSalesReversal : MYKI_CD_getGSTPercentage() failed, set zero vat_rate" );
        vatRate     = 0;
    }

/*  response[ "type" ]                                  = "SET";    */
    response[ "data" ][ "paymenttype" ]                 = request[ "data" ][ "paymenttype" ];
    response[ "data" ][ "route_id"    ]                 = routeId == 0 ? pDataIn->DynamicData.lineId : routeId;
    response[ "data" ][ "stop_id"     ]                 = stopId  == 0 ? pDataIn->DynamicData.stopId : stopId;
    response[ "data" ][ "cartitems"   ]                 = Json::Value( Json::arrayValue );
    #if     __IF_NEEDED__
    response[ "data" ][ "surcharge"   ][ "cartitems" ]  = Json::Value( Json::arrayValue );
    #endif

    if ( paymentType == "tpurse" )
    {
        if ( request[ "data" ][ "paymentmedia" ].empty() == false )
        {
            media                                       = request[ "data" ][ "paymentmedia" ];
            cardNumber                                  = getJsonString( media, "cardnumber" );
            txnSeqNo                                    = getJsonInt(    media, "txnseqno"   );
        }
        else
        {
            /*  DEPRECATED! */
            media[ "cardnumber" ]                       = cardNumber;
            media[ "txnseqno"   ]                       = txnSeqNo;
        }
    }
    response[ "data" ][ "saleseqno"     ]               = getJsonInt(    request[ "data" ], "saleseqno"     );
    response[ "data" ][ "saletimestamp" ]               = getJsonString( request[ "data" ], "saletimestamp" );

    for ( Json::ValueIterator itr = request[ "data" ][ "cartitems" ].begin();
            itr != request[ "data" ][ "cartitems" ].end();
            itr++ )
    {
        Json::Value        &cartItem        = ( *itr );
        int                 itemNumber      = getJsonInt(    cartItem, "itemnumber"    );
        int                 id              = getJsonInt(    cartItem, "id"            );
        std::string         type            = getJsonString( cartItem, "type"          );
        int                 issuerId        = getJsonInt(    cartItem, "issuer_id"     );
        Currency_t          price           = getJsonInt(    cartItem, "Price"         );
        bool                gstApplicable   = getJsonBool(   cartItem, "GSTApplicable" );
        Currency_t          priceGST        = CalculateGST( price, vatRate );
        bool                isSurcharge     = type == "PremiumSurcharge" ? true : false;
        Json::Value         productRefunded;

        if ( id == ProductId_TPurse )
        {
            /*  Only one TPurse Load is allowed */
            itemIndexTPurseLoad             = response[ "data" ][ "cartitems" ].size();
            tpurseTxnAmount                 = price;
            gstApplicable                   = false;
            priceGST                        = 0;
            if ( cartItem[ "addvaluemedia" ].empty() == false )
            {
                media                       = cartItem[ "addvaluemedia" ];
                cardNumber                  = getJsonString( media, "cardnumber" );
                txnSeqNo                    = getJsonInt(    media, "txnseqno"   );
            }
            else
            {
                /*  DEPRECATED! */
                media[ "cardnumber" ]       = cardNumber;
                media[ "txnseqno"   ]       = txnSeqNo;
            }
        }

        productRefunded[ "itemnumber"    ]  = itemNumber;
        productRefunded[ "GSTApplicable" ]  = gstApplicable;
        productRefunded[ "Price"         ]  = price;
        productRefunded[ "id"            ]  = cartItem[ "id"            ];
        productRefunded[ "issuer_id"     ]  = issuerId == 0 ? PRODUCT_OWNER_DEFAULT : issuerId;
        productRefunded[ "long_desc"     ]  = cartItem[ "long_desc"     ];
        productRefunded[ "short_desc"    ]  = cartItem[ "short_desc"    ];
        productRefunded[ "subtype"       ]  = cartItem[ "subtype"       ];
        productRefunded[ "type"          ]  = type;

        response[ "data" ][ "cartitems" ].append( productRefunded );
        amountTotal                        += price;
        if ( gstApplicable == true )
        {
            amountGST                      += priceGST;
        }

        #if     __IF_NEEDED__
        if ( isSurcharge == true )
        {
            response[ "data" ][ "surcharge" ][ "cartitems" ].append( productSold );
            amountSurchargeTotal           += price;
            if ( gstApplicable == true )
            {
                amountSurchargeGST         += priceGST;
            }
        }
        #endif
    }

    #if     OPT_ENABLE_ROUNDING
    if ( paymentType == "cash" )
    {
        /*  Round amount to be paid to nearest rounding portion */
        amountRefunded          = CurrencyRounding( amountTotal,          g_initCurrencyRoundingPortion );
        amountSurchargeRefunded = CurrencyRounding( amountSurchargeTotal, g_initCurrencyRoundingPortion );
    }
    else
    #endif
    {
        amountRefunded          = amountTotal;
        amountSurchargeRefunded = amountSurchargeTotal;
    }
    #if     !OPT_GST_ROUNDING_PER_PRODUCT
    amountGST                   = ( CurrencyRounding( amountGST,          _1CENT ) / _1CENT );
    amountSurchargeGST          = ( CurrencyRounding( amountSurchargeGST, _1CENT ) / _1CENT );
    #endif

    if ( paymentType == "tpurse" || itemIndexTPurseLoad >= 0 )
    {
        /*  Product sales transaction paid by TPurse or TPurse add value as one of the product sales item */
        SequenceFunction_e  brSequence              = itemIndexTPurseLoad >= 0 ?
                                                            SEQ_FUNCTION_ADD_VALUE_REVERSAL :
                                                            SEQ_FUNCTION_DEBIT_TPURSE_VALUE_REVERSAL;
        bool                isOperatorCard          = pDataOut->DynamicData.isOperator == FALSE ? false : true;
        int                 passengerCode           = 0;

        if ( isOperatorCard == false )
        {
            std::string  csn    = getMykiPAN( );

            if ( cardNumber == csn )
            {
                if ( brSequence == SEQ_FUNCTION_ADD_VALUE_REVERSAL )
                {
                    /*  Sets TPurse load reversal business rule parameters */
                    pDataIn->DynamicData.tpurseLoadAmount   = tpurseTxnAmount;
                }
                else
                {
                    /*  Sets TPurse debit reversal business rule parameters */
                    pDataIn->DynamicData.tPurseDebitAmount  = \
                    tpurseTxnAmount                         = amountRefunded;
                }
                pDataIn->DynamicData.reverseTxSeqNo         = txnSeqNo;

                /*  Execute business sequence */
                if ( ExecuteBusinessSequence( 1 /*ONE_CARD*/, isOperatorCard, passengerCode, brSequence, true ) == true )
                {
                    canRefundProduct                                        = true;

                    response[ "data" ][ "cardnumber"     ]                  = csn.c_str( );
                    response[ "data" ][ "remvalue"       ]                  = getRemainingValue( );
                    response[ "data" ][ "txnseqno"       ]                  = txnSeqNo;
                    response[ "data" ][ "revtxnseqno"    ]                  = pDataOut->ReturnedData.txnSeqNo;
                    media[ "cardnumber"  ]                                  = csn.c_str( );
                    media[ "remvalue"    ]                                  = getRemainingValue( );
                    media[ "txnseqno"    ]                                  = txnSeqNo;
                    media[ "revtxnseqno" ]                                  = pDataOut->ReturnedData.txnSeqNo;
                    if ( brSequence == SEQ_FUNCTION_ADD_VALUE_REVERSAL )
                    {
                        response[ "data" ][ "cartitems" ][ itemIndexTPurseLoad ][ "addvaluemedia" ]    = media;
                    }
                    else
                    {
                        response[ "data" ][ "paymentmedia" ]                = media;
                    }
                    response[ "data" ][ "txnamount"      ]                  = tpurseTxnAmount;
                    response[ "data" ][ "string_reason"  ]                  = getAcceptReasonString( pDataOut->ReturnedData.acceptReason );
                    response[ "data" ][ "amounttotal"    ]                  = amountTotal;
                    response[ "data" ][ "amountgst"      ]                  = amountGST;
                    response[ "data" ][ "amountrounding" ]                  = ( amountRefunded - amountTotal );
                    response[ "data" ][ "amountrefunded" ]                  = amountRefunded;
                    #if     __IF_NEEDED__
                    response[ "data" ][ "surcharge" ][ "amounttotal"    ]   = amountSurchargeTotal;
                    response[ "data" ][ "surcharge" ][ "amountgst"      ]   = amountSurchargeGST;
                    response[ "data" ][ "surcharge" ][ "amountrounding" ]   = ( amountSurchargeRefunded - amountSurchargeTotal );
                    response[ "data" ][ "surcharge" ][ "amountrefunded" ]   = amountSurchargeRefunded;
                    #endif
                    response[ "string_error" ]                              = getResultString( WS_SUCCESS );
                }
                else
                if ( pDataOut->ReturnedData.sequenceResult == SEQ_RESULT_REJECT )
                {
                    /*  Transaction rejected by business rule */
                    response[ "data" ][ "cardnumber" ]                      = csn.c_str( );
                    response[ "data" ][ "remvalue"   ]                      = getRemainingValue( );
                    media[ "cardnumber" ]                                   = csn.c_str( );
                    media[ "remvalue"   ]                                   = getRemainingValue( );
                    if ( brSequence == SEQ_FUNCTION_ADD_VALUE_REVERSAL )
                    {
                        response[ "data" ][ "cartitems" ][ itemIndexTPurseLoad ]    = media;
                    }
                    else
                    {
                        response[ "data" ][ "paymentmedia" ]                = media;
                    }
                    response[ "data" ][ "string_reason" ]                   = getRejectReasonString( pDataOut->ReturnedData.rejectReason );
                    response[ "string_error" ]                              = getResultString( WS_ERROR );
                    switch ( pDataOut->ReturnedData.rejectReason )
                    {
                    case    MYKI_BR_REJECT_REASON_CARD_BLOCKED:
                    case    MYKI_BR_REJECT_REASON_APPLICATION_BLOCKED:
                        response[ "data" ][ "string_blocking_reason" ]      = getBlockingReasonCode( pDataOut->ReturnedData.blockingReason );
                        break;

                    case    MYKI_BR_REJECT_REASON_EXPIRED:
                        response[ "data" ][ "expiry" ]                      = getExpiry( pDataOut );
                        break;

                    default:
                        /*  Others! DONOTHING! */
                        break;
                    }
                }
                else
                {
                    /*  Failed read/write card */
                    response[ "string_error" ]  = getResultString( WS_ERROR_READ_CARD );
                }

                /*  Done with transaction */
                pDataIn->DynamicData.tpurseLoadAmount       = 0;
                pDataIn->DynamicData.tPurseDebitAmount      = 0;
            }
            else
            {
                CsDbg( APP_DEBUG_STATE, "processProductSalesReversal : wrong card (%s/%s)",
                        cardNumber.c_str(), csn.c_str() );
                response[ "data" ][ "cardnumber" ]  = cardNumber;
                response[ "string_error" ]          = getResultString( WS_ERROR_WRONG_CARD );
            }
        }
        else
        {
            /*  Not Transit card!? */
            CsErrx( "processProductSalesReversal : none TRANSIT card detected" );
            response[ "data" ][ "cardnumber" ]  = cardNumber;
            response[ "string_error" ]          = getResultString( WS_ERROR_NOT_TRANSIT_CARD );
        }
    }
    else
    {
        /*  Product sales transaction does not involve updating patron card */
        canRefundProduct                                        = true;
        memcpy( pDataOut, pDataIn, sizeof( MYKI_BR_ContextData_t ) );
        {
            CsTime_t            nowTime                         = { 0 };
            CsTime( &nowTime );
            pDataOut->DynamicData.currentDateTime               = nowTime.tm_secs;
        }
        response[ "data" ][ "string_reason"  ]                  = getAcceptReasonString( MYKI_BR_ACCEPT_REASON_NOT_APPLICABLE );
        response[ "data" ][ "amounttotal"    ]                  = amountTotal;
        response[ "data" ][ "amountgst"      ]                  = amountGST;
        response[ "data" ][ "amountrounding" ]                  = ( amountRefunded - amountTotal );
        response[ "data" ][ "amountrefunded" ]                  = amountRefunded;
        #if     __IF_NEEDED__
        response[ "data" ][ "surcharge" ][ "amounttotal"    ]   = amountSurchargeTotal;
        response[ "data" ][ "surcharge" ][ "amountgst"      ]   = amountSurchargeGST;
        response[ "data" ][ "surcharge" ][ "amountrounding" ]   = ( amountSurchargeRefunded - amountSurchargeTotal );
        response[ "data" ][ "surcharge" ][ "amountrefunded" ]   = amountSurchargeRefunded;
        #endif
        response[ "string_error"             ]                  = getResultString( WS_SUCCESS );
    }

    if ( canRefundProduct == true )
    {
        if ( CreateProductSalesReversalTmi( pDataOut, response ) == 0 )
        {
            /*  Successfully created product sales TMI record */
            unsigned int     globalSalesTxnNum                  = 0;

            /*  Retrieves last generated (sale) transaction record number */
            if ( TMI_getCounter( "salesTransactionCount", &globalSalesTxnNum ) < 0 )
            {
                CsErrx( "processProductSalesReversal : TMI_getCounter() failed" );
                globalSalesTxnNum                               = 0;
            }

            response[ "data" ][ "revsaleseqno"  ]               = globalSalesTxnNum;
            response[ "data" ][ "vat_rate"      ]               = vatRate;
        }
        else
        {
            /*  Failed creating product sales TMI record */
            response[ "data" ][ "string_reason" ]               = getRejectReasonString( MYKI_BR_REJECT_REASON_NOT_APPLICABLE );
            response[ "string_error" ]                          = getResultString( WS_ERROR );

            /*  TODO:   Generate alarm */
        }
    }

    return fw.write( response );
}
ApplicationStatus::ApplicationStatus(json_object* item)
	: id("")
	, version("")
	, title("")
	, vendor("")
	, vendorUrl("")
	, iconPath("")
	, state(State_Unknown)
	, progress(0)
{
	json_object* prop = 0;
	
	prop = json_object_object_get(item, "id");
	if (prop) {
		id = getJsonString(prop);
	}
	
	prop = json_object_object_get(item, "details");
	if (prop && json_object_is_type(prop, json_type_object)) {
		json_object* detail = 0;

		detail = json_object_object_get(prop, "version");
		if (detail)
			version = getJsonString(detail);
		
		detail = json_object_object_get(prop, "title");
		if (detail)
			title = getJsonString(detail);

		detail = json_object_object_get(prop, "vendor");
		if (detail)
			vendor = getJsonString(detail);

		detail = json_object_object_get(prop, "vendorUrl");
		if (detail)
			vendorUrl = getJsonString(detail);

		detail = json_object_object_get(prop, "icon");
		if (detail)
			iconPath = getJsonString(detail);

		detail = json_object_object_get(prop, "progress");
		if (detail)
			progress = json_object_get_int(detail);

		detail = json_object_object_get(prop, "state");
		if (detail) {
			std::string stateStr = getJsonString(detail);

			if (stateStr == "icon download current") {
				state = State_Unknown;
				progress = 0;
			}
			else if (stateStr == "icon download complete") {
				state = State_IconDownload;
				progress = 0;
			}
			else if (stateStr == "ipk download current") {
				state = State_IpkDownloadCurrent;
			}
			else if (stateStr == "ipk download complete") {
				state = State_IpkDownloadComplete;
				// since progress is not given to us when in this state,
				// we will fake it in case this is the state it's in when
				// we receive our first state update
				progress = 100;
			}
			else if (stateStr == "ipk download paused") {
				state = State_IpkDownloadPaused;
			}
			else if (stateStr == "installing") {
				state = State_Installing;
				// since progress is not given to us when in this state,
				// we will fake it.
				progress = 100;
			}
			else if (stateStr == "canceled") {
				state = State_Canceled;
			}
			else if (stateStr == "install failed" ||
					stateStr == "remove failed" ||
					stateStr == "download failed") {
				state = State_Failed;
			}
			else 
				state = State_Unknown;
		}
	}
}