← Back to index

Berkeley Unified Checkouts - Hybrid admin integration

Created: 2025-12-09 20:48  |  Source: https://chatgpt.com/g/g-p-6910bdb1d9a481919675eb7556851521/c/69387304-efa0-832a-

Chat history

New chat
O
Search chats
K
Library
Mohammed Shamma
Plus
You said:
I need to integrate PaymentTransaction models into the Django admin. You've already generated an admin.py file for the core.cybersource.app. Because we've been using Cybersource HOP, we already have a MerchantPost object displayed in the GiftDetail admin. I need a hybrid admin that checks if the gift_id is in the MerchantPost table or the PaymentTransaction table before showing it in the admin screen. Please wait for the next message so that I can share the code with you.
ChatGPT said:

Okay — I’ll wait for your next message with the relevant code.

You said:
Here is our current GiftDetailAdmin class that uses the MerchantPost model to display Cybersource data: class GiftDetailAdmin(NumericFilterModelAdmin): """ Provide READ-ONLY (even to superusers!) access to GiftDetails from within giveadmin. """ fieldsets = ( (None, { 'fields': ( 'id', 'basket', 'donor', 'created', ('is_anonymous', 'is_joint'), ('is_matching', 'is_recurring'), ('is_payroll', 'is_pledge'), 'is_paid', ('get_appeal_code', 'get_cadsid'), 'get_matching_info', 'get_recurring_info', ) }), (mark_safe( 'Cybersource values (<a target="_blank" href="https://docs.google.com/spreadsheets/d/1VAC-jC558nPxF9I-0syYyHDNnAxjjf3rPK_j3Q65CVg/edit#gid=0">fields reference</a>)'), { 'fields': ( ('get_auth_avs_code', 'get_auth_time'), ('get_decision', 'get_message', 'get_invalid_fields'), ('get_reason_code', 'get_req_ignore_avs'), 'get_transaction_id', 'get_req_transaction_type', 'get_req_transaction_uuid', 'get_req_recurring_amount', 'get_req_recurring_frequency', 'get_req_recurring_start_date', 'get_req_recurring_number_of_installments', ) }), (mark_safe('WSGI Request Headers (<a target="_blank" href="https://docs.google.com/spreadsheets/d/1IAYv_NAZUmkQxCQd207wvFTY3qavhlaCovEFHFaWGBw/edit#gid=0">headers reference</a>)'), { 'fields': ( 'get_http_user_agent', 'get_http_x_forwarded_for', 'get_http_cf_connecting_ip', 'get_http_cf_ipcountry', 'get_remote_addr', 'get_http_cf_ray', 'get_content_length', 'get_http_origin', 'get_http_referer', ) }), ) # See comment below under has_change_permission() for why all fields are # declared readonly here. readonly_fields = flatten(fieldsets[0][1]['fields'] + fieldsets[1][1]['fields'] + fieldsets[2][1]['fields']) list_display = ( 'id', 'donor_fullname', 'grand_total', 'created', 'is_joint', 'is_anonymous', 'is_paid', ) list_display_links = ('id', 'donor_fullname') list_filter = ( 'is_paid', ('grand_total', RangeNumericFilter), ('created', DateTimeRangeFilter), ) list_max_show_all = sys.maxsize list_select_related = ('donor',) search_fields = [ 'id', 'grand_total', 'donor__forename', 'donor__middlename', 'donor__surname' ] class Media: # js for summing the grand_total column, displaying in paginator js = ('giveadmin/js/giftdetailadmin.js',) def change_view(self, request, object_id, form_url='', extra_context=None): """ If the user has permission to resend a gift receipt, add a 'show_resend' flag for the 'submit_line' template to see. """ if request.user.has_perm('transactions.resend_receipt'): try: extra_context['show_resend'] = True except TypeError: extra_context = {'show_resend': True} return super().change_view(request, object_id, form_url, extra_context) def has_add_permission(self, request): return False def has_change_permission(self, request, obj=None): """ It would be nice to simply return False here in all cases, because we want the model to be readonly for all users. But if we do that, then our "Resend Receipt" button does not work, because when you submit the view/change form, you get a 403 error because the model is readonly. So, to make that button work, we return True here if the user has permission to resend receipts. And up above, we ensure that all the displayed fields are readonly. """ return request.user.has_perm('transactions.resend_receipt') def has_delete_permission(self, request, obj=None): return False def response_change(self, request, obj): """ Override this ModelAdmin method so we can handle the 'Resend gift receipt' button. """ if "_resend" in request.POST: email = GiftEmail(gift_detail_id=obj.id) try: status = email.send() if status == 'queued': msg = format_html( ('The receipt has been successfully queued for resending.'), ) self.message_user(request, msg, messages.SUCCESS) except Exception as e: err = str(e).replace('n', '<br>') msg = format_html( (f'Sorry, an error occurred in resending this receipt. <br>{err}'), ) self.message_user(request, msg, messages.ERROR) return HttpResponseRedirect(request.path) else: return super().response_change(request, obj) # Merchant Post Redirect def get_auth_avs_code(self, obj): return obj.merchantpostredirect_set.first().auth_avs_code def get_auth_time(self, obj): return obj.merchantpostredirect_set.first().auth_time def get_req_ignore_avs(self, obj): return obj.merchantpostredirect_set.first().req_ignore_avs def get_decision(self, obj): return obj.merchantpostredirect_set.first().decision def get_message(self, obj): return obj.merchantpostredirect_set.first().message def get_reason_code(self, obj): return obj.merchantpostredirect_set.first().reason_code def get_req_transaction_type(self, obj): return obj.merchantpostredirect_set.first().req_transaction_type def get_req_transaction_uuid(self, obj): return obj.merchantpostredirect_set.first().req_transaction_uuid def get_transaction_id(self, obj): return obj.merchantpostredirect_set.first().transaction_id def get_req_recurring_amount(self, obj): return obj.merchantpostredirect_set.first().req_recurring_amount def get_req_recurring_frequency(self, obj): return obj.merchantpostredirect_set.first().req_recurring_frequency def get_req_recurring_start_date(self, obj): return obj.merchantpostredirect_set.first().req_recurring_start_date def get_req_recurring_number_of_installments(self, obj): return obj.merchantpostredirect_set.first().req_recurring_number_of_installments # WSGI Request Headers def get_http_user_agent(self, obj): return obj.gifthttprequestheaders.http_user_agent def get_http_x_forwarded_for(self, obj): return obj.gifthttprequestheaders.http_x_forwarded_for def get_http_cf_connecting_ip(self, obj): return obj.gifthttprequestheaders.http_cf_connecting_ip def get_http_cf_ipcountry(self, obj): return obj.gifthttprequestheaders.http_cf_ipcountry def get_remote_addr(self, obj): return obj.gifthttprequestheaders.remote_addr def get_http_cf_ray(self, obj): return obj.gifthttprequestheaders.http_cf_ray def get_content_length(self, obj): return obj.gifthttprequestheaders.content_length def get_http_origin(self, obj): return obj.gifthttprequestheaders.http_origin def get_http_referer(self, obj): return obj.gifthttprequestheaders.http_referer def get_invalid_fields(self, obj): if obj.merchantpostredirect_set.first.decision == "ERROR": return obj.merchantpostredirect_set.first().invalid_fields elif obj.merchantpostredirect_set.first.decision == "CANCEL": return obj.merchantpostredirect_set.first().invalid_fields elif obj.merchantpostredirect_set.first.decision == "DECLINE": return obj.merchantpostredirect_set.first().invalid_fields else: return "none" # the initial spaces in the following strings are INTENTIONAL: # they keep the admin from capitalizing the field names # merchant post redirect get_auth_avs_code.short_description = ' auth_avs_code' get_req_ignore_avs.short_description = ' req_ignore_avs' get_auth_time.short_description = ' auth_time' get_decision.short_description = ' decision' get_message.short_description = ' message' get_reason_code.short_description = ' reason_code' get_req_transaction_type.short_description = ' req_transaction_type' get_req_transaction_uuid.short_description = ' req_transaction_uuid' get_transaction_id.short_description = ' transaction_id' get_req_recurring_amount.short_description = ' req_recurring_amount' get_req_recurring_frequency.short_description = ' req_recurring_frequency' get_req_recurring_start_date.short_description = ' req_recurring_start_date' get_req_recurring_number_of_installments.short_description = ' req_recurring_number_of_installments' # wsgi request headers get_http_user_agent.short_description = ' http_user_agent' get_http_x_forwarded_for.short_description = ' http_x_forwarded_for' get_http_cf_connecting_ip.short_description = ' http_cf_connecting_ip' get_http_cf_ipcountry.short_description = ' http_cf_ipcountry' get_remote_addr.short_description = ' remote_addr' get_http_cf_ray.short_description = ' http_cf_ray' get_content_length.short_description = ' content_length' get_http_origin.short_description = ' http_origin' get_http_referer.short_description = ' http_referer' get_invalid_fields.short_description = ' invalid fields'
ChatGPT said:

