如何在Django电商项目中实现PayPal支付登录弹窗功能
Let’s walk through how to get the PayPal login window to open when users select the PayPal radio button on your checkout page, while saving their shipping/billing addresses first. Here’s a step-by-step solution tailored to your existing code:
1. Update Your Payment Model
First, add a field to track PayPal transactions in your models.py:
class Payment(models.Model): stripe_charge_id = models.CharField(max_length=50, blank=True, null=True) paypal_transaction_id = models.CharField(max_length=50, blank=True, null=True) # New field user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True) amount = models.FloatField() timestamp = models.DateTimeField(auto_now_add=True) def __str__(self): return self.user.username if self.user else "Anonymous Payment"
Run migrations to apply this change:
python manage.py makemigrations python manage.py migrate
2. Add PayPal Credentials to Settings
Add your PayPal sandbox (or live) credentials to settings.py:
# PayPal Settings (use live credentials for production) PAYPAL_CLIENT_ID = "your-sandbox-client-id" PAYPAL_SECRET = "your-sandbox-secret" PAYPAL_CURRENCY = "USD" # Adjust to your project's currency
You can get these credentials from the PayPal Developer Dashboard.
3. Create PayPal API Views
Add these views to views.py to handle creating and capturing PayPal orders:
import json import requests from django.http import JsonResponse from django.conf import settings from django.views import View from .models import Order, Payment class PayPalCreateOrderView(View): def post(self, *args, **kwargs): try: order = Order.objects.get(user=self.request.user, ordered=False) except Order.DoesNotExist: return JsonResponse({"error": "No active order found"}, status=400) # Get PayPal access token def get_access_token(): url = "https://api-m.sandbox.paypal.com/v1/oauth2/token" payload = "grant_type=client_credentials" headers = {"Content-Type": "application/x-www-form-urlencoded"} response = requests.post( url, data=payload, headers=headers, auth=(settings.PAYPAL_CLIENT_ID, settings.PAYPAL_SECRET) ) return response.json()["access_token"] # Create PayPal order access_token = get_access_token() url = "https://api-m.sandbox.paypal.com/v2/checkout/orders" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {access_token}" } payload = { "intent": "CAPTURE", "purchase_units": [ { "amount": { "currency_code": settings.PAYPAL_CURRENCY, "value": str(order.get_total()) # Ensure your Order model has this method } } ] } response = requests.post(url, json=payload, headers=headers) return JsonResponse(response.json()) class PayPalCaptureOrderView(View): def post(self, *args, **kwargs): try: order = Order.objects.get(user=self.request.user, ordered=False) except Order.DoesNotExist: return JsonResponse({"error": "No active order found"}, status=400) data = json.loads(self.request.body) order_id = data.get("order_id") # Get PayPal access token def get_access_token(): url = "https://api-m.sandbox.paypal.com/v1/oauth2/token" payload = "grant_type=client_credentials" headers = {"Content-Type": "application/x-www-form-urlencoded"} response = requests.post( url, data=payload, headers=headers, auth=(settings.PAYPAL_CLIENT_ID, settings.PAYPAL_SECRET) ) return response.json()["access_token"] # Capture PayPal order access_token = get_access_token() url = f"https://api-m.sandbox.paypal.com/v2/checkout/orders/{order_id}/capture" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {access_token}" } response = requests.post(url, headers=headers) response_data = response.json() if response_data["status"] == "COMPLETED": # Save payment record and mark order as completed payment = Payment.objects.create( user=self.request.user, amount=order.get_total(), paypal_transaction_id=response_data["id"] ) order.ordered = True order.payment = payment order.save() return JsonResponse({"status": "success", "message": "Payment completed!"}) else: return JsonResponse({"status": "error", "message": "Payment failed"}, status=400)
4. Modify CheckoutView for AJAX Support
Update your CheckoutView to handle AJAX requests when PayPal is selected:
class CheckoutView(View): # ... (keep your existing get method) def post(self, *args, **kwargs): form = CheckoutForm(self.request.POST or None) try: order = Order.objects.get(user=self.request.user, ordered=False) if form.is_valid(): # ... (keep all your existing address saving logic here) payment_option = form.cleaned_data.get("payment_option") if payment_option == "S": return redirect("core:payment", payment_option="stripe") elif payment_option == "P": # Handle AJAX request for PayPal if self.request.headers.get("X-Requested-With") == "XMLHttpRequest": return JsonResponse({"status": "success", "message": "Addresses saved"}) else: return redirect("core:payment", payment_option="paypal") else: messages.warning(self.request, "Invalid payment option selected") return redirect("core:checkout") except ObjectDoesNotExist: messages.warning(self.request, "You do not have an active order") return redirect("core:order-summary")
5. Update Checkout Template to Trigger PayPal Window
Modify your checkout.html to intercept form submission for PayPal and load the PayPal SDK:
<form id="checkout-form" method="post"> {% csrf_token %} <!-- ... (keep all your existing form fields) --> <div class="form-group"> <label>Payment Option</label> {% for choice in form.payment_option %} <div class="form-check"> {{ choice.tag }} <label class="form-check-label" for="{{ choice.id_for_label }}">{{ choice.choice_label }}</label> </div> {% endfor %} </div> <button type="submit" class="btn btn-primary">Proceed to Payment</button> </form> <!-- Hidden PayPal button container --> <div id="paypal-button-container" style="display: none; margin-top: 20px;"></div> <script src="https://www.paypal.com/sdk/js?client-id={{ settings.PAYPAL_CLIENT_ID }}¤cy={{ settings.PAYPAL_CURRENCY }}"></script> <script> document.getElementById('checkout-form').addEventListener('submit', function(e) { const selectedPayment = document.querySelector('input[name="payment_option"]:checked').value; // Handle PayPal submission via AJAX if (selectedPayment === 'P') { e.preventDefault(); // Submit form to save addresses fetch('{% url "core:checkout" %}', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': '{{ csrf_token }}' }, body: JSON.stringify(Object.fromEntries(new FormData(this))) }) .then(response => response.json()) .then(data => { if (data.status === 'success') { // Show PayPal button and trigger it document.getElementById('paypal-button-container').style.display = 'block'; paypal.Buttons({ createOrder: (data, actions) => { return fetch('{% url "core:paypal-create-order" %}', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': '{{ csrf_token }}' } }) .then(response => response.json()) .then(orderData => orderData.id); }, onApprove: (data, actions) => { return fetch('{% url "core:paypal-capture-order" %}', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': '{{ csrf_token }}' }, body: JSON.stringify({order_id: data.orderID}) }) .then(response => response.json()) .then(result => { alert(result.message); window.location.href = '{% url "core:order-confirmation" %}'; // Redirect to confirmation page }); }, onError: err => { console.error(err); alert('An error occurred. Please try again later.'); } }).render('#paypal-button-container'); } else { alert('Failed to save your address. Please check your input.'); } }) .catch(err => { console.error(err); alert('Something went wrong. Please try again.'); }); } // Stripe submission proceeds normally via redirect }); </script>
6. Add URLs for PayPal Views
Update your urls.py to include the new PayPal endpoints:
from django.urls import path from .views import ( CheckoutView, PayPalCreateOrderView, PayPalCaptureOrderView, # ... other views ) urlpatterns = [ path('checkout/', CheckoutView.as_view(), name='checkout'), path('paypal/create-order/', PayPalCreateOrderView.as_view(), name='paypal-create-order'), path('paypal/capture-order/', PayPalCaptureOrderView.as_view(), name='paypal-capture-order'), # ... other URLs ]
Key Notes
- Sandbox vs Live: Use sandbox credentials for testing, then switch to live credentials when you’re ready to launch.
- Error Handling: Expand the error handling in views/templates to cover edge cases (e.g., expired orders, failed API calls).
- Order Confirmation: Create an
order-confirmationview to show users their successful order details. - Dependencies: Install the
requestspackage if you haven’t already:pip install requests
内容的提问来源于stack exchange,提问作者A_K

