Inhalte aufrufen

Profilbild
- - - - -

Live Preis Abfrage über eine externe API


  • Bitte melden Sie sich an, um eine Antwort zu verfassen.
14 Antworten zu diesem Thema

#1 wseibel

wseibel

    Advanced Member

  • Members
  • PunktPunktPunkt
  • 47 Beiträge

Geschrieben: 14 March 2025 - 20:06

Hallo,

 

ich brauche einen Tipp geben und einen Anhaltspunkt, wie ich die cshtml Datei und welche so verändere, dass ich den Preis eines Artikels ändern kann, während das Produkt angezeigt wird.

Und auch so, dass die Änderung bis hin zum Warenkorb bestehen bleibt. Und wie ich die akrtuell eingeloggte CustomerId bekomme.

Ziel ist es, je nach eingeloggtem User, die Preise von einer externen API abzufragen und diese im Shop anzupassen für den aktuell eingeloggten User.

 

Danke.


Sydesoft GmbH
Haferstr. 20
49324 Melle
Tel.: +49 5422 96 399 25
Web:

http://www.sydesoft.de

https://www.sydesoft.de/webshop-erstellen.html

 

 

 


#2 Marcus Gesing

Marcus Gesing

    SmartStore AG

  • Administrators
  • 3859 Beiträge

Geschrieben: 14 March 2025 - 21:47

Wirklich Preise live bei der Anzeige (Produktdetailsseite, Suchergebnisse, Produktlisten etc.) gegen eine API abrufen? Würde ich nur machen, wenn die Performance egal ist. Ich würde auch nicht die Views ändern, weil die lediglich Daten zur Anzeige bringen, der restliche Code (z.B. Plugins) aber weiterhin mit den ursprünglich errechneten Preisen arbeitet.
 
Ich würde das ähnlich umsetzen, wie das DependingPrices-Plugin, über einen eigenen Preis-Calculator (implemetiert IPriceCalculator). Bei der Berechnung von Preise werden immer alle IPriceCalculator-Implementierungen aufgerufen. Dadurch ist sichergestellt, dass immer und überall mein gewünschter Preis berücksichtigt wird und bei einem Update geht auch nichts kaputt. Mal ein wenig Pseudo-Code eines solchen Preis-Calculator:
 
 
[CalculatorUsage(CalculatorTargets.Product, CalculatorOrdering.Late)]
public class MyLivePricesCalculator : IPriceCalculator
{
    protected readonly IHttpContextAccessor _httpContextAccessor;

    public MyLivePricesCalculator(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task CalculateAsync(CalculatorContext context, CalculatorDelegate next)
    {
        var options = context.Options;
        var product = context.Product;
        var customer = options.Customer;
        var customerId = customer.Id;   // ID des aktuellen Kunden.

        var session = _httpContextAccessor.HttpContext?.Session;
        if (session != null)
        {
            var apiSessionId = session.GetInt32("MyApiSessionIdKey");
            if (apiSessionId == 0)
            {
                apiSessionId = await createMyApiSession(customerId, session);
            }

            if (apiSessionId != 0)
            {
                decimal myCustomPrice = await getMyCustomerPriceFromAnywhereAsync(apiSessionId, customerId);

                // Custom Preis anwenden.
                context.FinalPrice = myCustomPrice;
            }
        }

        await next(context);
    }
}
 
PS: Die ID des aktuellen Customer bekommt man über IWorkContext.CurrentCustomer.Id. In einer View z.B. so
var customerId = WorkContext.CurrentCustomer.Id;

 


Marcus Gesing

Smartstore AG


#3 wseibel

wseibel

    Advanced Member

  • Members
  • PunktPunktPunkt
  • 47 Beiträge

Geschrieben: 01 April 2025 - 20:34

Hallo Gesing,

 

vielen Dank. Ja wirklich live und der Performance-Punkt ist mir bewusst. Muss ich den IPriceCalculator als Plugin umsetzen oder geht das irgendwie ohne, also doch irgendwie in einer View? :-)


Sydesoft GmbH
Haferstr. 20
49324 Melle
Tel.: +49 5422 96 399 25
Web:

http://www.sydesoft.de

https://www.sydesoft.de/webshop-erstellen.html

 

 

 


#4 Marcus Gesing

Marcus Gesing

    SmartStore AG

  • Administrators
  • 3859 Beiträge

Geschrieben: 02 April 2025 - 13:25

