Skip to content

Commit 3ba76fc

Browse files
committed
Update stripe payment handling
1 parent 556aebd commit 3ba76fc

File tree

10 files changed

+88
-40
lines changed

10 files changed

+88
-40
lines changed

backend/app/Http/Actions/Common/Webhooks/StripeIncomingWebhookAction.php

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,24 @@
1212

1313
class StripeIncomingWebhookAction extends BaseAction
1414
{
15-
private IncomingWebhookHandler $webhookHandler;
16-
17-
public function __construct(IncomingWebhookHandler $webhookHandler)
18-
{
19-
$this->webhookHandler = $webhookHandler;
20-
}
21-
2215
public function __invoke(Request $request): Response
2316
{
2417
try {
25-
$this->webhookHandler->handle(new StripeWebhookDTO(
26-
headerSignature: $request->server('HTTP_STRIPE_SIGNATURE'),
27-
payload: $request->getContent(),
28-
));
18+
$headerSignature = $request->server('HTTP_STRIPE_SIGNATURE');
19+
$payload = $request->getContent();
20+
21+
dispatch(static function (IncomingWebhookHandler $handler) use ($headerSignature, $payload) {
22+
$handler->handle(new StripeWebhookDTO(
23+
headerSignature: $headerSignature,
24+
payload: $payload,
25+
));
26+
})->catch(function (Throwable $exception) use ($payload) {
27+
logger()->error(__('Failed to handle incoming Stripe webhook'), [
28+
'exception' => $exception,
29+
'payload' => $payload,
30+
]);
31+
});
32+
2933
} catch (Throwable $exception) {
3034
logger()?->error($exception->getMessage(), $exception->getTrace());
3135
return $this->noContentResponse(ResponseCodes::HTTP_BAD_REQUEST);

backend/app/Services/Application/Handlers/Order/Payment/Stripe/CreatePaymentIntentHandler.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
use HiEvents\Services\Domain\Payment\Stripe\DTOs\CreatePaymentIntentResponseDTO;
2121
use HiEvents\Services\Domain\Payment\Stripe\StripePaymentIntentCreationService;
2222
use HiEvents\Services\Infrastructure\Session\CheckoutSessionManagementService;
23+
use Stripe\Exception\ApiErrorException;
24+
use Throwable;
2325

2426
readonly class CreatePaymentIntentHandler
2527
{
@@ -34,11 +36,15 @@ public function __construct(
3436
}
3537

3638
/**
39+
* @param string $orderShortId
40+
* @return CreatePaymentIntentResponseDTO
41+
* @throws CreatePaymentIntentFailedException
3742
* @throws MathException
3843
* @throws NumberFormatException
3944
* @throws RoundingNecessaryException
4045
* @throws UnknownCurrencyException
41-
* @throws CreatePaymentIntentFailedException
46+
* @throws ApiErrorException
47+
* @throws Throwable
4248
*/
4349
public function handle(string $orderShortId): CreatePaymentIntentResponseDTO
4450
{

backend/app/Services/Application/Handlers/Order/Payment/Stripe/IncomingWebhookHandler.php

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use HiEvents\Services\Domain\Payment\Stripe\EventHandlers\ChargeRefundUpdatedHandler;
99
use HiEvents\Services\Domain\Payment\Stripe\EventHandlers\PaymentIntentFailedHandler;
1010
use HiEvents\Services\Domain\Payment\Stripe\EventHandlers\PaymentIntentSucceededHandler;
11+
use Illuminate\Cache\Repository;
1112
use Illuminate\Log\Logger;
1213
use JsonException;
1314
use Stripe\Event;
@@ -16,14 +17,15 @@
1617
use Throwable;
1718
use UnexpectedValueException;
1819

19-
readonly class IncomingWebhookHandler
20+
class IncomingWebhookHandler
2021
{
2122
public function __construct(
22-
private ChargeRefundUpdatedHandler $refundEventHandlerService,
23-
private PaymentIntentSucceededHandler $paymentIntentSucceededHandler,
24-
private PaymentIntentFailedHandler $paymentIntentFailedHandler,
25-
private AccountUpdateHandler $accountUpdateHandler,
26-
private Logger $logger
23+
private readonly ChargeRefundUpdatedHandler $refundEventHandlerService,
24+
private readonly PaymentIntentSucceededHandler $paymentIntentSucceededHandler,
25+
private readonly PaymentIntentFailedHandler $paymentIntentFailedHandler,
26+
private readonly AccountUpdateHandler $accountUpdateHandler,
27+
private readonly Logger $logger,
28+
private readonly Repository $cache,
2729
)
2830
{
2931
}
@@ -42,6 +44,16 @@ public function handle(StripeWebhookDTO $webhookDTO): void
4244
config('services.stripe.webhook_secret'),
4345
);
4446

47+
if ($this->hasEventBeenHandled($event)) {
48+
$this->logger->debug('Stripe event already handled', [
49+
'event_id' => $event->id,
50+
'type' => $event->type,
51+
'data' => $event->data->object->toArray(),
52+
]);
53+
54+
return;
55+
}
56+
4557
$this->logger->debug('Stripe event received', $event->data->object->toArray());
4658

4759
switch ($event->type) {
@@ -60,6 +72,8 @@ public function handle(StripeWebhookDTO $webhookDTO): void
6072
default:
6173
$this->logger->debug(sprintf('Unhandled Stripe webhook: %s', $event->type));
6274
}
75+
76+
$this->markEventAsHandled($event);
6377
} catch (CannotAcceptPaymentException $exception) {
6478
$this->logger->error(
6579
'Cannot accept payment: ' . $exception->getMessage(), [
@@ -88,4 +102,14 @@ public function handle(StripeWebhookDTO $webhookDTO): void
88102
throw $exception;
89103
}
90104
}
105+
106+
private function hasEventBeenHandled(Event $event): bool
107+
{
108+
return $this->cache->has('stripe_event_' . $event->id);
109+
}
110+
111+
private function markEventAsHandled(Event $event): void
112+
{
113+
$this->cache->put('stripe_event_' . $event->id, true, now()->addMinutes(60));
114+
}
91115
}

backend/app/Services/Application/Handlers/Order/Payment/Stripe/RefundOrderHandler.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,15 @@ private function refundOrder(RefundOrderDTO $refundOrderDTO): OrderDomainObject
126126
$this->orderCancelService->cancelOrder($order);
127127
}
128128

129-
if ($refundOrderDTO->notify_buyer) {
130-
$this->notifyBuyer($order, $event, $amount);
131-
}
132-
133129
$this->refundService->refundPayment(
134130
amount: $amount,
135131
payment: $order->getStripePayment()
136132
);
137133

134+
if ($refundOrderDTO->notify_buyer) {
135+
$this->notifyBuyer($order, $event, $amount);
136+
}
137+
138138
return $this->markOrderRefundPending($order);
139139
}
140140
}

backend/app/Services/Domain/Payment/Stripe/EventHandlers/ChargeRefundUpdatedHandler.php

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,14 @@
1515
use Stripe\Refund;
1616
use Throwable;
1717

18-
readonly class ChargeRefundUpdatedHandler
18+
class ChargeRefundUpdatedHandler
1919
{
2020
public function __construct(
21-
private OrderRepositoryInterface $orderRepository,
22-
private StripePaymentsRepositoryInterface $stripePaymentsRepository,
23-
private Logger $logger,
24-
private DatabaseManager $databaseManager,
25-
private EventStatisticsUpdateService $eventStatisticsUpdateService,
26-
21+
private readonly OrderRepositoryInterface $orderRepository,
22+
private readonly StripePaymentsRepositoryInterface $stripePaymentsRepository,
23+
private readonly Logger $logger,
24+
private readonly DatabaseManager $databaseManager,
25+
private readonly EventStatisticsUpdateService $eventStatisticsUpdateService,
2726
)
2827
{
2928
}
@@ -54,6 +53,13 @@ public function handleEvent(Refund $refund): void
5453
$this->updateOrderRefundedAmount($order->getId(), $refundedAmount);
5554
$this->updateOrderStatus($order, $refundedAmount);
5655
$this->updateEventStatistics($order, MoneyValue::fromMinorUnit($refund->amount, $order->getCurrency()));
56+
57+
$this->logger->info(__('Stripe refund successful'), [
58+
'order_id' => $order->getId(),
59+
'refunded_amount' => $refundedAmount,
60+
'currency' => $order->getCurrency(),
61+
'refund_id' => $refund->id,
62+
]);
5763
});
5864
}
5965

