feat: link transaction with balance and payment (#3052)

* feat: add and update transaction when recharging

* feat: add pay with balance

* feat: improve code format

* feat: update icon url for balance
This commit is contained in:
DacongDA
2024-07-12 15:48:37 +08:00
committed by GitHub
parent cef2ab213b
commit 7f2869cecb
9 changed files with 130 additions and 6 deletions

View File

@ -201,7 +201,7 @@ func notifyPayment(body []byte, owner string, paymentName string) (*Payment, *pp
} }
if payment.IsRecharge { if payment.IsRecharge {
err = updateUserBalance(payment.Owner, payment.User, payment.Price) err = UpdateUserBalance(payment.Owner, payment.User, payment.Price)
return payment, notifyResult, err return payment, notifyResult, err
} }
@ -222,6 +222,19 @@ func NotifyPayment(body []byte, owner string, paymentName string) (*Payment, err
if err != nil { if err != nil {
return nil, err 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 return payment, nil

View File

@ -227,13 +227,17 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
NotifyUrl: notifyUrl, NotifyUrl: notifyUrl,
PaymentEnv: paymentEnv, PaymentEnv: paymentEnv,
} }
// custom process for WeChat & WeChat Pay // custom process for WeChat & WeChat Pay
if provider.Type == "WeChat Pay" { if provider.Type == "WeChat Pay" {
payReq.PayerId, err = getUserExtraProperty(user, "WeChat", idp.BuildWechatOpenIdKey(provider.ClientId2)) payReq.PayerId, err = getUserExtraProperty(user, "WeChat", idp.BuildWechatOpenIdKey(provider.ClientId2))
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
} else if provider.Type == "Balance" {
payReq.PayerId = user.GetId()
} }
payResp, err := pProvider.Pay(payReq) payResp, err := pProvider.Pay(payReq)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -264,12 +268,46 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
OutOrderId: payResp.OrderId, 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" { if provider.Type == "Dummy" {
payment.State = pp.PaymentStatePaid payment.State = pp.PaymentStatePaid
err = updateUserBalance(user.Owner, user.Name, payment.Price) err = UpdateUserBalance(user.Owner, user.Name, payment.Price)
if err != nil { if err != nil {
return nil, nil, err 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) affected, err := AddPayment(payment)
@ -280,6 +318,17 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
if !affected { if !affected {
return nil, nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment)) 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 return payment, payResp.AttachInfo, nil
} }

View File

@ -309,6 +309,12 @@ func GetPaymentProvider(p *Provider) (pp.PaymentProvider, error) {
return nil, err return nil, err
} }
return pp, nil return pp, nil
} else if typ == "Balance" {
pp, err := pp.NewBalancePaymentProvider()
if err != nil {
return nil, err
}
return pp, nil
} else { } else {
return nil, fmt.Errorf("the payment provider type: %s is not supported", p.Type) return nil, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
} }

View File

@ -17,6 +17,7 @@ package object
import ( import (
"fmt" "fmt"
"github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/xorm-io/core" "github.com/xorm-io/core"
) )
@ -43,7 +44,7 @@ type Transaction struct {
Application string `xorm:"varchar(100)" json:"application"` Application string `xorm:"varchar(100)" json:"application"`
Payment string `xorm:"varchar(100)" json:"payment"` 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) { func GetTransactionCount(owner, field, value string) (int64, error) {

View File

@ -1158,7 +1158,7 @@ func GenerateIdForNewUser(application *Application) (string, error) {
return res, nil 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) user, err := getUser(owner, name)
if err != nil { if err != nil {
return err return err

50
pp/balance.go Normal file
View File

@ -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 ""
}

View File

@ -122,7 +122,7 @@ class PaymentResultPage extends React.Component {
payment: payment, payment: payment,
}); });
if (payment.state === "Created") { 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({ this.setState({
timeout: setTimeout(async() => { timeout: setTimeout(async() => {
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName); await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);

View File

@ -725,7 +725,7 @@ class ProviderEditPage extends React.Component {
(this.state.provider.category === "Web3") || (this.state.provider.category === "Web3") ||
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") || (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 === "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 : (
<React.Fragment> <React.Fragment>
{ {
(this.state.provider.category === "Storage" && this.state.provider.type === "Google Cloud Storage") || (this.state.provider.category === "Storage" && this.state.provider.type === "Google Cloud Storage") ||

View File

@ -247,6 +247,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/payment_paypal.png`, logo: `${StaticBaseUrl}/img/payment_paypal.png`,
url: "", url: "",
}, },
"Balance": {
logo: `${StaticBaseUrl}/img/payment_balance.svg`,
url: "",
},
"Alipay": { "Alipay": {
logo: `${StaticBaseUrl}/img/payment_alipay.png`, logo: `${StaticBaseUrl}/img/payment_alipay.png`,
url: "https://www.alipay.com/", url: "https://www.alipay.com/",
@ -1067,6 +1071,7 @@ export function getProviderTypeOptions(category) {
} else if (category === "Payment") { } else if (category === "Payment") {
return ([ return ([
{id: "Dummy", name: "Dummy"}, {id: "Dummy", name: "Dummy"},
{id: "Balance", name: "Balance"},
{id: "Alipay", name: "Alipay"}, {id: "Alipay", name: "Alipay"},
{id: "WeChat Pay", name: "WeChat Pay"}, {id: "WeChat Pay", name: "WeChat Pay"},
{id: "PayPal", name: "PayPal"}, {id: "PayPal", name: "PayPal"},