diff --git a/messages/de.json b/messages/de.json index 6061798f..be335c67 100644 --- a/messages/de.json +++ b/messages/de.json @@ -267,7 +267,9 @@ "policyConsentRequired": "Sie müssen den Geschäftsrichtlinien zustimmen, bevor Sie Ihre Bestellung aufgeben", "iAgreeToThe": "Ich stimme zu", "policySeparatorComma": ", ", - "policySeparatorAnd": " und " + "policySeparatorAnd": " und ", + "failedToApplyStoreCredit": "Guthaben konnte nicht angewendet werden. Bitte versuchen Sie es erneut.", + "storeCreditPartiallyCovers": "Ihr Guthaben deckt einen Teil dieser Bestellung. {amount} sind noch offen — wählen Sie eine andere Zahlungsart, um abzuschließen." }, "address": { "firstName": "Vorname", diff --git a/messages/en.json b/messages/en.json index 8c5a8807..a063f3fc 100644 --- a/messages/en.json +++ b/messages/en.json @@ -267,7 +267,9 @@ "policyConsentRequired": "You must agree to the store policies before placing your order", "iAgreeToThe": "I agree to the", "policySeparatorComma": ", ", - "policySeparatorAnd": " and " + "policySeparatorAnd": " and ", + "failedToApplyStoreCredit": "Failed to apply store credit. Please try again.", + "storeCreditPartiallyCovers": "Your store credit covers part of this order. {amount} is still due — choose another payment method to finish." }, "address": { "firstName": "First name", diff --git a/messages/es.json b/messages/es.json index 9db1c1d4..508c7841 100644 --- a/messages/es.json +++ b/messages/es.json @@ -267,7 +267,9 @@ "policyConsentRequired": "Debes aceptar las politicas de la tienda antes de realizar tu pedido", "iAgreeToThe": "Acepto las", "policySeparatorComma": ", ", - "policySeparatorAnd": " y " + "policySeparatorAnd": " y ", + "failedToApplyStoreCredit": "No se pudo aplicar el crédito de la tienda. Inténtalo de nuevo.", + "storeCreditPartiallyCovers": "Tu crédito de la tienda cubre parte de este pedido. Aún quedan {amount} — elige otro método de pago para finalizar." }, "address": { "firstName": "Nombre", diff --git a/messages/fr.json b/messages/fr.json index 81e2ecaf..ec82ce81 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -267,7 +267,9 @@ "policyConsentRequired": "Vous devez accepter les politiques du magasin avant de passer votre commande", "iAgreeToThe": "J'accepte les", "policySeparatorComma": ", ", - "policySeparatorAnd": " et " + "policySeparatorAnd": " et ", + "failedToApplyStoreCredit": "Impossible d'appliquer l'avoir. Veuillez réessayer.", + "storeCreditPartiallyCovers": "Votre avoir couvre une partie de cette commande. Il reste {amount} à payer — choisissez un autre moyen de paiement pour finaliser." }, "address": { "firstName": "Prenom", diff --git a/messages/pl.json b/messages/pl.json index 22ac0a33..b5d2b62a 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -267,7 +267,9 @@ "policyConsentRequired": "Musisz zaakceptować regulamin sklepu przed złożeniem zamówienia", "iAgreeToThe": "Zgadzam się z", "policySeparatorComma": ", ", - "policySeparatorAnd": " i " + "policySeparatorAnd": " i ", + "failedToApplyStoreCredit": "Nie udało się użyć środków na koncie. Spróbuj ponownie.", + "storeCreditPartiallyCovers": "Środki na koncie pokrywają część tego zamówienia. Do zapłaty pozostaje {amount} — wybierz inną metodę płatności, aby zakończyć." }, "address": { "firstName": "Imię", diff --git a/src/app/[country]/[locale]/(checkout)/checkout/[id]/CheckoutPageContent.tsx b/src/app/[country]/[locale]/(checkout)/checkout/[id]/CheckoutPageContent.tsx index 79622303..89406688 100644 --- a/src/app/[country]/[locale]/(checkout)/checkout/[id]/CheckoutPageContent.tsx +++ b/src/app/[country]/[locale]/(checkout)/checkout/[id]/CheckoutPageContent.tsx @@ -740,6 +740,7 @@ function CheckoutPageContentInner({ fetchStates={fetchStates} onUpdateBillingAddress={handleUpdateBillingAddress} onPaymentComplete={handlePaymentComplete} + onCartUpdate={setCart} processing={processing} setProcessing={setProcessing} onSessionMethodChange={setIsSessionPayment} diff --git a/src/components/checkout/PaymentSection.tsx b/src/components/checkout/PaymentSection.tsx index ac3b3d63..679451a7 100644 --- a/src/components/checkout/PaymentSection.tsx +++ b/src/components/checkout/PaymentSection.tsx @@ -39,6 +39,7 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { useCountryStates } from "@/hooks/useCountryStates"; import { getCreditCards } from "@/lib/data/credit-cards"; import { + applyStoreCredit, createCheckoutPaymentSession, createDirectPayment, updateCheckoutPaymentSession, @@ -72,6 +73,7 @@ interface PaymentSectionProps { use_shipping?: boolean; }) => Promise; onPaymentComplete: (result: PaymentCompleteResult) => Promise; + onCartUpdate?: (cart: Cart) => void; processing: boolean; setProcessing: (processing: boolean) => void; onSessionMethodChange?: (isSessionBased: boolean) => void; @@ -86,6 +88,7 @@ export function PaymentSection({ fetchStates, onUpdateBillingAddress, onPaymentComplete, + onCartUpdate, processing, setProcessing, onSessionMethodChange, @@ -616,6 +619,37 @@ export function PaymentSection({ return {}; } + // Store credit is drawn through its own endpoint, which returns the + // cart: a balance spread over several credits takes more than one + // payment, so there is no single payment to create. + if (selectedMethod.type === "store_credit") { + const creditResult = await applyStoreCredit(cart.id); + if (!creditResult.success) { + const msg = creditResult.error || t("failedToApplyStoreCredit"); + setGatewayError(msg); + setProcessing(false); + return { error: msg }; + } + + // The credit is applied either way, so the parent's cart is now + // stale — hand it the new totals before deciding what to do. + onCartUpdate?.(creditResult.cart); + + // Credit covering only part of the order leaves a balance for + // another method. What was applied stands. + if (!creditResult.cart.covered_by_store_credit) { + const msg = t("storeCreditPartiallyCovers", { + amount: creditResult.cart.display_amount_due ?? "", + }); + setGatewayError(msg); + setProcessing(false); + return { error: msg }; + } + + await onPaymentComplete({ type: "direct" }); + return {}; + } + // Direct payment flow (Check, Cash on Delivery, etc.) const paymentResult = await createDirectPayment( cart.id, @@ -651,6 +685,7 @@ export function PaymentSection({ billAddress, onUpdateBillingAddress, onPaymentComplete, + onCartUpdate, cart.id, setProcessing, t, @@ -881,18 +916,6 @@ export function PaymentSection({ )} - {/* Shared: gateway error */} - {gatewayError && !loading && ( -
-
-

- - {gatewayError} -

-
-
- )} - {/* Gateway-specific payment form */} {!loading && sessionExternalData && @@ -984,6 +1007,17 @@ export function PaymentSection({ })} + {/* Payment error — outside the method list so it reaches every method, + not only the session-based ones that mount a gateway form. */} + {gatewayError && !loading && ( +
+

+ + {gatewayError} +

+
+ )} + {/* Billing address — below payment box */}