backend/app/Services/Domain/Payment/Stripe/StripePaymentIntentRefundService.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public function refundPayment(
3535
'payment_intent' => $payment->getPaymentIntentId(),
3636
'amount' => $amount->toMinorUnit()
3737
],
38-
opts: $this->getStripeAccountData($payment));
38+
opts: $this->getStripeAccountData($payment),
39+
);
3940
}
4041

4142
private function getStripeAccountData(StripePaymentDomainObject $payment): array
@@ -47,7 +48,9 @@ private function getStripeAccountData(StripePaymentDomainObject $payment): array
4748
);
4849
}
4950

50-
return ['stripe_account' => $payment->getConnectedAccountId()];
51+
return [
52+
'stripe_account' => $payment->getConnectedAccountId(),
53+
];
5154
}
5255

5356
return [];

backend/resources/views/emails/auth/reset-password-success.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
{{ __('Your password has been reset for your account on :appName.', ['appName' => config('app.name')]) }}
55

6-
{{ __('If you did not request a password reset, please immediately contact reset your password.') }}
6+
{{ __('If you did not request a password reset, please immediately reset your password.') }}
77

88
{{ __('Thank you') }}
99
</x-mail::message>

backend/vapor.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@ id: 68983
22
name: HiEvents
33
environments:
44
production:
5+
gateway-version: 2.0
56
domain: api.hi.events
67
memory: 1024
78
cli-memory: 512
89
runtime: 'php-8.3:al2'
9-
warm: 10
10+
warm: 3
1011
cache: hievents-redis-prod
1112
database: hievents-db-prod
1213
queue: hievents-queue-prod
1314
queue-memory: 1024
1415
queue-concurrency: 5
16+
concurrency: 100
1517
build:
1618
- 'composer install --no-dev'
1719
- 'php artisan optimize'