Nein, in einer View geht es nicht. Eine Interface-Implementierung muss aber auch nicht zwingend in einem Plugin erfolgen. Man kann sie auch in eine der vorhandenen Smartstore-Projekte einbinden, z.B. über einen Fork. Ein Plugin ist aber immer der empfehlenswerte Weg.

Marcus Gesing

Smartstore AG


#5 wseibel

wseibel

    Advanced Member

  • Members
  • PunktPunktPunkt
  • 47 Beiträge

Geschrieben: 03 April 2025 - 19:22

Hallo Herr Gesing,

 

würde der Code so, wie Sie das geschrieben haben funktionieren, bis auf den Psydo-Aufruf getMyCustomerPriceFromAnywhereAsync?

Also existiert context.Options mit den notwendigen Felden, die ich benötigen würde (customerId)? Ich habe das jetzt selbst noch nicht ausprobiert.

 

Und noch eine Frage:

Unsere API erfordert eine Session-Anmeldung, ist es also irgendwie möglich eine Globale Instanz im Plugin zu erstellen, die die Anfragen über eine API-Session kanalisiert ohne, dass ich dabei bei jedem Request eine neue API-Session starten muss?

 

Oder muss ich das mit einem eigenen Webservice bauen, den ich dazwischen schalte und der die Anfragen dann kanalisiert?

 

Vielen Dank.


Sydesoft GmbH
Haferstr. 20
49324 Melle
Tel.: +49 5422 96 399 25
Web:

http://www.sydesoft.de

https://www.sydesoft.de/webshop-erstellen.html

 

 

 


#6 Marcus Gesing

Marcus Gesing

    SmartStore AG

  • Administrators
  • 3859 Beiträge

Geschrieben: 04 April 2025 - 08:43

Ja, der Code würde funktionieren. Es ist nur der Rumpf einer Implementierung, der so im Prinzip bei jeder IPriceCalculator Implmentierung gleich ist.
 
Der Lösungsweg für eine API-Session richtet sich nach dem erforderlichen Scope. Mir kommt hier spontan eine HTTP-Session in den Sinn. Die kriegen Sie in einem PriceCalculator über IHttpContextAccessor und Dependency Injection. Ich hab das obige Beispiel entsprechend erweitert.

Marcus Gesing

Smartstore AG


#7 wseibel

wseibel

    Advanced Member

  • Members
  • PunktPunktPunkt
  • 47 Beiträge

Geschrieben: 08 April 2025 - 09:25

Ok danke für die Hinweise und Codebeispiele. Ich werde es mal versuchen. Die allgemeine API Session werde ich vermutlich auslagern und nicht im Plugin, bzw. Smartstore implementeiren. Ich brauche Shop-Übergreifend eine einzige Session zur Ziel-API, also baue ich wahrscheinlich eine vorangestellte API, die die Zugriffe aus dem Plugin/Smartstore kanalisiert und immer eine einzige Session zur Ziel-API offenhält.


Sydesoft GmbH
Haferstr. 20
49324 Melle
Tel.: +49 5422 96 399 25
Web:

http://www.sydesoft.de

https://www.sydesoft.de/webshop-erstellen.html

 

 

 


#8 wseibel

wseibel

    Advanced Member

  • Members
  • PunktPunktPunkt
  • 47 Beiträge

Geschrieben: 09 April 2025 - 13:24

Hallo Herr Gesing,

 

ist es richtig den MyLivePricesCalculator bei Services einzubauen, wenn ich ein Plugin baue? Es scheint dort zu funktionieren...

 

Eine Frage dazu noch: warum wird CalculateAsync mehrmals aufgerufen (2mal) für ein Produkt, obwohl ich auf der Produktseite bin und den Preis dort nur einmal sehe. Hier z.B.:

http://localhost:593...ess-controller/

 

Unten in "Zuletzt gesehen" taucht der Artikel nicht auf, wenn ich auf der Produktseite bin, aber trotzdem bekomme ich 2mal den CalculateAsync.

Ich muss den Preis also mehrmals für das selbe Produkt ermitteln.


Sydesoft GmbH
Haferstr. 20
49324 Melle
Tel.: +49 5422 96 399 25
Web:

http://www.sydesoft.de

https://www.sydesoft.de/webshop-erstellen.html

 

 

 


#9 Marcus Gesing

Marcus Gesing

    SmartStore AG

  • Administrators
  • 3859 Beiträge

Geschrieben: 09 April 2025 - 15:41

