Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize/payment #5000

Merged
merged 8 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions controllers/account/api/v1/debt_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const (
// DebtSpec defines the desired state of Debt
type DebtSpec struct {
UserName string `json:"userName,omitempty"`
UserID string `json:"userID,omitempty"`
}

// DebtStatus defines the observed state of Debt
Expand Down
2 changes: 2 additions & 0 deletions controllers/account/api/v1/payment_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ type PaymentSpec struct {

// UserID is the user id who want to recharge
UserID string `json:"userID,omitempty"`
// UserCr is the user cr name who want to recharge
UserCR string `json:"userCR,omitempty"`
// Amount is the amount of recharge
Amount int64 `json:"amount,omitempty"`
// e.g. wechat, alipay, creditcard, etc.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ spec:
properties:
userName:
type: string
userID:
type: string
type: object
status:
description: DebtStatus defines the observed state of Debt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ spec:
userID:
description: UserID is the user id who want to recharge
type: string
userCR:
description: UserCr is the user cr name who want to recharge
type: string
type: object
status:
description: PaymentStatus defines the observed state of Payment
Expand Down
226 changes: 40 additions & 186 deletions controllers/account/controllers/account_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import (
"strings"
"time"

"sigs.k8s.io/controller-runtime/pkg/event"

"go.mongodb.org/mongo-driver/bson/primitive"

"github.com/google/uuid"
Expand All @@ -46,7 +44,6 @@ import (

accountv1 "github.com/labring/sealos/controllers/account/api/v1"
"github.com/labring/sealos/controllers/pkg/database"
"github.com/labring/sealos/controllers/pkg/pay"
"github.com/labring/sealos/controllers/pkg/resources"
pkgtypes "github.com/labring/sealos/controllers/pkg/types"
"github.com/labring/sealos/controllers/pkg/utils/env"
Expand All @@ -60,7 +57,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
)

const (
Expand Down Expand Up @@ -99,11 +95,6 @@ type AccountReconciler struct {
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete

func (r *AccountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
//It should not stop the normal process for the failure to delete the payment
// delete payments that exist for more than 5 minutes
if err := r.DeletePayment(ctx); err != nil {
r.Logger.Error(err, "delete payment failed")
}
user := &userv1.User{}
owner := ""
if err := r.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: req.Name}, user); err == nil {
Expand All @@ -122,77 +113,6 @@ func (r *AccountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
return ctrl.Result{}, err
}

payment := &accountv1.Payment{}
if err := r.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: req.Name}, payment); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if payment.Spec.UserID == "" || payment.Spec.Amount == 0 {
return ctrl.Result{}, fmt.Errorf("payment is invalid: %v", payment)
}
if payment.Status.TradeNO == "" {
return ctrl.Result{Requeue: true, RequeueAfter: time.Millisecond * 300}, nil
}
if payment.Status.Status == pay.PaymentSuccess {
return ctrl.Result{}, nil
}

account, err := r.syncAccount(ctx, getUsername(payment.Spec.UserID), payment.Namespace)
if err != nil {
return ctrl.Result{}, fmt.Errorf("get account failed: %v", err)
}

// get payment handler
payHandler, err := pay.NewPayHandler(payment.Spec.PaymentMethod)
if err != nil {
r.Logger.Error(err, "get payment handler failed")
return ctrl.Result{}, err
}
// get payment details(status, amount)
// TODO The GetPaymentDetails may cause issues when using Stripe
status, orderAmount, err := payHandler.GetPaymentDetails(payment.Status.TradeNO)
if err != nil {
return ctrl.Result{}, fmt.Errorf("query order failed: %v", err)
}
r.Logger.V(1).Info("query order details", "orderStatus", status, "orderAmount", orderAmount)
switch status {
case pay.PaymentSuccess:
//1¥ = 100WechatPayAmount; 1 WechatPayAmount = 10000 SealosAmount
payAmount := orderAmount * 10000
gift, err := r.getAmountWithRates(payAmount, account)
if err != nil {
r.Logger.Error(err, "get gift error")
}
if err = r.AccountV2.Payment(&pkgtypes.Payment{
PaymentRaw: pkgtypes.PaymentRaw{
UserUID: account.UserUID,
Amount: payAmount,
Gift: gift,
CreatedAt: payment.CreationTimestamp.Time,
RegionUserOwner: owner,
Method: payment.Spec.PaymentMethod,
TradeNO: payment.Status.TradeNO,
CodeURL: payment.Status.CodeURL,
},
}); err != nil {
r.Logger.Error(err, "save payment failed", "payment", payment)
return ctrl.Result{}, nil
}
payment.Status.Status = pay.PaymentSuccess
if err := r.Status().Update(ctx, payment); err != nil {
return ctrl.Result{}, fmt.Errorf("update payment failed: %v", err)
}

case pay.PaymentProcessing, pay.PaymentNotPaid:
return ctrl.Result{Requeue: true, RequeueAfter: time.Second}, nil
case pay.PaymentFailed, pay.PaymentExpired:
if err := r.Delete(ctx, payment); err != nil {
return ctrl.Result{}, fmt.Errorf("delete payment failed: %v", err)
}
return ctrl.Result{}, nil
default:
return ctrl.Result{}, fmt.Errorf("unknown status: %v", err)
}

return ctrl.Result{}, nil
}

Expand Down Expand Up @@ -246,82 +166,16 @@ func (r *AccountReconciler) adaptEphemeralStorageLimitRange(ctx context.Context,
})
}

