Skip to main content

Requirement

After clicking "Prepare Invoice" on the Shipments screen, I need to recalculate the invoice line items to match the actual quantity for the credit card charge. For instance, if the Sales Order (SO) has a quantity of 10 but only 2 have been shipped, the credit card fee should be based on the 2 quantities instead of the original 10. This involves two steps:

  1. Recalculate the invoice line items and update the document total accordingly.
  2. Once the recalculation is complete and the document is ready, programmatically trigger the capture button on the Applications tab.

I’m currently using the ARInvoice_RowPersisting event in the SOInvoiceEntry graph. While the recalculation works well, I’m struggling to find the right place to invoke the capture button. Whenever I attempt this, I encounter an Acuminator warning, and in some cases, it leads to an infinite loop or stack overflow exception.

I would appreciate your assistance.

CODE

 /// <summary>         
/// AR Invoice row persisting event to recalculate the CCFee        
///         
/// TODO: Only SO types and Credit Card. No other payment.        
/// </summary>        
/// <param name="cache"></param>        
/// <param name="e"></param>        
protected void ARInvoice_RowPersisting(PXCache cache, PXRowPersistingEventArgs e)        
{            
if (e.Operation == PXDBOperation.Delete && e.Operation == PXDBOperation.Update) return;            
// Get inserted Sales Order detail line            
var row = e.Row as ARInvoice;
    SOInvoice invoice = PXSelect<SOInvoice,
Where<SOInvoice.docType, Equal<Required<SOInvoice.docType>>,
And<SOInvoice.refNbr,Equal<Required<SOInvoice.refNbr>>>>>
.Select(Base, row.DocType, row.RefNbr);            
if(invoice != null && invoice.SOOrderType == "SO" )             


//Extract the payment number associated with the invoice            
ARAdjust2 adjust = PXSelect<ARAdjust2,                
Where<ARAdjust2.adjdDocType, Equal<Required<ARAdjust.adjdDocType>>,                    
And<ARAdjust.adjdRefNbr, Equal<Required<ARAdjust.adjdRefNbr>>>>>                
.Select(Base, row.DocType, row.RefNbr);

        ExternalTransaction extTran = null;            
//If there's any payment associated then             
if (adjust != null)            
{
// extract the Processing Status of                 
extTran = PXSelect<ExternalTransaction,
Where<ExternalTransaction.docType, Equal<Required<ExternalTransaction.docType>>,                          
And<ExternalTransaction.refNbr, Equal<Required<ExternalTransaction.refNbr>>>>>                      
.Select(Base, adjust.AdjgDocType, adjust.AdjgRefNbr);            
}
//Check if the status of payment is Pre-Authorized or not. If not dont do anything. Let acumatic run its default algorithm.            
if (extTran != null && extTran.ProcStatus !=null)            
{                
if (extTran.ProcStatus == "AUS"  )                
{
InventoryItem inventoryItem = PXSelect<InventoryItem,
Where<InventoryItem.inventoryCD, Equal<Required<InventoryItem.inventoryCD>>>>                        
.Select(Base, "CREDITCARDFEE");                    
decimal? taxAmt = Base.Taxes.Current.TaxAmt;                    
if (taxAmt == null)                    
{                        
taxAmt = 0;
}
if (inventoryItem != null)
{
var markupPct = inventoryItem.MarkupPct;
if (markupPct != null && markupPct > 0)                            
{
List<ARTran> trans = Base.Transactions.Select().RowCast<ARTran>().ToList();

if (trans != null && trans.Any())                                
{                                    
ARTran currentInv = PXSelect<ARTran,
Where<ARTran.refNbr, Equal<Required<ARTran.refNbr>>,
And<ARTran.inventoryID, Equal<Required<ARTran.inventoryID>>>>>
.Select(Base, row.RefNbr, inventoryItem.InventoryID);

                            decimal? sumofAllLineItemsExceptCCFee = trans.Where(c => c.InventoryID != inventoryItem.InventoryID).Sum(c => c.CuryExtPrice);
var newCuryUnitPrice = ((sumofAllLineItemsExceptCCFee + taxAmt) * markupPct) / 100;                                    
currentInv.CuryUnitPrice = newCuryUnitPrice;                                    
currentInv.CuryExtPrice = newCuryUnitPrice;                                    
//cache.SetValueExt<ARTran.curyUnitPrice>(row, newCuryUnitPrice);                                    
//cache.SetValueExt<ARTran.curyExtPrice>(row, newCuryUnitPrice);                                    
Base.Transactions.Update(currentInv);                                    
Base.Document.Update(row);
}                            
}                        
}                    
}               
}            
}        
}


@amitr70 It looks like you are trying to implement the CC Surcharge mechanism. 

If it’s ok to share, what is your current CC Payment Processing Provider? 

We do have Surcharge support in beta for Acumatica Payments. 

 

As for the code, you’ll need something like

Base.GetExtension<PX.Objects.SO.GraphExtensions.SOInvoiceEntryExt.CreatePaymentExt>().captureDocumentPayment.Press();

But I don’t think RowPersisting is the right place for it. Maybe move it to Persist override?

 

P.S. Edited your code to look better


@amitr70 It looks like you are trying to implement the CC Surcharge mechanism. 

If it’s ok to share, what is your current CC Payment Processing Provider? 

We do have Surcharge support in beta for Acumatica Payments. 

 

As for the code, you’ll need something like

Base.GetExtension<PX.Objects.SO.GraphExtensions.SOInvoiceEntryExt.CreatePaymentExt>().captureDocumentPayment.Press();

But I don’t think RowPersisting is the right place for it. Maybe move it to Persist override?

 