Der Ordner einer Klasse (z.B. Services) spielt für die Funktionalität i.d.R. keine Rolle, sondern folgt üblichen Konventionen, damit sich Entwickler leichter in einem umfangreichen Projekt zurechtfinden.
 
"Eine Frage dazu noch: warum wird CalculateAsync mehrmals aufgerufen (2mal) für ein Produkt, obwohl ich auf der Produktseite bin und den Preis dort nur einmal sehe."
Ich würde mal debuggen und im Stacktrace nachschauen. Kann unterschiedliche Gründe haben. Z.B. die bereits erwähnten Panels wie "Schon gesehen", "Benutzer, die diesen Artikel gekauft haben, haben auch gekauft", "Zuletzt angesehene Artikel", eine Staffelpreisberechnung, Attribute die andere Produkte referenzieren... Ich würde den ermittelten Preis per Request-Cache zwischenspeichern, dann wird nur einmal pro Request und Produkt gegen die API aufgerufen. Hier ist ein Beispiel, wie man den Request-Cache benutzt. "cacheKey" würde also wahrscheinlich die Product.Id und die Customer.Id beinhalten.

Marcus Gesing

Smartstore AG


#10 wseibel

wseibel

    Advanced Member

  • Members
  • PunktPunktPunkt
  • 47 Beiträge

Geschrieben: 10 April 2025 - 08:05

Das Problem mit dem reqeustCache ist aber, dass dann der Preis für das Produkt nur an einer Stelle geändert wird. Wenn ich das Produkt z.B. in der Produktliste habe und dann zusätzlich unten nochmal in "Zuletzt angesehen", dann wird nur der Preis in der Produktliste geändert. Ich muss den Preis für die Kombi also doch mehrmals ändern, aber versuchen nur einmal abzufragen von der API.


Sydesoft GmbH
Haferstr. 20
49324 Melle
Tel.: +49 5422 96 399 25
Web:

http://www.sydesoft.de

https://www.sydesoft.de/webshop-erstellen.html

 

 

 


#11 wseibel

wseibel

    Advanced Member

  • Members
  • PunktPunktPunkt
  • 47 Beiträge

Geschrieben: 10 April 2025 - 09:05

Ich habe es jetzt so gelöst, um die API zu schonen:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Smartstore.Core.Catalog.Pricing;
using System.Text.Json;

namespace Sydesoft.LivePrice.Services
{
    [CalculatorUsage(CalculatorTargets.Product, CalculatorOrdering.Late)]
    public class LivePriceCalculator : IPriceCalculator
    {
        public static object ApiLock = new object();
        public static bool ApiSessionValid = false;

        protected readonly IHttpContextAccessor _httpContextAccessor;

        public LivePriceCalculator(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        public async Task CalculateAsync(CalculatorContext context, CalculatorDelegate next)
        {
            var productId = context.Product.Id;
            var customerId = context.Options.Customer.Id;
            var session = _httpContextAccessor.HttpContext?.Session;

            if (context.Product.Name == "DUALSHOCK 4 Wireless Controller")      // Für debugzwecke nur für ein Produkt testen
            {
                var sessionKey = $"LivePrice_{productId}_{customerId}";

                decimal price;

                // Ist Preis für das Produkt und den Kunden bereits in der Session?
                if (session != null && session.TryGetValue(sessionKey, out var cachedBytes))
                {
                    price = JsonSerializer.Deserialize<decimal>(cachedBytes);
                }
                else
                {
                    lock (ApiLock)
                    {
                        if (!ApiSessionValid)
                        {
                            ApiSessionValid = CheckApiSession();
                        }

                        price = GetProductPrice(productId, customerId);

                        if (session != null)
                        {
                            // Preis für Produkt/Kunde in der Session speichern
                            var bytes = JsonSerializer.SerializeToUtf8Bytes(price);
                            session.Set(sessionKey, bytes);
                        }
                    }
                }

                context.FinalPrice = price;
            }

            await next(context);
        }

        private bool CheckApiSession()
        {
            // Psydo Methode zum testen
            return true;
        }

        private decimal GetProductPrice(int productId, int customerId)
        {  
            // Psydo Methode zum testen
            return 25.75m;
        }
    }
}

 


Sydesoft GmbH
Haferstr. 20
49324 Melle
Tel.: +49 5422 96 399 25
Web:

http://www.sydesoft.de

https://www.sydesoft.de/webshop-erstellen.html

 

 

 


#12 Marcus Gesing

Marcus Gesing

