This screen is difficult to use via web services for a few different reasons:
- There is no unique key exposed via the screen that you can use to locate payment methods. The internal key is a field named PMInstanceID, and although you can technically refer to it via web service commands, it is not obvious to use
- In Acumatica, payment method details are locked once one transaction using this payment method has been recorded. This means that to make modifications to it, you need to create a new payment method, and set the old one inactive.
- There's a bug in the screen that only manifests when using it via web services, which is related to item above. When loading a payment method, system will disable the payment method details if there has been any transactions, but will fail to re-enable it if not. This is never a problem when using Acumatica from a web browser, since the fields are re-enabled automatically between every round-trip. But when automating this screen through web services, you're actually performing all the operations in a single round-trip. This problem has been reported and will be fixed shortly (internal JIRA reference is AC-54456)
That being said, there's a few things at our disposal that we can use to make this screen as easy to use from web services as other screens, and work around its limitations. By creating a customization project, we can add the PMInstanceID field (hereby referred to as the Token ID field) to the screen. You can store this token on your system, and use it for future operations with the payment method. Within the same customization project, we can also make the payment method details always enabled, allowing you to update expiration date on existing cards. Doing so also resolves the bug mentioned above where system won't allow you to add any new payment method to the system. The customization has two parts:
- Overriding
CustomerPaymentMethod DAC to make the PMInstanceID field accessible from the UI. This is done by appending the PXUIField attribute to the PMInstanceID field: [PXUIField(DisplayName="Token ID", Visibility=PXUIVisibility.SelectorVisible)]. After that, the field may be added to the screen using the layout editor.
Handling the CustomerPaymentMethod_RowSelected event to force the payment methods details to always be enabled. We also use this event handler to hide the Token ID field when adding a new payment method, since this field would otherwise show int.MinValue until the payment method has been saved. The full code of the event looks like this:
protected void CustomerPaymentMethod_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
// Force the payment method details to always be enabled to facilitate working via web services
PXUIFieldAttribute.SetEnabled(Base.Details.Cache, null, true);
// When adding a new method, field will have a temporary value corresponding to int.MinValue - don't show it
PXUIFieldAttribute.SetVisible<CustomerPaymentMethod.pMInstanceID>(cache, e.Row, cache.GetStatus(e.Row) != PXEntryStatus.Inserted);
}
Once the customization is published, it becomes far easier to update existing payment methods. The code below shows how you can add a payment method, and retrieve the token ID that you will use later on to update the payment method:
public int AddCreditCard(string customerID, string paymentMethod, string cardNumber, string expirationDate, string cvv, string nameOnCard)
{
if(_AR303010 == null) _AR303010 = _context.AR303010GetSchema();
_context.AR303010Clear();
var commands = new Command[]
{
new Value { Value = customerID, LinkedCommand = _AR303010.PaymentMethodSelection.Customer },
new Value { Commit = true, LinkedCommand = _AR303010.Actions.Insert },
new Value { Value = paymentMethod, LinkedCommand = _AR303010.PaymentMethodSelection.PaymentMethod },
new Value { Value = "CCDNUM", LinkedCommand = _AR303010.PaymentMethodDetails.Description },
new Value { Value = cardNumber, LinkedCommand = _AR303010.PaymentMethodDetails.Value, Commit = true },
new Value { Value = "EXPDATE", LinkedCommand = _AR303010.PaymentMethodDetails.Description },
new Value { Value = expirationDate, LinkedCommand = _AR303010.PaymentMethodDetails.Value, Commit = true},
new Value { Value = "CVV", LinkedCommand = _AR303010.PaymentMethodDetails.Description },
new Value { Value = cvv, LinkedCommand = _AR303010.PaymentMethodDetails.Value, Commit = true },
new Value { Value = "NAMEONCC", LinkedCommand = _AR303010.PaymentMethodDetails.Description },
new Value { Value = nameOnCard, LinkedCommand = _AR303010.PaymentMethodDetails.Value, Commit = true },
_AR303010.Actions.Save,
_AR303010.PaymentMethodSelection.TokenID
};
var result = _context.AR303010Submit(commands.ToArray());
return int.Parse(result[0].PaymentMethodSelection.TokenID.Value);
}
public void UpdateCreditCardExpirationDate(string customerID, string paymentMethod, int tokenID, string expirationDate)
{
if (_AR303010 == null) _AR303010 = _context.AR303010GetSchema();
_context.AR303010Clear();
var commands = new Command[]
{
new Value { Value = customerID, LinkedCommand = _AR303010.PaymentMethodSelection.Customer },
new Value { Commit = true, LinkedCommand = _AR303010.Actions.Insert },
new Value { Value = paymentMethod, LinkedCommand = _AR303010.PaymentMethodSelection.PaymentMethod },
new Value { Value = tokenID.ToString(), LinkedCommand = _AR303010.PaymentMethodSelection.TokenID },
new Value { Value = "EXPDATE", LinkedCommand = _AR303010.PaymentMethodDetails.Description },
new Value { Value = expirationDate, LinkedCommand = _AR303010.PaymentMethodDetails.Value, Commit = true},
_AR303010.Actions.Save,
};
var result = _context.AR303010Submit(commands.ToArray());
}
public void MakeCardInactive(string customerID, string paymentMethod, int tokenID)
{
if (_AR303010 == null) _AR303010 = _context.AR303010GetSchema();
_context.AR303010Clear();
var commands = new Command[]
{
new Value { Value = customerID, LinkedCommand = _AR303010.PaymentMethodSelection.Customer },
new Value { Commit = true, LinkedCommand = _AR303010.Actions.Insert },
new Value { Value = paymentMethod, LinkedCommand = _AR303010.PaymentMethodSelection.PaymentMethod },
new Value { Value = tokenID.ToString(), LinkedCommand = _AR303010.PaymentMethodSelection.TokenID },
new Value { Value = "False", LinkedCommand = _AR303010.PaymentMethodSelection.Active },
_AR303010.Actions.Save,
};
var result = _context.AR303010Submit(commands.ToArray());
}
I wrapped these 3 functions into a class, and actual usage becomes quite simple:
var paymentMethodManager = new PaymentMethodManager(context);
int tokenID = paymentMethodManager.AddCreditCard("ABARTENDE", "MASTERCARD", "5111111111111118", "122016", "123", "John Doe");
paymentMethodManager.UpdateCreditCardExpirationDate("ABARTENDE", "MASTERCARD", tokenID, "032017");
paymentMethodManager.MakeCardInactive("ABARTENDE", "MASTERCARD", tokenID);
As of now, it is not possible to delete an existing payment method, and you must make it inactive instead. I've made the request for this enhancement, and it may come in the future.
Note: I have placed all the code used in this answer on GitHub at https://github.com/gmichaud/acumatica-paymentmethod-ws-extensions.