From aefc130f150fc617a11d6ce420e51d6543ce4ce0 Mon Sep 17 00:00:00 2001 From: Alan Woodman Date: Fri, 22 Aug 2025 15:26:09 +0800 Subject: [PATCH] New Features --- blueprints/main.py | 87 +++++++++- stripe_payment_processor.py | 3 +- templates/main/single_payment.html | 11 +- templates/main/single_payment_detail.html | 198 ++++++++++++++++++++-- templates/main/single_payments_list.html | 5 +- 5 files changed, 286 insertions(+), 18 deletions(-) diff --git a/blueprints/main.py b/blueprints/main.py index 6e9439f..894b302 100644 --- a/blueprints/main.py +++ b/blueprints/main.py @@ -277,11 +277,11 @@ def get_stripe_payment_methods(stripe_customer_id): api_key = "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" else: api_key = "sk_test_51Rsi9gPfYyg6zE1S4ZpaPI1ehpbsHRLsGhysYXKwAWCZ7w6KYgVXy4pV095Nd8tyjUw9AkBhqfxqsIiiWJg5fexI00Dw36vnvx" - + print(api_key) processor = StripePaymentProcessor(api_key=api_key, enable_logging=False) # Get payment methods from Stripe - stripe_customer_id = "cus_SoMyDihTxRsa7U" + #stripe_customer_id = "cus_SoMyDihTxRsa7U" payment_methods = processor.get_payment_methods(stripe_customer_id) return payment_methods @@ -446,8 +446,12 @@ def single_payment_detail(payment_id): SinglePayments.Payment_Amount, SinglePayments.PI_JSON, SinglePayments.PI_FollowUp_JSON, + SinglePayments.Refund_JSON, SinglePayments.Error, SinglePayments.Success, + SinglePayments.Refund, + SinglePayments.Stripe_Refund_ID, + SinglePayments.Stripe_Refund_Created, SinglePayments.Created, Users.FullName.label('processed_by') ).outerjoin(Users, SinglePayments.Who == Users.id)\ @@ -1050,6 +1054,85 @@ def check_batch_payment_intent(payment_id): print(f"Check batch payment intent error: {e}") return jsonify({'success': False, 'error': 'Failed to check payment intent'}), 500 +@main_bp.route('/single-payment/refund/', methods=['POST']) +@login_required +def process_single_payment_refund(payment_id): + """Process refund for a single payment.""" + try: + # Get the payment record + payment = db.session.query(SinglePayments).filter(SinglePayments.id == payment_id).first() + + if not payment: + return jsonify({'success': False, 'error': 'Payment not found'}), 404 + + # Check if payment can be refunded + if payment.Success != True: + return jsonify({'success': False, 'error': 'Cannot refund unsuccessful payment'}), 400 + + if payment.Refund == True: + return jsonify({'success': False, 'error': 'Payment has already been refunded'}), 400 + + if not payment.Payment_Intent: + return jsonify({'success': False, 'error': 'No payment intent found for this payment'}), 400 + + # Get refund reason from request + data = request.get_json() + reason = data.get('reason', 'requested_by_customer') if data else 'requested_by_customer' + + # Initialize Stripe + if Config.PROCESS_LIVE: + api_key = "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" + else: + api_key = "sk_test_51Rsi9gPfYyg6zE1S4ZpaPI1ehpbsHRLsGhysYXKwAWCZ7w6KYgVXy4pV095Nd8tyjUw9AkBhqfxqsIiiWJg5fexI00Dw36vnvx" + + import stripe + stripe.api_key = api_key + + # Process refund through Stripe + refund = stripe.Refund.create( + payment_intent=payment.Payment_Intent, + reason=reason, + metadata={ + 'splynx_customer_id': str(payment.Splynx_ID), + 'payment_id': str(payment_id), + 'processed_by': current_user.FullName + } + ) + + # Update payment record + payment.Refund = True + payment.Refund_JSON = json.dumps(refund, default=str) + payment.Stripe_Refund_ID = refund.id + + # Convert timestamp to datetime + if hasattr(refund, 'created') and refund.created: + from datetime import datetime + payment.Stripe_Refund_Created = datetime.fromtimestamp(refund.created) + + db.session.commit() + + # Log the refund activity + log_activity( + user_id=current_user.id, + action="process_refund", + entity_type="single_payment", + entity_id=payment_id, + details=f"Processed refund for single payment ID {payment_id}, amount ${payment.Payment_Amount}, reason: {reason}" + ) + + return jsonify({ + 'success': True, + 'refund_id': refund.id, + 'amount_refunded': f"${payment.Payment_Amount:.2f}", + 'reason': reason + }) + + except stripe.StripeError as e: + return jsonify({'success': False, 'error': f'Stripe error: {str(e)}'}), 500 + except Exception as e: + print(f"Error processing single payment refund: {e}") + return jsonify({'success': False, 'error': 'Internal server error'}), 500 + @main_bp.route('/payment/refund/', methods=['POST']) @login_required def process_payment_refund(payment_id): diff --git a/stripe_payment_processor.py b/stripe_payment_processor.py index 16ac65b..0d98d3b 100644 --- a/stripe_payment_processor.py +++ b/stripe_payment_processor.py @@ -553,7 +553,7 @@ class StripePaymentProcessor: customer=customer_id, limit=10 ) - + print(json.dumps(payment_methods,indent=2)) methods_list = [] for pm in payment_methods.data: @@ -580,6 +580,7 @@ class StripePaymentProcessor: methods_list.append(pm_info) self._log('info', f"Found {len(methods_list)} payment methods") + print(methods_list) return methods_list except stripe.StripeError as e: diff --git a/templates/main/single_payment.html b/templates/main/single_payment.html index 5d55bc8..c78d123 100644 --- a/templates/main/single_payment.html +++ b/templates/main/single_payment.html @@ -452,10 +452,13 @@ function displayPaymentMethods(paymentMethods) { ${paymentMethods.map(pm => { let displayText = ''; - if (pm.type === 'card') { - displayText = `${pm.display_brand.toUpperCase()} ending in ${pm.last4}`; - } else if (pm.type === 'au_becs_debit') { - displayText = `AU Bank Account ending in ${pm.last4}`; + if (pm.type === 'card' && pm.card) { + const brand = pm.card.brand || 'Card'; + const last4 = pm.card.last4 || '****'; + displayText = `${brand.toUpperCase()} ending in ${last4}`; + } else if (pm.type === 'au_becs_debit' && pm.au_becs_debit) { + const last4 = pm.au_becs_debit.last4 || '****'; + displayText = `AU Bank Account ending in ${last4}`; } else { displayText = pm.type.toUpperCase(); } diff --git a/templates/main/single_payment_detail.html b/templates/main/single_payment_detail.html index 0f9148b..eeae853 100644 --- a/templates/main/single_payment_detail.html +++ b/templates/main/single_payment_detail.html @@ -31,7 +31,11 @@
- {% if payment.Success == True %} + {% if payment.Refund == True %} + + + + {% elif payment.Success == True %} @@ -47,7 +51,13 @@
- {% if payment.Success == True %} + {% if payment.Refund == True %} +