Great — thank you.
I now have everything I need to build the hybrid admin integration, but before I generate the final code, I need one crucial piece from you:


I need your PaymentTransaction model (the exact version stored in memory)

I need:

  • All fields

  • Any FK or OneToOne relationships (especially whether it has a FK to GiftDetail, and the field name)

  • Any helper properties or methods I should surface in admin

  • Whether your PaymentTransaction rows can be multiple per gift or exactly one per gift

Your model is stored in memory (as of 2025-12-08), but you modified it several times, so I want to ensure I use the correct, final version.


Why I need the PaymentTransaction model

Your current GiftDetailAdmin calls things like:

python
obj.merchantpostredirect_set.first()

To create a hybrid admin we need to support:

  1. Legacy HOP flow → MerchantPostRedirect

  2. Unified Checkout flow → PaymentTransaction

The key logic must be:

python
if MerchantPostRedirect exists: show MerchantPostRedirect fields elif PaymentTransaction exists: show PaymentTransaction fields else: show “no payment data”

To implement this cleanly, I will:

✔ Add helper methods on GiftDetailAdmin:

python
def _get_payment(self, obj): return obj.merchantpostredirect_set.first() or obj.paymenttransaction_set.first()

✔ Replace all existing getters (e.g., get_decision, get_reason_code)