frontend/src/components/common/OrdersTable/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,10 @@ export const OrdersTable = ({orders, event}: OrdersTableProps) => {
132132
};
133133

134134
const ActionMenu = ({order}: { order: Order }) => {
135-
136135
const isRefundable = !order.is_free_order
137136
&& order.status !== 'AWAITING_OFFLINE_PAYMENT'
138-
&& order.payment_provider === 'STRIPE';
137+
&& order.payment_provider === 'STRIPE'
138+
&& order.refund_status !== 'REFUNDED';
139139

140140
return <Group wrap={'nowrap'} gap={0} justify={'flex-end'}>
141141
<Menu shadow="md" width={200}>

misc/k6/event-hompage-load-test.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@ import { check, sleep } from 'k6';
33

44
export let options = {
55
vus: 50,
6-
duration: '60',
6+
duration: '60s', // Using '60s' instead of '60000' for clarity
77
};
88

99
export default function () {
10-
let res = http.get('https://demo.hi.events/event/1/dog-conf-2030');
10+
let res = http.get('https://api.hi.events/public/events/1', {
11+
headers: {
12+
'Accept': 'application/json',
13+
},
14+
});
1115

12-
// Check if the response status is 200 (OK)
1316
check(res, {
1417
'status is 200': (r) => r.status === 200,
15-
'response time < 200ms': (r) => r.timings.duration < 400,
18+
'response time < 400ms': (r) => r.timings.duration < 400,
19+
'response is JSON': (r) => r.headers['Content-Type'] && r.headers['Content-Type'].includes('application/json'),
1620
});
1721

18-
// Add a delay between requests
1922
sleep(1);
2023
}

0 commit comments

Comments
 (0)