Skip to content

Fix 409 conflict on Clientify merge by taxpayer_identification_number - #259

Merged
davidperezgar merged 7 commits into
trunkfrom
fix-error-409
Aug 6, 2026
Merged

Fix 409 conflict on Clientify merge by taxpayer_identification_number#259
davidperezgar merged 7 commits into
trunkfrom
fix-error-409

Conversation

@matiasqueroceballos

@matiasqueroceballos matiasqueroceballos commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary:

When merge_strategy is set to taxpayer_identification_number, form submissions for contacts whose NIF/DNI already exists in Clientify were failing with a 409 conflict instead of updating the existing contact.

Root cause:

determine_search_by() in CRMLIB_Clientify mapped email and business_name to Clientify's generic query search param, but passed taxpayer_identification_number through literally as a query param. Clientify's API does not support filtering GET /contacts/ by that field name directly — confirmed against the real API, where GET /contacts/?query= correctly matches the contact but GET /contacts/?taxpayer_identification_number= does not. As a result, the existence check always reported "not found," the integration fell through to POST (create), and Clientify rejected it with 409 since the NIF was already a duplicate.

Fix:

Added taxpayer_identification_number => query to the search-field mapping in determine_search_by() (includes/crm-library/class-crmlib-clientify.php), so merging by NIF now correctly finds the existing contact via GET /contacts/?query= and updates it via PATCH instead of attempting a duplicate POST.

Tests:

Added to tests/API/test-clientify.php:

test_create_entry_v2_merge_nif_found_patched — existing NIF is found and updated via PATCH (no 409).
test_create_entry_v2_merge_nif_not_found_posted — new NIF is created via POST.
Regression guard in the POST mock: simulates the real 409 Clientify returns for a duplicate NIF, so this suite would catch a future regression in the determine_search_by() mapping.

Open WordPress Playground Preview

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@davidperezgar

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02b08cce27

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +151 to +154
foreach ( $active_feeds as $feed ) {
if ( $addon->is_feed_condition_met( $feed, $form, $entry ) ) {
$matched_feed = $feed;
break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Retain the originating feed when retrying

When two active feeds have conditions that both match the same entry—including two unconditional feeds—each feed can produce its own error log, but this loop selects the first matching feed for every log. A resend of a failure from the second feed therefore inherits the first feed's module and merge strategy, potentially updating the wrong object or creating a duplicate; the originating feed ID needs to be stored with the error and used directly during retries.

Useful? React with 👍 / 👎.

Comment on lines +166 to +169
foreach ( $matched_feed['meta'] as $key => $value ) {
if ( ! empty( $value ) ) {
$settings[ $key ] = $value;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Translate custom-feed credentials before retrying

For a feed configured with a custom CRM/account, its credentials are stored as fc_crm_custom_*, while CRM clients read fc_crm_*; the original submission handles this conversion in GFCRM::get_api_settings_custom(). Copying the feed metadata verbatim leaves the global credential in fc_crm_apipassword, so manual and scheduled retries can authenticate against the wrong account or fail even though the custom feed credentials are valid; apply the same custom-setting translation here.

Useful? React with 👍 / 👎.

Comment on lines +502 to +504
* Reproduces the real-world case: Antonio Luque Oliveros (DNI 50997453J)
* already exists in the account with merge_strategy set to
* taxpayer_identification_number — this must resolve to an update, not a 409.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Replace the real taxpayer identity with synthetic test data

This comment explicitly identifies the fixture as a real-world case and publishes a person's full name together with a checksum-valid Spanish DNI. Committing that sensitive identifier exposes it through the repository and any distributed source archives; replace every occurrence with clearly synthetic data and treat the committed value as already disclosed when cleaning up history or logs.

Useful? React with 👍 / 👎.

@davidperezgar
davidperezgar merged commit e096dda into trunk Aug 6, 2026
8 checks passed
@davidperezgar
davidperezgar deleted the fix-error-409 branch August 6, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants