PAYATHON 2026

通过 REST API 添加带 Pre-Authorized Payment 的 Sales Order 失败

支付老李

结论

创建 Sales Order 时,不要在第一次 PUT 请求中同时提交 Payments。正确做法是分两步处理:

  1. 先创建 Sales Order,并从响应中获取 Acumatica 实际生成的 OrderType 和 OrderNbr。
  2. 再用这两个字段定位已创建的订单,提交 Payments,将 Pre-Authorized Payment 应用到该订单。

这个错误并不表示信用卡授权号不存在。Acumatica 在处理 Payments 时,需要将付款应用到一张已经存在的订单。第一次请求执行到付款逻辑时,新订单可能还没有保存完成,或者请求中没有可用于重新定位订单的 OrderNbr,所以系统找不到对应的订单类型和订单号组合。

为什么本地可以,而 Acumatica.com 失败

两个环境的行为不同,通常与以下差异有关:

  • Acumatica 版本或补丁级别不同。
  • Contract-Based REST Endpoint 的版本或扩展字段定义不同。
  • 本地环境安装了定制代码,而 Acumatica.com 使用了另一套事件处理逻辑。
  • 两个环境的 Sales Order 编号规则、支付方式、Processing Center 或 Payment Method 配置不同。
  • 本地环境可能恰好允许在保存父记录的过程中插入付款,但这种处理顺序并不可靠。

Payments 不是普通的订单明细。添加付款时,Acumatica 会执行”将付款应用到订单”的业务逻辑,因此必须能通过 OrderType + OrderNbr 找到已经持久化的订单。

第一次请求中只有:

"OrderType": {
  "value": "SO"
}

但没有确定的 OrderNbr。如果订单号由 Acumatica 自动生成,付款处理开始时可能还没有有效的目标订单,于是系统报错:

The order cannot be applied, the specified combination of the order type and order number cannot be found in the system.

正确的处理流程

第一步:创建 Sales Order

第一次调用只发送订单头、地址和明细,不发送 Payments:

var request = _mapper.Map<DataModel.SalesOrder>(order);

// 新订单创建阶段不要提交 Payments
request.Payments = null;

DataModel.SalesOrder createdOrder =
    await api.PutEntityAsync(request, null, null, null, null);

string orderType = createdOrder.OrderType?.Value;
string orderNbr = createdOrder.OrderNbr?.Value;

if (string.IsNullOrWhiteSpace(orderType) ||
    string.IsNullOrWhiteSpace(orderNbr))
{
    throw new InvalidOperationException(
        "Acumatica did not return OrderType or OrderNbr.");
}

后续操作必须使用 Acumatica 响应中的 OrderNbr。不要把外部系统的 order.OrderNumber 当成 Acumatica 已经生成的 Sales Order 编号,除非当前环境明确允许外部指定订单号。

第二步:向已创建的订单添加付款

接下来构造更新请求,用明确的订单键定位刚创建的订单:

var payment = new DataModel.SalesOrderPayment
{
    Description = new StringValue
    {
        Value = $"Payment for {orderNbr}"
    },
    PaymentMethod = new StringValue
    {
        Value = "CUSTOMPAYMC"
    },
    CardAccountNbr = new StringValue
    {
        Value = order.PaymentDetails.CardID
    },
    CashAccount = new StringValue
    {
        Value = "10600"
    },
    ProcessingCenterID = new StringValue
    {
        Value = "CUSTOMPAY"
    },
    PaymentAmount = new DecimalValue
    {
        Value = order.Header.TotalAmount
    },
    AppliedToOrder = new DecimalValue
    {
        Value = order.Header.TotalAmount
    }
};

var transaction = new DataModel.SalesOrderCreditCardTransactionDetail
{
    AuthNbr = new StringValue
    {
        Value = order.PaymentDetails.AuthorizationNumber
    },
    NeedValidation = new BooleanValue
    {
        Value = false
    },
    TranDate = new DateTimeValue
    {
        Value = order.PaymentDetails.AuthorizationDateTime
    },
    TranNbr = new StringValue
    {
        Value = order.PaymentDetails.TransactionID
    },
    TranType = new StringValue
    {
        Value = "Authorize Only"
    }
};

payment.CreditCardTransactionInfo =
    new List<DataModel.SalesOrderCreditCardTransactionDetail>
    {
        transaction
    };

var paymentRequest = new DataModel.SalesOrder
{
    OrderType = new StringValue
    {
        Value = orderType
    },
    OrderNbr = new StringValue
    {
        Value = orderNbr
    },
    Payments = new List<DataModel.SalesOrderPayment>
    {
        payment
    }
};

DataModel.SalesOrder updatedOrder =
    await api.PutEntityAsync(paymentRequest, null, null, null, null);

即使 SDK 的 PutEntityAsync 支持通过 URL 参数传入实体键,也建议在请求体中保留 OrderType 和 OrderNbr,以免付款应用逻辑取不到订单键。具体的方法签名取决于所使用的 Acumatica REST SDK 和 Endpoint 定义。

原代码中还需要修正的问题

原代码虽然创建了 detail,却没有把它加入集合:

payment.CreditCardTransactionInfo = new List();
// payment.CreditCardTransactionInfo.Add(detail);

由于 Add 被注释掉,实际请求中不会包含授权交易明细。可以改为:

payment.CreditCardTransactionInfo =
    new List<DataModel.SalesOrderCreditCardTransactionDetail>
    {
        detail
    };

也可以先创建集合,再添加明细:

payment.CreditCardTransactionInfo =
    new List<DataModel.SalesOrderCreditCardTransactionDetail>();

payment.CreditCardTransactionInfo.Add(detail);

另外,除非当前 Endpoint 明确将以下字段声明为可写,否则不建议主动写入这类由系统计算或只读的字段:

payment.Status = new StringValue
{
    Value = "Pre-Authorized"
};

Status 通常应由支付处理结果决定。仅将它设为 "Pre-Authorized",并不会创建有效的预授权交易。订单中的 OrderTotal、OrderedQty、OpenQty、UnbilledAmount 和行金额合计等字段,也应尽量交给 Acumatica 计算。

配置检查

即使拆成两次请求,以下配置不一致仍可能导致付款失败:

  • CUSTOMPAYMC 是否存在,并且允许用于目标客户。
  • CardAccountNbr 是否是该客户在 Acumatica 中有效的 Payment Profile,而不是任意拼接的字符串。
  • CUSTOMPAY Processing Center 是否存在,并且已与 Payment Method 正确关联。
  • 10600 是否是当前 Branch 可用的 Cash Account。
  • Endpoint 是否公开了 Payments、AppliedToOrder 和 CreditCardTransactionInfo。
  • TranType 是否允许使用 "Authorize Only"。
  • 当前 Acumatica 版本是否允许通过 Contract-Based REST API 导入外部授权交易。

最后一点需要特别确认。部分版本或支付中心只允许通过 Acumatica 的支付处理 Action 生成预授权记录,不允许直接写入 CreditCardTransactionInfo。如果第二次 PUT 能添加付款,却无法建立 Pre-Authorized 状态,需要检查对应实例的 Endpoint 是否提供相关 Action,并按该版本支持的”授权”、“记录外部交易”或付款创建流程调用。不能只靠写入 Status、AuthNbr 和 TranNbr 来模拟预授权。

处理时应先保存订单,让它获得有效的 OrderNbr,再创建或关联付款。预授权状态则应通过该实例实际支持的支付业务流程生成。

备注:内容仅供参考。