Skip to content

Commit

Permalink
Optimize/payment (labring#5000)
Browse files Browse the repository at this point in the history
* optimize payment controller

* add backward compatible code

* fix ci
  • Loading branch information
bxy4543 committed Aug 28, 2024
1 parent 3c418c3 commit e91f227
Show file tree
Hide file tree
Showing 11 changed files with 402 additions and 263 deletions.
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

0 comments on commit e91f227

Please sign in to comment.