Skip to content

feat(integrations): add subscription and donation metadata#4597

Merged
chickenn00dle merged 5 commits intotrunkfrom
feat/add-subscription-donation-metadata
Apr 2, 2026
Merged

feat(integrations): add subscription and donation metadata#4597
chickenn00dle merged 5 commits intotrunkfrom
feat/add-subscription-donation-metadata

Conversation

@chickenn00dle
Copy link
Copy Markdown
Contributor

@chickenn00dle chickenn00dle commented Mar 24, 2026

Summary

  • Implements get_metadata() for the Subscription contact metadata class with 13 get methods covering subscriber status, active count, dates, billing cycle, payments, product names, coupon codes, and cancellation reasons — all filtered to non-donation subscriptions only.
  • Implements get_metadata() for the Donation class by extending Subscription, inverting the filter to donation-only, and adding donor status labels (Monthly/Yearly/Ex-* Donor), one-time donation fallbacks, and previous donation amount tracking.

Closes NPPD-1388

Test plan

  1. Enabled the following in wp-config.php:
define( 'NEWSPACK_LOG_LEVEL', 3 );
define( `NEWSPACK_SYNC_METADATA_VERSION`, '1.0' );
define( 'NEWSPACK_INTEGRATIONS_SETTINGS_ENABLED', true );
  1. Navigate to Audience > Integrations > ESP and confirm the donation and subscription fields listed in the linear task are available in the outgoing metadata section
  2. Check them all and save, confirming they persist on reload
  3. As a new reader, register to the site
  4. Check the logs and verify the contact is synced with the relevant metadata in a default empty state (note you can also just check the connected ESP, however you may run into issues with merge field limits depending on the ESP):
    Screenshot 2026-03-25 at 16 47 37
  5. As the same reader, purchase a one-time donation
  6. Check the logs for another sync entry, this time with the donation status, product name, amount, and date populated
  7. As the same reader, purchase a recurring donation
  8. Check the logs for another sync entry, this time with all relevant donation fields populated (only end date and previous product/amount should be empty)
  9. As the reader, purchase a non-donation subscription product
  10. Check the logs for another sync entry, this time with all the relevant subscription fields populated (only end date, cancellation reason, and previous product fields should be empty)
  11. Now cancel the subscription (either as the reader or manually as the admin)
  12. Check the logs for another sync entry, this time the subscription status, count, end date, and reason should be populated
  13. Finally, cancel the recurring donation
  14. Check the logs for another sync entry, this time the donation status, count, and end date should be populated

🤖 Generated with Claude Code

@chickenn00dle chickenn00dle force-pushed the feat/add-subscription-donation-metadata branch 2 times, most recently from 0980297 to ec9dfe1 Compare March 24, 2026 21:34
chickenn00dle and others added 2 commits March 25, 2026 15:50
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chickenn00dle chickenn00dle force-pushed the feat/add-subscription-donation-metadata branch from ec9dfe1 to 33c8baf Compare March 25, 2026 21:41
@chickenn00dle chickenn00dle marked this pull request as ready for review March 25, 2026 21:41
@chickenn00dle chickenn00dle requested a review from a team as a code owner March 25, 2026 21:41
Copilot AI review requested due to automatic review settings March 25, 2026 21:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds Reader Activation contact metadata for WooCommerce Subscriptions and Donations so ESP syncs can include richer subscription/donation lifecycle fields.

Changes:

  • Implemented Subscription::get_metadata() with 13 subscription-related fields (status, counts, dates, billing, product, coupon, last payment, cancellation reason).
  • Implemented Donation::get_metadata() by extending Subscription, filtering to donation-only, and adding donor status + one-time donation fallbacks and previous amount tracking.
  • Added unit tests + expanded WooCommerce test mocks; added a shared date formatter in the Contact_Metadata base class and extra sync logging.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/unit-tests/reader-activation-sync/class-test-subscription-metadata.php New unit tests covering Subscription metadata fields and edge cases.
tests/unit-tests/reader-activation-sync/class-test-donation-metadata.php New unit tests covering Donation metadata fields, donor labels, and switch/previous amount logic.
tests/mocks/wc-mocks.php Extends WC mocks to support new metadata logic (products/items, coupons, related orders, order lookup).
includes/reader-activation/sync/contact-metadata/class-subscription.php Implements Subscription metadata retrieval with caching and helpers.
includes/reader-activation/sync/contact-metadata/class-donation.php Implements Donation metadata by reusing Subscription helpers + donation-specific fallbacks.
includes/reader-activation/sync/class-contact-sync.php Adds additional logging around contact sync calls.
includes/reader-activation/sync/class-contact-metadata.php Adds a shared date format constant and formatter helper.
Comments suppressed due to low confidence (1)

tests/mocks/wc-mocks.php:318

  • The mock WC_Subscription::get_date() only accepts one parameter, but production WC_Subscription::get_date() is commonly called with a timezone/context argument (e.g., get_date('start','site')). The new Subscription metadata code passes a second argument, which can trigger warnings/errors in tests on newer PHP versions; update the mock signature to accept an optional second parameter (and ignore it).
	public function get_date( $type ) {
		return $this->data['dates'][ $type ] ?? 0;
	}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

tests/mocks/wc-mocks.php:318

  • WC_Subscription::get_date() in this test mock only accepts one argument, but the new metadata code calls $subscription->get_date( ..., 'site' ) (matching the real WooCommerce Subscriptions signature). In PHP 8 this will throw an ArgumentCountError in unit tests. Update the mock method signature to accept the optional timezone/context parameter (or use variadic args) and ignore it as needed so tests exercise the production call shape.
	public function get_date( $type ) {
		return $this->data['dates'][ $type ] ?? 0;
	}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chickenn00dle chickenn00dle added the [Status] Needs Review The issue or pull request needs to be reviewed label Mar 26, 2026
