Automattic / Automattic/jetpack-crm

API create_customer drops date custom fields (double date parse)

Open
#21 2 comments 0 reactions 1 assignee Claimed by @donnchawp View on GitHub
bug ready-for-agent
Dominant language
PHP
Stars
13
Forks
8
Avg merge
1d 10h
Merged PRs (30d)
13

Description

### Impacted plugin

Jetpack

### Quick summary

Contact date custom fields are not saved when creating/updating a contact via the CRM REST/API create_customer endpoint, even when a valid Y-m-d value is sent and the API response looks successful.

Root cause: the date is converted to a Unix timestamp once in api/create_customer.php, then converted again inside zeroBS_addUpdateCustomer() → zeroBS_buildContactMeta(). The second pass expects Y-m-d, receives a timestamp string, fails parsing (false), and removeEmpties=true strips the field before save.

### Steps to reproduce

In Jetpack CRM → Settings → Custom Fields, create a Contact custom field of type Date (example label: Contract Date, slug e.g. contract-date).

Enable the CRM API and note the API key/secret.

POST to the create/update customer API endpoint with JSON similar to:

{
"email": "apitest-date-cf@example.com",
"fname": "API",
"lname": "DateTest",
"status": "Customer",
"contract-date": "2026-07-23"
}

Confirm the API response returns success / echoes the payload (including the date).

Open the contact in CRM admin, or fetch via the customers API (withCustomFields is enabled there), or query wp_zbs_customfields for the contact.

Expected

contract-date is stored as a Unix timestamp (UTS) and shown as 2026-07-23 in the contact UI / API read responses.

Actual

The contact is created/updated, but the date custom field is missing/empty. The create endpoint response is misleading because it returns the request payload, not the persisted custom-field state.

Technical root cause

Pass 1 — api/create_customer.php

$customer_array = zeroBS_buildContactMeta( $new_customer, array(), '', 'zbsc_', $remove_empties, $autogen_autonumbers );

Input key: contract-date = "2026-07-23"

Date branch in zeroBS_buildObjArr():

case 'date':
$retArray[ $outputPrefix . $fK ] = jpcrm_date_str_to_uts( $safe_text, '!Y-m-d', true );

Result: zbsc_contract-date = e.g. 1784764800 (int UTS)

Pass 2 — zeroBS_addUpdateCustomer() in includes/ZeroBSCRM.DAL3.Helpers.php

$zbsCustomerMeta = zeroBS_buildContactMeta( $cFields, $existingMeta, $metaBuilderPrefix, '', true );

Re-reads zbsc_contract-date (already UTS)

Date branch again runs jpcrm_date_str_to_uts( '1784764800', '!Y-m-d', true )

DateTime::createFromFormat( '!Y-m-d', '1784764800' ) fails → false

With $removeEmpties = true, empty(false) removes the key

addUpdateContact() never receives the custom field value

Same pattern exists for companies (api/create_company.php + zeroBS_addUpdateCompany / shared zeroBS_buildObjArr date handling).

### Site owner impact

More than 60% of the total website/platform users

### Severity

Critical

### What other impact(s) does this issue have?

Platform revenue

### If a workaround is available, please outline it here.

Suggested fix

One of:

Skip re-normalization for already-parsed UTS in the date case of zeroBS_buildObjArr():

case 'date':
$raw = $arraySource[ $fieldPrefix . $fK ];
if ( is_numeric( $raw ) ) {
$retArray[ $outputPrefix . $fK ] = (int) $raw;
break;
}
$safe_text = sanitize_text_field( $raw );
$retArray[ $outputPrefix . $fK ] = jpcrm_date_str_to_uts( $safe_text, '!Y-m-d', true );
break;

Or stop calling zeroBS_buildContactMeta twice in the API → zeroBS_addUpdateCustomer path (pass already-built meta through, or only sanitize once).

Add a regression test: API create contact with a date custom field, assert custom field row exists with expected UTS.

### Platform (Simple and/or Atomic)

Self-hosted

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.