P.S. Edited your code to look better

Hi Dmitrii, thanks for your input! I'll give the code a try and let you know how it goes. Additionally, could you share some links to articles or documents about surcharge support?
FYI, I am using Authorize.Net for CC Processing


@amitr70  FYI Auth.Net is being discontinued in 2025, see release notes for 2024R2 version. 

 

Since it’s in beta, there no articles publicly available. I’ll forward you the details in a message.


@amitr70 It looks like you are trying to implement the CC Surcharge mechanism. 

If it’s ok to share, what is your current CC Payment Processing Provider? 

We do have Surcharge support in beta for Acumatica Payments. 

 

As for the code, you’ll need something like

Base.GetExtension<PX.Objects.SO.GraphExtensions.SOInvoiceEntryExt.CreatePaymentExt>().captureDocumentPayment.Press();

But I don’t think RowPersisting is the right place for it. Maybe move it to Persist override?

 

P.S. Edited your code to look better

One more concern, do I need to move my all RowPersisting code to Persist override as well?


@Dmitrii Naumov 

I have this piece of code inside persist override along with ARInvoice RowPersisting throwing StackOverflow exception

 public delegate void PersistDelegate();
         PXOverride]
        public  void Persist(PersistDelegate baseMethod)
        {
            baseMethod();
            var row = Base.Document.Current;
            if (row != null && row.Status == "W")
            {
                //Extract the payment number associated with the invoice
                ARAdjust2 adjust = PXSelect<
                    ARAdjust2,
                    Where<ARAdjust2.adjdDocType, Equal<Required<ARAdjust.adjdDocType>>,
                        And<ARAdjust.adjdRefNbr, Equal<Required<ARAdjust.adjdRefNbr>>>>>
                    .Select(Base, row.DocType, row.RefNbr);

                ExternalTransaction extTran = null;
                //If there's any payment associated then
                if (adjust != null)
                {

                    // extract the Processing Status of
                    extTran = PXSelect<
                        ExternalTransaction,
                        Where<ExternalTransaction.docType, Equal<Required<ExternalTransaction.docType>>,
                            And<ExternalTransaction.refNbr, Equal<Required<ExternalTransaction.refNbr>>>>>
                        .Select(Base, adjust.AdjgDocType, adjust.AdjgRefNbr);
                }

                //Check if the status of payment is Pre-Authorized or not. If not dont do anything. Let acumatic run its default algorithm.
                if (extTran != null && extTran.ProcStatus != null)
                {
                    if (extTran.ProcStatus == "AUS")
                    {
                        Base.GetExtension<PX.Objects.SO.GraphExtensions.SOInvoiceEntryExt.CreatePaymentExt>().captureDocumentPayment.Press();
                    }
                }
            }
        }, 


@amitr70  I think it’s because save is also being invoked from the captureDocumentPayment.Press() itself. 

You should have some kind of flag that prevents the infinite recursion here. 

e.g. 

private bool ProcessingSurcharge = false;
public delegate void PersistDelegate();
PXOverride]
public void Persist(PersistDelegate baseMethod)
{
baseMethod();
var row = Base.Document.Current;
if (row != null && row.Status == "W" && ProcessingSurcharge == false)
{
//Extract the payment number associated with the invoice
ARAdjust2 adjust = PXSelect<
ARAdjust2,
Where<ARAdjust2.adjdDocType, Equal<Required<ARAdjust.adjdDocType>>,
And<ARAdjust.adjdRefNbr, Equal<Required<ARAdjust.adjdRefNbr>>>>>
.Select(Base, row.DocType, row.RefNbr);

ExternalTransaction extTran = null;
//If there's any payment associated then
if (adjust != null)
{

// extract the Processing Status of
extTran = PXSelect<
ExternalTransaction,
Where<ExternalTransaction.docType, Equal<Required<ExternalTransaction.docType>>,
And<ExternalTransaction.refNbr, Equal<Required<ExternalTransaction.refNbr>>>>>
.Select(Base, adjust.AdjgDocType, adjust.AdjgRefNbr);
}

//Check if the status of payment is Pre-Authorized or not. If not dont do anything. Let acumatic run its default algorithm.
if (extTran != null && extTran.ProcStatus != null)
{
if (extTran.ProcStatus == "AUS")
{
try {
ProcessingSurcharge=true;
Base.GetExtension<PX.Objects.SO.GraphExtensions.SOInvoiceEntryExt.CreatePaymentExt>().captureDocumentPayment.Press();
}
finally{ProcessingSurcharge=false;}
}
}
}
},

 


Thanks, @Dmitrii Naumov! I managed to solve the issue, but I approached it differently. I’m sharing my solution here as well. In addition to the ARInvoice_RowPersisting event where my calculations take place, I also added the ARInvoice_Status_FieldUpdated event and included these lines of code. I hope this helps others facing similar challenges!

 


protected void ARInvoice_Status_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
{
var row = e.Row as ARInvoice;
if (row != null)
{
if(row.Status =="W")
{
if (!PXLongOperation.Exists(Base.UID))
{
// Calling Action Button asynchronously so it can run in the context of a PXAction callback
Base.Actionso"CaptureDocumentPayment"].PressButton();
}
}
}
}

 


Generally, I’d not recommend executing Actions from event handlers, especially field event handlers.


@Dmitrii Naumov Yes , I knew thats not recommended but this is the only solution that worked for me. I saw your comments as well on the post from which i took the code. It was an answer from Hugues Beauséjour on which you commented. Below is the reference
acumatica - Running a long operation within an event handler - Stack Overflow


Reply