feat: add marketing consent extension for per-channel opt-in#407
feat: add marketing consent extension for per-channel opt-in#407wsbrunson wants to merge 2 commits intoUniversal-Commerce-Protocol:mainfrom
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
3cbc98a to
7e690b8
Compare
|
@jamesandersen wanted to make you aware of this feature request |
|
Great work on this @wsbrunson — this is needed. The regulatory divergence between channels is real and growing: CAN-SPAM treats email as opt-out while the TCPA requires prior express written consent for SMS, with statutory damages of $500–$1,500 per message. A merchant receiving today's This capability has been on my TODO list as well, so I've had a few design ideas brewing. I realize this comment has a lot of feedback — happy to keep iterating here, or I can throw up a counter-proposal PR if that's easier to compare. 1. Extend
|
|
@jamesandersen First of all, thank you for the detailed feedback! I actually agree or mostly agree with all your points. I'll push up a new commit to address this feedback. I'll go one-by-one below: 1. Extend
|
| } | ||
| ``` | ||
|
|
||
| ### Deprecation: `marketing` Boolean |
There was a problem hiding this comment.
@jamesandersen included this section about deprecating the marketing boolean so that we don't have to make a breaking change. Let me know if you agree with the wording
There was a problem hiding this comment.
The deprecation wording reads well, but I think we should handle this at the schema level rather than as a docs section — using the transition annotation pattern established by prior UCP deprecations (#145, #203).
I'd suggest removing the ### Deprecation: marketing Boolean section from the docs and instead updating buyer_consent.json:
"marketing": {
"type": "boolean",
"description": "Deprecated. Use marketing_channels for per-channel consent.",
"deprecated": true,
"ucp_request": {
"complete": {
"transition": {
"from": "optional",
"to": "omit",
"description": "Replaced by marketing_channels. Platforms should use marketing_channels when marketing_consent_options is present."
}
}
}
}This keeps the deprecation machine-readable and consistent with how the spec has handled deprecations before.
Adds dev.ucp.shopping.marketing_consent extension supporting two flows: platform-collected consent and business-requested consent with per-channel granularity (email, sms).
Extends the existing buyer_consent extension with per-channel marketing consent capture instead of a separate extension. Adds marketing_consent_options as a checkout-level field for business-declared channels, and marketing_channels on buyer.consent for platform-submitted consent at checkout completion. Deprecates the marketing boolean. Widens buyer on complete from omit to optional.
5ff77fa to
4af7636
Compare
Description
Evolves the existing
buyer_consentextension with per-channel marketing consent capture. Rather than introducing a separate extension, this adds two fields tobuyer_consent:marketing_consent_options(checkout level, business → platform): An array of marketing channels the business offers for opt-in, each with a channel identifier, display text, and privacy policy URL. Included in create and update checkout responses only.marketing_channels(onbuyer.consent, platform → business): An array of the buyer's per-channel opt-in decisions, submitted at checkout completion.The
marketingboolean onbuyer.consentis deprecated but not removed. When the business includesmarketing_consent_optionsin the checkout response, platforms MUST usemarketing_channelsinstead ofmarketing.Examples
Business-Requested Consent (Checkout Response)
{ "id": "checkout_789", "status": "ready_for_complete", "currency": "USD", "buyer": { "email": "jane@example.com", "consent": { "analytics": true, "preferences": true, "sale_of_data": false } }, "marketing_consent_options": [ { "channel": "email", "display_text": "Promotional emails and exclusive offers", "privacy_policy_url": "https://example.com/privacy" }, { "channel": "sms", "display_text": "Order updates and deals via text", "privacy_policy_url": "https://example.com/privacy" } ], "line_items": [...], "totals": [...] }Consent Capture (Complete Request)
{ "buyer": { "consent": { "analytics": true, "preferences": true, "sale_of_data": false, "marketing_channels": [ { "channel": "email", "opted_in": true }, { "channel": "sms", "opted_in": false } ] } }, "payment": { "handler": "dev.ucp.payments.example", "details": { "token": "tok_abc123" } } }Motivation
Marketing opt-in is a standard feature on most e-commerce checkout pages. The existing
buyer_consentextension includes amarketingboolean, but this does not cover more advanced use cases for collecting marketing consent:Category
Checklist
Alternative Approaches
Option 1: Separate
marketing_consentExtensionThe original proposal in this PR. Creates a new
dev.ucp.shopping.marketing_consentextension with amarketing_consentobject on the buyer containingoptionsandconsents.Details
This approach adds a new extension schema (
marketing_consent.json) and documentation page (marketing-consent.md) alongside the existingbuyer_consentextension.The extension adds a
marketing_consentobject to thebuyerwithin checkout, containing:options(business → platform): An array of marketing channels the business offers for opt-in.consents(platform → business): An array of the buyer's opt-in decisions per channel.It supports two flows:
marketing_consent.consentson create/update.marketing_consent.optionsin the checkout response, the platform collects consent and sends it on complete.Why we moved away from this: Two extensions composing onto the same
buyerobject viaallOfcreates schema resolution complexity. A normative rule thatmarketing_consentsupersedesbuyer.consent.marketingadds fragmentation. Evolving the existing extension is cleaner.Examples
Business-Requested Consent (Checkout Response):
{ "buyer": { "email": "jane@example.com", "marketing_consent": { "options": [ { "channel": "email", "display_text": "Promotional emails and exclusive offers", "privacy_policy_url": "https://example.com/privacy" }, { "channel": "sms", "display_text": "Order updates and deals via text", "privacy_policy_url": "https://example.com/privacy" } ] } } }Consent Capture (Complete Request):
{ "buyer": { "marketing_consent": { "consents": [ { "channel": "email", "opted_in": true }, { "channel": "sms", "opted_in": false } ] } } }Option 2: Breaking Change to Buyer Consent Object
Replace
marketing: booleanwithmarketing: marketing_channel_consenton the existing consent object. Cleaner than deprecation but introduces a breaking change.Details
The reason we decided against this was that there are multiple marketing consent properties we want to add and it is not clear whether any of them would apply to the other types of consents (analytics, sale_of_data).
If this kind of divergence is acceptable, we could propose a breaking change to the Buyer Consent extension and change
marketing: booleantomarketing: marketing_channel_consent.Option 3: Add
marketing_consentField to Existing Buyer ConsentKeep
marketing: booleanand add a separatemarketing_consentobject underbuyerorconsent. Non-breaking but introduces two sources of truth.Details
For this option, we would keep:
{ "buyer": { "consent": { "marketing": true } } }And then add either under
buyerorconsentthemarketing_consentobject with options. This would probably be the least amount of changes and would not be a breaking change, but it would introduce two separate sources of truth for marketing consent. There would still be themarketing: booleanproperty and there would bemarketing_consent: { consents: [...] }, which could conflict with the top-level boolean if not kept in sync by the platform.One could argue that this is technically true in the current proposal but we believe that including a specific extension for marketing_consent is strong enough signal to the Platform and Business that the
buyer.consent.marketingfield should be ignored.If documented correctly, this could be a viable option, but we would only consider it if the governing body was okay with the complexity introduced.
Option 4: Generic Advanced Consent Extension
Create a broader
buyer_consent_advancedextension that could extend any consent type, not just marketing.Details
Instead of making a marketing-specific change, we could create a more generic advanced consent extension. The problem with this approach is that we are not sure how the other types of consents would be extended with more advanced options. We currently only see a use case for marketing, but if there are additional thoughts on this, we could explore this option.