1, 'country_id' => 1]); $shopOwner = User::forceCreate([ 'email' => 'inv-owner-'.uniqid('', true).'@example.com', 'password' => bcrypt('secret'), 'lang' => 'de', ]); $userShop = UserShop::first() ?? UserShop::create([ 'user_id' => $shopOwner->id, 'name' => 'Invoice Shop', 'slug' => 'inv-shop-'.uniqid(), 'domain' => 'inv-test.local', ]); $shoppingUser = ShoppingUser::create([ 'auth_user_id' => $shopOwner->id, 'billing_firstname' => 'Rechnung', 'billing_lastname' => 'Kunde', 'billing_email' => 'inv-'.uniqid().'@example.com', 'billing_country_id' => $country->id, 'shipping_country_id' => $country->id, ]); $order = ShoppingOrder::create([ 'shopping_user_id' => $shoppingUser->id, 'auth_user_id' => $shopOwner->id, 'country_id' => $country->id, 'user_shop_id' => $userShop->id, 'total' => 89.00, 'paid' => true, 'txaction' => 'paid', 'mode' => 'test', ]); $payment = ShoppingPayment::create(array_merge([ 'shopping_order_id' => $order->id, 'payment_provider' => 'stripe', 'clearingtype' => 'stripe', 'reference' => 'inv-ref-'.uniqid(), 'external_reference' => 'pi_invoice_'.uniqid(), 'amount' => 8900, 'currency' => 'EUR', 'mode' => 'test', ], $paymentOverrides)); $invoice = UserInvoice::create([ 'shopping_order_id' => $order->id, 'year' => 2026, 'month' => 8, 'date' => '26.08.2026', 'full_number' => '202657900', 'number' => 57900, 'filename' => '202657900-MIVITA-Rechnung.pdf', 'dir' => 'invoices/2026/08/', 'disk' => 'public', ]); return [$order->fresh(), $payment, $invoice]; } /** * @param array $existingMetadata * @return array{0: StripeInvoiceReferenceService, 1: callable} */ function invoiceRefService(array $existingMetadata = []): array { $captured = []; $client = Mockery::mock(StripeClient::class.'[request]', [['api_key' => 'sk_test_fake']]); $client->shouldReceive('request') ->andReturnUsing(function ($method, $path, $params) use (&$captured, $existingMetadata) { if ($method === 'post' && $params !== []) { $captured = $params; } return PaymentIntent::constructFrom([ 'id' => 'pi_invoice_x', 'object' => 'payment_intent', 'status' => 'succeeded', 'metadata' => $existingMetadata, ]); }); $wrapper = Mockery::mock(StripeClientWrapper::class); $wrapper->shouldReceive('isConfigured')->andReturn(true); $wrapper->shouldReceive('client')->andReturn($client); return [new StripeInvoiceReferenceService($wrapper), function () use (&$captured) { return $captured; }]; } it('writes invoice number and date into the payment intent metadata', function () { [, $payment, $invoice] = invoiceRefBootstrap(); [$service, $captured] = invoiceRefService([ 'shopping_order_id' => '4711', 'reference' => 'mref123', ]); expect($service->sync($payment, $invoice))->toBeTrue(); $params = $captured(); expect($params['metadata']['invoice_number'])->toBe('202657900') ->and($params['metadata']['invoice_date'])->toBe('2026-08-26') ->and($params['metadata']['shopping_order_id'])->toBe('4711') ->and($params['description'])->toContain('Rechnung 202657900'); }); it('does nothing when the invoice number is already set', function () { [, $payment, $invoice] = invoiceRefBootstrap(); [$service, $captured] = invoiceRefService(['invoice_number' => '202657900']); expect($service->sync($payment, $invoice))->toBeFalse() ->and($captured())->toBe([]); }); it('skips payments that are not stripe or have no payment intent yet', function () { [, $payonePayment, $invoice] = invoiceRefBootstrap(['payment_provider' => 'payone', 'external_reference' => null]); [$service] = invoiceRefService(); expect($service->sync($payonePayment, $invoice))->toBeFalse(); [, $sessionOnly] = invoiceRefBootstrap(['external_reference' => 'cs_test_still_open']); expect(StripeInvoiceReferenceService::isSyncable($sessionOnly))->toBeFalse(); }); it('dispatches the sync job from the paid path for stripe payments', function () { Bus::fake(); [$order, $payment] = invoiceRefBootstrap(); Payment::paymentStatusPaidAction($order, true, $payment); Bus::assertDispatched(SyncStripeInvoiceReference::class, function ($job) use ($payment) { return $job->shoppingPayment->id === $payment->id && $job->userInvoice->full_number === '202657900'; }); }); it('does not dispatch the sync job for payone payments', function () { Bus::fake(); [$order, $payment] = invoiceRefBootstrap([ 'payment_provider' => 'payone', 'clearingtype' => 'vor', 'external_reference' => null, ]); Payment::paymentStatusPaidAction($order, true, $payment); Bus::assertNotDispatched(SyncStripeInvoiceReference::class); });