// DeletePayment delete payments that exist for more than 5 minutes
func (r *AccountReconciler) DeletePayment(ctx context.Context) error {
payments := &accountv1.PaymentList{}
err := r.List(ctx, payments)
if err != nil {
return err
}
for _, payment := range payments.Items {
//get payment handler
payHandler, err := pay.NewPayHandler(payment.Spec.PaymentMethod)
if err != nil {
r.Logger.Error(err, "get payment handler failed")
return err
}
//delete payment if it is exist for more than 5 minutes
if time.Since(payment.CreationTimestamp.Time) > time.Minute*5 {
if payment.Status.TradeNO != "" {
status, amount, err := payHandler.GetPaymentDetails(payment.Status.TradeNO)
if err != nil {
r.Logger.Error(err, "get payment details failed")
}
if status == pay.PaymentSuccess {
if payment.Status.Status != pay.PaymentSuccess {
continue
}
r.Logger.Info("payment success, post delete payment cr", "payment", payment, "amount", amount)
}
// expire session
if err = payHandler.ExpireSession(payment.Status.TradeNO); err != nil {
r.Logger.Error(err, "cancel payment failed")
}
}
if err := r.Delete(ctx, &payment); err != nil {
return err
}
}
}
return nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *AccountReconciler) SetupWithManager(mgr ctrl.Manager, rateOpts controller.Options) error {
r.Logger = ctrl.Log.WithName("account_controller")
r.AccountSystemNamespace = env.GetEnvWithDefault(ACCOUNTNAMESPACEENV, DEFAULTACCOUNTNAMESPACE)
return ctrl.NewControllerManagedBy(mgr).
For(&userv1.User{}, builder.WithPredicates(OnlyCreatePredicate{})).
Watches(&accountv1.Payment{}, &handler.EnqueueRequestForObject{}, builder.WithPredicates(PaymentPredicate{})).
WithOptions(rateOpts).
Complete(r)
}

type PaymentPredicate struct{}

func (PaymentPredicate) Create(e event.CreateEvent) bool {
if payment, ok := e.Object.(*accountv1.Payment); ok {
fmt.Println("payment create", payment.Status.TradeNO, payment.Status.Status)
return payment.Status.TradeNO != "" && payment.Status.Status != pay.PaymentSuccess
}
return false
}

func (PaymentPredicate) Update(e event.UpdateEvent) bool {
if payment, ok := e.ObjectNew.(*accountv1.Payment); ok {
return payment.Status.TradeNO != "" && payment.Status.Status != pay.PaymentSuccess
}
return false
}

func (PaymentPredicate) Delete(_ event.DeleteEvent) bool {
return false
}

func (PaymentPredicate) Generic(_ event.GenericEvent) bool {
return false
}

func RawParseRechargeConfig() (activities pkgtypes.Activities, discountsteps []int64, discountratios []float64, returnErr error) {
// local test
//config, err := clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG"))
Expand Down Expand Up @@ -378,49 +232,49 @@ func parseConfigList(s string, list interface{}, configName string) error {
return nil
}

func GetUserOwner(user *userv1.User) string {
own := user.Annotations[userv1.UserAnnotationOwnerKey]
if own == "" {
return user.Name
}
return own
}
//func GetUserOwner(user *userv1.User) string {
// own := user.Annotations[userv1.UserAnnotationOwnerKey]
// if own == "" {
// return user.Name
// }
// return own
//}

const BaseUnit = 1_000_000

func (r *AccountReconciler) getAmountWithRates(amount int64, account *pkgtypes.Account) (amt int64, err error) {
//userActivities, err := pkgtypes.ParseUserActivities(account.Annotations)
//if err != nil {
// return nil, 0, fmt.Errorf("parse user activities failed: %w", err)
//}
//
//rechargeDiscount := pkgtypes.RechargeDiscount{
// DiscountSteps: r.RechargeStep,
// DiscountRates: r.RechargeRatio,
//}
//if len(userActivities) > 0 {
// if activityType, phase, _ := pkgtypes.GetUserActivityDiscount(r.Activities, &userActivities); phase != nil {
// if len(phase.RechargeDiscount.DiscountSteps) > 0 {
// rechargeDiscount.DiscountSteps = phase.RechargeDiscount.DiscountSteps
// rechargeDiscount.DiscountRates = phase.RechargeDiscount.DiscountRates
// }
// rechargeDiscount.SpecialDiscount = phase.RechargeDiscount.SpecialDiscount
// rechargeDiscount = phase.RechargeDiscount
// currentPhase := userActivities[activityType].Phases[userActivities[activityType].CurrentPhase]
// anno = pkgtypes.SetUserPhaseRechargeTimes(account.Annotations, activityType, currentPhase.Name, currentPhase.RechargeNums+1)
// }
//}
//return anno, getAmountWithDiscount(amount, rechargeDiscount), nil

discount, err := r.AccountV2.GetUserAccountRechargeDiscount(&pkgtypes.UserQueryOpts{UID: account.UserUID})
if err != nil {
return 0, fmt.Errorf("get user %s account recharge discount failed: %w", account.UserUID, err)
}
if discount == nil || discount.DiscountSteps == nil || discount.DiscountRates == nil {
return getAmountWithDiscount(amount, r.DefaultDiscount), nil
}
return getAmountWithDiscount(amount, *discount), nil
}
//func (r *AccountReconciler) getAmountWithRates(amount int64, account *pkgtypes.Account) (amt int64, err error) {
// //userActivities, err := pkgtypes.ParseUserActivities(account.Annotations)
// //if err != nil {
// // return nil, 0, fmt.Errorf("parse user activities failed: %w", err)
// //}
// //
// //rechargeDiscount := pkgtypes.RechargeDiscount{
// // DiscountSteps: r.RechargeStep,
// // DiscountRates: r.RechargeRatio,
// //}
// //if len(userActivities) > 0 {
// // if activityType, phase, _ := pkgtypes.GetUserActivityDiscount(r.Activities, &userActivities); phase != nil {
// // if len(phase.RechargeDiscount.DiscountSteps) > 0 {
// // rechargeDiscount.DiscountSteps = phase.RechargeDiscount.DiscountSteps
// // rechargeDiscount.DiscountRates = phase.RechargeDiscount.DiscountRates
// // }
// // rechargeDiscount.SpecialDiscount = phase.RechargeDiscount.SpecialDiscount
// // rechargeDiscount = phase.RechargeDiscount
// // currentPhase := userActivities[activityType].Phases[userActivities[activityType].CurrentPhase]
// // anno = pkgtypes.SetUserPhaseRechargeTimes(account.Annotations, activityType, currentPhase.Name, currentPhase.RechargeNums+1)
// // }
// //}
// //return anno, getAmountWithDiscount(amount, rechargeDiscount), nil
//
// discount, err := r.AccountV2.GetUserAccountRechargeDiscount(&pkgtypes.UserQueryOpts{UID: account.UserUID})
// if err != nil {
// return 0, fmt.Errorf("get user %s account recharge discount failed: %w", account.UserUID, err)
// }
// if discount == nil || discount.DiscountSteps == nil || discount.DiscountRates == nil {
// return getAmountWithDiscount(amount, r.DefaultDiscount), nil
// }
// return getAmountWithDiscount(amount, *discount), nil
//}

func getAmountWithDiscount(amount int64, discount pkgtypes.RechargeDiscount) int64 {
if discount.SpecialDiscount != nil && discount.SpecialDiscount[amount/BaseUnit] != 0 {
Expand Down
Loading
Loading