Copy link
Copy Markdown
Contributor

@leogermani leogermani left a comment

Choose a reason for hiding this comment

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

Beautiful!

Works well and has a clean implementation!

The only blocker comment is that it's missing the rule to prioritize non-gift subscriptions over gifts in the current subscription.

I also left a few minor comments.

And I have one kind of a NIT, let me know what you think.

I think that instead of Donations extending Subscriptions, it would be better if both classes extended a common shared class. Only because the relationship would be more explicit and it's less likely that someone in the future makes a change to the Subscription class without realizing it also affects Donations...


$active = $this->get_active_subscriptions();
if ( ! empty( $active ) ) {
$this->current_subscription_cache = reset( $active );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you are just getting the first review in the array. Will it be the most recent? How are they ordered?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looking at the current methods, it seems that they do the same thing... but worth checking if this is true.

Also, I think there's a requirement to favor non-gift subscriptions over gifts, so it's worth adding this rule as well

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

you are just getting the first review in the array. Will it be the most recent? How are they ordered?

Yes. It should be the most recent. Subscriptions are populated here:

$all_subscriptions = \wcs_get_users_subscriptions( $this->user->ID );
foreach ( $all_subscriptions as $subscription ) {
if ( $this->is_relevant_subscription( $subscription ) ) {
$this->user_subscriptions_cache[] = $subscription;
}
}

The wcs_get_users_subscriptions method returns an array of subscriptions in descending order by ID. We should be respecting this order when populating our user subscriptions cache and so the first entry should be the subscription with the largest ID (the most recent).

That said, if we feel this is too fragile, I'm happy to implement our own sorting logic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch about non-gift preference! Added a commit to account for this here: df3220b

return $this->current_subscription_cache;
}

foreach ( $this->get_user_subscriptions() as $subscription ) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same question about order.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

/**
* The date format to use for all date fields, which is MM/DD/YYYY.
*/
const DATE_FORMAT = 'm/d/Y';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The current default format is 'Y-m-d H:i:s'. Any reason to use a different one?

const DATE_FORMAT = 'Y-m-d H:i:s';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good question. No reason in particular. This was an arbitrary decision made by Claude 😝. Updated to use the current default in df3220b

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh wait. Sorry. This was not arbitrary. Looks like the metadata spec sheet defines the 'm/d/Y' format.

Is this a hard requirement? If so I'll revert this format change to what I had initially.

@github-actions github-actions bot added the [Status] Needs Changes or Feedback The issue or pull request needs action from the original creator label Mar 31, 2026
@chickenn00dle
Copy link
Copy Markdown
Contributor Author

Thanks for your review @leogermani! I addressed most of your feedback and this is ready for another round of review.

I think that instead of Donations extending Subscriptions, it would be better if both classes extended a common shared class. Only because the relationship would be more explicit and it's less likely that someone in the future makes a change to the Subscription class without realizing it also affects Donations...

My thinking is donations overlap subscriptions in functionality so much so that if there needs to be a change to the subscription class in the future, its very likely it will also need to be made to the donation class as well. There are also references to the donation class in the is_relevant_subscription method used throughout the class although admittedly the file is a bit long so this could be missed. I've left it as is for this round of changes, but if you feel strongly about this, I can abstract some of the functionality into a common shared one.

@chickenn00dle chickenn00dle requested a review from leogermani April 2, 2026 17:03
@chickenn00dle chickenn00dle removed the [Status] Needs Changes or Feedback The issue or pull request needs action from the original creator label Apr 2, 2026
@leogermani
Copy link
Copy Markdown
Contributor

Thanks for your review @leogermani! I addressed most of your feedback and this is ready for another round of review.

I think that instead of Donations extending Subscriptions, it would be better if both classes extended a common shared class. Only because the relationship would be more explicit and it's less likely that someone in the future makes a change to the Subscription class without realizing it also affects Donations...

My thinking is donations overlap subscriptions in functionality so much so that if there needs to be a change to the subscription class in the future, its very likely it will also need to be made to the donation class as well. There are also references to the donation class in the is_relevant_subscription method used throughout the class although admittedly the file is a bit long so this could be missed. I've left it as is for this round of changes, but if you feel strongly about this, I can abstract some of the functionality into a common shared one.

Makes sense

Copy link
Copy Markdown
Contributor

@leogermani leogermani left a comment

Choose a reason for hiding this comment

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

Thanks for the changes.

Approving for now, we can check with Katie and others about the date format later

@github-actions github-actions bot added [Status] Approved The pull request has been reviewed and is ready to merge and removed [Status] Needs Review The issue or pull request needs to be reviewed labels Apr 2, 2026
@chickenn00dle chickenn00dle merged commit ca928f8 into trunk Apr 2, 2026
15 checks passed
@chickenn00dle chickenn00dle deleted the feat/add-subscription-donation-metadata branch April 2, 2026 20:40
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 2, 2026

Hey @chickenn00dle, good job getting this PR merged! 🎉

Now, the needs-changelog label has been added to it.

Please check if this PR needs to be included in the "Upcoming Changes" and "Release Notes" doc. If it doesn't, simply remove the label.

If it does, please add an entry to our shared document, with screenshots and testing instructions if applicable, then remove the label.

Thank you! ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Status] Approved The pull request has been reviewed and is ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants