Back to Blog
Payment IntegrationApril 19, 202610 min read

How We Cracked Microsoft'sUndocumented Payment SDK Filter

Our CardPointe connector DLL loaded, implemented the right interface, was signed and GAC-installed — yet the D365 Payment connector dropdown refused to show it. No error. No log. Just silence. Here's how we traced through closed-source IL code and found the answer in a single line.

The Project

A client needed CardPointe payment processing integrated natively into their Dynamics 365 Finance & Operations environment. Not a workaround. Not a portal redirect. A native payment connector that appears in the standard D365 Payment services dropdown — alongside Microsoft's own Adyen, PayPal, Apple Pay, and Google Pay connectors.

The scope: 7 X++ classes, 2 tables, 3 forms, 4 form extensions, a C# connector assembly, and 8 REST API integration points covering authorization, capture, void, refund, inquire, tokenization, profile management, and settlement.

Everything was built, compiled, signed, and deployed. Then the connector vanished.

The Blocker

D365's Payment connector dropdown is populated by Microsoft's PaymentProcessorManager SDK — a closed-source assembly that scans the Connectors folder, loads DLLs, and returns discovered connectors. Our DLL passed every verifiable check:

Correct Connectors folder location
Strong-name signed assembly
GAC-installed
Correct .NET target framework
IPaymentProcessor interface implemented
Manual Assembly.LoadFrom() succeeded

Yet the SDK silently filtered it out. The rejection happened after the DLL was successfully loaded into memory — inside the closed-source code, with no error message, no log entry, and no documentation on what the filter rule was.

The Investigation

Tracing Through Closed-Source IL

With no documentation and no error messages, we decompiled the SDK and traced the execution path instruction by instruction.

Step 01

The Symptom — Silent Rejection

The DLL loaded into memory. It was signed, GAC-installed, implemented IPaymentProcessor, and sat in the correct Connectors folder. Yet the Payment connector dropdown refused to show it. No error. No log. Just silence.

Microsoft's PaymentProcessorManager SDK was filtering it out after successful load — inside closed-source code with no documentation on the rejection criteria.

Step 02

Decompiling the SDK

We traced the execution path through the SDK using ILDASM: HasPortableProcessor → Assembly.GetTypes().Any(predicate) → typeof(IPaymentProcessor).IsAssignableFrom(type). The interface check passed — the filter was elsewhere.

The real logic lived in SDKExtensions.Extension.GetConnectorsFromDirectory(), delegated through IExtensionsV1 to ConnectorConvertor — a 7-byte thin delegate forwarding to the actual implementation.

Step 03

The Reveal — Line 432

CompositionContext.GetExports<IPaymentProcessor>(). The SDK uses MEF (Managed Extensibility Framework). It doesn't scan types implementing IPaymentProcessor — it only discovers types explicitly exported via MEF's attribute model.

One missing attribute: [Export(typeof(IPaymentProcessor))]. Microsoft's own TestConnector used it. Their documentation never mentioned it. This single finding collapsed every observation from the investigation.

Step 04

The GAC Gotcha

After adding the [Export] attribute and redeploying, the connector still didn't appear. The Global Assembly Cache had the OLD DLL (10,240 bytes) while the Connectors folder had the NEW one (10,752 bytes). .NET loads from GAC first.

GAC binding shadows file-system deployment. The fix: uninstall old DLL from GAC, reinstall the new one with gacutil /i, restart IIS. Byte-size comparison and timestamps were the diagnostic tell.

Step 05

Victory — Dropdown Populated

CardPointe appeared in the Payment connector dropdown. MEF discovered the export, resolved the connector name, and the SDK returned it alongside the five Microsoft connectors. The central blocker was cleared.

Debug output confirmed: Connector count = 6. DES.CardPointe.Connector.CardPointePaymentProcessor identified by type and by name — "CardPointe Connector for D365."

The Root Cause

Why MEF Explains Everything

Every observation from the investigation collapses into one explanation.

DLL loads into memory

MEF loads the assembly fine — loading is not discovery

Implements IPaymentProcessor

Required but not sufficient — MEF also requires the [Export] attribute

Signed, GAC'd, correct .NET target

All irrelevant to MEF discovery — MEF doesn't care about signing

Manual Assembly.LoadFrom() works

Because manual loading bypasses MEF's composition container

Silently rejected at GetConnectorsFromDirectory

GetExports<IPaymentProcessor>() returns zero — no [Export] attribute found

The Fix — Two Lines of Code

// In CardPointePaymentProcessor.cs:

using System.Composition;

[Export(typeof(IPaymentProcessor))]

public class CardPointePaymentProcessor : IPaymentProcessor

// In .csproj:

<PackageReference Include="System.Composition.AttributedModel" Version="6.0.0" />

The "undocumented check" wasn't a signing filter, a namespace filter, or a Microsoft-only gate. It was MEF discovery via attribute metadata — a standard .NET pattern, just poorly documented in the Payment SDK context.

The Pipeline

How the Dropdown Gets Built

Seven steps from form initialization to the connector appearing in the dropdown.

1.Form Init
|
2.CreditCardConnectorSetup
|
3.PaymentProcessorManager SDK
|
4.Folder Scan
|
5.DLL Load
|
6.MEF Composition
|
7.Dropdown Population

Step 6 is where our connector was silently dropped — and where the [Export] attribute fix landed

After the Fix

From Dropdown to Active Processor

Once CardPointe appeared in the dropdown, the standard D365 activation workflow took over.

Step 1

Save the Record

Persist all entered values — Service Account ID, Merchant ID, connector name, supported currencies and tender types.

Step 2

Map Electronic Payment Types

Add card types (Visa, MasterCard, Amex, Discover) and map each to a Customer Payment journal. We reused the existing CustPay journal — no new journal creation needed.

Step 3

Validate Configuration

Click Validate — D365's standard guard checks that the connector config is complete and the payment journal mapping is in place. Blue banner: "Validation is successful."

Step 4

Activate Default Processor

Toggle "Default processor for new credit cards" to Yes. CardPointe shows as active in the Payment services list. The connector is live.

Validation Successful

Payment connector

CardPointe Connector for D365

Default processor

Yes (Active)

Test mode

Enabled for UAT

The Full Build

What It Took to Get Here

A native D365 payment connector spans three layers: X++ metadata, X++ runtime, and C# assembly.

7

X++ Classes

Helper, PaymentService, FormHandler, and more

5

Tables & EDTs

2 tables, 3 Extended Data Types

7

Forms & Extensions

3 forms, 4 form extensions

8

API Endpoints

Auth, Capture, Void, Refund, Inquire, Tokenize, Profile, Settle

1

C# Assembly

DES.CardPointe.Connector.dll — strong-named, MEF-exported

4

Deployment Locations

Connectors folder, WebRoot/bin, K: drive, GAC

CardPointe REST API Coverage

Authorization

Authorize a card payment

Capture

Capture an authorized amount

Void

Void a pending transaction

Refund

Refund a captured payment

Inquire

Check transaction status

Tokenize

Generate a card token

Profile

Manage stored profiles

Settlement

Batch settlement

Why It Matters

The Payment Journal — Why You Can't Skip It

During activation, a question came up: "Can we skip the payment journal requirement and just pay from the credit card screen directly?" The short answer: no. Here's why.

Without Payment Journal

  • Money received at bank
  • CardPointe returns success
  • D365 has no record of payment
  • Invoice stays "Unpaid" forever
  • AR still shows the full amount
  • GL bank account not updated
  • Reconciliation broken

With Payment Journal

  • Money received at bank
  • CardPointe returns success
  • D365 creates payment journal entry
  • Invoice marked "Paid"
  • AR reduced by payment amount
  • GL bank account increased
  • Books balanced

The journal isn't a D365 validation quirk — it's an accounting necessity. Without it, the financial chain (invoice → payment → bank → ledger) has nowhere to land.

Lessons Learned

What We'll Carry Forward

Six engineering insights from two weeks of rigorous work — every one earned the hard way.

MEF Discovery Is the Gatekeeper

Microsoft's Payment SDK uses Managed Extensibility Framework for connector discovery. Implementing IPaymentProcessor is necessary but not sufficient — you must also decorate with [Export(typeof(IPaymentProcessor))]. This is undocumented in the Payment SDK context.

GAC Shadows File Deployments

.NET loads from the Global Assembly Cache before checking the file system. A stale GAC entry silently shadows new deployments. After rebuilding, always: uninstall old → gacutil /i new → iisreset.

Four-Location Deployment Discipline

The connector DLL must exist in four locations simultaneously: Connectors folder, WebRoot/bin, K: drive bin, and GAC. Missing any one can cause silent failures at different stages.

Per-Legal-Entity Configuration

The connector DLL ships globally (ISV layer), but Payment Services configuration — journal selection, electronic payment types, card mapping — is manual per legal entity. CustPay in USMF doesn't exist in production.

Schema-First SQL Queries

D365 table column names are non-obvious. Always query the schema before writing SQL against D365 tables — assumed column names burn cycles on failing queries.

Byte-Size Comparison as Diagnostic

When you can't inspect DLL content directly, file size deltas and timestamps catch "same path, different contents" faster than any other check. 10,240 bytes vs 10,752 bytes told the whole GAC story.

The Complete Arc

Phase AX++ Build & C# AssemblyComplete
Phase BSDK Investigation & MEF FixComplete
Phase CDropdown Registration & ActivationComplete
Phase DFunctional Test MatrixNext
Phase EJournal Posting End-to-EndUpcoming
Phase FHardening & Deployable PackageUpcoming

Two weeks of rigorous work. The hardest technical hurdle — connector DLL discovery via an undocumented MEF requirement — is solved. From here it's configuration and functional testing, not reverse engineering.

Need a Custom Payment Connector for D365?

We build native D365 payment integrations — from ISV-layer connectors to full end-to-end transaction flows. CardPointe, Stripe, Square, or any gateway your business needs.