Payment Refunded

+

This payment has been refunded to the customer.

+ {% if payment.Stripe_Refund_Created %} +

Refunded: {{ payment.Stripe_Refund_Created.strftime('%Y-%m-%d %H:%M:%S') }}

+ {% endif %} + {% elif payment.Success == True %}

Payment Successful

This payment has been completed successfully.

{% elif payment.Success == False %} @@ -61,12 +71,24 @@
- {% if payment.PI_FollowUp %} - - {% endif %} +
+ {% if payment.Refund != True and payment.Success == True %} +
+ +
+ {% endif %} + {% if payment.PI_FollowUp %} +
+ +
+ {% endif %} +
@@ -165,21 +187,58 @@ {% endif %} {% endif %} + + {% if payment.Refund == True %} +
+ + Refund Processed: This payment has been refunded. + {% if payment.Stripe_Refund_Created %} +
Refunded: {{ payment.Stripe_Refund_Created.strftime('%Y-%m-%d %H:%M:%S') }} + {% endif %} + {% if payment.Stripe_Refund_ID %} +
Refund ID: {{ payment.Stripe_Refund_ID }} + {% endif %} +
+ {% endif %} {% if payment.Error %} +{% set error_alert = payment | error_alert %}

- Error Information + Payment Error Details

+ {% if error_alert %} +
+
+ + {{ error_alert.title }} +
+
+

{{ error_alert.message }}

+

Suggested Action: {{ error_alert.suggestion }}

+
+ View Technical Details +
{{ error_alert.raw_error }}
+
+
+
+ {% else %} +
-
{{ payment.Error }}
+
Payment Error
+

An error occurred during payment processing.

+
+ Technical Details +
{{ payment.Error }}
+
+ {% endif %}
{% endif %} @@ -230,6 +289,76 @@ {% endif %} + + {% if payment.Refund_JSON %} +
+
+

+ + Refund JSON +

+ +
+
+ +
+
+ +
{{ payment.Refund_JSON | format_json }}
+ +
+
+ {% endif %} + + + + @@ -292,6 +421,55 @@