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:
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.
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.
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.
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.
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.
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.
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.
Save the Record
Persist all entered values — Service Account ID, Merchant ID, connector name, supported currencies and tender types.
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.
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."
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
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.