    SmartStore AG

  • Administrators
  • 3859 Beiträge

Geschrieben: 10 April 2025 - 10:34

Beim Lock besteht die Gefahr, dass wenn die API nicht antwortet, die gesamte Anwendung blockiert wird. Wenn man das dennoch einsetzen möchte, dann sollte der HTTP-Timeout, mit dem man die API anspricht, möglichst klein gewählt werden. Bei den Update-Checks, die die Anwendung macht, haben wir z.B. einen Timeout von nur 3 Sekunden eingestellt.
 
Wenn aus irgendeinem Grund der Preis nicht abgerufen oder die API-Session nicht etabliert werden konnte, dann sollte nicht erneut in eine Lock gelaufen werden. Vor allem dann nicht, wenn wahrscheinlich ist, dass erneut kein Ergebnis von der API zu erwarten ist. Stattdessen sollte es eine Art Fallback für unvorhergesehene Fälle geben.

Marcus Gesing

Smartstore AG


#13 wseibel

wseibel

    Advanced Member

  • Members
  • PunktPunktPunkt
  • 47 Beiträge

Geschrieben: 10 April 2025 - 10:46

Danke für den Tipp. Es hat sich bei den Tests herausgestellt, dass die API wohl doch nicht so dolle ist, was die Performance angeht, also schlecht als gedacht. Deshalb werde ich wohl alle Preise in eine separate Tabelle über Migration auslagern und diese extern befüllen periodisch. Das Plugin fragt dann gegen diese Tabelle, anstatt gegen die API. Also im Prinzip, wie das euer DependencyPrice Plugin macht, glaub ich. Eigentlich könnte ich das Plugin von euch nehmen, aber ich weiß nicht, genau, ob es die gesamte Anforderung erfüllt. Ich muss nämlich wissen, ob für den speziellen eingeloggten Kunde ein Preis für den gerade angezeigten Artikel verfügbar ist und wenn das nicht der Fall ist, dann muss ich den Standardpreis aus dem shop nehmen. Kann das euer Plugin vielleicht schon, also kann ich ein Produktpreis mit einem Kunden verknüpfen? Und auch so, dass jeder Kunde einen anderen Preis haben kann, wenn dieser Kunde in der Liste mit einem speziellen Preis auftaucht.


Sydesoft GmbH
Haferstr. 20
49324 Melle
Tel.: +49 5422 96 399 25
Web:

http://www.sydesoft.de

https://www.sydesoft.de/webshop-erstellen.html

 

 

 


#14 Marcus Gesing

Marcus Gesing

    SmartStore AG

  • Administrators
  • 3859 Beiträge

Geschrieben: 10 April 2025 - 14:34

Ja, im Prinzip macht das DependingPrices-Plugin genau das. Wenn für ein Produkt abhängig von Parametern ein Preis konfiguriert ist, dann wendet es diesen an. Ansonsten wird das Plugin nicht tätig und weil es in Form eines IPriceCalculator implementiert ist, wird der reguläre Produktpreis berechnet (also so wie wenn es das Plugin gar nicht gäbe). Zu den Parametern für eine Preisbindung gehören u.a. die Zugehörigkeit zu Kundengruppen oder eine bestimmte Kundennummer, also das Feld Customer.CustomerNumber, welches der Admin übers Backend festlegen/ändern kann.
 

Marcus Gesing

Smartstore AG


#15 wseibel

wseibel

    Advanced Member

  • Members
  • PunktPunktPunkt
  • 47 Beiträge

Geschrieben: 11 April 2025 - 09:38

Danke für Ihre Hilfe, jetzt klappt alles. Ich habe eine neue Tabelle erstellt (Migration) und greife darauf innerhalb des Plugins zu. Die Tabelle fülle ich periodisch mit Daten aus dem ERP (SAP B1).

Das DependingPrices-Plugin würde sicher auch gehen, aber jetzt habe ich halt ein eigenes kleines Plugin :-).

 

Den fertigen Quellcode habe ich im Anhang hier beigefügt, falls sonst noch jemand sowas ähnliches bauen will.

Angehängte Bilder


  • Marcus Gesing gefällt das

Sydesoft GmbH
Haferstr. 20
49324 Melle
Tel.: +49 5422 96 399 25
Web:

http://www.sydesoft.de

https://www.sydesoft.de/webshop-erstellen.html