Feat: Updating the Privacy Providers, moving them into the appropriate Modules, Updating Privacy views
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Payment\Privacy;
|
||||
|
||||
use Lunar\Models\Order;
|
||||
use Lunar\Models\Transaction;
|
||||
use Modules\Core\Payment\Models\StripePaymentIntent;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Payment records (lunar_transactions, stripe_payment_intents) belong to the
|
||||
* Customer (business account) via the Order they're attached to, not to an
|
||||
* individual User, so this is Customer-scope only — same chain
|
||||
* OrderDataProvider already uses (Order.customer_id).
|
||||
*
|
||||
* Like Order itself, payment/transaction records are subject to the same
|
||||
* tax/accounting legal retention argument (GDPR Art. 17(3)(b)) — a payment
|
||||
* record is part of the same financial audit trail as the order it settled,
|
||||
* so this pseudonymizes the card-identifying fields in place rather than
|
||||
* deleting the transaction: amount, status, and the transaction/order link
|
||||
* all remain intact and auditable.
|
||||
*
|
||||
* No Stripe Customer object exists anywhere in this app (see docs/
|
||||
* payments.md "Reconciliation") — there is nothing to request deletion of
|
||||
* on Stripe's side. The only local, erasable PII is the card brand/last-4
|
||||
* on Transaction and the cart_id/order_id/context correlation row on
|
||||
* stripe_payment_intents, which is deleted outright once its Order is
|
||||
* settled (its only purpose was resolving an async webhook callback — see
|
||||
* docs/payments.md "Async resolution" — which has already happened by the
|
||||
* time an erasure request would run).
|
||||
*/
|
||||
class PaymentDataProvider implements PersonalDataProvider
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'payments';
|
||||
}
|
||||
|
||||
public function exportForCustomer(CustomerSubject $subject): ProviderExportResult
|
||||
{
|
||||
$orderIds = Order::where('customer_id', $subject->customerId)->pluck('id');
|
||||
|
||||
$transactions = Transaction::whereIn('order_id', $orderIds)->get();
|
||||
$intents = StripePaymentIntent::whereIn('order_id', $orderIds)->get();
|
||||
|
||||
return new ProviderExportResult('payments', [
|
||||
'transactions' => $transactions->map(fn (Transaction $transaction) => [
|
||||
'id' => $transaction->id,
|
||||
'order_id' => $transaction->order_id,
|
||||
'type' => $transaction->type,
|
||||
'status' => $transaction->status,
|
||||
'amount' => $transaction->amount,
|
||||
'card_type' => $transaction->card_type,
|
||||
'last_four' => $transaction->last_four,
|
||||
'reference' => $transaction->reference,
|
||||
])->all(),
|
||||
'stripe_payment_intents' => $intents->map(fn (StripePaymentIntent $intent) => [
|
||||
'id' => $intent->id,
|
||||
'order_id' => $intent->order_id,
|
||||
'intent_id' => $intent->intent_id,
|
||||
'status' => $intent->status,
|
||||
])->all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function exportForUser(UserSubject $subject): ProviderExportResult
|
||||
{
|
||||
return new ProviderExportResult('payments', []);
|
||||
}
|
||||
|
||||
public function eraseForCustomer(CustomerSubject $subject): ProviderErasureResult
|
||||
{
|
||||
$orderIds = Order::where('customer_id', $subject->customerId)->pluck('id');
|
||||
|
||||
if ($orderIds->isEmpty()) {
|
||||
return new ProviderErasureResult('payments', ErasureOutcome::Skipped, 'No orders, and therefore no payment records, for this customer.');
|
||||
}
|
||||
|
||||
Transaction::whereIn('order_id', $orderIds)->update([
|
||||
'card_type' => null,
|
||||
'last_four' => null,
|
||||
]);
|
||||
|
||||
// stripe_payment_intents only ever existed to correlate a webhook
|
||||
// callback back to a cart/order (see docs/payments.md "Async
|
||||
// resolution") — that correlation has already served its purpose by
|
||||
// the time an erasure request runs, so these rows are deleted
|
||||
// outright rather than pseudonymized, unlike Transaction, which is
|
||||
// the actual audit-trail record.
|
||||
StripePaymentIntent::whereIn('order_id', $orderIds)->delete();
|
||||
|
||||
return new ProviderErasureResult(
|
||||
'payments',
|
||||
ErasureOutcome::Pseudonymized,
|
||||
'Card brand/last-four cleared from transaction records; amounts, statuses, and references retained for legal/tax record-keeping. Stripe correlation rows (no longer needed post-settlement) deleted.'
|
||||
);
|
||||
}
|
||||
|
||||
public function eraseForUser(UserSubject $subject): ProviderErasureResult
|
||||
{
|
||||
return new ProviderErasureResult('payments', ErasureOutcome::Skipped, 'Payments belong to Customer-owned orders, not individual users.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user