USP_CREDIT_REFUNDSALESORDERITEMS

Parameters

Parameter Parameter Type Mode Description
@ID uniqueidentifier INOUT
@CHANGEAGENTID uniqueidentifier IN
@ORDERID uniqueidentifier IN
@ITEMS xml IN
@TAXES xml IN
@REFUNDMETHODS xml IN
@COMMENT nvarchar(500) IN
@CREDITREASONCODEID uniqueidentifier IN
@CURRENTAPPUSERID uniqueidentifier IN
@TOTAL money IN
@TIMESTAMP bigint IN
@ADDRESSID uniqueidentifier IN

Definition

Copy


CREATE procedure dbo.USP_CREDIT_REFUNDSALESORDERITEMS
(
    @ID uniqueidentifier = null output,
    @CHANGEAGENTID uniqueidentifier = null,
    @ORDERID uniqueidentifier,
    @ITEMS xml = null,
    @TAXES xml = null,
    @REFUNDMETHODS xml = null,
    @COMMENT nvarchar(500) = null,
    @CREDITREASONCODEID uniqueidentifier = null,
    @CURRENTAPPUSERID uniqueidentifier = null,
    @TOTAL money = null,  -- OBSOLETE - Maximum refundable amount (i.e. total value of the order including discounts).

    @TIMESTAMP bigint = null,
    @ADDRESSID uniqueidentifier = null
)
as
begin
    set nocount on;

    if @ID is null
        set @ID = newid();

    if @CHANGEAGENTID is null
        exec dbo.USP_CHANGEAGENT_GETORCREATECHANGEAGENT @CHANGEAGENTID output;

    declare @CURRENTDATE datetime = getdate();
    declare @POSTSTATUSCODE tinyint;

    --Make sure nobody else has done a refund since the form was opened.

    if @TIMESTAMP <> coalesce((select TSLONG from dbo.SALESORDER where ID = @ORDERID), -1) begin
        if @ORDERID is null or @ORDERID = '00000000-0000-0000-0000-000000000000'
            raiserror('ERR_TRANSACTIONID_ORDERNOTSELECTED', 13, 1);
        else
            raiserror('ERR_CREDITITEMS_CONFLICTINGREFUND', 13, 1);

        return 1;
    end
    else begin
        update dbo.SALESORDER
        set DATECHANGED = @CURRENTDATE, CHANGEDBYID = @CHANGEAGENTID
        where ID = @ORDERID;

        select @POSTSTATUSCODE = FT.POSTSTATUSCODE
        from dbo.SALESORDER SO
        inner join dbo.FINANCIALTRANSACTION FT on FT.ID = SO.REVENUEID
        where SO.ID = @ORDERID;
    end

    -- Get refund items from input parameter @ITEMS

    declare @CREDITITEMS table(
        ID uniqueidentifier,
        SALESORDERITEMID uniqueidentifier,
        PRICE money,
        ITEMDISCOUNTS money,  -- Item-level discounts being reversed for a particular item (not included in the @ITEMS xml).

        ORDERDISCOUNTS money,  -- Order-level discounts being reversed for a particular item (not included in the @ITEMS xml).

        AMOUNTPAID money,
        AMOUNTTOREFUND money,  -- This will be the BASEAMOUNT on the refund line item.

        TYPECODE tinyint,
        [DESCRIPTION] nvarchar(255),
        EVENTREGISTRANTID uniqueidentifier,
        ITEMID uniqueidentifier,  -- membership transaction ID, membership addon ID, or itemized item ID (ticket/merch unit/item-level fee)

        REVENUESPLITID uniqueidentifier,
        CONTRIBUTEDREVENUE money
    );

    insert into @CREDITITEMS
    select
        newid(),
        T.item.value('(SALESORDERITEMID)[1]','uniqueidentifier') SALESORDERITEMID,
        T.item.value('(PRICE)[1]','money') PRICE,
        0 ITEMDISCOUNTS,
        0 ORDERDISCOUNTS,
        T.item.value('(AMOUNTPAID)[1]','money') AMOUNTPAID,
        T.item.value('(AMOUNTTOREFUND)[1]','money') AMOUNTTOREFUND,
        T.item.value('(TYPECODE)[1]','tinyint') TYPECODE,
        (select [DESCRIPTION] from dbo.SALESORDERITEM where ID = T.item.value('(SALESORDERITEMID)[1]','uniqueidentifier')) [DESCRIPTION],
        case
            when T.item.value('(EVENTREGISTRANTID)[1]','uniqueidentifier') = '00000000-0000-0000-0000-000000000000'
                then null
            else
                T.item.value('(EVENTREGISTRANTID)[1]','uniqueidentifier')
        end EVENTREGISTRANTID,
        case
            when T.item.value('(ITEMID)[1]','uniqueidentifier') = '00000000-0000-0000-0000-000000000000'
                then null
            else
                T.item.value('(ITEMID)[1]','uniqueidentifier')
        end ITEMID,
        case
            when T.item.value('(REVENUESPLITID)[1]','uniqueidentifier') = '00000000-0000-0000-0000-000000000000'
                then null
            else
                T.item.value('(REVENUESPLITID)[1]','uniqueidentifier')
        end REVENUESPLITID,
        0 CONTRIBUTEDREVENUE
    from @ITEMS.nodes('/ITEMS/ITEM') T(item)
    where T.item.value('(INCLUDE)[1]','bit') = 1;

    declare @TAXTABLE table (
        TAXID uniqueidentifier,  -- actually refers to SALESORDERITEM, not TAX.

        [DESCRIPTION] nvarchar(200),
        REVENUESPLITID uniqueidentifier,
        CURRENTTAX money  -- amount of tax to refund

    );

    insert into @TAXTABLE
    select
        T.item.value('(TAXID)[1]','uniqueidentifier'),
        (select [DESCRIPTION] from dbo.SALESORDERITEM where ID = T.item.value('(TAXID)[1]','uniqueidentifier')),
        T.item.value('(REVENUESPLITID)[1]','uniqueidentifier'),
        round(T.item.value('(CURRENTTAX)[1]','money'), 2)
    from @TAXES.nodes('/TAXES/ITEM') T(item)
    where T.item.value('(CURRENTTAX)[1]','money') > 0;


    declare @REFUNDEDITEMTOTAL money = (
        (
            select sum(case when TYPECODE = 5 then 0 else AMOUNTTOREFUND end)
            from @CREDITITEMS
        )
        + coalesce((select sum(CURRENTTAX) from @TAXTABLE),0)
    );

    -- As of 2013/09/20, we have decided that order-level discounts will be reversed only if everything they apply to is fully refunded.

    -- In other words, if there is a discounted item that is *not* fully refunded, then we *do not* reverse order discounts.

    declare @SHOULDREVERSEORDERDISCOUNTS bit = case
        when (
            -- the number of discounted ticket and merch items that are being fully refunded in this transaction

            select count(ID)
            from @CREDITITEMS CREDITITEMS
            where TYPECODE in (0,14)  -- Only tickets and merch can have order-level discounts (although merch might not)

                and AMOUNTPAID < PRICE  -- Ticket or merch item was discounted => order-level discount was applicable (this excludes non-discountable merch)

                and AMOUNTTOREFUND = AMOUNTPAID  -- fully refunding the item (because if any like this exist, we do NOT reverse the order-level discount)

        ) = (
            -- the number of discounted ticket and merch items on the entire order

            select count(TICKET.ID) + count(MERCH.ID)
            from dbo.SALESORDERITEM SOI
            left join dbo.TICKET on TICKET.SALESORDERITEMTICKETID = SOI.ID
            left join dbo.SALESORDERITEMMERCHANDISEUNIT MERCH on MERCH.SALESORDERITEMMERCHANDISEID = SOI.ID
            where SOI.SALESORDERID = @ORDERID
                and (
                    coalesce(TICKET.ORDERLEVELDISCOUNTSAPPLIED, MERCH.ORDERLEVELDISCOUNTSAPPLIED) > 0
                    or coalesce(TICKET.ITEMLEVELDISCOUNTSAPPLIED, MERCH.ITEMLEVELDISCOUNTSAPPLIED) > 0
                )
        ) then 1
        else 0
    end;

    -- Determine which item-level discounts will be reversed (only if the full amount paid for the ticket/merch unit is being refunded).

    update @CREDITITEMS
    set
        ITEMDISCOUNTS = TICKET.ITEMLEVELDISCOUNTSAPPLIED,
        ORDERDISCOUNTS = case when @SHOULDREVERSEORDERDISCOUNTS = 1 then TICKET.ORDERLEVELDISCOUNTSAPPLIED else 0 end
    from @CREDITITEMS CREDITITEMS
    inner join dbo.TICKET on TICKET.ID = CREDITITEMS.ITEMID
    where TICKET.AMOUNTPAID = CREDITITEMS.AMOUNTTOREFUND;

    update @CREDITITEMS
    set
        ITEMDISCOUNTS = MERCHANDISE.ITEMLEVELDISCOUNTSAPPLIED,
        ORDERDISCOUNTS = case when @SHOULDREVERSEORDERDISCOUNTS = 1 then MERCHANDISE.ORDERLEVELDISCOUNTSAPPLIED else 0 end
    from @CREDITITEMS CREDITITEMS
    inner join dbo.SALESORDERITEMMERCHANDISEUNIT MERCHANDISE on MERCHANDISE.ID = CREDITITEMS.ITEMID
    where MERCHANDISE.AMOUNTPAID = CREDITITEMS.AMOUNTTOREFUND;

    update @CREDITITEMS
    set
        ITEMDISCOUNTS = PROMO.AMOUNT
    from @CREDITITEMS CREDITITEMS
    inner join dbo.SALESORDERITEM MEMBERSHIPITEM on MEMBERSHIPITEM.ID = CREDITITEMS.SALESORDERITEMID
    inner join dbo.SALESORDERITEMMEMBERSHIPITEMPROMOTION PROMO on PROMO.SALESORDERITEMID = MEMBERSHIPITEM.ID
    where MEMBERSHIPITEM.PRICE = CREDITITEMS.AMOUNTTOREFUND + PROMO.AMOUNT;


    /******** Validate refund items and refund payments *******/

    -- Error if no items were selected

    if not exists (select 1 from @CREDITITEMS) begin
        raiserror('ERR_ITEMS_NONESELECTED', 13, 1);
        return 1;
    end

    -- Error if the quantity refunding for any item is greater than the quantity available

    -- -- (original quantity - already refunded quantity - quantity we're refunding right now)

    if exists(
        select SALESORDERITEM.ID
        from @CREDITITEMS CREDITITEMS
        inner join dbo.SALESORDERITEM on SALESORDERITEM.ID = CREDITITEMS.SALESORDERITEMID
        left join dbo.CREDITITEM_EXT on CREDITITEM_EXT.SALESORDERITEMID = SALESORDERITEM.ID
        left join dbo.FINANCIALTRANSACTIONLINEITEM FTLI on FTLI.ID = CREDITITEM_EXT.ID
        where
            not CREDITITEMS.TYPECODE in (0,3,14) -- tickets, fees, and merch items each have their own ITEMID, which will be validated roughly 30 lines below

        group by SALESORDERITEM.ID, SALESORDERITEM.QUANTITY
        having SALESORDERITEM.QUANTITY - coalesce(sum(FTLI.QUANTITY),0) - count(CREDITITEMS.ID) < 0
    ) begin
        raiserror('ERR_ITEMS_EXCEEDSQUANTITYAVAILABLE', 13, 1);
        return 1;
    end

    -- Error if the amount refunding is less than zero for any of the items

    if exists (select 1 from @CREDITITEMS where AMOUNTTOREFUND < 0) begin
        raiserror('ERR_ITEMS_NEGATIVEAMOUNT', 13, 1);
        return 1;
    end

    -- Error if the payments do not add up to the refund total

    if coalesce((select sum(T.item.value('(AMOUNTREFUNDING)[1]','money')) from @REFUNDMETHODS.nodes('/REFUNDMETHODS/ITEM') T(item)),0) <> @REFUNDEDITEMTOTAL begin
        raiserror('ERR_REFUNDMETHODS_DIFFERENTFROMTOTALAMOUNT', 13, 1);
        return 1;
    end

    -- Error if the refund amount is greater than the total payment amount remaining

    if @REFUNDEDITEMTOTAL > (
        select
            FINANCIALTRANSACTION.BASEAMOUNT - coalesce((
                select sum(coalesce(CREDITPAYMENT.AMOUNT, 0)) from dbo.CREDITPAYMENT where REVENUEID = FINANCIALTRANSACTION.ID
            ),0) -- (original amount - refunded amount)

        from dbo.SALESORDER
        inner join dbo.SALESORDERPAYMENT on SALESORDERPAYMENT.SALESORDERID = SALESORDER.ID
        inner join dbo.FINANCIALTRANSACTION on SALESORDERPAYMENT.PAYMENTID = FINANCIALTRANSACTION.ID
        where SALESORDER.ID = @ORDERID
    )
    begin
        raiserror('ERR_REFUNDAMOUNT_EXCEEDSLIMIT', 13, 1);
        return 1;
    end

    -- Error if an item has already been refunded

    if exists (
        select 1 from @CREDITITEMS ITEMS
        inner join dbo.CREDITITEM_EXT on ITEMS.ITEMID = CREDITITEM_EXT.SALESORDERITEMIZEDITEMID
        inner join dbo.FINANCIALTRANSACTIONLINEITEM FTLI on FTLI.ID = CREDITITEM_EXT.ID
        where FTLI.BASEAMOUNT > 0
    )
    begin
        raiserror('ERR_ITEMALREADYREFUNDED', 13, 1);
        return 1;
    end

    -- Error if an itemized item has no ITEMID

    if exists (
        select 1
        from @CREDITITEMS ITEMS
        where TYPECODE in (0,14)  -- ticket, merch

            and ITEMID is null

        union all

        -- item-level fees

        select 1
        from @CREDITITEMS ITEMS
        inner join dbo.SALESORDERITEMFEE FEEITEM on FEEITEM.ID = ITEMS.SALESORDERITEMID
        where FEEITEM.APPLIESTOCODE = 1  -- item-level

            and ITEMS.ITEMID is null
    )
    begin
        raiserror('ERR_ITEMWITHOUTITEMID', 13, 1);
        return 1;
    end

    -- Error if there are any later membership transactions

    if exists (
        select 1 from @CREDITITEMS MEMBERSHIPITEM
        inner join dbo.MEMBERSHIPTRANSACTION REFUNDINGTRANSACTION on REFUNDINGTRANSACTION.ID = MEMBERSHIPITEM.ITEMID
        outer apply (
            select top 1 ID
            from dbo.MEMBERSHIPTRANSACTION
            where MEMBERSHIPID = REFUNDINGTRANSACTION.MEMBERSHIPID
            order by DATEADDED desc
        ) LATESTTRANSACTION
        where MEMBERSHIPITEM.ITEMID <> LATESTTRANSACTION.ID
    )
    begin
        raiserror('ERR_ITEMS_MEMBERSHIPREFUND_HASNEWERMEMBERSHIPTRANSACTIONS', 13, 1);
        return 1;
    end

    -- Error if there are memberships with contributed revenue that are not being fully refunded

    if exists (
        select 1 from @CREDITITEMS MEMBERSHIPITEM
        inner join dbo.MEMBERSHIPTRANSACTION REFUNDINGTRANSACTION on REFUNDINGTRANSACTION.ID = MEMBERSHIPITEM.ITEMID
        inner join dbo.FINANCIALTRANSACTIONLINEITEM MEMBERSHIPLI on MEMBERSHIPLI.ID = REFUNDINGTRANSACTION.REVENUESPLITID
        inner join dbo.FINANCIALTRANSACTIONLINEITEM CONTRIBUTEDLI on CONTRIBUTEDLI.SOURCELINEITEMID = MEMBERSHIPLI.ID
        inner join dbo.REVENUESPLIT_EXT EXT
            on (EXT.ID = CONTRIBUTEDLI.ID and EXT.TYPECODE = 0 and EXT.APPLICATIONCODE = 0 and CONTRIBUTEDLI.TYPECODE = 0)  -- Standard donation line item whose source is the membership line item

        group by MEMBERSHIPLI.ID, MEMBERSHIPLI.BASEAMOUNT, MEMBERSHIPITEM.AMOUNTTOREFUND, MEMBERSHIPITEM.ITEMDISCOUNTS
        having MEMBERSHIPLI.BASEAMOUNT + sum(CONTRIBUTEDLI.BASEAMOUNT) <> MEMBERSHIPITEM.AMOUNTTOREFUND + MEMBERSHIPITEM.ITEMDISCOUNTS
    )
    begin
        raiserror('ERR_ITEMS_MEMBERSHIPPARTIALREFUND_HASCONTRIBUTEDREVENUE', 13, 1);
        return 1;
    end

    -- Error if there are combination tickets that are not being fully refunded

    -- TODO: verify

    if exists (
        select 1
        from @CREDITITEMS COMBOITEMS
        inner join dbo.SALESORDERITEMTICKET TICKETS on COMBOITEMS.SALESORDERITEMID = TICKETS.ID
        inner join dbo.SALESORDERITEMTICKETCOMBINATION COMBO on TICKETS.ID = COMBO.ID
        where COMBOITEMS.AMOUNTPAID <> COMBOITEMS.AMOUNTTOREFUND
    )
    begin
        raiserror('ERR_ITEMS_COMBINATIONTICKETPARTIALREFUND', 13, 1);
        return 1;
    end


    -- Verify that the credit card we want to refund to has actually been processed

    if exists
    (
        select
            CREDITCARDPAYMENTMETHODDETAIL.ID
        from dbo.CREDITCARDPAYMENTMETHODDETAIL
        inner join dbo.REVENUEPAYMENTMETHOD on REVENUEPAYMENTMETHOD.ID = CREDITCARDPAYMENTMETHODDETAIL.ID
        where
            REVENUEPAYMENTMETHOD.REVENUEID in (
                select
                    case when T.item.value('(REVENUEID)[1]','uniqueidentifier') = '00000000-0000-0000-0000-000000000000'
                        then (select FINANCIALTRANSACTIONID from dbo.FINANCIALTRANSACTIONLINEITEM where ID = T.item.value('(REVENUESPLITID)[1]','uniqueidentifier'))
                        else T.item.value('(REVENUEID)[1]','uniqueidentifier')
                    end
                from @REFUNDMETHODS.nodes('/REFUNDMETHODS/ITEM') T(item)
                where T.item.value('(PAYMENTTYPECODE)[1]','integer') = 2
            )
            and CREDITCARDPAYMENTMETHODDETAIL.TRANSACTIONID is null
    )
        raiserror('BBERR_REFUNDCREDITCARDPAYMENT_MUSTBEPROCESSED', 16, 1);


    -- TODO: this does not currently work!

    -- If there are combination items to refund, get the same type of the tickets of all the programs in the combination.

    -- (the item that is actually displayed to the user only represents a single ticket from the combo, but all need to be refunded.)

    if exists (
        select ITEMS.SALESORDERITEMID
        from @CREDITITEMS ITEMS
        inner join dbo.SALESORDERITEMTICKET TICKETS on ITEMS.SALESORDERITEMID = TICKETS.ID
        inner join dbo.SALESORDERITEMTICKETCOMBINATION COMBO on TICKETS.ID = COMBO.ID
    ) begin
        -- Update the price of the item already in @CREDITITEMS

        -- the price returned from input parameter is the price for the whole combo,

        -- not the price for the individual program.

        update @CREDITITEMS set
            PRICE = TICKETS.PRICE
        from @CREDITITEMS ITEMS
        inner join dbo.SALESORDERITEMTICKET TICKETS on ITEMS.SALESORDERITEMID = TICKETS.ID
        inner join dbo.SALESORDERITEMTICKETCOMBINATION COMBO on TICKETS.ID = COMBO.ID

        --update price for applying tickets to membership

        update @CREDITITEMS set
            PRICE = SALESORDERITEM.PRICE
        from @CREDITITEMS ITEMS
        inner join dbo.SALESORDERITEM on ITEMS.SALESORDERITEMID = SALESORDERITEM.ID
        inner join dbo.SALESORDERITEMMEMBERSHIPITEMPROMOTION on SALESORDERITEM.ID = SALESORDERITEMMEMBERSHIPITEMPROMOTION.SALESORDERITEMID

        insert into @CREDITITEMS
        select
            newid() as ID,
            SALESORDERITEM.ID,
            SALESORDERITEM.PRICE,
            0 as ITEMDISCOUNTS,
            0 as ORDERDISCOUNTS,
            0 AMOUNTPAID,
            SALESORDERITEM.PRICE as AMOUNTTOREFUND,
            0 as TYPECODE,
            SALESORDERITEM.[DESCRIPTION],
            null as EVENTREGISTRANTID,
            null as ITEMID,
            LI.ID as REVENUESPLITID,
            0 CONTRIBUTEDREVENUE
        from dbo.SALESORDERITEM
        inner join dbo.SALESORDERITEMTICKET on SALESORDERITEM.ID = SALESORDERITEMTICKET.ID
        inner join dbo.SALESORDERITEMTICKETCOMBINATION on SALESORDERITEMTICKET.ID = SALESORDERITEMTICKETCOMBINATION.ID
        inner join dbo.SALESORDER on SALESORDER.ID = SALESORDERITEM.SALESORDERID
        inner join dbo.FINANCIALTRANSACTIONLINEITEM as LI on LI.FINANCIALTRANSACTIONID = SALESORDER.REVENUEID
        inner join dbo.REVENUESPLITORDER on REVENUESPLITORDER.ID = LI.ID
        where
            SALESORDERITEM.ID not in (select SALESORDERITEMID from @CREDITITEMS)
            and SALESORDERITEMTICKETCOMBINATION.TICKETCOMBINATIONID in
            (
                select COMBO.TICKETCOMBINATIONID
                from @CREDITITEMS ITEMS
                inner join dbo.SALESORDERITEMTICKET TICKETS on ITEMS.SALESORDERITEMID = TICKETS.ID
                inner join dbo.SALESORDERITEMTICKETCOMBINATION COMBO on TICKETS.ID = COMBO.ID
            )
            and SALESORDERITEMTICKET.PRICETYPECODEID in
            (
                select TICKETS.PRICETYPECODEID from @CREDITITEMS ITEMS
                inner join dbo.SALESORDERITEMTICKET TICKETS on ITEMS.SALESORDERITEMID = TICKETS.ID
                inner join dbo.SALESORDERITEMTICKETCOMBINATION COMBO on TICKETS.ID = COMBO.ID
                where
                    COMBO.COMBINATIONID = SALESORDERITEMTICKETCOMBINATION.COMBINATIONID and
                    COMBO.TICKETCOMBINATIONID = SALESORDERITEMTICKETCOMBINATION.TICKETCOMBINATIONID
            )
            and (
                REVENUESPLITORDER.PROGRAMID = SALESORDERITEMTICKET.PROGRAMID
                and (SALESORDERITEMTICKET.EVENTID is null or SALESORDERITEMTICKET.EVENTID = REVENUESPLITORDER.EVENTID)
            )
    end

    if exists
    (
        select 1
        from @CREDITITEMS ITEMS
        where ITEMS.TYPECODE = 2 -- Donation

    )
    begin
        -- Error if there are matching gifts

        if exists
        (
            select 1
            from dbo.REVENUEMATCHINGGIFT
            inner join @REFUNDMETHODS.nodes('/REFUNDMETHODS/ITEM') T(item) on
                T.item.value('(REVENUEID)[1]', 'uniqueidentifier') = REVENUEMATCHINGGIFT.MGSOURCEREVENUEID
        ) begin
            raiserror('ERR_REFUNDMETHODS_DONATIONMATCHINGGIFTS', 13, 1);
            return 1;
        end

        -- Recognition credits have no significant financial implications, so we'll just cut them by the same percentage as the donation.

        declare @DONATIONREFUNDS table (REVENUESPLITID uniqueidentifier, PERCENTREFUNDED real);
        insert into @DONATIONREFUNDS
        select
            SALESORDERITEMDONATION.REVENUESPLITID,
            case
                when SALESORDERITEMDONATION.AMOUNT = 0.0 or CREDITITEMS.AMOUNTTOREFUND = SALESORDERITEMDONATION.AMOUNT
                    then 1.0
                else CREDITITEMS.AMOUNTTOREFUND / SALESORDERITEMDONATION.AMOUNT
            end
        from dbo.SALESORDERITEMDONATION
        inner join @CREDITITEMS CREDITITEMS on CREDITITEMS.SALESORDERITEMID = SALESORDERITEMDONATION.ID
        where CREDITITEMS.TYPECODE = 2;

        -- Delete if we're refunding the entire donation

        delete from dbo.REVENUERECOGNITION
        where REVENUERECOGNITION.REVENUESPLITID in
            (
                select ITEMS.REVENUESPLITID
                from @DONATIONREFUNDS ITEMS
                where PERCENTREFUNDED = 1.0
            );

        -- Reduce if we're only refunding part of the donation.

        update dbo.REVENUERECOGNITION
        set AMOUNT = AMOUNT * (1.0 - DONATIONREFUNDS.PERCENTREFUNDED)
        from dbo.REVENUERECOGNITION
        inner join @DONATIONREFUNDS DONATIONREFUNDS on REVENUERECOGNITION.REVENUESPLITID = DONATIONREFUNDS.REVENUESPLITID
        where DONATIONREFUNDS.PERCENTREFUNDED <> 1.0;
    end


    --Throw an error if we're refunding a membership, but all the add-ons have not been refunded.

    if exists(
        select MEMBERSHIPITEM.ID
        from @CREDITITEMS MEMBERSHIPITEM
        inner join dbo.MEMBERSHIPADDON on MEMBERSHIPADDON.MEMBERSHIPTRANSACTIONID = MEMBERSHIPITEM.ITEMID  -- Addons that were sold as part of the transaction we're refunding

        left join @CREDITITEMS CREDITADDONS on CREDITADDONS.ITEMID = MEMBERSHIPADDON.ID  -- Addons we're refunding right now

        left join
        (
            select FTLI.SOURCELINEITEMID, FTLI.QUANTITY
            from dbo.FINANCIALTRANSACTIONLINEITEM FTLI
            inner join dbo.CREDITITEM_EXT on CREDITITEM_EXT.ID = FTLI.ID
            where CREDITITEM_EXT.TYPECODE = 16 -- refunded membership addon

        ) PREVIOUSLYREFUNDEDADDONREFUNDITEMS
            on PREVIOUSLYREFUNDEDADDONREFUNDITEMS.SOURCELINEITEMID = MEMBERSHIPADDON.REVENUESPLITID
        where MEMBERSHIPITEM.TYPECODE = 1
        group by
            MEMBERSHIPADDON.QUANTITY,
            MEMBERSHIPITEM.ID,
            MEMBERSHIPADDON.ID
        having (coalesce(count(CREDITADDONS.ID),0) + coalesce(sum(PREVIOUSLYREFUNDEDADDONREFUNDITEMS.QUANTITY),0) <> MEMBERSHIPADDON.QUANTITY)
    )
    begin
        raiserror('BBERR_ITEMS_REFUNDINGMEMBERSHIPWITHOUTADDONS', 16, 1);
        return 1;
    end

    declare @CURRENTDATETIMEOFFSET datetimeoffset = dbo.UFN_TIMEZONEENTRY_GETSYSTEMDEFAULTDATEWITHTIMEOFFSET(getutcdate(), 1);

    -- Bug #167166 - MDC

    declare @TRANSACTIONDATE datetime = dbo.UFN_TIMEZONEENTRY_GETSYSTEMDEFAULTDATEFROMUTC(getutcdate());

    begin try
        declare @CONSTITUENTID uniqueidentifier = (select CONSTITUENTID from dbo.SALESORDER where ID = @ORDERID);

        -- handle inserting the data

        declare @CURRENCYID uniqueidentifier = (select ID from dbo.CURRENCY where ISORGANIZATIONCURRENCY = 1);

        declare @PDACCOUNTSYSTEMID uniqueidentifier;
        declare @ALLOWGLDISTRIBUTIONS bit;

        select
            @PDACCOUNTSYSTEMID = ID,
            @ALLOWGLDISTRIBUTIONS = ALLOWGLDISTRIBUTIONS
        from
            dbo.UFN_PDACCOUNTSYSTEM_DEFAULTORSYSTEM();

        insert into dbo.FINANCIALTRANSACTION
        (
            ID
            ,TRANSACTIONAMOUNT
            ,BASEAMOUNT
            ,ORGAMOUNT
            ,TRANSACTIONCURRENCYID
            ,TYPECODE
            ,PDACCOUNTSYSTEMID
            ,[DESCRIPTION]
            ,[DATE]
            ,POSTDATE
            ,POSTSTATUSCODE
            ,PARENTID
            ,APPUSERID
            ,CONSTITUENTID
            ---- Boilerplate

            , DATEADDED, DATECHANGED, ADDEDBYID, CHANGEDBYID
        )
        values
        (
            @ID
            ,@REFUNDEDITEMTOTAL
            ,@REFUNDEDITEMTOTAL
            ,@REFUNDEDITEMTOTAL
            ,@CURRENCYID
            ,23 --Refund

            ,@PDACCOUNTSYSTEMID
            ,@COMMENT
            ,@TRANSACTIONDATE
            ,case when @ALLOWGLDISTRIBUTIONS = 1 and isnull(@POSTSTATUSCODE, 1) != 3 then cast(@TRANSACTIONDATE as date) else null end
            ,case when @ALLOWGLDISTRIBUTIONS = 1 and isnull(@POSTSTATUSCODE, 1) != 3 then 1 else 3 end
            ,(select REVENUEID from dbo.SALESORDER where ID = @ORDERID)
            ,@CURRENTAPPUSERID
            ,@CONSTITUENTID
            ,@CURRENTDATE, @CURRENTDATE, @CHANGEAGENTID, @CHANGEAGENTID
        );

        insert into dbo.CREDIT_EXT
        (
            ID
            ,SALESORDERID
            ,CREDITREASONCODEID
            ---- Boilerplate

            ,DATEADDED ,DATECHANGED ,ADDEDBYID ,CHANGEDBYID
        )
        values
        (
            @ID
            ,@ORDERID
            ,case @CREDITREASONCODEID
                when '00000000-0000-0000-0000-000000000000' then null
                else @CREDITREASONCODEID
            end
            ,@CURRENTDATE, @CURRENTDATE, @CHANGEAGENTID, @CHANGEAGENTID
        );

        insert into dbo.CREDITPAYMENT
        (
            ID,
            CREDITID,
            APPUSERID,
            CREDITPAYMENTDATEWITHTIMEOFFSET,
            AMOUNT,
            PAYMENTMETHODCODE,
            OTHERPAYMENTMETHODCODEID,
            REVENUEID,
            REVENUESPLITID,
            REFUNDPROCESSED,
            [STATUS],
            ADDEDBYID,
            CHANGEDBYID,
            DATEADDED,
            DATECHANGED
        )
        select
            T.item.value('(CREDITPAYMENTID)[1]','uniqueidentifier'),
            @ID,
            @CURRENTAPPUSERID,
            @CURRENTDATETIMEOFFSET,
            T.item.value('(AMOUNTREFUNDING)[1]','money'),
            T.item.value('(PAYMENTTYPECODE)[1]','tinyint'),
            case
                when T.item.value('(PAYMENTTYPECODE)[1]','tinyint') = 10
                    then T.item.value('(OTHERPAYMENTMETHODCODEID)[1]','uniqueidentifier')
                else
                    null
            end,
            case
                when T.item.value('(REVENUEID)[1]','uniqueidentifier') = '00000000-0000-0000-0000-000000000000'
                    then null
                else
                    T.item.value('(REVENUEID)[1]','uniqueidentifier')
            end,
            case
                when T.item.value('(REVENUESPLITID)[1]','uniqueidentifier') = '00000000-0000-0000-0000-000000000000'
                    then null
                else
                    T.item.value('(REVENUESPLITID)[1]','uniqueidentifier')
            end,
            case T.item.value('(PAYMENTTYPECODE)[1]','tinyint')
                when 2 then 0
                else 1
            end,
            T.item.value('(STATUS)[1]','nvarchar(255)'),
            @CHANGEAGENTID,
            @CHANGEAGENTID,
            @CURRENTDATE,
            @CURRENTDATE
        from @REFUNDMETHODS.nodes('/REFUNDMETHODS/ITEM') T(item);

        if @ADDRESSID = '00000000-0000-0000-0000-000000000000'
            set @ADDRESSID = null;

        insert into dbo.CREDITPAYMENTCHECKPAYMENTMETHODDETAIL
        (
            ID,
            ADDRESSID,
            ADDEDBYID,
            CHANGEDBYID,
            DATEADDED,
            DATECHANGED
        )
        select
            T.item.value('(CREDITPAYMENTID)[1]','uniqueidentifier'),
            @ADDRESSID,
            @CHANGEAGENTID,
            @CHANGEAGENTID,
            @CURRENTDATE,
            @CURRENTDATE
        from
            @REFUNDMETHODS.nodes('/REFUNDMETHODS/ITEM') T(item)
        where
            T.item.value('(PAYMENTTYPECODE)[1]','tinyint') = 1;

        begin
            -- This section handles memberships and event registrations with contributed revenue.

            -- The gist of what's about to happen is:

            -- 1. Find the contributory line items (donation line items whose sourcelineitemid is the revenuesplitid of an item we're trying to refund).

            -- 2. For discounted memberships, prorate the discount across the contributory line items and subtract it from the membership line item.

            -- 3. Subtract the contributory amount from the amount to refund of the membership or event registration item.


            declare @CONTRIBUTEDREVENUE table (
                CONTRIBUTEDLINEITEMID uniqueidentifier,
                SOURCELINEITEMID uniqueidentifier,
                TYPECODE tinyint,
                [DESCRIPTION] nvarchar(200),
                ORIGINALAMOUNT money,
                DISCOUNTS money
            );

            -- Find contributed revenue (for memberships and event registrations).

            insert into @CONTRIBUTEDREVENUE
            select
                CONTRIBUTEDLI.ID CONTRIBUTEDLINEITEMID,
                CONTRIBUTEDLI.SOURCELINEITEMID,
                case
                    when CONTRIBUTEDLI_EXT.APPLICATIONCODE = 1 then 6
                    else 2
                end TYPECODE,
                CONTRIBUTEDLI.[DESCRIPTION],
                CONTRIBUTEDLI.BASEAMOUNT ORIGINALAMOUNT,
                null DISCOUNTS
            from @CREDITITEMS CREDITITEMS
            inner join dbo.FINANCIALTRANSACTIONLINEITEM CONTRIBUTEDLI on CONTRIBUTEDLI.SOURCELINEITEMID = CREDITITEMS.REVENUESPLITID
            inner join dbo.REVENUESPLIT_EXT CONTRIBUTEDLI_EXT on CONTRIBUTEDLI_EXT.ID = CONTRIBUTEDLI.ID
            inner join dbo.FINANCIALTRANSACTIONLINEITEM PAYMENTLI on PAYMENTLI.SOURCELINEITEMID = CONTRIBUTEDLI.ID
            inner join dbo.FINANCIALTRANSACTION PAYMENTFT on PAYMENTFT.ID = PAYMENTLI.FINANCIALTRANSACTIONID
            where
                CONTRIBUTEDLI_EXT.TYPECODE = 0
                and CONTRIBUTEDLI_EXT.APPLICATIONCODE in (0,1)
                and CONTRIBUTEDLI.TYPECODE = 0
                and PAYMENTFT.TYPECODE = 0;

            if exists (select 1 from @CONTRIBUTEDREVENUE)
            begin
                -- Set the contributed amount on items with partially contributed revenue.

                update @CREDITITEMS
                set CONTRIBUTEDREVENUE = coalesce((select sum(ORIGINALAMOUNT) from @CONTRIBUTEDREVENUE where SOURCELINEITEMID = CI.REVENUESPLITID),0)
                from @CREDITITEMS CI;

                -- Prorate item-level discounts across contributed items.

                -- This could be more efficient (when discount is more than contributed amount, no proration is necessary, because the full contributory amount is discounted), but that's not important.

                update @CONTRIBUTEDREVENUE
                set DISCOUNTS = PRORATEDAMOUNTS.AMOUNT
                from @CREDITITEMS CREDITITEMS
                cross apply dbo.UFN_SPLITS_PRORATEAMOUNTS(
                    CREDITITEMS.CONTRIBUTEDREVENUE,
                    (select case when CREDITITEMS.ITEMDISCOUNTS > CREDITITEMS.CONTRIBUTEDREVENUE then CREDITITEMS.CONTRIBUTEDREVENUE else CREDITITEMS.ITEMDISCOUNTS end),
                    2,
                    (
                        select CONTRIBUTEDREVENUE.CONTRIBUTEDLINEITEMID ID, CONTRIBUTEDREVENUE.ORIGINALAMOUNT AMOUNT
                        from @CONTRIBUTEDREVENUE CONTRIBUTEDREVENUE
                        where CONTRIBUTEDREVENUE.SOURCELINEITEMID = CREDITITEMS.REVENUESPLITID
                        for xml raw('ITEM'),type,elements,root('AMOUNTSTOPRORATE'),binary base64
                    )
                ) PRORATEDAMOUNTS
                where PRORATEDAMOUNTS.ID = CONTRIBUTEDLINEITEMID;

                -- Reduce the discount amount for partially contributed items (since the discounts have been moved to the contributory line items).

                -- Also reduce the refund amount to account for discounts in excess of the contributory revenue.

                update @CREDITITEMS
                set
                    AMOUNTTOREFUND = case when CONTRIBUTEDREVENUE > ITEMDISCOUNTS then PRICE - CONTRIBUTEDREVENUE else AMOUNTTOREFUND end,
                    ITEMDISCOUNTS = case when CONTRIBUTEDREVENUE > ITEMDISCOUNTS then 0 else ITEMDISCOUNTS - CONTRIBUTEDREVENUE end
                where CONTRIBUTEDREVENUE > 0;

                -- At this point:

                -- -- 1. The ITEMDISCOUNTS of a partially contributed credit item will be max(0, discounted amount - contributed amount)

                -- -- 2. The AMOUNTTOREFUND of a partially contributed credit item will be PRICE - max(contributed amount, discounted amount).

            end
        end


        declare @INS table
        (
            ID uniqueidentifier
            ,CREDITID uniqueidentifier
            ,SALESORDERITEMID uniqueidentifier
            ,PRICE money
            ,DISCOUNTS money
            ,TYPECODE tinyint
            ,[DESCRIPTION] nvarchar(700)
            ,GROUPID uniqueidentifier
            ,GROUPTYPECODE tinyint
            ,SOURCELINEITEMID uniqueidentifier
            ,REFUNDAMOUNT money
            ,UNITVALUE money
            ,ITEMID uniqueidentifier
        );

        -- This may not be the most intuitive data set-up in the world (particularly the roles that UNITVALUE and DISCOUNTS play in GL creation).

        -- However, the alternatives are breaking backward-compatibility of refund data or converting all existing refunds into something that makes more sense,

        -- neither of which we have time to do right now.

        insert into @INS
        (
            ID
            ,CREDITID
            ,SALESORDERITEMID
            ,PRICE
            ,DISCOUNTS
            ,TYPECODE
            ,[DESCRIPTION]
            ,GROUPID
            ,GROUPTYPECODE
            ,SOURCELINEITEMID
            ,REFUNDAMOUNT
            ,UNITVALUE
            ,ITEMID
        )
        select
            ID,
            @ID,
            SALESORDERITEMID,
            PRICE,
            ITEMDISCOUNTS,
            TYPECODE,
            [DESCRIPTION],
            EVENTREGISTRANTID,
            case when TYPECODE = 6 then 1 else 0 end GROUPTYPECODE,
            REVENUESPLITID,
            AMOUNTTOREFUND + ORDERDISCOUNTS,  -- Refund amount needs to include order discounts (reversed in separate line item)

            AMOUNTTOREFUND + CONTRIBUTEDREVENUE + ITEMDISCOUNTS + ORDERDISCOUNTS,  -- Unit value needs to include discounts and contributed revenue (for GL generation)

            ITEMID
        from @CREDITITEMS

        union all

        --taxes

        select
            newid(),
            @ID,
            TAXES.TAXID,
            TAXES.CURRENTTAX PRICE,
            0 DISCOUNTS,
            4 TYPECODE,
            (select [DESCRIPTION] from dbo.SALESORDERITEM where ID = TAXES.TAXID),
            null GROUPID,
            0 GROUPTYPECODE,
            TAXES.REVENUESPLITID,
            TAXES.CURRENTTAX,
            TAXES.CURRENTTAX UNITVALUE,
            null ITEMID
        from @TAXTABLE TAXES

        union all

        -- Contributory portions of memberships and event registrations

        select
            newid(),
            @ID,
            null SALESORDERITEMID,
            ORIGINALAMOUNT PRICE,
            DISCOUNTS,
            TYPECODE,
            [DESCRIPTION],
            null GROUPID,
            0 GROUPTYPECODE,
            CONTRIBUTEDLINEITEMID,
            ORIGINALAMOUNT - DISCOUNTS REFUNDAMOUNT,
            ORIGINALAMOUNT UNITVALUE,
            null ITEMID
        from @CONTRIBUTEDREVENUE;


        -- TODO: verify that this is still correct behavior (I think it is accounted for above with DISCOUNTS = 0, but not sure)

        --earned income needs to reflect the FTLI table entry, not the SOI table entry for events that have designations on fees.

        update @INS
        set REFUNDAMOUNT = (select TRANSACTIONAMOUNT from dbo.FINANCIALTRANSACTIONLINEITEM LI where LI.ID = I.SOURCELINEITEMID)
        from @INS I
        inner join dbo.SALESORDERITEM SOI on SOI.ID = I.SALESORDERITEMID
        inner join dbo.SALESORDERITEMEVENTREGISTRATION ER on ER.ID = SOI.ID
        inner join dbo.REGISTRANT R on R.ID = ER.REGISTRANTID
        inner join dbo.[EVENT] E on E.ID = R.EVENTID
        where E.DESIGNATIONSONFEES = 1;


        insert into dbo.FINANCIALTRANSACTIONLINEITEM
        (
            ID
            ,FINANCIALTRANSACTIONID
            ,UNITVALUE
            ,QUANTITY
            ,[DESCRIPTION]
            ,TRANSACTIONAMOUNT
            ,BASEAMOUNT
            ,ORGAMOUNT
            ,POSTDATE
            ,POSTSTATUSCODE
            ,TYPECODE
            ,SOURCELINEITEMID
            ,ADDEDBYID, CHANGEDBYID, DATEADDED, DATECHANGED
        )
        select
            I.ID
            ,I.CREDITID
            ,I.UNITVALUE
            ,1 QUANTITY
            ,I.[DESCRIPTION]
            ,I.REFUNDAMOUNT
            ,I.REFUNDAMOUNT
            ,I.REFUNDAMOUNT
            ,FT.POSTDATE
            ,FT.POSTSTATUSCODE
            ,case I.TYPECODE
                when 3 then 7 --Fee

                when 4 then 8 --Tax

                when 5 then 5 --Discount

                else 0 end
            ,I.SOURCELINEITEMID
            ,@CHANGEAGENTID, @CHANGEAGENTID, @CURRENTDATE, @CURRENTDATE
        from @INS I
        inner join dbo.FINANCIALTRANSACTION FT on FT.ID = I.CREDITID;

        insert into dbo.CREDITITEM_EXT
        (
            ID
            ,CREDITID
            ,DISCOUNTS
            ,FEES
            ,GROUPID
            ,GROUPTYPECODE
            ,SALESORDERITEMID
            ,TYPECODE
            ,SALESORDERITEMIZEDITEMID
            ,ADDEDBYID, CHANGEDBYID, DATEADDED, DATECHANGED
        )
        select
            ID
            ,CREDITID
            ,DISCOUNTS
            ,0 FEES
            ,GROUPID
            ,GROUPTYPECODE
            ,SALESORDERITEMID
            ,TYPECODE
            ,case when TYPECODE in (1,16) then null else ITEMID end  -- Membership and Add-on are not itemized

            ,@CHANGEAGENTID, @CHANGEAGENTID, @CURRENTDATE, @CURRENTDATE
        from @INS;

        -- Update ticket status for fully refunded tickets

        update dbo.TICKET
        set TICKET.STATUSCODE = 2
        where ID in (select ITEMID from @CREDITITEMS where TYPECODE = 0 and AMOUNTTOREFUND = AMOUNTPAID);

        -- Update ticket applied to membership fields for tickets that were applied to a fully refunded membership

        -- TODO: verify that applied ticket discount is reversed when partially contributory membership is refunded.

        update dbo.TICKET
        set
            TICKET.APPLIEDTOMEMBERSHIP = 0,
            TICKET.APPLIEDTOMEMBERSHIPSALESORDERID = null
        from dbo.TICKET
        inner join dbo.SALESORDERITEMMEMBERSHIPITEMPROMOTIONAPPLIEDITEMTICKET APPLIEDTICKETS on TICKET.ID = APPLIEDTICKETS.TICKETID
        inner join dbo.SALESORDERITEMMEMBERSHIPITEMPROMOTIONAPPLIEDITEM APPLIEDITEM on APPLIEDTICKETS.SALESORDERITEMMEMBERSHIPITEMPROMOTIONAPPLIEDITEMID = APPLIEDITEM.ID
        inner join dbo.SALESORDERITEMMEMBERSHIPITEMPROMOTION PROMOTION on APPLIEDITEM.SALESORDERITEMMEMBERSHIPITEMPROMOTIONID = PROMOTION.ID
        inner join @CREDITITEMS MEMBERSHIPCREDITITEMS on PROMOTION.SALESORDERITEMID = MEMBERSHIPCREDITITEMS.SALESORDERITEMID
        where MEMBERSHIPCREDITITEMS.AMOUNTTOREFUND = (MEMBERSHIPCREDITITEMS.AMOUNTPAID - MEMBERSHIPCREDITITEMS.CONTRIBUTEDREVENUE);

        -- Update on hand quantity for returned merchandise items

        with MERCHANDISEREFUNDS as (
            select
                SOIM.MERCHANDISEPRODUCTINSTANCEID,
                count(CI.ID) QUANTITYREFUNDING
            from @CREDITITEMS CI
            inner join dbo.SALESORDERITEMMERCHANDISE SOIM on SOIM.ID = CI.SALESORDERITEMID
            where CI.AMOUNTTOREFUND = CI.AMOUNTPAID
            group by SOIM.MERCHANDISEPRODUCTINSTANCEID
        )
        update dbo.MERCHANDISEPRODUCTINSTANCE
        set ONHANDQUANTITY += MERCHANDISEREFUNDS.QUANTITYREFUNDING
        from dbo.MERCHANDISEPRODUCTINSTANCE
        inner join MERCHANDISEREFUNDS on MERCHANDISEREFUNDS.MERCHANDISEPRODUCTINSTANCEID = MERCHANDISEPRODUCTINSTANCE.ID;

        -- Update refunded amount for returned merchandise item

        update dbo.SALESORDERITEMMERCHANDISEUNIT
        set REFUNDEDAMOUNT = coalesce(REFUNDEDAMOUNT,0) + ITEMS.AMOUNTTOREFUND
        from dbo.SALESORDERITEMMERCHANDISEUNIT
        inner join @CREDITITEMS ITEMS on ITEMS.ITEMID = SALESORDERITEMMERCHANDISEUNIT.ID;

        -- Update refunded amount for refunded ticket fees

        update dbo.SALESORDERITEMTICKETFEE
        set REFUNDEDAMOUNT = coalesce(REFUNDEDAMOUNT,0) + ITEMS.AMOUNTTOREFUND
        from dbo.SALESORDERITEMTICKETFEE
        inner join @CREDITITEMS ITEMS on ITEMS.ITEMID = SALESORDERITEMTICKETFEE.ID;

        --Add credit membership items

        insert into dbo.CREDITITEMMEMBERSHIP
        (
            ID,
            MEMBERSHIPID,
            MEMBERSHIPPROGRAMID,
            MEMBERSHIPPROGRAMNAME,
            MEMBERSHIPLEVELID,
            MEMBERSHIPLEVELTERMID,
            MEMBERSHIPLEVELTYPECODEID,
            NUMBEROFCHILDREN,
            COMMENTS,
            ISGIFT,
            GIVENBYID,
            EXPIRATIONDATE,
            MEMBERS,
            ACTIONCODE,
            ADDEDBYID,
            CHANGEDBYID,
            DATEADDED,
            DATECHANGED
        )
        select
            CREDITITEMS.ID,
            SALESORDERITEMMEMBERSHIP.MEMBERSHIPID,
            SALESORDERITEMMEMBERSHIP.MEMBERSHIPPROGRAMID,
            SALESORDERITEMMEMBERSHIP.MEMBERSHIPPROGRAMNAME,
            SALESORDERITEMMEMBERSHIP.MEMBERSHIPLEVELID,
            SALESORDERITEMMEMBERSHIP.MEMBERSHIPLEVELTERMID,
            SALESORDERITEMMEMBERSHIP.MEMBERSHIPLEVELTYPECODEID,
            SALESORDERITEMMEMBERSHIP.NUMBEROFCHILDREN,
            SALESORDERITEMMEMBERSHIP.COMMENTS,
            SALESORDERITEMMEMBERSHIP.ISGIFT,
            SALESORDERITEMMEMBERSHIP.GIVENBYID,
            SALESORDERITEMMEMBERSHIP.EXPIRATIONDATE,
            (
                select dbo.UFN_CONSTITUENT_BUILDNAME(SOIM.CONSTITUENTID) MEMBERNAME
                from dbo.SALESORDERITEMMEMBER as SOIM
                inner join dbo.SALESORDERITEMMEMBERSHIP as SOIMS on SOIM.SALESORDERITEMMEMBERSHIPID = SOIMS.ID
                inner join dbo.MEMBER on MEMBER.CONSTITUENTID = SOIM.CONSTITUENTID and MEMBER.MEMBERSHIPID = SOIMS.MEMBERSHIPID
                where SALESORDERITEMMEMBERSHIP.ID = SOIM.SALESORDERITEMMEMBERSHIPID and MEMBER.ISDROPPED = 0
                for xml raw('ITEM'),type,elements,root('MEMBERSHIPMEMBERS'),binary base64
            ),
            coalesce(
                MEMBERSHIPTRANSACTION.ACTIONCODE,
                (select MEMBERSHIPTRANSACTION.ACTIONCODE from dbo.MEMBERSHIPTRANSACTION where ID = dbo.UFN_MEMBERSHIP_GETLASTMEMBERSHIPTRANSACTIONID(SALESORDERITEMMEMBERSHIP.MEMBERSHIPID))
            ),
            @CHANGEAGENTID,
            @CHANGEAGENTID,
            @CURRENTDATE,
            @CURRENTDATE
        from @CREDITITEMS CREDITITEMS
        inner join dbo.SALESORDERITEM on CREDITITEMS.SALESORDERITEMID = SALESORDERITEM.ID
        inner join dbo.SALESORDERITEMMEMBERSHIP on SALESORDERITEM.ID = SALESORDERITEMMEMBERSHIP.ID
        left join dbo.MEMBERSHIPTRANSACTION on SALESORDERITEMMEMBERSHIP.MEMBERSHIPTRANSACTIONID = MEMBERSHIPTRANSACTION.ID

        --Add credit event registration items

        insert into dbo.CREDITITEMEVENTREGISTRATION
        (
            ID,
            REGISTRANTID,
            EVENTNAME,
            ADDEDBYID,
            CHANGEDBYID,
            DATEADDED,
            DATECHANGED
        )
        select
            CREDITITEMS.ID,
            SALESORDERITEMEVENTREGISTRATION.REGISTRANTID,
            SALESORDERITEMEVENTREGISTRATION.EVENTNAME,
            @CHANGEAGENTID,
            @CHANGEAGENTID,
            @CURRENTDATE,
            @CURRENTDATE
        from @CREDITITEMS as CREDITITEMS
        inner join dbo.SALESORDERITEM on CREDITITEMS.SALESORDERITEMID = SALESORDERITEM.ID
        inner join dbo.SALESORDERITEMEVENTREGISTRATION on SALESORDERITEM.ID = SALESORDERITEMEVENTREGISTRATION.ID

        -- Add item level discounts (via the DISCOUNTS column on the CREDITITEM_EXT records we already created)

        insert into dbo.CREDITITEMITEMDISCOUNT
        (
            CREDITITEMID,
            SALESORDERITEMITEMDISCOUNTID,
            AMOUNT,
            ADDEDBYID,
            CHANGEDBYID,
            DATEADDED,
            DATECHANGED
        )
        select distinct
            FTLI.ID,
            SALESORDERITEMITEMDISCOUNT.ID,
            EXT.DISCOUNTS,
            @CHANGEAGENTID,
            @CHANGEAGENTID,
            @CURRENTDATE,
            @CURRENTDATE
        from dbo.SALESORDERITEMITEMDISCOUNT
        inner join dbo.CREDITITEM_EXT as EXT on EXT.SALESORDERITEMID = SALESORDERITEMITEMDISCOUNT.SALESORDERITEMID
        inner join dbo.FINANCIALTRANSACTIONLINEITEM as FTLI on FTLI.ID = EXT.ID
        where FTLI.FINANCIALTRANSACTIONID = @ID
            and EXT.DISCOUNTS > 0;


        -- Add membership promotions (if the full amount paid for the membership is being returned)

        insert into dbo.CREDITITEMMEMBERSHIPITEMPROMOTION
        (
            CREDITITEMID,
            SALESORDERITEMMEMBERSHIPITEMPROMOTIONID,
            AMOUNT,
            ADDEDBYID,
            CHANGEDBYID,
            DATEADDED,
            DATECHANGED
        )
        select distinct
            REFUNDEDMEMBERSHIPLI.ID,
            SALESORDERITEMMEMBERSHIPITEMPROMOTION.ID,
            SALESORDERITEMMEMBERSHIPITEMPROMOTION.AMOUNT, --Right now, the entire promotion has to be returned

            @CHANGEAGENTID,
            @CHANGEAGENTID,
            @CURRENTDATE,
            @CURRENTDATE
        from dbo.SALESORDERITEMMEMBERSHIPITEMPROMOTION
        inner join dbo.CREDITITEM_EXT as EXT on EXT.SALESORDERITEMID = SALESORDERITEMMEMBERSHIPITEMPROMOTION.SALESORDERITEMID
        inner join dbo.FINANCIALTRANSACTIONLINEITEM as REFUNDEDMEMBERSHIPLI on REFUNDEDMEMBERSHIPLI.ID = EXT.ID
        where REFUNDEDMEMBERSHIPLI.FINANCIALTRANSACTIONID = @ID
            and (
                EXT.DISCOUNTS > 0  -- Unfortunately, this is 0 when the discount has moved to contributed revenue.

                or exists (        -- Instead, link to the original thing we were trying to refund to determine whether we reversed discounts.

                    select 1
                    from @CONTRIBUTEDREVENUE CR
                    where CR.SOURCELINEITEMID = REFUNDEDMEMBERSHIPLI.SOURCELINEITEMID
                        and DISCOUNTS > 0
                )
            );


        if @SHOULDREVERSEORDERDISCOUNTS = 1
        begin
            -- We need to store the ID's because we have two tables to insert into.

            declare @ORDERDISCOUNTLINEITEMID uniqueidentifier = newid();
            declare @ADJUSTABLEDISCOUNTLINEITEMID uniqueidentifier = newid();

            insert into dbo.FINANCIALTRANSACTIONLINEITEM
            (
                ID
                ,FINANCIALTRANSACTIONID
                ,UNITVALUE
                ,QUANTITY
                ,[DESCRIPTION]
                ,TRANSACTIONAMOUNT
                ,BASEAMOUNT
                ,ORGAMOUNT
                ,POSTDATE
                ,POSTSTATUSCODE
                ,SOURCELINEITEMID
                ,TYPECODE
                ,ADDEDBYID, CHANGEDBYID, DATEADDED, DATECHANGED
            )
            select
                case when ORDERDISCOUNT.ISADJUSTABLEDISCOUNT = 1 then @ADJUSTABLEDISCOUNTLINEITEMID else @ORDERDISCOUNTLINEITEMID end
                ,@ID
                ,-PRICE
                ,1
                ,[DESCRIPTION]
                ,-PRICE
                ,-PRICE
                ,-PRICE
                ,case when @ALLOWGLDISTRIBUTIONS = 1 and isnull(@POSTSTATUSCODE, 1) != 3 then cast(@TRANSACTIONDATE as date) else null end
                ,case when @ALLOWGLDISTRIBUTIONS = 1 and isnull(@POSTSTATUSCODE, 1) != 3 then 1 else 3 end
                ,null -- the original discount LI id has never been inserted here. Not changing yet.

                ,5
                ,@CHANGEAGENTID,@CHANGEAGENTID,@CURRENTDATE,@CURRENTDATE
            from dbo.SALESORDERITEM
            inner join dbo.SALESORDERITEMORDERDISCOUNT ORDERDISCOUNT on ORDERDISCOUNT.ID = SALESORDERITEM.ID
            where SALESORDERITEM.TYPECODE = 5
                and SALESORDERITEM.SALESORDERID = @ORDERID;

            insert into dbo.CREDITITEM_EXT
            (
                ID
                ,CREDITID
                ,DISCOUNTS
                ,FEES
                ,GROUPID
                ,GROUPTYPECODE
                ,SALESORDERITEMID
                ,TYPECODE
                ,SALESORDERITEMIZEDITEMID
                ,ADDEDBYID, CHANGEDBYID, DATEADDED, DATECHANGED
            )
            select
                case when ORDERDISCOUNT.ISADJUSTABLEDISCOUNT = 1 then @ADJUSTABLEDISCOUNTLINEITEMID else @ORDERDISCOUNTLINEITEMID end
                ,@ID
                ,0
                ,0
                ,null
                ,0
                ,SALESORDERITEM.ID
                ,5
                ,null
                ,@CHANGEAGENTID, @CHANGEAGENTID, @CURRENTDATE, @CURRENTDATE
            from dbo.SALESORDERITEM
            inner join dbo.SALESORDERITEMORDERDISCOUNT ORDERDISCOUNT on ORDERDISCOUNT.ID = SALESORDERITEM.ID
            where SALESORDERITEM.TYPECODE = 5
                and SALESORDERITEM.SALESORDERID = @ORDERID;
        end


        --If there are extra-member type add-ons, we might need to drop some members.

        if exists (
            select FINANCIALTRANSACTIONLINEITEM.ID
            from dbo.FINANCIALTRANSACTIONLINEITEM
            inner join dbo.MEMBERSHIPADDON on MEMBERSHIPADDON.REVENUESPLITID = FINANCIALTRANSACTIONLINEITEM.SOURCELINEITEMID
            inner join dbo.ADDON on ADDON.ID = MEMBERSHIPADDON.ADDONID
            where FINANCIALTRANSACTIONLINEITEM.FINANCIALTRANSACTIONID = @ID
                and ADDON.ADDONTYPECODE = 1
        ) begin
            declare @MEMBERSTODROP dbo.UDT_GENERICID;

            --To figure out how many to drop, we have to calculate the total number of members allowed

            --    (number of members the level allows + number of extra member add-ons sold - previously refunded add-ons).

            -- If there are now too many members, we drop as many as we need to.


            --Note: we can assume that there will only ever be one membershiptransaction

            --in the refund form for each membership

            with CTE_ADDONINFO as
            (
                select
                    MEMBERSHIPADDON.MEMBERSHIPTRANSACTIONID,
                    sum(FTLI.QUANTITY) as NUMBEROFADDONSREFUNDING,
                    (
                        select
                            count(MEMBER.ID)
                        from dbo.MEMBERSHIPTRANSACTION
                        inner join dbo.MEMBERSHIP on MEMBERSHIP.ID = MEMBERSHIPTRANSACTION.MEMBERSHIPID
                        inner join dbo.MEMBER on MEMBER.MEMBERSHIPID = MEMBERSHIP.ID
                        where
                            MEMBERSHIPTRANSACTION.ID = MEMBERSHIPADDON.MEMBERSHIPTRANSACTIONID
                            and MEMBER.ISDROPPED = 0

                    ) as TOTALNUMBEROFMEMBERS,
                    (
                        select
                            MEMBERSHIPLEVEL.MEMBERSALLOWED + sum(MA.QUANTITY)
                        from dbo.MEMBERSHIPTRANSACTION
                        inner join dbo.MEMBERSHIP on MEMBERSHIP.ID = MEMBERSHIPTRANSACTION.MEMBERSHIPID
                        inner join dbo.MEMBERSHIPLEVEL on MEMBERSHIPLEVEL.ID = MEMBERSHIP.MEMBERSHIPLEVELID
                        inner join dbo.MEMBERSHIPADDON MA on MA.MEMBERSHIPTRANSACTIONID = MEMBERSHIPTRANSACTION.ID
                        inner join dbo.ADDON on ADDON.ID = MA.ADDONID
                        where
                            MEMBERSHIPTRANSACTION.ID = MEMBERSHIPADDON.MEMBERSHIPTRANSACTIONID
                            and ADDON.ADDONTYPECODE = 1
                        group by MEMBERSHIPTRANSACTION.ID, MEMBERSHIPLEVEL.MEMBERSALLOWED

                    ) as NUMBERMEMBERSALLOWED,  --level + add-ons sold. We'll subtract out previously refunded ones in the next cte.

                    (
                        select
                            coalesce(sum(FINANCIALTRANSACTIONLINEITEM.QUANTITY), 0)
                        from dbo.MEMBERSHIPTRANSACTION
                        inner join dbo.MEMBERSHIPADDON MA on MA.MEMBERSHIPTRANSACTIONID = MEMBERSHIPTRANSACTION.ID
                        inner join dbo.ADDON on ADDON.ID = MA.ADDONID
                        inner join dbo.FINANCIALTRANSACTIONLINEITEM on FINANCIALTRANSACTIONLINEITEM.SOURCELINEITEMID = MA.REVENUESPLITID
                        inner join dbo.FINANCIALTRANSACTION FT on FINANCIALTRANSACTIONLINEITEM.FINANCIALTRANSACTIONID = FT.ID
                        inner join dbo.CREDITITEM_EXT on CREDITITEM_EXT.ID = FINANCIALTRANSACTIONLINEITEM.ID
                        where
                            MEMBERSHIPTRANSACTION.ID = MEMBERSHIPADDON.MEMBERSHIPTRANSACTIONID
                            and ADDON.ADDONTYPECODE = 1
                            and FT.TYPECODE = 23 --refund

                            and CREDITITEM_EXT.TYPECODE = 16 --membershipaddon

                            and FINANCIALTRANSACTIONLINEITEM.FINANCIALTRANSACTIONID <> @ID
                    ) as NUMBERPREVIOUSLYREFUNDED

                from dbo.FINANCIALTRANSACTION
                inner join dbo.FINANCIALTRANSACTIONLINEITEM FTLI on FTLI.FINANCIALTRANSACTIONID = FINANCIALTRANSACTION.ID
                inner join dbo.MEMBERSHIPADDON on FTLI.SOURCELINEITEMID = MEMBERSHIPADDON.REVENUESPLITID
                inner join dbo.ADDON on ADDON.ID = MEMBERSHIPADDON.ADDONID
                where FINANCIALTRANSACTION.ID = @ID
                    and ADDON.ADDONTYPECODE = 1
                group by MEMBERSHIPTRANSACTIONID, MEMBERSHIPADDON.MEMBERSHIPID
            ),
            CTE_MEMBERINFO as
            (
                select
                    MEMBER.ID as MEMBERID,
                    row_number() over (partition by MEMBERSHIPTRANSACTION.ID order by MEMBER.DATEADDED desc) as ROWNUMBER,
                    ADDONINFO.NUMBEROFADDONSREFUNDING - (ADDONINFO.NUMBERMEMBERSALLOWED - ADDONINFO.NUMBERPREVIOUSLYREFUNDED - ADDONINFO.TOTALNUMBEROFMEMBERS) as NUMBERTODROP
                from CTE_ADDONINFO as ADDONINFO
                inner join dbo.MEMBERSHIPTRANSACTION on MEMBERSHIPTRANSACTION.ID = ADDONINFO.MEMBERSHIPTRANSACTIONID
                inner join dbo.MEMBER on MEMBER.MEMBERSHIPID = MEMBERSHIPTRANSACTION.MEMBERSHIPID
                where MEMBER.ISPRIMARY = 0
                    and MEMBER.ISDROPPED = 0
            )
            insert into @MEMBERSTODROP (ID)
            select MEMBERINFO.MEMBERID
            from CTE_MEMBERINFO MEMBERINFO
            where MEMBERINFO.ROWNUMBER <= MEMBERINFO.NUMBERTODROP;

            update dbo.MEMBER set
                ISDROPPED = 1,
                CHANGEDBYID = @CHANGEAGENTID,
                DATECHANGED = @CURRENTDATE
            where
                ID in (select ID from @MEMBERSTODROP);

            update dbo.MEMBERSHIPCARD set
                STATUSCODE = 2,  -- Cancelled

                CHANGEDBYID = @CHANGEAGENTID,
                DATECHANGED = @CURRENTDATE
            where
                MEMBERID in (select ID from @MEMBERSTODROP)
                and STATUSCODE <> 2;  -- Cancelled

        end


        if @ALLOWGLDISTRIBUTIONS = 1 and isnull(@POSTSTATUSCODE, 1) != 3 begin
            exec dbo.USP_REFUND_CREATEGLDISTRIBUTION @ID, @CHANGEAGENTID, @CURRENTDATE;
            exec dbo.USP_REFUND_CREATEDISCOUNTGLDISTRIBUTION @ID, @CHANGEAGENTID, @CURRENTDATE;
        end


        -- Refunding memberships. This happens after GL since refunding the membership might delete the last membership transaction,

        -- and that transaction is the path to the revenue split for sales order item memberships.

        declare @SALESORDERITEMMEMBERSHIPID uniqueidentifier;
        declare memberships_cursor cursor local fast_forward for
        (
            select SALESORDERITEMMEMBERSHIP.ID
            from @CREDITITEMS CREDITITEMS
            inner join dbo.SALESORDERITEMMEMBERSHIP
                on CREDITITEMS.SALESORDERITEMID = SALESORDERITEMMEMBERSHIP.ID
        );

        open memberships_cursor;
        fetch next from memberships_cursor into @SALESORDERITEMMEMBERSHIPID;

        while @@FETCH_STATUS = 0 begin
            exec dbo.USP_CREDIT_REFUNDSALESORDERITEMMEMBERSHIP @SALESORDERITEMMEMBERSHIPID, @CHANGEAGENTID;

            fetch next from memberships_cursor
            into @SALESORDERITEMMEMBERSHIPID;
        end
--raiserror('blob', 13, 1);

        close memberships_cursor;
        deallocate memberships_cursor;
    end try
    begin catch
        exec dbo.USP_RAISE_ERROR;
        return 1;
    end catch

    return 0;
end