checkoutRepo = $checkoutRepository; } /** * Zeigt die Checkout-Seite an * * @return \Illuminate\View\View */ public function checkout() { /* @if(Auth::guard('customers')->check()) {{ __('navigation.logout') }} @else {{ __('website.to_customer_portal') }} @endif @if(Auth::guard('user')->check()) */ $shopping_data = Yard::instance($this->instance)->getYardExtra('shopping_data'); $is_from = $shopping_data['is_from'] ?? 'shopping'; $is_for = $shopping_data['is_for'] ?? false; $is_abo = isset($shopping_data['is_abo']) ? (bool) $shopping_data['is_abo'] : false; $abo_interval = $shopping_data['abo_interval'] ?? 0; $homeparty_id = $shopping_data['homeparty_id'] ?? null; $shopping_user = null; if ($is_for === 'ot-customer' || $is_for === 'abo-ot-customer') { $is_from = 'shopping'; } Util::setInstanceStatus(1, true); // link_check if ($is_abo) { $instance_status = Util::getInstanceStatus(); if ($instance_status === 'link_paid') { return $this->redirectToIsFinal($instance_status); } } if (Session::has('new_session')) { $this->checkoutRepo->sessionDestroy(); Session::forget('new_session'); } $shopping_user = $this->initializeShoppingUserSession($is_from, $is_for, $shopping_data, $homeparty_id); $this->prepareShoppingUserData($shopping_user); $payment_methods = $this->checkoutRepo->getPaymentsMethods($is_from, $is_abo); if ($shopping_user === null) { abort(403, 'ShoppingUser not found'); } $data = [ 'is_from' => $is_from, 'is_for' => $is_for, 'is_abo' => $is_abo, 'abo_interval' => $abo_interval, 'shopping_data' => $shopping_data, 'user_shop' => Util::getUserShop(), 'shopping_user' => $shopping_user, 'shopping_mode' => Util::getUserShoppingMode(), 'payment_methods' => $payment_methods['default'], 'payment_methods_active' => $payment_methods['active'], 'payment_data' => $payment_methods['data'], 'instance_status' => $instance_status ?? false, 'is_checkout' => true, 'yard_instance' => $this->instance, ]; CheckoutFunnelTracker::visitedCheckout( consultantUserId: Util::getUserShop()?->id ?? null, metadata: ['is_from' => $is_from, 'is_for' => $is_for, 'is_abo' => $is_abo], ); return view('web.templates.checkout', $data); } /** * Bereitet die ShoppingUser-Daten vor * * @return void */ private function prepareShoppingUserData(ShoppingUser $shopping_user) { if ($shopping_user->same_as_billing === null) { $shopping_user->same_as_billing = false; } if (! $shopping_user->billing_country_id) { $shopping_user->billing_country_id = Yard::instance($this->instance)->getUserCountryId(); // Die Zeile unten entfernen, da die Relation automatisch geladen wird // $shopping_user->billing_country = Yard::instance($this->instance)->getUserCountry(); } if (! $shopping_user->shipping_country_id) { $shopping_user->shipping_country_id = Yard::instance($this->instance)->getUserCountryId(); // Die Zeile unten entfernen, da die Relation automatisch geladen wird // $shopping_user->shipping_country = Yard::instance($this->instance)->getUserCountry(); } if (old('selected_country') && old('selected_country') === 'change') { Session::forget('_old_input.selected_country'); $shopping_user->billing_state = old('billing_state'); $shopping_user->shipping_state = old('shipping_state'); } else { $shopping_user->billing_state = Yard::instance($this->instance)->getShippingCountryId(); $shopping_user->shipping_state = Yard::instance($this->instance)->getShippingCountryId(); } } /** * Verarbeitet den Checkout-Prozess * * @return \Illuminate\Http\RedirectResponse */ public function checkoutFinal() { $data = Request::all(); if (isset($data['payment_method'])) { $this->checkoutRepo->isPaymentsMethodsActive($data['payment_method'], $data['is_from'], $data['is_abo']); } Util::setInstanceStatus(2, true); // link_check // Länderwechsel verarbeiten if (isset($data['selected_country']) && $data['selected_country'] === 'change') { return $this->handleCountryChange($data); } // Validierung $validator = $this->validateCheckoutData(); if ($validator->fails()) { return back()->withErrors($validator)->withInput(Request::all()); } // Benutzer und Bestellung erstellen $shopping_user = $this->checkoutRepo->makeShoppingUser($data); $shopping_order = $this->checkoutRepo->makeShoppingOrder($shopping_user, $data); // CustomerPriority prüfen if ($shopping_user->is_from === 'shopping') { CustomerPriority::checkOne(ShoppingUser::find($shopping_user->id), true); } Util::setUserHistoryValue(['status' => 2, 'shopping_order_id' => $shopping_order->id]); CheckoutFunnelTracker::submittedForm( shoppingUserId: $shopping_user->id, shoppingOrderId: $shopping_order->id, consultantUserId: $shopping_user->auth_user_id ?? null, paymentMethod: Request::get('payment_method'), amountCents: (int) (Yard::instance($this->instance)->totalWithShipping(2, '.', '') * 100), ); // Zahlungsmethode verarbeiten if (Request::get('payment_method')) { return $this->processPaymentMethod($data, $shopping_user, $shopping_order); } return redirect()->back(); } /** * Verarbeitet den Länderwechsel * * @param array $data * @return \Illuminate\Http\RedirectResponse */ private function handleCountryChange($data) { if (! Request::get('same_as_billing')) { Yard::instance($this->instance)->setShippingCountryWithPrice($data['billing_state'], $data['is_for']); } else { Yard::instance($this->instance)->setShippingCountryWithPrice($data['shipping_state'], $data['is_for']); } return back()->withInput(Request::all()); } /** * Validiert die Checkout-Daten * * @return \Illuminate\Validation\Validator */ private function validateCheckoutData() { $rules = [ 'billing_salutation' => 'required', 'billing_firstname' => 'required', 'billing_lastname' => 'required', 'billing_email' => 'required|email', 'billing_address' => 'required', 'billing_zipcode' => 'required', 'billing_city' => 'required', 'accepted_data_checkbox' => 'accepted', ]; if (Request::get('same_as_billing')) { $rules = array_merge($rules, [ 'shipping_firstname' => 'required', 'shipping_lastname' => 'required', 'shipping_address' => 'required', 'shipping_zipcode' => 'required', 'shipping_city' => 'required', 'shipping_salutation' => 'required', ]); } return Validator::make(Request::all(), $rules); } /** * Verarbeitet die Zahlungsmethode * * @param array $data * @param ShoppingUser $shopping_user * @param ShoppingOrder $shopping_order * @return mixed */ private function processPaymentMethod($data, $shopping_user, $shopping_order) { $result = []; $payment_method = Request::get('payment_method'); // Kreditkarte prüfen if ($payment_method === 'cc') { $result = $this->checkCreditCard($data, $shopping_user, $shopping_order); if (! is_array($result) || ! isset($result['returnstatus']) || $result['returnstatus'] !== 'VALID') { return $result; } } // SEPA prüfen if ($payment_method === 'elv') { $result = $this->checkSepaAccount($data, $shopping_user, $shopping_order); if (! is_array($result) || ! isset($result['returnstatus']) || $result['returnstatus'] !== 'VALID') { return $result; } } // Zahlung vorbereiten $pay = new PayoneController; $pay->init($shopping_user, $shopping_order); $amount = Yard::instance($this->instance)->totalWithShipping(2, '.', '') * 100; $reference = $pay->setPrePayment($payment_method, $amount, 'EUR', $result); $this->checkoutRepo->putSessionPayments('payment_reference', $reference); $pay->setPersonalData(); return $pay->ResponseData(); } /** * Prüft die Kreditkartendaten * * @param array $data * @param ShoppingUser $shopping_user * @param ShoppingOrder $shopping_order * @return bool|\Illuminate\Http\RedirectResponse */ private function checkCreditCard($data, $shopping_user, $shopping_order) { $pay = new PayoneController; $pay->init($shopping_user, $shopping_order); $ret['cc'] = $pay->checkCreditCard($data); if ($ret['cc']['status'] === 'ERROR' || $ret['cc']['status'] === 'INVALID') { Session::flash('cc-error', 1); Session::flash('errormessage', $ret['cc']['errormessage']); Session::flash('customermessage', $ret['cc']['customermessage']); return redirect(route('checkout.checkout_card'))->withInput(Request::all()); } $ret['returnstatus'] = 'VALID'; return $ret; } /** * Prüft die SEPA-Kontodaten * * @param array $data * @param ShoppingUser $shopping_user * @param ShoppingOrder $shopping_order * @return bool|\Illuminate\Http\RedirectResponse */ private function checkSepaAccount($data, $shopping_user, $shopping_order) { if (is_null(Request::get('mandate_identification'))) { $pay = new PayoneController; $pay->init($shopping_user, $shopping_order); $amount = Yard::instance($this->instance)->totalWithShipping(2, '.', '') * 100; $ret['elv'] = $pay->checkBankAccount($data, $amount, 'EUR', $shopping_user); if ($ret['elv']['status'] === 'ERROR' || $ret['elv']['status'] === 'INVALID') { Session::flash('elv-error', 1); Session::flash('errormessage', $ret['elv']['errormessage']); Session::flash('customermessage', $ret['elv']['customermessage']); return redirect(route('checkout.checkout_card'))->withInput(Request::all()); } if ($ret['elv']['status'] === 'APPROVED' && $ret['elv']['mandate_status'] !== 'active') { Session::flash('elv-managemandate', 1); Session::flash('elv-mandate_identification', $ret['elv']['mandate_identification']); Session::flash('elv-mandate_text', $ret['elv']['mandate_text']); Session::flash('elv-creditor_identifier', $ret['elv']['creditor_identifier']); return redirect(route('checkout.checkout_card'))->withInput(Request::all()); } $ret['elv']['bankaccountholder'] = $data['elv_bankaccountholder']; } else { $ret['elv'] = [ 'mandate_identification' => Request::get('mandate_identification'), 'creditor_identifier' => Request::get('creditor_identifier'), 'iban' => $data['elv_iban'], 'bic' => $data['elv_bic'], 'bankaccountholder' => $data['elv_bankaccountholder'], ]; $this->storeUserPaymentsData($shopping_user, $ret); } $ret['returnstatus'] = 'VALID'; return $ret; } /** * Leitet zur Abschlussseite weiter * * @return \Illuminate\View\View */ public function redirectToIsFinal() { $data = [ 'user_shop' => Util::getUserShop(), 'is_checkout' => true, 'yard_instance' => $this->instance, ]; return view('web.templates.checkout-is-final', $data); } /** * Verarbeitet den Transaktionsstatus (POST-Anfragen) * Einige Zahlungsanbieter senden POST-Anfragen zurück * * @param string $status * @param string $reference * @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse */ public function transactionStatusPost($status, $reference) { return $this->transactionStatus($status, $reference); } /** * Verarbeitet den Transaktionsstatus * * @param string $status * @param string $reference * @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse */ public function transactionStatus($status, $reference) { // Suche ShoppingPayment nur über reference (nicht Session-abhängig) // Dies ist wichtig, da die Session bei Redirect-Zahlungen verloren gehen kann $ShoppingPayment = ShoppingPayment::where('reference', $reference)->first(); if (! $ShoppingPayment) { Util::setUserHistoryValue(['status' => 21]); \Log::warning('CheckoutController::transactionStatus - ShoppingPayment nicht gefunden', [ 'reference' => $reference, 'status' => $status, ]); // Zeige eine dedizierte Fehlerseite anstatt zur Hauptseite weiterzuleiten return $this->showTransactionError( __('payment.payment_not_found'), __('payment.payment_not_found_description', ['reference' => $reference]) ); } $ShoppingPayment->status = $status; $ShoppingPayment->save(); CheckoutFunnelTracker::returnedFromPayment( shoppingPaymentId: $ShoppingPayment->id, returnStatus: $status, ); if ($status === 'success') { return $this->handleSuccessfulTransaction($ShoppingPayment, $reference); } if ($status === 'cancel') { Util::setUserHistoryValue(['status' => 22]); Util::setInstanceStatusByPayment($ShoppingPayment, 6); // link_canceled return $this->showTransactionError( __('payment.payment_canceled'), __('payment.payment_canceled_description'), 'cancel', ['checkout_url' => route('checkout.checkout_card')], ); } if ($status === 'error') { Util::setUserHistoryValue(['status' => 23]); Util::setInstanceStatusByPayment($ShoppingPayment, 5); // link_failed $latestTransaction = $ShoppingPayment->payment_transactions() ->whereNotNull('errorcode') ->orWhere(fn ($q) => $q->whereNotNull('transmitted_data')) ->latest() ->first(); $errorcode = null; $errorDescription = null; if ($latestTransaction) { $errorcode = $latestTransaction->errorcode ?? ($latestTransaction->transmitted_data['errorcode'] ?? null); $errorDescription = $latestTransaction->error_description; } return $this->showTransactionError( __('payment.payment_error'), __('payment.payment_error_description'), 'error', [ 'errorcode' => $errorcode, 'error_description' => $errorDescription, 'checkout_url' => route('checkout.checkout_card'), ], ); } // Fallback für unbekannte Status return $this->showTransactionError( __('payment.payment_unknown_status'), __('payment.payment_unknown_status_description') ); } /** * Zeigt eine Transaktionsfehlerseite an * * @return \Illuminate\View\View */ /** * @param array $extra */ private function showTransactionError(string $title, string $message, string $type = 'error', array $extra = []) { $data = array_merge([ 'user_shop' => Util::getUserShop(), 'is_checkout' => true, 'yard_instance' => $this->instance, 'error_title' => $title, 'error_message' => $message, 'error_type' => $type, ], $extra); return view('web.templates.checkout-error', $data); } /** * Verarbeitet eine erfolgreiche Transaktion * * @param ShoppingPayment $ShoppingPayment * @param string $reference * @return \Illuminate\View\View */ private function handleSuccessfulTransaction($ShoppingPayment, $reference) { Yard::instance($this->instance)->destroy(); $this->checkoutRepo->sessionDestroy(true); Util::setInstanceStatus(3, true); // link_pending // Abo erstellen, falls nötig if ($ShoppingPayment->shopping_order->is_abo) { AboHelper::createNewAbo($ShoppingPayment); } $payt = $ShoppingPayment->payment_transactions->last(); $data = [ 'user_shop' => Util::getUserShop(), 'order_reference' => $reference, 'pay_trans' => $payt, 'is_checkout' => true, 'yard_instance' => $this->instance, ]; return view('web.templates.checkout-final', $data); } /** * Verarbeitet eine genehmigte Transaktion * * @param int $transactionId * @param string $reference * @return \Illuminate\View\View */ public function transactionApproved($transactionId, $reference) { $payt = PaymentTransaction::findOrFail($transactionId); if ($payt->shopping_payment->reference != $reference) { abort(404); } CheckoutFunnelTracker::confirmedPayment( shoppingPaymentId: $payt->shopping_payment_id, txaction: $payt->txaction ?? $payt->status ?? 'approved', ); Yard::instance($this->instance)->destroy(); $this->checkoutRepo->sessionDestroy(true); Util::setInstanceStatus(3, true); // link_pending // Abo erstellen, falls nötig if ($payt->shopping_payment->shopping_order->is_abo) { AboHelper::createNewAbo($payt->shopping_payment); } // Rechnung MIV if ($payt->status === 'FNCMIV') { $this->directPaymentStatus($payt); } $data = [ 'user_shop' => Util::getUserShop(), 'order_reference' => $payt->shopping_payment->reference, 'pay_trans' => $payt, 'is_checkout' => true, 'yard_instance' => $this->instance, ]; return view('web.templates.checkout-final', $data); } /** * Speichert die Zahlungsdaten des Benutzers * * @param ShoppingUser $shopping_user * @param array $ret * @return void */ private function storeUserPaymentsData($shopping_user, $ret) { if ($shopping_user->auth_user_id) { $user = User::find($shopping_user->auth_user_id); if ($user && $user->account) { if (isset($ret['elv']) && is_array($ret['elv'])) { $user->account->payment_data = $ret['elv']; $user->account->save(); } } } } /** * Verarbeitet den direkten Zahlungsstatus (Rechnung MIV) * * @return void */ private function directPaymentStatus(PaymentTransaction $payt) { if (isset($payt->transmitted_data['param'])) { $shopping_order = ShoppingOrder::find($payt->transmitted_data['param']); $shopping_order->txaction = 'invoice_open'; $shopping_order->save(); $shopping_payment = ShoppingPayment::where('reference', $payt->transmitted_data['reference'])->first(); if ($shopping_payment) { $shopping_payment->txaction = 'invoice_open'; $shopping_payment->save(); } $send_link = Payment::paymentStatusPaidAction($shopping_order, false, $shopping_payment); $data = [ 'mode' => $payt->transmitted_data['mode'], 'txaction' => $payt->txaction, 'send_link' => $send_link, ]; Payment::paymentStatusSendMail($shopping_order, $shopping_payment, $data); } } /** * Initialisiert oder ruft einen Shopping-Benutzer ab * * @param string|null $is_from = shopping | user_order | user_order_ot | user_order_abo | user_order_abo_ot | user_order_ot_customer | user_order_abo_ot_customer * @param string|null $is_for = me | ot | abo-me | abo-ot | ot-customer | abo-ot-customer * @param array|null $shopping_data * @param int|null $homeparty_id * @return \App\Models\ShoppingUser */ private function initializeShoppingUserSession($is_from, $is_for, $shopping_data = null, $homeparty_id = null) { // check if shopping_user_id is set - der user ist bereits angelegt if ($this->checkoutRepo->getSessionPayments('shopping_user_id')) { return $this->getExistingShoppingUser(); } // kommt vom Salescenter if ($shopping_data && $is_from !== 'shopping') { $shopping_user = $this->checkoutRepo->shoppingUserAuthData($is_from, $is_for, $shopping_data); $shopping_user->save(); $this->checkoutRepo->putSessionPayments('shopping_user_id', $shopping_user->id); return $shopping_user; } // kommt aus dem Salescenter mit bestelllink oder aus dem Webshop if ($is_from === 'shopping') { // Bestelllink if ($is_for === 'ot-customer' || $is_for === 'abo-ot-customer') { // customer shop mit den Daten aus dem Salescenter shopping_data return $this->checkoutRepo->makeCustomerShoppingUser($shopping_data, $is_for, $is_from); } // Webshop return $this->checkoutRepo->initShoppingUser($is_for, $is_from, $homeparty_id); } return $this->getExistingShoppingUser(); } /** * Holt den existierenden ShoppingUser und bereitet ihn vor * * @return ShoppingUser */ private function getExistingShoppingUser() { $shopping_user = ShoppingUser::findOrFail($this->checkoutRepo->getSessionPayments('shopping_user_id')); $shopping_user->billing_state = Shop::getCountryShippingCountryId($shopping_user->billing_country_id); $shopping_user->shipping_state = Shop::getCountryShippingCountryId($shopping_user->shipping_country_id); $shopping_user->same_as_billing = $shopping_user->same_as_billing ? false : true; // reinvert return $shopping_user; } }