Files
core/src/Cart/Privacy/CartDataProvider.php
T

109 lines
3.8 KiB
PHP

<?php
namespace Modules\Core\Cart\Privacy;
use Lunar\Models\Cart;
use Lunar\Models\CartAddress;
use Modules\Core\Privacy\Contracts\PersonalDataProvider;
use Modules\Core\Privacy\DTOs\CustomerSubject;
use Modules\Core\Privacy\Enums\ErasureOutcome;
use Modules\Core\Privacy\DTOs\ProviderErasureResult;
use Modules\Core\Privacy\DTOs\ProviderExportResult;
use Modules\Core\Privacy\DTOs\UserSubject;
/**
* Carts and cart addresses (lunar_carts, lunar_cart_addresses) belong to the
* Customer (business account) via customer_id, not to an individual User, so this
* is Customer-scope only. Unlike Order/OrderAddress, an abandoned cart has no
* legal retention requirement, so its addresses are freely deleted. The Cart row
* itself is left alone (any completed order it produced is handled separately by
* OrderDataProvider, which is what retention law actually cares about) — only its
* address PII is removed.
*
* Also covers Cart.meta's own PII-adjacent keys — Modules\Core\Checkout\Services\
* CheckoutService::setRecoveryConsent()/selectPaymentMethod() write
* recovery_consent/recovery_consent_at/recovery_consent_policy_version and
* payment_method/checkout_fingerprint directly onto this same Cart row, which the
* address-only erase above never touched. Kept Customer-scope, consistent with
* how Cart itself is already classified — see docs/privacy.md for the
* User-vs-Customer discussion this raised.
*/
class CartDataProvider implements PersonalDataProvider
{
private const META_KEYS = [
'recovery_consent',
'recovery_consent_at',
'recovery_consent_policy_version',
'payment_method',
'checkout_fingerprint',
];
public function name(): string
{
return 'carts';
}
public function exportForCustomer(CustomerSubject $subject): ProviderExportResult
{
$carts = Cart::where('customer_id', $subject->customerId)->get();
$addresses = CartAddress::whereIn('cart_id', $carts->pluck('id'))->get();
return new ProviderExportResult('carts', [
'addresses' => $addresses->map(fn (CartAddress $address) => [
'type' => $address->type,
'first_name' => $address->first_name,
'last_name' => $address->last_name,
'line_one' => $address->line_one,
'city' => $address->city,
'postcode' => $address->postcode,
'contact_email' => $address->contact_email,
'contact_phone' => $address->contact_phone,
])->all(),
'carts' => $carts->map(fn (Cart $cart) => [
'id' => $cart->id,
'meta' => $this->metaOnly($cart),
])->all(),
]);
}
public function exportForUser(UserSubject $subject): ProviderExportResult
{
return new ProviderExportResult('carts', []);
}
public function eraseForCustomer(CustomerSubject $subject): ProviderErasureResult
{
$carts = Cart::where('customer_id', $subject->customerId)->get();
CartAddress::whereIn('cart_id', $carts->pluck('id'))->delete();
foreach ($carts as $cart) {
$meta = (array) $cart->meta;
foreach (self::META_KEYS as $key) {
unset($meta[$key]);
}
$cart->update(['meta' => $meta]);
}
return new ProviderErasureResult('carts', ErasureOutcome::Erased);
}
public function eraseForUser(UserSubject $subject): ProviderErasureResult
{
return new ProviderErasureResult('carts', ErasureOutcome::Skipped, 'Carts belong to Customer accounts, not individual users.');
}
/**
* @return array<string, mixed>
*/
private function metaOnly(Cart $cart): array
{
$meta = (array) $cart->meta;
return array_intersect_key($meta, array_flip(self::META_KEYS));
}
}