Retire payment links and capture card payments (#327)
## Summary - retire Stripe hosted payment-link creation routes used by POS and order management - automatically capture authorized payment intents rather than requiring a separate manual capture action - preserve ordinary terminal payment and payment-intent lifecycle behavior - add API and wiring regressions for payment-link retirement and automatic capture Paired frontend change: https://github.com/copenhagentruckwash/pleno-vue/pull/233 ## Verification - focused backend unit suite: 2 tests, 36 assertions passed - PHP syntax checks passed - paired frontend unit and Playwright suites passed locally - full required GitHub runner suites are required before this task may enter Review or merge ## Security and operational notes - no credentials, terminal secrets, or payment data are added - no live Stripe account or physical terminal was exercised locally - automatic merge remains gated on both paired PRs having passing required checks and current branches
This commit is contained in:
@@ -644,6 +644,13 @@ class ordersRoute
|
||||
$this->isStripePaymentIntentReusable($storedPaymentIntent)
|
||||
&& $this->doesStripePaymentIntentMatchOrder($storedPaymentIntent, $expectedPaymentIntentAmount, $tax_percentage ?? 0)
|
||||
) {
|
||||
if (strtolower((string)($storedPaymentIntent->status ?? '')) === 'requires_capture') {
|
||||
$storedPaymentIntent = $this->captureApprovedStripePaymentIntent(
|
||||
$order,
|
||||
$stripePaymentIntents,
|
||||
$stripe
|
||||
);
|
||||
}
|
||||
(new logs_o())->add('orders', $order->department_id->value(), 1, $user->id, 'CHARGE_ORDER', 'Reused Stripe payment intent for order (ID: ' . $data['id'] . ')');
|
||||
$response->success($this->buildStripePaymentIntentResponse($storedPaymentIntent, $stripePaymentIntents, [
|
||||
'reused' => true,
|
||||
@@ -696,6 +703,9 @@ class ordersRoute
|
||||
try {
|
||||
$paymentIntent = $stripe->payment_intents->get($paymentIntent->id);
|
||||
$stripePaymentIntents->updateStoredPaymentIntent($paymentIntent);
|
||||
if (strtolower((string)($paymentIntent->status ?? '')) === 'requires_capture') {
|
||||
$paymentIntent = $this->captureApprovedStripePaymentIntent($order, $stripePaymentIntents, $stripe);
|
||||
}
|
||||
} catch (\Stripe\Exception\InvalidRequestException) {
|
||||
// Keep the created intent payload if Stripe retrieve is temporarily unavailable.
|
||||
}
|
||||
@@ -760,6 +770,9 @@ class ordersRoute
|
||||
'message' => 'No active payment intent for this order.',
|
||||
]));
|
||||
}
|
||||
if ($status === 'requires_capture') {
|
||||
$paymentIntent = $this->captureApprovedStripePaymentIntent($order, $stripePaymentIntents, $stripe);
|
||||
}
|
||||
|
||||
$stripePaymentIntents->updateStoredPaymentIntent($paymentIntent);
|
||||
$response->success($this->buildStripePaymentIntentResponse($paymentIntent, $stripePaymentIntents));
|
||||
@@ -841,6 +854,7 @@ class ordersRoute
|
||||
if (!$order->exists()) {
|
||||
$response->error('Order not found', 400);
|
||||
}
|
||||
self::requireDepartmentAccess((int)$order->department_id->value());
|
||||
|
||||
$stripePaymentIntents = new stripe_payment_intents_o();
|
||||
if (!$stripePaymentIntents->doesOrderHavePaymentIntent((int)$order->id)) {
|
||||
@@ -871,23 +885,7 @@ class ordersRoute
|
||||
$response->error('Payment intent is not ready to capture.', 409);
|
||||
}
|
||||
|
||||
try {
|
||||
$paymentIntent = $stripe->payment_intents->capture(
|
||||
$stripePaymentIntents->payment_intent_id->value(),
|
||||
[]
|
||||
);
|
||||
} catch (\Stripe\Exception\InvalidRequestException) {
|
||||
$stripePaymentIntents->deletePermanently();
|
||||
$response->error('Stored payment intent is stale. Start the payment again.', 409);
|
||||
}
|
||||
|
||||
$stripePaymentIntents->updateStoredPaymentIntent($paymentIntent);
|
||||
if (strtolower((string)($paymentIntent->status ?? '')) !== 'succeeded') {
|
||||
$response->error('Payment intent is not ready to capture.', 409);
|
||||
}
|
||||
|
||||
$order_collection = $order->getOrderCollection();
|
||||
$order_collection->paidWithStripe($paymentIntent->id);
|
||||
$paymentIntent = $this->captureApprovedStripePaymentIntent($order, $stripePaymentIntents, $stripe);
|
||||
|
||||
(new logs_o())->add('orders', $order->department_id->value(), 1, $user->id, 'CONFIRM_PAYMENT_INTENT', 'Successfully confirmed a payment intent (ID: ' . $data['id'] . ')');
|
||||
$response->success($this->buildStripePaymentIntentResponse($paymentIntent, $stripePaymentIntents));
|
||||
@@ -994,6 +992,31 @@ class ordersRoute
|
||||
], true);
|
||||
}
|
||||
|
||||
private function captureApprovedStripePaymentIntent(orders_o $order, stripe_payment_intents_o $stripePaymentIntents, stripe $stripe): object
|
||||
{
|
||||
global $response;
|
||||
|
||||
try {
|
||||
$paymentIntent = $stripe->payment_intents->capture(
|
||||
$stripePaymentIntents->payment_intent_id->value(),
|
||||
[]
|
||||
);
|
||||
} catch (\Stripe\Exception\InvalidRequestException) {
|
||||
$stripePaymentIntents->deletePermanently();
|
||||
$response->error('Stored payment intent is stale. Start the payment again.', 409);
|
||||
}
|
||||
|
||||
$stripePaymentIntents->updateStoredPaymentIntent($paymentIntent);
|
||||
if (strtolower((string)($paymentIntent->status ?? '')) !== 'succeeded') {
|
||||
$response->error('Payment intent is not ready to capture.', 409);
|
||||
}
|
||||
|
||||
$order_collection = $order->getOrderCollection();
|
||||
$order_collection->paidWithStripe($paymentIntent->id);
|
||||
|
||||
return $paymentIntent;
|
||||
}
|
||||
|
||||
private function buildStripePaymentIntentResponse(?object $paymentIntent, ?stripe_payment_intents_o $storedIntent, array $extra = []): array
|
||||
{
|
||||
$paymentIntentPayload = null;
|
||||
|
||||
Reference in New Issue
Block a user