with hybrid-aware versions that read from:

  • MerchantPostRedirect fields OR

  • PaymentTransaction fields (proper mappings)


⚠ Before generating the hybrid class, I need:

➡ Please paste your PaymentTransaction model again (the current final version).

I will then produce:

A complete replacement GiftDetailAdmin that:

  • Supports both MerchantPostRedirect and PaymentTransaction

  • Renames fields appropriately based on UC vs HOP

  • Displays only the relevant section

  • Avoids breaking your existing resend-receipt workflow

  • Preserves readonly state logic

  • Keeps list_display, filters, permissions, and headers intact

  • Adds a clear “Payment Source: HOP vs UC” indicator


Please send me your PaymentTransaction model now, and I will generate the full hybrid admin ready to paste into admin.py.

You said:
class PaymentTransaction(BaseGiveObject): """ Persistent storage of each Cybersource Unified Checkout transaction. Why this exists: - Allows confirmation page to survive refreshes. - Allows reporting, debugging, and reconciliation. - Stores the raw UC 'complete' JSON. """ gift = models.OneToOneField( "transactions.GiftDetail", on_delete=models.SET_NULL, null=True, blank=True, related_name="payment_transaction", ) # Cybersource fields transaction_id = models.CharField(max_length=64, db_index=True) # id # Raw UC payload (entire JSON response from complete()) raw_payload = models.JSONField(default=dict) # State metadata status = models.CharField(max_length=32, default="CAPTURED") # AUTHORIZED / CAPTURED / FAILED decision = models.CharField(max_length=32, default="ACCEPT") # ACCEPT / REJECT / ERROR # Verification flags signature_verified = models.BooleanField(default=False) amount_verified = models.BooleanField(default=False) # === Recurring Billing Additions (RBv2 / TMS) === tms_customer_id = models.CharField(max_length=64, null=True, blank=True) tms_instrument_identifier_id = models.CharField(max_length=64, null=True, blank=True) tms_payment_instrument_id = models.CharField(max_length=64, null=True, blank=True) tms_subscription_id = models.CharField(max_length=64, null=True, blank=True) # === NEW: Transient token storage === uc_transient_token = models.TextField(null=True, blank=True) # === NEW: TMS and Subscription audit fields === tms_token_create_response = models.JSONField(null=True, blank=True) tms_subscription_create_response = models.JSONField(null=True, blank=True) class Meta: ordering = ["-created"] def __str__(self): return f"PaymentTransaction {self.transaction_id} for Gift {self.gift_id}"
ChatGPT said:

Here’s a hybrid version of GiftDetailAdmin that:

  • Still supports legacy HOP via merchantpostredirect_set

  • Adds support for Unified Checkout via the PaymentTransaction OneToOne (gift.payment_transaction)

  • Prefers HOP data if it exists, otherwise falls back to PaymentTransaction

  • Adds a clear payment_source and some UC-specific fields

  • Fixes the get_invalid_fields bug and makes all Cybersource getters null-safe

You can drop this into your existing transactions/admin.py (replacing the current GiftDetailAdmin).

python
class GiftDetailAdmin(NumericFilterModelAdmin): """ Provide READ-ONLY (even to superusers!) access to GiftDetails from within giveadmin. """ # ------------------------------------------------------------------ # Helper accessors for HOP vs Unified Checkout # ------------------------------------------------------------------ def _get_hop(self, obj): """ Legacy Secure Acceptance (HOP) record, if any. """ try: return obj.merchantpostredirect_set.first() except Exception: return None def _get_uc(self, obj): """ Unified Checkout PaymentTransaction, if any. """ # OneToOneField with related_name="payment_transaction" return getattr(obj, "payment_transaction", None) def get_payment_source(self, obj): """ Indicates whether this gift was processed by: - Secure Acceptance (HOP) - Unified Checkout (UC) - None (no payment record found) """ hop = self._get_hop(obj) uc = self._get_uc(obj) if hop and uc: # Shouldn't normally happen, but be explicit if it does return "HOP + UC (both)" if hop: return "Secure Acceptance (HOP)" if uc: return "Unified Checkout (UC)" return "No payment record" # ------------------------------------------------------------------ # Existing Cybersource (primarily HOP) getters, now hybrid-aware # ------------------------------------------------------------------ def get_auth_avs_code(self, obj): hop = self._get_hop(obj) return getattr(hop, "auth_avs_code", None) if hop else None def get_auth_time(self, obj): hop = self._get_hop(obj) return getattr(hop, "auth_time", None) if hop else None def get_req_ignore_avs(self, obj): hop = self._get_hop(obj) return getattr(hop, "req_ignore_avs", None) if hop else None def get_decision(self, obj): """ HOP: merchantpostredirect.decision UC : PaymentTransaction.decision """ hop = self._get_hop(obj) if hop and getattr(hop, "decision", None): return hop.decision uc = self._get_uc(obj) return getattr(uc, "decision", None) if uc else None def get_message(self, obj): """ HOP: merchantpostredirect.message UC : raw_payload["message"] (top-level UC field) if present """ hop = self._get_hop(obj) if hop and getattr(hop, "message", None): return hop.message uc = self._get_uc(obj) if not uc or not uc.raw_payload: return None payload = uc.raw_payload or {} # UC complete() responses typically include a top-level "message" return payload.get("message") def get_reason_code(self, obj): """ HOP: merchantpostredirect.reason_code UC : best-effort mapping from raw_payload: - payload["reasonCode"] if present - else payload["details"]["processorInformation"]["responseCode"] """ hop = self._get_hop(obj) if hop and getattr(hop, "reason_code", None): return hop.reason_code uc = self._get_uc(obj) if not uc or not uc.raw_payload: return None payload = uc.raw_payload or {} if "reasonCode" in payload: return payload["reasonCode"] details = payload.get("details", {}) processor = details.get("processorInformation", {}) return processor.get("responseCode") def get_req_transaction_type(self, obj): hop = self._get_hop(obj) return getattr(hop, "req_transaction_type", None) if hop else None def get_req_transaction_uuid(self, obj): hop = self._get_hop(obj) return getattr(hop, "req_transaction_uuid", None) if hop else None def get_transaction_id(self, obj): """ HOP: merchantpostredirect.transaction_id UC : PaymentTransaction.transaction_id """ hop = self._get_hop(obj) if hop and getattr(hop, "transaction_id", None): return hop.transaction_id uc = self._get_uc(obj) return getattr(uc, "transaction_id", None) if uc else None def get_req_recurring_amount(self, obj): hop = self._get_hop(obj) return getattr(hop, "req_recurring_amount", None) if hop else None def get_req_recurring_frequency(self, obj): hop = self._get_hop(obj) return getattr(hop, "req_recurring_frequency", None) if hop else None def get_req_recurring_start_date(self, obj): hop = self._get_hop(obj) return getattr(hop, "req_recurring_start_date", None) if hop else None def get_req_recurring_number_of_installments(self, obj): hop = self._get_hop(obj) return getattr(hop, "req_recurring_number_of_installments", None) if hop else None def get_invalid_fields(self, obj): """ HOP-only concept. For UC gifts, say 'n/a for Unified Checkout' to avoid confusion. """ hop = self._get_hop(obj) if not hop: # There is no HOP record; if we have UC, be explicit. if self._get_uc(obj): return "n/a for Unified Checkout" return "none" decision = getattr(hop, "decision", "") or "" decision = decision.upper() if decision in {"ERROR", "CANCEL", "DECLINE"}: return getattr(hop, "invalid_fields", None) or "none" return "none" # ------------------------------------------------------------------ # UC-specific helpers (from PaymentTransaction fields) # ------------------------------------------------------------------ def get_uc_status(self, obj): uc = self._get_uc(obj) return getattr(uc, "status", None) if uc else None def get_uc_signature_verified(self, obj): uc = self._get_uc(obj) return getattr(uc, "signature_verified", None) if uc else None def get_uc_amount_verified(self, obj): uc = self._get_uc(obj) return getattr(uc, "amount_verified", None) if uc else None def get_tms_customer_id(self, obj): uc = self._get_uc(obj) return getattr(uc, "tms_customer_id", None) if uc else None def get_tms_subscription_id(self, obj): uc = self._get_uc(obj) return getattr(uc, "tms_subscription_id", None) if uc else None # ------------------------------------------------------------------ # WSGI Request Headers (unchanged; still HOP-era capture) # ------------------------------------------------------------------ def get_http_user_agent(self, obj): return getattr(obj.gifthttprequestheaders, "http_user_agent", None) def get_http_x_forwarded_for(self, obj): return getattr(obj.gifthttprequestheaders, "http_x_forwarded_for", None) def get_http_cf_connecting_ip(self, obj): return getattr(obj.gifthttprequestheaders, "http_cf_connecting_ip", None) def get_http_cf_ipcountry(self, obj): return getattr(obj.gifthttprequestheaders, "http_cf_ipcountry", None) def get_remote_addr(self, obj): return getattr(obj.gifthttprequestheaders, "remote_addr", None) def get_http_cf_ray(self, obj): return getattr(obj.gifthttprequestheaders, "http_cf_ray", None) def get_content_length(self, obj): return getattr(obj.gifthttprequestheaders, "content_length", None) def get_http_origin(self, obj): return getattr(obj.gifthttprequestheaders, "http_origin", None) def get_http_referer(self, obj): return getattr(obj.gifthttprequestheaders, "http_referer", None) # ------------------------------------------------------------------ # Fieldsets / readonly / list configuration # ------------------------------------------------------------------ fieldsets = ( (None, { 'fields': ( 'id', 'basket', 'donor', 'created', ('is_anonymous', 'is_joint'), ('is_matching', 'is_recurring'), ('is_payroll', 'is_pledge'), 'is_paid', ('get_appeal_code', 'get_cadsid'), 'get_matching_info', 'get_recurring_info', ) }), (mark_safe( 'Cybersource values (<a target="_blank" ' 'href="https://docs.google.com/spreadsheets/d/1VAC-jC558nPxF9I-0syYyHDNnAxjjf3rPK_j3Q65CVg/edit#gid=0">' 'fields reference</a>)' ), { 'fields': ( # New: show which pipeline handled this gift 'get_payment_source', # Mostly HOP-derived, with hybrid fallbacks ('get_auth_avs_code', 'get_auth_time'), ('get_decision', 'get_message', 'get_invalid_fields'), ('get_reason_code', 'get_req_ignore_avs'), 'get_transaction_id', ('get_req_transaction_type', 'get_req_transaction_uuid'), ('get_req_recurring_amount', 'get_req_recurring_frequency'), ('get_req_recurring_start_date', 'get_req_recurring_number_of_installments'), # UC-specific fields (no HOP equivalents) ('get_uc_status', 'get_uc_signature_verified', 'get_uc_amount_verified'), ('get_tms_customer_id', 'get_tms_subscription_id'), ) }), (mark_safe( 'WSGI Request Headers ' '(<a target="_blank" ' 'href="https://docs.google.com/spreadsheets/d/1IAYv_NAZUmkQxCQd207wvFTY3qavhlaCovEFHFaWGBw/edit#gid=0">' 'headers reference</a>)' ), { 'fields': ( 'get_http_user_agent', 'get_http_x_forwarded_for', 'get_http_cf_connecting_ip', 'get_http_cf_ipcountry', 'get_remote_addr', 'get_http_cf_ray', 'get_content_length', 'get_http_origin', 'get_http_referer', ) }), ) # See comment below under has_change_permission() for why all fields are # declared readonly here. readonly_fields = flatten( fieldsets[0][1]['fields'] + fieldsets[1][1]['fields'] + fieldsets[2][1]['fields'] ) list_display = ( 'id', 'donor_fullname', 'grand_total', 'created', 'is_joint', 'is_anonymous', 'is_paid', ) list_display_links = ('id', 'donor_fullname') list_filter = ( 'is_paid', ('grand_total', RangeNumericFilter), ('created', DateTimeRangeFilter), ) list_max_show_all = sys.maxsize list_select_related = ('donor',) search_fields = [ 'id', 'grand_total', 'donor__forename', 'donor__middlename', 'donor__surname' ] class Media: # js for summing the grand_total column, displaying in paginator js = ('giveadmin/js/giftdetailadmin.js',) def change_view(self, request, object_id, form_url='', extra_context=None): """ If the user has permission to resend a gift receipt, add a 'show_resend' flag for the 'submit_line' template to see. """ if request.user.has_perm('transactions.resend_receipt'): try: extra_context['show_resend'] = True except TypeError: extra_context = {'show_resend': True} return super().change_view(request, object_id, form_url, extra_context) def has_add_permission(self, request): return False def has_change_permission(self, request, obj=None): """ It would be nice to simply return False here in all cases, because we want the model to be readonly for all users. But if we do that, then our "Resend Receipt" button does not work, because when you submit the view/change form, you get a 403 error because the model is readonly. So, to make that button work, we return True here if the user has permission to resend receipts. And up above, we ensure that all the displayed fields are readonly. """ return request.user.has_perm('transactions.resend_receipt') def has_delete_permission(self, request, obj=None): return False def response_change(self, request, obj): """ Override this ModelAdmin method so we can handle the 'Resend gift receipt' button. """ if "_resend" in request.POST: email = GiftEmail(gift_detail_id=obj.id) try: status = email.send() if status == 'queued': msg = format_html( 'The receipt has been successfully queued for resending.', ) self.message_user(request, msg, messages.SUCCESS) except Exception as e: err = str(e).replace('n', '<br>') msg = format_html( f'Sorry, an error occurred in resending this receipt. <br>{err}', ) self.message_user(request, msg, messages.ERROR) return HttpResponseRedirect(request.path) else: return super().response_change(request, obj) # ------------------------------------------------------------------ # Short descriptions (labels) — keep leading spaces as in your original # ------------------------------------------------------------------ # merchant post redirect + hybrid get_payment_source.short_description = ' payment_source' get_auth_avs_code.short_description = ' auth_avs_code' get_req_ignore_avs.short_description = ' req_ignore_avs' get_auth_time.short_description = ' auth_time' get_decision.short_description = ' decision' get_message.short_description = ' message' get_reason_code.short_description = ' reason_code' get_req_transaction_type.short_description = ' req_transaction_type' get_req_transaction_uuid.short_description = ' req_transaction_uuid' get_transaction_id.short_description = ' transaction_id' get_req_recurring_amount.short_description = ' req_recurring_amount' get_req_recurring_frequency.short_description = ' req_recurring_frequency' get_req_recurring_start_date.short_description = ' req_recurring_start_date' get_req_recurring_number_of_installments.short_description = ' req_recurring_number_of_installments' get_invalid_fields.short_description = ' invalid fields' # UC-specific labels get_uc_status.short_description = ' uc_status' get_uc_signature_verified.short_description = ' uc_signature_verified' get_uc_amount_verified.short_description = ' uc_amount_verified' get_tms_customer_id.short_description = ' tms_customer_id' get_tms_subscription_id.short_description = ' tms_subscription_id' # wsgi request headers get_http_user_agent.short_description = ' http_user_agent' get_http_x_forwarded_for.short_description = ' http_x_forwarded_for' get_http_cf_connecting_ip.short_description = ' http_cf_connecting_ip' get_http_cf_ipcountry.short_description = ' http_cf_ipcountry' get_remote_addr.short_description = ' remote_addr' get_http_cf_ray.short_description = ' http_cf_ray' get_content_length.short_description = ' content_length' get_http_origin.short_description = ' http_origin' get_http_referer.short_description = ' http_referer'

Optional: PaymentTransactionAdmin (core.cybersource)

In your core/cybersource/admin.py, make sure you’ve got something like:

python
@admin.register(PaymentTransaction) class PaymentTransactionAdmin(admin.ModelAdmin): list_display = ( 'id', 'transaction_id', 'gift', 'status', 'decision', 'signature_verified', 'amount_verified', 'created', ) list_filter = ('status', 'decision', 'signature_verified', 'amount_verified', 'created') search_fields = ('transaction_id', 'gift__id') readonly_fields = ( 'gift', 'transaction_id', 'raw_payload', 'status', 'decision', 'signature_verified', 'amount_verified', 'tms_customer_id', 'tms_instrument_identifier_id', 'tms_payment_instrument_id', 'tms_subscription_id', 'uc_transient_token', 'tms_token_create_response', 'tms_subscription_create_response', 'created', 'modified', )

If you’d like, next step I can:

  • Add a link from GiftDetailAdmin to the PaymentTransaction change page (for UC gifts)

  • Or add a readonly JSON viewer for raw_payload right in the gift admin.

var(--tw-content)

ChatGPT can make mistakes. Check important info.