feat(i18n,stripe): de/pl translations + order address snapshots from Checkout Session
- lang/de and lang/pl for cart.php and auth.php (informal register), every key of lang/en translated; README documents the `shop::` namespace and the lang/vendor/shop/<locale>/ override path. - StripeWebhookController::handleCheckoutSessionCompleted now persists the session's customer_details as Order.billing_address and shipping_details as Order.shipping_address (falls back to billing when Stripe sent no shipping block). Snapshot shape: name, email, phone, line1, line2, postal_code, city, state, country, uid (first tax id). Only empty columns are filled; a session without either block leaves the order untouched. Optional host-added customer_email/customer_name columns are filled when present and empty. Accepts Stripe objects, stdClass and arrays. - Controller formatted with pint (composer lint). Tests: tests/Unit/Stripe/StripeWebhookAddressTest.php (5), tests/Feature/ShopServiceProvider/TranslationsTest.php (4). Full suite green (1477, 7 skipped). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
68d02b01fc
commit
b4125c1ee1
14
README.md
14
README.md
|
|
@ -184,6 +184,20 @@ $room = Product::create([
|
|||
$isAvailable = $room->availableOnDate(now(), now()->addHour());
|
||||
```
|
||||
|
||||
## Translations
|
||||
|
||||
The package ships its user-facing messages under the `shop` translation
|
||||
namespace (`lang/{en,de,pl}/cart.php` and `auth.php`, registered by
|
||||
`ShopServiceProvider` via `loadTranslationsFrom(..., 'shop')`). Use them with
|
||||
`__('shop::cart.added')`; the active `app()->getLocale()` picks the language
|
||||
(`en`, `de`, `pl` shipped; other locales fall back to `en`).
|
||||
|
||||
To override or extend a message, drop a file at
|
||||
`lang/vendor/shop/<locale>/<file>.php` in your application, e.g.
|
||||
`lang/vendor/shop/de/cart.php` returning only the keys you want to change.
|
||||
Laravel merges vendor overrides on top of the package defaults, so untouched
|
||||
keys keep their shipped text.
|
||||
|
||||
## Testing
|
||||
|
||||
We test this package for many edge cases across every surface — products,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'invalid_credentials' => 'Ungültige Zugangsdaten.',
|
||||
'login_ok' => 'Du bist angemeldet.',
|
||||
];
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'session_updated' => 'Warenkorb aktualisiert.',
|
||||
'not_found' => 'Dieser Artikel wurde nicht gefunden.',
|
||||
'item_not_found' => 'Dieser Warenkorb-Artikel wurde nicht gefunden.',
|
||||
'out_of_stock' => 'Nicht genügend Bestand verfügbar.',
|
||||
'add_failed' => 'Der Artikel konnte nicht in deinen Warenkorb gelegt werden.',
|
||||
'added' => 'In den Warenkorb gelegt.',
|
||||
'removed' => 'Aus dem Warenkorb entfernt.',
|
||||
'converted' => 'Dieser Warenkorb wurde bereits bestellt.',
|
||||
'checkout_failed' => 'Der Checkout konnte nicht gestartet werden.',
|
||||
'checkout_unavailable' => 'Der Checkout ist derzeit nicht verfügbar.',
|
||||
'checkout_created' => 'Du wirst zur Zahlung weitergeleitet.',
|
||||
];
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'invalid_credentials' => 'Nieprawidłowe dane logowania.',
|
||||
'login_ok' => 'Jesteś zalogowany.',
|
||||
];
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'session_updated' => 'Koszyk zaktualizowany.',
|
||||
'not_found' => 'Nie znaleziono tego produktu.',
|
||||
'item_not_found' => 'Nie znaleziono tej pozycji w koszyku.',
|
||||
'out_of_stock' => 'Brak wystarczającej ilości na stanie.',
|
||||
'add_failed' => 'Nie udało się dodać produktu do twojego koszyka.',
|
||||
'added' => 'Dodano do koszyka.',
|
||||
'removed' => 'Usunięto z koszyka.',
|
||||
'converted' => 'Ten koszyk został już zamówiony.',
|
||||
'checkout_failed' => 'Nie udało się rozpocząć płatności.',
|
||||
'checkout_unavailable' => 'Płatność jest obecnie niedostępna.',
|
||||
'checkout_created' => 'Przekierowujemy do płatności.',
|
||||
];
|
||||
|
|
@ -10,6 +10,7 @@ use Blax\Shop\Enums\PurchaseStatus;
|
|||
use Blax\Shop\Models\Cart;
|
||||
use Blax\Shop\Models\Order;
|
||||
use Blax\Shop\Models\OrderNote;
|
||||
use Blax\Shop\Models\Product;
|
||||
use Blax\Shop\Models\ProductPurchase;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
|
@ -50,9 +51,11 @@ class StripeWebhookController
|
|||
}
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
Log::error('Stripe webhook invalid payload', ['error' => $e->getMessage()]);
|
||||
|
||||
return response()->json(['error' => 'Invalid payload'], 400);
|
||||
} catch (SignatureVerificationException $e) {
|
||||
Log::error('Stripe webhook signature verification failed', ['error' => $e->getMessage()]);
|
||||
|
||||
return response()->json(['error' => 'Invalid signature'], 400);
|
||||
}
|
||||
|
||||
|
|
@ -106,6 +109,7 @@ class StripeWebhookController
|
|||
protected function handleUnknownEvent(string $type): bool
|
||||
{
|
||||
Log::info('Stripe webhook unhandled event type', ['type' => $type]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -118,12 +122,14 @@ class StripeWebhookController
|
|||
|
||||
if (! $cartId) {
|
||||
Log::warning('Stripe checkout session completed without cart ID', ['session_id' => $session->id]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$cart = Cart::find($cartId);
|
||||
if (! $cart) {
|
||||
Log::warning('Stripe checkout session for non-existent cart', ['cart_id' => $cartId]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +163,10 @@ class StripeWebhookController
|
|||
]);
|
||||
}
|
||||
|
||||
// Persist the addresses Stripe collected during checkout so the
|
||||
// order carries a buyer snapshot (invoicing, shipping labels, ...).
|
||||
$this->persistSessionAddresses($order, $session);
|
||||
|
||||
// Record payment on the order
|
||||
// Stripe provides amounts in cents, which matches our storage format
|
||||
$amountPaid = (int) ($session->amount_total ?? 0);
|
||||
|
|
@ -167,7 +177,7 @@ class StripeWebhookController
|
|||
|
||||
// Add a detailed note (customer-visible)
|
||||
$order->addNote(
|
||||
"Payment of " . Order::formatMoney($amountPaid, $currency) . " received",
|
||||
'Payment of '.Order::formatMoney($amountPaid, $currency).' received',
|
||||
OrderNote::TYPE_PAYMENT,
|
||||
true
|
||||
);
|
||||
|
|
@ -187,6 +197,111 @@ class StripeWebhookController
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the Checkout Session's customer_details / shipping_details onto the
|
||||
* order as address snapshots. Only empty columns are filled so an address
|
||||
* the host application set beforehand is never overwritten. Null-safe:
|
||||
* a session without either block leaves the order untouched.
|
||||
*
|
||||
* Snapshot shape (both columns): name, email, phone, line1, line2,
|
||||
* postal_code, city, state, country, uid.
|
||||
*/
|
||||
protected function persistSessionAddresses(Order $order, $session): void
|
||||
{
|
||||
$customer = data_get($session, 'customer_details');
|
||||
$shipping = data_get($session, 'shipping_details');
|
||||
|
||||
$billing = $this->addressSnapshotFromStripe($customer);
|
||||
$shipped = $this->addressSnapshotFromStripe($shipping, $customer) ?? $billing;
|
||||
|
||||
$updates = [];
|
||||
|
||||
if ($billing && empty((array) $order->billing_address)) {
|
||||
$updates['billing_address'] = $billing;
|
||||
}
|
||||
|
||||
if ($shipped && empty((array) $order->shipping_address)) {
|
||||
$updates['shipping_address'] = $shipped;
|
||||
}
|
||||
|
||||
// Optional customer columns (not part of the shipped schema, but a host
|
||||
// may add them): fill only when present and still empty.
|
||||
$email = data_get($customer, 'email');
|
||||
$name = data_get($customer, 'name');
|
||||
foreach (['customer_email' => $email, 'customer_name' => $name] as $column => $value) {
|
||||
if ($value === null || $value === '') {
|
||||
continue;
|
||||
}
|
||||
if (! $this->orderHasColumn($order, $column)) {
|
||||
continue;
|
||||
}
|
||||
if (empty($order->getAttribute($column))) {
|
||||
$updates[$column] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($updates === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order->forceFill($updates)->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the JSON address snapshot from a Stripe details block
|
||||
* (customer_details or shipping_details). Contact fields missing on the
|
||||
* block (shipping_details carries no email/phone) are taken from
|
||||
* $contactFallback. Returns null when the block is empty.
|
||||
*/
|
||||
protected function addressSnapshotFromStripe($details, $contactFallback = null): ?array
|
||||
{
|
||||
if (! $details) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$address = data_get($details, 'address');
|
||||
$taxIds = data_get($details, 'tax_ids');
|
||||
$uid = null;
|
||||
if (is_iterable($taxIds)) {
|
||||
foreach ($taxIds as $taxId) {
|
||||
$uid = data_get($taxId, 'value');
|
||||
if ($uid) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$snapshot = [
|
||||
'name' => data_get($details, 'name'),
|
||||
'email' => data_get($details, 'email') ?? data_get($contactFallback, 'email'),
|
||||
'phone' => data_get($details, 'phone') ?? data_get($contactFallback, 'phone'),
|
||||
'line1' => data_get($address, 'line1'),
|
||||
'line2' => data_get($address, 'line2'),
|
||||
'postal_code' => data_get($address, 'postal_code'),
|
||||
'city' => data_get($address, 'city'),
|
||||
'state' => data_get($address, 'state'),
|
||||
'country' => data_get($address, 'country'),
|
||||
'uid' => $uid,
|
||||
];
|
||||
|
||||
// A block with no usable data at all is treated as absent.
|
||||
$hasData = array_filter($snapshot, fn ($v) => $v !== null && $v !== '');
|
||||
|
||||
return $hasData === [] ? null : $snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the orders table has an (optional, host-added) column.
|
||||
*/
|
||||
protected function orderHasColumn(Order $order, string $column): bool
|
||||
{
|
||||
try {
|
||||
return $order->getConnection()->getSchemaBuilder()->hasColumn($order->getTable(), $column);
|
||||
} catch (\Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle checkout.session failed event
|
||||
*/
|
||||
|
|
@ -196,6 +311,7 @@ class StripeWebhookController
|
|||
|
||||
if (! $cartId) {
|
||||
Log::warning('Stripe checkout session failed without cart ID', ['session_id' => $session->id]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -539,7 +655,7 @@ class StripeWebhookController
|
|||
if ($order) {
|
||||
$amountPaid = ($invoice->amount_paid ?? 0) / 100;
|
||||
$order->addNote(
|
||||
"Subscription invoice paid: " . Order::formatMoney($amountPaid, $order->currency) . " (Invoice: {$invoice->id})",
|
||||
'Subscription invoice paid: '.Order::formatMoney($amountPaid, $order->currency)." (Invoice: {$invoice->id})",
|
||||
OrderNote::TYPE_PAYMENT
|
||||
);
|
||||
}
|
||||
|
|
@ -666,7 +782,7 @@ class StripeWebhookController
|
|||
protected function claimStockForPurchase(ProductPurchase $purchase)
|
||||
{
|
||||
$product = $purchase->purchasable;
|
||||
if (!($product instanceof \Blax\Shop\Models\Product)) {
|
||||
if (! ($product instanceof Product)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Blax\Shop\Tests\Feature\ShopServiceProvider;
|
||||
|
||||
use Blax\Shop\Tests\TestCase;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
|
||||
class TranslationsTest extends TestCase
|
||||
{
|
||||
protected function langDir(): string
|
||||
{
|
||||
return dirname(__DIR__, 3).'/lang';
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shop_namespace_is_registered()
|
||||
{
|
||||
$this->assertEquals('Added to cart.', __('shop::cart.added'));
|
||||
$this->assertEquals('Signed in.', __('shop::auth.login_ok'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function de_and_pl_translate_every_english_key()
|
||||
{
|
||||
foreach (['cart', 'auth'] as $file) {
|
||||
$en = require $this->langDir()."/en/{$file}.php";
|
||||
|
||||
foreach (['de', 'pl'] as $locale) {
|
||||
$path = $this->langDir()."/{$locale}/{$file}.php";
|
||||
$this->assertFileExists($path);
|
||||
$translated = require $path;
|
||||
|
||||
$this->assertEqualsCanonicalizing(
|
||||
array_keys($en),
|
||||
array_keys($translated),
|
||||
"lang/{$locale}/{$file}.php must carry exactly the keys of lang/en/{$file}.php"
|
||||
);
|
||||
|
||||
foreach ($en as $key => $english) {
|
||||
$this->assertNotSame('', trim((string) $translated[$key]), "{$locale}: {$file}.{$key} is empty");
|
||||
$this->assertNotSame($english, $translated[$key], "{$locale}: {$file}.{$key} is still English");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function locale_switch_resolves_de_and_pl_messages()
|
||||
{
|
||||
app()->setLocale('de');
|
||||
$this->assertEquals('In den Warenkorb gelegt.', __('shop::cart.added'));
|
||||
|
||||
app()->setLocale('pl');
|
||||
$this->assertEquals('Dodano do koszyka.', __('shop::cart.added'));
|
||||
|
||||
app()->setLocale('fr');
|
||||
$this->assertEquals('Added to cart.', __('shop::cart.added'), 'unknown locales fall back to en');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function apps_can_override_via_lang_vendor_shop()
|
||||
{
|
||||
$dir = $this->app->langPath('vendor/shop/de');
|
||||
@mkdir($dir, 0777, true);
|
||||
file_put_contents($dir.'/cart.php', "<?php\nreturn ['added' => 'Liegt im Korb.'];\n");
|
||||
|
||||
try {
|
||||
app()->setLocale('de');
|
||||
app('translator')->setLoaded([]);
|
||||
|
||||
$this->assertEquals('Liegt im Korb.', __('shop::cart.added'));
|
||||
$this->assertEquals('Aus dem Warenkorb entfernt.', __('shop::cart.removed'), 'untouched keys keep the package text');
|
||||
} finally {
|
||||
@unlink($dir.'/cart.php');
|
||||
@rmdir($dir);
|
||||
@rmdir(dirname($dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
<?php
|
||||
|
||||
namespace Blax\Shop\Tests\Unit;
|
||||
|
||||
use Blax\Shop\Http\Controllers\StripeWebhookController;
|
||||
use Blax\Shop\Models\Order;
|
||||
use Blax\Shop\Models\Product;
|
||||
use Blax\Shop\Tests\TestCase;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use ReflectionClass;
|
||||
use Workbench\App\Models\User;
|
||||
|
||||
/**
|
||||
* checkout.session.completed → Order.billing_address / shipping_address
|
||||
* snapshots from the session's customer_details / shipping_details.
|
||||
*/
|
||||
class StripeWebhookAddressTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected StripeWebhookController $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
config(['shop.stripe.enabled' => true]);
|
||||
$this->controller = new StripeWebhookController;
|
||||
}
|
||||
|
||||
protected function handle(object $session): bool
|
||||
{
|
||||
$method = (new ReflectionClass($this->controller))->getMethod('handleCheckoutSessionCompleted');
|
||||
$method->setAccessible(true);
|
||||
|
||||
return $method->invokeArgs($this->controller, [$session]);
|
||||
}
|
||||
|
||||
protected function mockSession(array $overrides = []): object
|
||||
{
|
||||
return (object) array_merge([
|
||||
'id' => 'cs_test_'.uniqid(),
|
||||
'payment_intent' => 'pi_test_'.uniqid(),
|
||||
'metadata' => (object) ['cart_id' => null],
|
||||
'client_reference_id' => null,
|
||||
'amount_total' => 10000,
|
||||
'currency' => 'eur',
|
||||
'payment_status' => 'paid',
|
||||
'customer' => 'cus_test_123',
|
||||
], $overrides);
|
||||
}
|
||||
|
||||
protected function product(int $price = 10000): Product
|
||||
{
|
||||
return Product::factory()->withPrices(unit_amount: $price)->create(['manage_stock' => false]);
|
||||
}
|
||||
|
||||
protected function stripeCustomerDetails(): object
|
||||
{
|
||||
return (object) [
|
||||
'email' => 'anna@example.at',
|
||||
'name' => 'Anna Beispiel',
|
||||
'phone' => '+43 660 1234567',
|
||||
'address' => (object) [
|
||||
'line1' => 'Hauptstraße 1',
|
||||
'line2' => 'Top 4',
|
||||
'postal_code' => '1010',
|
||||
'city' => 'Wien',
|
||||
'state' => 'Wien',
|
||||
'country' => 'AT',
|
||||
],
|
||||
'tax_ids' => [(object) ['type' => 'eu_vat', 'value' => 'ATU12345678']],
|
||||
];
|
||||
}
|
||||
|
||||
protected function stripeShippingDetails(): object
|
||||
{
|
||||
return (object) [
|
||||
'name' => 'Anna Beispiel (Büro)',
|
||||
'address' => (object) [
|
||||
'line1' => 'Lagergasse 9',
|
||||
'line2' => null,
|
||||
'postal_code' => '8010',
|
||||
'city' => 'Graz',
|
||||
'state' => 'Steiermark',
|
||||
'country' => 'AT',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function checkout_session_completed_persists_billing_and_shipping_snapshots()
|
||||
{
|
||||
$customer = User::factory()->create();
|
||||
$customer->addToCart($this->product());
|
||||
$cart = $customer->currentCart();
|
||||
|
||||
$session = $this->mockSession([
|
||||
'metadata' => (object) ['cart_id' => $cart->id],
|
||||
'customer_details' => $this->stripeCustomerDetails(),
|
||||
'shipping_details' => $this->stripeShippingDetails(),
|
||||
]);
|
||||
|
||||
$this->assertTrue($this->handle($session));
|
||||
|
||||
$order = $cart->fresh()->order;
|
||||
$this->assertNotNull($order);
|
||||
|
||||
$this->assertSame([
|
||||
'name' => 'Anna Beispiel',
|
||||
'email' => 'anna@example.at',
|
||||
'phone' => '+43 660 1234567',
|
||||
'line1' => 'Hauptstraße 1',
|
||||
'line2' => 'Top 4',
|
||||
'postal_code' => '1010',
|
||||
'city' => 'Wien',
|
||||
'state' => 'Wien',
|
||||
'country' => 'AT',
|
||||
'uid' => 'ATU12345678',
|
||||
], (array) $order->billing_address);
|
||||
|
||||
$this->assertSame([
|
||||
'name' => 'Anna Beispiel (Büro)',
|
||||
'email' => 'anna@example.at',
|
||||
'phone' => '+43 660 1234567',
|
||||
'line1' => 'Lagergasse 9',
|
||||
'line2' => null,
|
||||
'postal_code' => '8010',
|
||||
'city' => 'Graz',
|
||||
'state' => 'Steiermark',
|
||||
'country' => 'AT',
|
||||
'uid' => null,
|
||||
], (array) $order->shipping_address);
|
||||
|
||||
// Raw JSON in the DB carries the same shape
|
||||
$raw = json_decode(DB::table($order->getTable())->where('id', $order->id)->value('billing_address'), true);
|
||||
$this->assertSame('ATU12345678', $raw['uid']);
|
||||
$this->assertSame('Wien', $raw['city']);
|
||||
$this->assertSame(
|
||||
['name', 'email', 'phone', 'line1', 'line2', 'postal_code', 'city', 'state', 'country', 'uid'],
|
||||
array_keys($raw)
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shipping_falls_back_to_billing_when_session_has_no_shipping_details()
|
||||
{
|
||||
$customer = User::factory()->create();
|
||||
$customer->addToCart($this->product());
|
||||
$cart = $customer->currentCart();
|
||||
|
||||
$this->handle($this->mockSession([
|
||||
'metadata' => (object) ['cart_id' => $cart->id],
|
||||
'customer_details' => $this->stripeCustomerDetails(),
|
||||
'shipping_details' => null,
|
||||
]));
|
||||
|
||||
$order = $cart->fresh()->order;
|
||||
$this->assertEquals((array) $order->billing_address, (array) $order->shipping_address);
|
||||
$this->assertSame('Hauptstraße 1', $order->shipping_address->line1);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function session_without_customer_details_leaves_addresses_untouched()
|
||||
{
|
||||
$customer = User::factory()->create();
|
||||
$customer->addToCart($this->product());
|
||||
$cart = $customer->checkoutCart();
|
||||
$order = $cart->fresh()->order;
|
||||
|
||||
$this->assertTrue($this->handle($this->mockSession([
|
||||
'metadata' => (object) ['cart_id' => $cart->id],
|
||||
])));
|
||||
|
||||
$order->refresh();
|
||||
$this->assertNull($order->billing_address);
|
||||
$this->assertNull($order->shipping_address);
|
||||
$this->assertEquals(10000, $order->amount_paid);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function existing_addresses_are_not_overwritten()
|
||||
{
|
||||
$customer = User::factory()->create();
|
||||
$customer->addToCart($this->product());
|
||||
$cart = $customer->checkoutCart();
|
||||
$order = $cart->fresh()->order;
|
||||
$order->update(['billing_address' => ['name' => 'Preset', 'line1' => 'Kept 1']]);
|
||||
|
||||
$this->handle($this->mockSession([
|
||||
'metadata' => (object) ['cart_id' => $cart->id],
|
||||
'customer_details' => $this->stripeCustomerDetails(),
|
||||
]));
|
||||
|
||||
$order->refresh();
|
||||
$this->assertSame('Preset', $order->billing_address->name);
|
||||
// shipping was empty, so it is filled from the session
|
||||
$this->assertSame('Anna Beispiel', $order->shipping_address->name);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function array_shaped_session_details_are_accepted()
|
||||
{
|
||||
$customer = User::factory()->create();
|
||||
$customer->addToCart($this->product());
|
||||
$cart = $customer->currentCart();
|
||||
|
||||
$this->handle($this->mockSession([
|
||||
'metadata' => (object) ['cart_id' => $cart->id],
|
||||
'customer_details' => [
|
||||
'email' => 'jan@example.pl',
|
||||
'name' => 'Jan Kowalski',
|
||||
'phone' => null,
|
||||
'address' => ['line1' => 'ul. Prosta 5', 'postal_code' => '00-001', 'city' => 'Warszawa', 'country' => 'PL'],
|
||||
],
|
||||
]));
|
||||
|
||||
$order = $cart->fresh()->order;
|
||||
$this->assertSame('Jan Kowalski', $order->billing_address->name);
|
||||
$this->assertSame('PL', $order->billing_address->country);
|
||||
$this->assertNull($order->billing_address->line2);
|
||||
$this->assertNull($order->billing_address->uid);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue