diff --git a/object/payment.go b/object/payment.go index 4282e9be30ed..02d8bf290de8 100644 --- a/object/payment.go +++ b/object/payment.go @@ -201,7 +201,7 @@ func notifyPayment(body []byte, owner string, paymentName string) (*Payment, *pp } if payment.IsRecharge { - err = updateUserBalance(payment.Owner, payment.User, payment.Price) + err = UpdateUserBalance(payment.Owner, payment.User, payment.Price) return payment, notifyResult, err } @@ -222,6 +222,19 @@ func NotifyPayment(body []byte, owner string, paymentName string) (*Payment, err if err != nil { return nil, err } + + transaction, err := GetTransaction(payment.GetId()) + if err != nil { + return nil, err + } + + if transaction != nil { + transaction.State = payment.State + _, err = UpdateTransaction(transaction.GetId(), transaction) + if err != nil { + return nil, err + } + } } return payment, nil diff --git a/object/product.go b/object/product.go index 15feef0979db..d27b0b1caaeb 100644 --- a/object/product.go +++ b/object/product.go @@ -227,13 +227,17 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host NotifyUrl: notifyUrl, PaymentEnv: paymentEnv, } + // custom process for WeChat & WeChat Pay if provider.Type == "WeChat Pay" { payReq.PayerId, err = getUserExtraProperty(user, "WeChat", idp.BuildWechatOpenIdKey(provider.ClientId2)) if err != nil { return nil, nil, err } + } else if provider.Type == "Balance" { + payReq.PayerId = user.GetId() } + payResp, err := pProvider.Pay(payReq) if err != nil { return nil, nil, err @@ -264,12 +268,46 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host OutOrderId: payResp.OrderId, } + transaction := &Transaction{ + Owner: payment.Owner, + Name: payment.Name, + DisplayName: payment.DisplayName, + Provider: provider.Name, + Category: provider.Category, + Type: provider.Type, + + ProductName: product.Name, + ProductDisplayName: product.DisplayName, + Detail: product.Detail, + Tag: product.Tag, + Currency: product.Currency, + Amount: payment.Price, + ReturnUrl: payment.ReturnUrl, + + User: payment.User, + Application: owner, + Payment: payment.GetId(), + + State: pp.PaymentStateCreated, + } + if provider.Type == "Dummy" { payment.State = pp.PaymentStatePaid - err = updateUserBalance(user.Owner, user.Name, payment.Price) + err = UpdateUserBalance(user.Owner, user.Name, payment.Price) if err != nil { return nil, nil, err } + } else if provider.Type == "Balance" { + if product.Price > user.Balance { + return nil, nil, fmt.Errorf("insufficient user balance") + } + transaction.Amount = -transaction.Amount + err = UpdateUserBalance(user.Owner, user.Name, -product.Price) + if err != nil { + return nil, nil, err + } + payment.State = pp.PaymentStatePaid + transaction.State = pp.PaymentStatePaid } affected, err := AddPayment(payment) @@ -280,6 +318,17 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host if !affected { return nil, nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment)) } + + if product.IsRecharge || provider.Type == "Balance" { + affected, err = AddTransaction(transaction) + if err != nil { + return nil, nil, err + } + if !affected { + return nil, nil, fmt.Errorf("failed to add transaction: %s", util.StructToJson(payment)) + } + } + return payment, payResp.AttachInfo, nil } diff --git a/object/provider.go b/object/provider.go index fe07d734debe..5262502ba7f8 100644 --- a/object/provider.go +++ b/object/provider.go @@ -309,6 +309,12 @@ func GetPaymentProvider(p *Provider) (pp.PaymentProvider, error) { return nil, err } return pp, nil + } else if typ == "Balance" { + pp, err := pp.NewBalancePaymentProvider() + if err != nil { + return nil, err + } + return pp, nil } else { return nil, fmt.Errorf("the payment provider type: %s is not supported", p.Type) } diff --git a/object/transaction.go b/object/transaction.go index a47920fff06f..92ab5eeb63b6 100644 --- a/object/transaction.go +++ b/object/transaction.go @@ -17,6 +17,7 @@ package object import ( "fmt" + "github.com/casdoor/casdoor/pp" "github.com/casdoor/casdoor/util" "github.com/xorm-io/core" ) @@ -43,7 +44,7 @@ type Transaction struct { Application string `xorm:"varchar(100)" json:"application"` Payment string `xorm:"varchar(100)" json:"payment"` - State string `xorm:"varchar(100)" json:"state"` + State pp.PaymentState `xorm:"varchar(100)" json:"state"` } func GetTransactionCount(owner, field, value string) (int64, error) { diff --git a/object/user.go b/object/user.go index 1d3a28b7f3a5..47cce810bd3b 100644 --- a/object/user.go +++ b/object/user.go @@ -1158,7 +1158,7 @@ func GenerateIdForNewUser(application *Application) (string, error) { return res, nil } -func updateUserBalance(owner string, name string, balance float64) error { +func UpdateUserBalance(owner string, name string, balance float64) error { user, err := getUser(owner, name) if err != nil { return err diff --git a/pp/balance.go b/pp/balance.go new file mode 100644 index 000000000000..7c400c3f76cb --- /dev/null +++ b/pp/balance.go @@ -0,0 +1,50 @@ +// Copyright 2024 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pp + +import ( + "fmt" + + "github.com/casdoor/casdoor/util" +) + +type BalancePaymentProvider struct{} + +func NewBalancePaymentProvider() (*BalancePaymentProvider, error) { + pp := &BalancePaymentProvider{} + return pp, nil +} + +func (pp *BalancePaymentProvider) Pay(r *PayReq) (*PayResp, error) { + owner, _ := util.GetOwnerAndNameFromId(r.PayerId) + return &PayResp{ + PayUrl: r.ReturnUrl, + OrderId: fmt.Sprintf("%s/%s", owner, r.PaymentName), + }, nil +} + +func (pp *BalancePaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) { + return &NotifyResult{ + PaymentStatus: PaymentStatePaid, + }, nil +} + +func (pp *BalancePaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) { + return "", nil +} + +func (pp *BalancePaymentProvider) GetResponseError(err error) string { + return "" +} diff --git a/web/src/PaymentResultPage.js b/web/src/PaymentResultPage.js index c445293a8525..4de000ece58c 100644 --- a/web/src/PaymentResultPage.js +++ b/web/src/PaymentResultPage.js @@ -122,7 +122,7 @@ class PaymentResultPage extends React.Component { payment: payment, }); if (payment.state === "Created") { - if (["PayPal", "Stripe", "Alipay", "WeChat Pay"].includes(payment.type)) { + if (["PayPal", "Stripe", "Alipay", "WeChat Pay", "Balance"].includes(payment.type)) { this.setState({ timeout: setTimeout(async() => { await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName); diff --git a/web/src/ProviderEditPage.js b/web/src/ProviderEditPage.js index 780d44f0a278..ac3e5289b46b 100644 --- a/web/src/ProviderEditPage.js +++ b/web/src/ProviderEditPage.js @@ -725,7 +725,7 @@ class ProviderEditPage extends React.Component { (this.state.provider.category === "Web3") || (this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") || (this.state.provider.category === "SMS" && this.state.provider.type === "Custom HTTP SMS") || - (this.state.provider.category === "Notification" && (this.state.provider.type === "Google Chat" || this.state.provider.type === "Custom HTTP")) ? null : ( + (this.state.provider.category === "Notification" && (this.state.provider.type === "Google Chat" || this.state.provider.type === "Custom HTTP") || this.state.provider.type === "Balance") ? null : ( { (this.state.provider.category === "Storage" && this.state.provider.type === "Google Cloud Storage") || diff --git a/web/src/Setting.js b/web/src/Setting.js index 8a5277547217..14f75cb920cd 100644 --- a/web/src/Setting.js +++ b/web/src/Setting.js @@ -247,6 +247,10 @@ export const OtherProviderInfo = { logo: `${StaticBaseUrl}/img/payment_paypal.png`, url: "", }, + "Balance": { + logo: `${StaticBaseUrl}/img/payment_balance.svg`, + url: "", + }, "Alipay": { logo: `${StaticBaseUrl}/img/payment_alipay.png`, url: "https://www.alipay.com/", @@ -1067,6 +1071,7 @@ export function getProviderTypeOptions(category) { } else if (category === "Payment") { return ([ {id: "Dummy", name: "Dummy"}, + {id: "Balance", name: "Balance"}, {id: "Alipay", name: "Alipay"}, {id: "WeChat Pay", name: "WeChat Pay"}, {id: "PayPal", name: "PayPal"},