Compare commits

..

17 Commits

Author SHA1 Message Date
Yixiang Zhao
d43d7d1ae9 feat: support master password for ldap user (#561)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-12 21:06:38 +08:00
Yang Luo
c906f1e5d2 Add user and state to payment pages. 2022-03-12 20:03:58 +08:00
Gucheng Wang
37a26e2a91 Fix delete-resource authz check. 2022-03-11 11:27:52 +08:00
leoshine
e7018e3de4 docs: add a tip to create db for the first time (#550)
* add a tip to create db schema ahead of time

* add a tip to create db schema ahead of time

* docs: add a tip to create db schema ahead of time
2022-03-10 11:03:52 +08:00
halozhy
3a64e4dcd8 docs: add a tip to create db schema ahead of time (#547) 2022-03-10 09:58:00 +08:00
Gucheng Wang
380cdc5f7e fix: The top-right logout button sometimes disappears for small screen size (#544) 2022-03-08 21:14:04 +08:00
Gucheng Wang
3602d9b9a7 fix: improve error messages 2022-03-07 15:16:09 +08:00
Yixiang Zhao
8a9cc2eb8f fix: change client_secret in refresh_token API as optional (#540)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-07 13:52:51 +08:00
Gucheng Wang
4f9a13f18a fix: comment TestReadSheet() 2022-03-07 13:50:08 +08:00
Yang Luo
a4fc04474e Add NotifyPayment API. 2022-03-07 00:33:45 +08:00
Yang Luo
bf5d4eea48 Add alipay provider. 2022-03-06 22:46:02 +08:00
Yang Luo
0e40a1d922 Check application existence in login(). 2022-03-06 00:09:57 +08:00
Yang Luo
ab777c1d73 Add Conf.EnableExtraPages 2022-03-05 23:51:55 +08:00
Товарищ программист
ca0fa5fc40 fix: fix missing parameters when signup (#533) 2022-03-05 16:47:08 +08:00
Nekotoxin
cfbce79e32 fix: add ie support (ie >= 9) (#538)
* fix: add ie support (ie > 9)

* fix: add support for IE11

* fix: small fix

* fix: fix
2022-03-05 16:32:37 +08:00
Yang Luo
efc07f0919 Improve translation. 2022-03-05 00:53:59 +08:00
fuh
a783315fa2 fix: Returns a valid userId when form.Username is empty (#523)
* fix: Returns a valid userId when form.Username is empty

* fix: format code
2022-03-04 23:39:12 +08:00
46 changed files with 679 additions and 143 deletions

View File

@@ -82,6 +82,14 @@ Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which fo
username:password@tcp(database_ip:database_port)/
```
Then create an empty schema (database) named `casdoor` in your relational database. After the program runs for the first time, it will automatically create tables in this schema.
You can also edit `main.go`, modify `false` to `true`. It will automatically create the schema (database) named `casdoor` in this database.
```bash
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
```
#### Run
Casdoor provides two run modes, the difference is binary size and user prompt.

View File

@@ -130,8 +130,6 @@ func (c *ApiController) Signup() {
}
}
userId := fmt.Sprintf("%s/%s", form.Organization, form.Username)
id := util.GenerateId()
if application.GetSignupItemRule("ID") == "Incremental" {
lastUser := object.GetLastUser(form.Organization)
@@ -203,6 +201,7 @@ func (c *ApiController) Signup() {
record.User = user.Name
go object.AddRecord(record)
userId := fmt.Sprintf("%s/%s", user.Owner, user.Name)
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
c.ResponseOk(userId)

View File

@@ -16,6 +16,7 @@ package controllers
import (
"encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
@@ -85,7 +86,7 @@ func (c *ApiController) GetUserApplication() {
id := c.Input().Get("id")
user := object.GetUser(id)
if user == nil {
c.ResponseError("No such user.")
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", id))
return
}

View File

@@ -192,7 +192,7 @@ func (c *ApiController) Login() {
user = object.GetUserByFields(form.Organization, form.Username)
if user == nil {
c.ResponseError("No such user.")
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", form.Organization, form.Username))
return
}
} else {
@@ -204,6 +204,11 @@ func (c *ApiController) Login() {
resp = &Response{Status: "error", Msg: msg}
} else {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if application == nil {
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
return
}
resp = c.HandleLoggedIn(application, user, &form)
record := object.NewRecord(c.Ctx)
@@ -213,6 +218,11 @@ func (c *ApiController) Login() {
}
} else if form.Provider != "" {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if application == nil {
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
return
}
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider))
providerItem := application.GetProviderItem(provider.Name)
@@ -383,6 +393,11 @@ func (c *ApiController) Login() {
if c.GetSessionUsername() != "" {
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if application == nil {
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
return
}
user := c.getCurrentUser()
resp = c.HandleLoggedIn(application, user, &form)
} else {

View File

@@ -132,3 +132,11 @@ func wrapActionResponse(affected bool) *Response {
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
}
}
func wrapErrorResponse(err error) *Response {
if err == nil {
return &Response{Status: "ok", Msg: ""}
} else {
return &Response{Status: "error", Msg: err.Error()}
}
}

View File

@@ -16,10 +16,12 @@ package controllers
import (
"encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
"github.com/go-pay/gopay/alipay"
)
// GetPayments
@@ -114,3 +116,26 @@ func (c *ApiController) DeletePayment() {
c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment))
c.ServeJSON()
}
// @Title NotifyPayment
// @Tag Payment API
// @Description notify payment
// @Param body body object.Payment true "The details of the payment"
// @Success 200 {object} controllers.Response The Response object
// @router /notify-payment [post]
func (c *ApiController) NotifyPayment() {
bm, err := alipay.ParseNotifyToBodyMap(c.Ctx.Request)
if err != nil {
panic(err)
}
ok := object.NotifyPayment(bm)
if ok {
_, err = c.Ctx.ResponseWriter.Write([]byte("success"))
if err != nil {
panic(err)
}
} else {
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))
}
}

View File

@@ -16,6 +16,7 @@ package controllers
import (
"encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
@@ -114,3 +115,36 @@ func (c *ApiController) DeleteProduct() {
c.Data["json"] = wrapActionResponse(object.DeleteProduct(&product))
c.ServeJSON()
}
// @Title BuyProduct
// @Tag Product API
// @Description buy product
// @Param id query string true "The id of the product"
// @Param providerId query string true "The id of the provider"
// @Success 200 {object} controllers.Response The Response object
// @router /buy-product [post]
func (c *ApiController) BuyProduct() {
id := c.Input().Get("id")
providerId := c.Input().Get("providerId")
host := c.Ctx.Request.Host
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError("Please login first")
return
}
user := object.GetUser(userId)
if user == nil {
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
return
}
payUrl, err := object.BuyProduct(id, providerId, user, host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payUrl)
}

View File

@@ -193,7 +193,7 @@ func (c *ApiController) GetOAuthToken() {
// @Param refresh_token query string true "OAuth refresh token"
// @Param scope query string true "OAuth scope"
// @Param client_id query string true "OAuth client id"
// @Param client_secret query string true "OAuth client secret"
// @Param client_secret query string false "OAuth client secret"
// @Success 200 {object} object.TokenWrapper The Response object
// @router /login/oauth/refresh_token [post]
func (c *ApiController) RefreshToken() {

View File

@@ -190,7 +190,7 @@ func (c *ApiController) GetEmailAndPhone() {
user := object.GetUserByFields(form.Organization, form.Username)
if user == nil {
c.ResponseError("No such user.")
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", form.Organization, form.Username))
return
}
@@ -226,7 +226,7 @@ func (c *ApiController) SetPassword() {
requestUserId := c.GetSessionUsername()
if requestUserId == "" {
c.ResponseError("Please login first.")
c.ResponseError("Please login first")
return
}

View File

@@ -68,13 +68,12 @@ func (c *ApiController) SendVerificationCode() {
organization := object.GetOrganization(orgId)
application := object.GetApplicationByOrganizationName(organization.Name)
if checkUser == "true" && user == nil &&
object.GetUserByFields(organization.Name, dest) == nil {
c.ResponseError("No such user.")
if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil {
c.ResponseError("Please login first")
return
}
sendResp := errors.New("Invalid dest type.")
sendResp := errors.New("Invalid dest type")
switch destType {
case "email":
if !util.IsEmailValid(dest) {
@@ -121,7 +120,7 @@ func (c *ApiController) ResetEmailOrPhone() {
user := object.GetUser(userId)
if user == nil {
c.ResponseError("No such user.")
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
return
}

3
go.mod
View File

@@ -13,6 +13,7 @@ require (
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-pay/gopay v1.5.72
github.com/go-sql-driver/mysql v1.5.0
github.com/golang-jwt/jwt/v4 v4.1.0
github.com/google/uuid v1.2.0
@@ -29,7 +30,7 @@ require (
github.com/stretchr/testify v1.7.0
github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect

6
go.sum
View File

@@ -124,6 +124,8 @@ github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-pay/gopay v1.5.72 h1:3zm64xMBhJBa8rXbm//q5UiGgOa4WO5XYEnU394N2Zw=
github.com/go-pay/gopay v1.5.72/go.mod h1:0qOGIJuFW7PKDOjmecwKyW0mgsVImgwB9yPJj0ilpn8=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
@@ -379,8 +381,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954 h1:BkypuErRT9A9I/iljuaG3/zdMjd/J6m8tKKJQtGfSdA=
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=

View File

@@ -33,8 +33,10 @@ type Cert struct {
BitSize int `json:"bitSize"`
ExpireInYears int `json:"expireInYears"`
PublicKey string `xorm:"mediumtext" json:"publicKey"`
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
PublicKey string `xorm:"mediumtext" json:"publicKey"`
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"`
AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"`
}
func GetMaskedCert(cert *Cert) *Cert {

View File

@@ -179,13 +179,14 @@ func CheckUserPassword(organization string, username string, password string) (*
if user.IsForbidden {
return nil, "the user is forbidden to sign in, please contact the administrator"
}
//for ldap users
if user.Ldap != "" {
return checkLdapUserPassword(user, password)
}
msg := CheckPassword(user, password)
if msg != "" {
//for ldap users
if user.Ldap != "" {
return checkLdapUserPassword(user, password)
}
return nil, msg
}

View File

@@ -18,6 +18,8 @@ import (
"fmt"
"github.com/casdoor/casdoor/util"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/alipay"
"xorm.io/core"
)
@@ -27,15 +29,17 @@ type Payment struct {
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Provider string `xorm:"varchar(100)" json:"provider"`
Type string `xorm:"varchar(100)" json:"type"`
Organization string `xorm:"varchar(100)" json:"organization"`
User string `xorm:"varchar(100)" json:"user"`
Good string `xorm:"varchar(100)" json:"good"`
Amount string `xorm:"varchar(100)" json:"amount"`
Currency string `xorm:"varchar(100)" json:"currency"`
Provider string `xorm:"varchar(100)" json:"provider"`
Type string `xorm:"varchar(100)" json:"type"`
Organization string `xorm:"varchar(100)" json:"organization"`
User string `xorm:"varchar(100)" json:"user"`
ProductId string `xorm:"varchar(100)" json:"productId"`
ProductName string `xorm:"varchar(100)" json:"productName"`
Price float64 `json:"price"`
Currency string `xorm:"varchar(100)" json:"currency"`
State string `xorm:"varchar(100)" json:"state"`
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
State string `xorm:"varchar(100)" json:"state"`
}
func GetPaymentCount(owner, field, value string) int {
@@ -124,6 +128,65 @@ func DeletePayment(payment *Payment) bool {
return affected != 0
}
func NotifyPayment(bm gopay.BodyMap) bool {
owner := "admin"
productName := bm.Get("subject")
paymentId := bm.Get("out_trade_no")
priceString := bm.Get("total_amount")
price := util.ParseFloat(priceString)
productId := bm.Get("productId")
providerId := bm.Get("providerId")
product := getProduct(owner, productId)
if product == nil {
panic(fmt.Errorf("the product: %s does not exist", productId))
}
if productName != product.DisplayName {
panic(fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", productName, product.DisplayName))
}
if price != product.Price {
panic(fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", price, product.Price))
}
payment := getPayment(owner, paymentId)
if payment == nil {
panic(fmt.Errorf("the payment: %s does not exist", paymentId))
}
provider, err := product.getProvider(providerId)
if err != nil {
panic(err)
}
cert := getCert(owner, provider.Cert)
if cert == nil {
panic(fmt.Errorf("the cert: %s does not exist", provider.Cert))
}
ok, err := alipay.VerifySignWithCert(cert.AuthorityPublicKey, bm)
if err != nil {
panic(err)
}
if ok {
payment.State = "Paid"
} else {
if cert == nil {
panic(fmt.Errorf("VerifySignWithCert() failed: %v", ok))
}
//payment.State = "Failed"
}
affected, err := adapter.Engine.ID(core.PK{owner, paymentId}).AllCols().Update(payment)
if err != nil {
panic(err)
}
return affected != 0
}
func (payment *Payment) GetId() string {
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
}

View File

@@ -17,6 +17,7 @@ package object
import (
"fmt"
"github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@@ -31,7 +32,7 @@ type Product struct {
Detail string `xorm:"varchar(100)" json:"detail"`
Tag string `xorm:"varchar(100)" json:"tag"`
Currency string `xorm:"varchar(100)" json:"currency"`
Price int `json:"price"`
Price float64 `json:"price"`
Quantity int `json:"quantity"`
Sold int `json:"sold"`
Providers []string `xorm:"varchar(100)" json:"providers"`
@@ -128,3 +129,83 @@ func DeleteProduct(product *Product) bool {
func (product *Product) GetId() string {
return fmt.Sprintf("%s/%s", product.Owner, product.Name)
}
func (product *Product) isValidProvider(provider *Provider) bool {
for _, providerName := range product.Providers {
if providerName == provider.Name {
return true
}
}
return false
}
func (product *Product) getProvider(providerId string) (*Provider, error) {
provider := getProvider(product.Owner, providerId)
if provider == nil {
return nil, fmt.Errorf("the payment provider: %s does not exist", providerId)
}
if !product.isValidProvider(provider) {
return nil, fmt.Errorf("the payment provider: %s is not valid for the product: %s", providerId, product.Name)
}
return provider, nil
}
func BuyProduct(id string, providerId string, user *User, host string) (string, error) {
product := GetProduct(id)
if product == nil {
return "", fmt.Errorf("the product: %s does not exist", id)
}
provider, err := product.getProvider(providerId)
if err != nil {
return "", err
}
cert := getCert(product.Owner, provider.Cert)
if cert == nil {
return "", fmt.Errorf("the cert: %s does not exist", provider.Cert)
}
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
if pProvider == nil {
return "", fmt.Errorf("the payment provider type: %s is not supported", provider.Type)
}
paymentId := util.GenerateTimeId()
productName := product.DisplayName
productId := product.Name
originFrontend, originBackend := getOriginFromHost(host)
returnUrl := fmt.Sprintf("%s/payments/%s", originFrontend, paymentId)
notifyUrl := fmt.Sprintf("%s/api/notify-payment", originBackend)
payUrl, err := pProvider.Pay(productName, productId, providerId, paymentId, product.Price, returnUrl, notifyUrl)
if err != nil {
return "", err
}
payment := Payment{
Owner: product.Owner,
Name: paymentId,
CreatedTime: util.GetCurrentTime(),
DisplayName: paymentId,
Provider: provider.Name,
Type: provider.Type,
Organization: user.Owner,
User: user.Name,
ProductId: productId,
ProductName: productName,
Price: product.Price,
Currency: product.Currency,
PayUrl: payUrl,
State: "Created",
}
affected := AddPayment(&payment)
if !affected {
return "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
}
return payUrl, err
}

43
object/product_test.go Normal file
View File

@@ -0,0 +1,43 @@
// Copyright 2022 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.
//go:build !skipCi
package object
import (
"testing"
"github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util"
)
func TestProduct(t *testing.T) {
InitConfig()
product := GetProduct("admin/product_123")
provider := getProvider(product.Owner, "provider_pay_alipay")
cert := getCert(product.Owner, "cert-pay-alipay")
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
paymentId := util.GenerateTimeId()
returnUrl := ""
notifyUrl := ""
payUrl, err := pProvider.Pay(product.DisplayName, product.Name, provider.Name, paymentId, product.Price, returnUrl, notifyUrl)
if err != nil {
panic(err)
}
println(payUrl)
}

View File

@@ -35,6 +35,7 @@ type Provider struct {
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
Cert string `xorm:"varchar(100)" json:"cert"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`

View File

@@ -379,7 +379,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
Scope: "",
}
}
if application.ClientSecret != clientSecret {
if clientSecret != "" && application.ClientSecret != clientSecret {
return &TokenWrapper{
AccessToken: "error: invalid client_secret",
TokenType: "",

67
pp/alipay.go Normal file
View File

@@ -0,0 +1,67 @@
// Copyright 2022 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 (
"context"
"fmt"
"strings"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/alipay"
)
type AlipayPaymentProvider struct {
Client *alipay.Client
}
func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) *AlipayPaymentProvider {
pp := &AlipayPaymentProvider{}
client, err := alipay.NewClient(appId, appPrivateKey, true)
if err != nil {
panic(err)
}
err = client.SetCertSnByContent([]byte(appPublicKey), []byte(authorityRootPublicKey), []byte(authorityPublicKey))
if err != nil {
panic(err)
}
pp.Client = client
return pp
}
func (pp *AlipayPaymentProvider) Pay(productName string, productId string, providerId string, paymentId string, price float64, returnUrl string, notifyUrl string) (string, error) {
//pp.Client.DebugSwitch = gopay.DebugOn
priceString := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", price), "0"), ".")
bm := gopay.BodyMap{}
bm.Set("subject", productName)
bm.Set("out_trade_no", paymentId)
bm.Set("total_amount", priceString)
bm.Set("return_url", returnUrl)
bm.Set("notify_url", notifyUrl)
bm.Set("productId", productId)
bm.Set("providerId", productId)
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
if err != nil {
return "", err
}
return payUrl, nil
}

26
pp/provider.go Normal file
View File

@@ -0,0 +1,26 @@
// Copyright 2022 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
type PaymentProvider interface {
Pay(productName string, productId string, providerId string, paymentId string, price float64, returnUrl string, notifyUrl string) (string, error)
}
func GetPaymentProvider(typ string, appId string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
if typ == "Alipay" {
return NewAlipayPaymentProvider(appId, appPublicKey, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
}
return nil
}

View File

@@ -84,8 +84,8 @@ func getObject(ctx *context.Context) (string, string) {
if path == "/api/delete-resource" {
tokens := strings.Split(obj.Name, "/")
if len(tokens) >= 2 {
obj.Name = tokens[len(tokens)-2]
if len(tokens) >= 5 {
obj.Name = tokens[4]
}
}

View File

@@ -156,12 +156,14 @@ func initAPI() {
beego.Router("/api/update-product", &controllers.ApiController{}, "POST:UpdateProduct")
beego.Router("/api/add-product", &controllers.ApiController{}, "POST:AddProduct")
beego.Router("/api/delete-product", &controllers.ApiController{}, "POST:DeleteProduct")
beego.Router("/api/buy-product", &controllers.ApiController{}, "POST:BuyProduct")
beego.Router("/api/get-payments", &controllers.ApiController{}, "GET:GetPayments")
beego.Router("/api/get-payment", &controllers.ApiController{}, "GET:GetPayment")
beego.Router("/api/update-payment", &controllers.ApiController{}, "POST:UpdatePayment")
beego.Router("/api/add-payment", &controllers.ApiController{}, "POST:AddPayment")
beego.Router("/api/delete-payment", &controllers.ApiController{}, "POST:DeletePayment")
beego.Router("/api/notify-payment", &controllers.ApiController{}, "POST:NotifyPayment")
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")

View File

@@ -23,6 +23,7 @@ import (
"os"
"strconv"
"strings"
"time"
"unicode"
"github.com/google/uuid"
@@ -41,6 +42,15 @@ func ParseInt(s string) int {
return i
}
func ParseFloat(s string) float64 {
f, err := strconv.ParseFloat(s, 64)
if err != nil {
panic(err)
}
return f
}
func ParseBool(s string) bool {
i := ParseInt(s)
return i != 0
@@ -88,6 +98,17 @@ func GenerateId() string {
return uuid.NewString()
}
func GenerateTimeId() string {
timestamp := time.Now().Unix()
tm := time.Unix(timestamp, 0)
t := tm.Format("20060102_150405")
random := uuid.NewString()[0:7]
res := fmt.Sprintf("%s_%s", t, random)
return res
}
func GetId(name string) string {
return fmt.Sprintf("admin/%s", name)
}

View File

@@ -29,7 +29,9 @@
"react-i18next": "^11.8.7",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"react-social-login-buttons": "^3.4.0"
"react-social-login-buttons": "^3.4.0",
"react-app-polyfill": "^3.0.0",
"core-js": "^3.21.1"
},
"scripts": {
"start": "cross-env PORT=7001 craco start",
@@ -46,12 +48,14 @@
"production": [
">0.2%",
"not dead",
"not op_mini all"
"not op_mini all",
"ie > 8"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
"last 1 safari version",
"ie > 8"
]
},
"devDependencies": {

View File

@@ -51,6 +51,7 @@ import PaymentEditPage from "./PaymentEditPage";
import AccountPage from "./account/AccountPage";
import HomePage from "./basic/HomePage";
import CustomGithubCorner from "./CustomGithubCorner";
import * as Conf from "./Conf";
import * as Auth from "./auth/Auth";
import SignupPage from "./auth/SignupPage";
@@ -434,20 +435,24 @@ class App extends Component {
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/products">
<Link to="/products">
{i18next.t("general:Products")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/payments">
<Link to="/payments">
{i18next.t("general:Payments")}
</Link>
</Menu.Item>
);
if (Conf.EnableExtraPages) {
res.push(
<Menu.Item key="/products">
<Link to="/products">
{i18next.t("general:Products")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/payments">
<Link to="/payments">
{i18next.t("general:Payments")}
</Link>
</Menu.Item>
);
}
res.push(
<Menu.Item key="/swagger">
<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>
@@ -550,7 +555,7 @@ class App extends Component {
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{ lineHeight: '64px'}}
style={{lineHeight: '64px', width: '100%', position: 'absolute'}}
>
{
this.renderMenu()

View File

@@ -166,7 +166,7 @@ class ApplicationEditPage extends React.Component {
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel("general:Logo", i18next.t("general:Logo - Tooltip"))} :
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
</Col>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth:'100%'} :{}}>
<Row style={{marginTop: '20px'}} >

View File

@@ -17,3 +17,5 @@ export const GithubRepo = "https://github.com/casdoor/casdoor";
export const ForceLanguage = "";
export const DefaultLanguage = "en";
export const EnableExtraPages = false;

View File

@@ -112,7 +112,7 @@ class PaymentEditPage extends React.Component {
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
{Setting.getLabel(i18next.t("payment:Type"), i18next.t("payment:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.payment.type} onChange={e => {
@@ -122,20 +122,20 @@ class PaymentEditPage extends React.Component {
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("payment:Good"), i18next.t("payment:Good - Tooltip"))} :
{Setting.getLabel(i18next.t("payment:Product"), i18next.t("payment:Product - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.payment.good} onChange={e => {
// this.updatePaymentField('good', e.target.value);
<Input value={this.state.payment.productName} onChange={e => {
// this.updatePaymentField('productName', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("payment:Amount"), i18next.t("payment:Amount - Tooltip"))} :
{Setting.getLabel(i18next.t("payment:Price"), i18next.t("payment:Price - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.payment.amount} onChange={e => {
<Input value={this.state.payment.price} onChange={e => {
// this.updatePaymentField('amount', e.target.value);
}} />
</Col>
@@ -150,6 +150,16 @@ class PaymentEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("payment:State"), i18next.t("payment:State - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.payment.state} onChange={e => {
// this.updatePaymentField('state', e.target.value);
}} />
</Col>
</Row>
</Card>
)
}

View File

@@ -34,8 +34,9 @@ class PaymentListPage extends BaseListPage {
type: "PayPal",
organization: "built-in",
user: "admin",
good: "A notebook computer",
amount: "300",
productId: "computer-1",
productName: "A notebook computer",
price: 300.00,
currency: "USD",
state: "Paid",
}
@@ -72,11 +73,11 @@ class PaymentListPage extends BaseListPage {
const columns = [
{
title: i18next.t("general:Organization"),
dataIndex: 'owner',
key: 'owner',
dataIndex: 'organization',
key: 'organization',
width: '120px',
sorter: true,
...this.getColumnSearchProps('owner'),
...this.getColumnSearchProps('organization'),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
@@ -104,7 +105,7 @@ class PaymentListPage extends BaseListPage {
title: i18next.t("general:Name"),
dataIndex: 'name',
key: 'name',
width: '150px',
width: '180px',
fixed: 'left',
sorter: true,
...this.getColumnSearchProps('name'),
@@ -151,10 +152,10 @@ class PaymentListPage extends BaseListPage {
}
},
{
title: i18next.t("provider:Type"),
title: i18next.t("payment:Type"),
dataIndex: 'type',
key: 'type',
width: '110px',
width: '140px',
align: 'center',
filterMultiple: false,
filters: Setting.getProviderTypeOptions('Payment').map((o) => {return {text:o.id, value:o.name}}),
@@ -165,20 +166,20 @@ class PaymentListPage extends BaseListPage {
}
},
{
title: i18next.t("payment:Good"),
dataIndex: 'good',
key: 'good',
width: '160px',
title: i18next.t("payment:Product"),
dataIndex: 'productName',
key: 'productName',
// width: '160px',
sorter: true,
...this.getColumnSearchProps('good'),
...this.getColumnSearchProps('productName'),
},
{
title: i18next.t("payment:Amount"),
dataIndex: 'amount',
key: 'amount',
title: i18next.t("payment:Price"),
dataIndex: 'price',
key: 'price',
width: '120px',
sorter: true,
...this.getColumnSearchProps('amount'),
...this.getColumnSearchProps('price'),
},
{
title: i18next.t("payment:Currency"),
@@ -188,6 +189,14 @@ class PaymentListPage extends BaseListPage {
sorter: true,
...this.getColumnSearchProps('currency'),
},
{
title: i18next.t("payment:State"),
dataIndex: 'state',
key: 'state',
width: '120px',
sorter: true,
...this.getColumnSearchProps('state'),
},
{
title: i18next.t("general:Action"),
dataIndex: '',

View File

@@ -13,11 +13,12 @@
// limitations under the License.
import React from "react";
import {Button, Descriptions} from "antd";
import {Button, Descriptions, Spin} from "antd";
import i18next from "i18next";
import * as ProductBackend from "./backend/ProductBackend";
import * as ProviderBackend from "./backend/ProviderBackend";
import * as Provider from "./auth/Provider";
import * as Setting from "./Setting";
class ProductBuyPage extends React.Component {
constructor(props) {
@@ -27,6 +28,7 @@ class ProductBuyPage extends React.Component {
productName: props.match?.params.productName,
product: null,
providers: [],
isPlacingOrder: false,
};
}
@@ -107,6 +109,29 @@ class ProductBuyPage extends React.Component {
// }
}
buyProduct(product, provider) {
this.setState({
isPlacingOrder: true,
});
ProductBackend.buyProduct(this.state.product.owner, this.state.productName, provider.name)
.then((res) => {
if (res.msg === "") {
const payUrl = res.data;
Setting.goToLink(payUrl);
} else {
Setting.showMessage("error", res.msg);
this.setState({
isPlacingOrder: false,
});
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
});
}
getPayButton(provider) {
let text = provider.type;
if (provider.type === "Alipay") {
@@ -131,11 +156,11 @@ class ProductBuyPage extends React.Component {
renderProviderButton(provider, product) {
return (
<span key={provider.name} style={{width: "200px", marginRight: "20px", marginBottom: "10px"}}>
<a style={{width: "200px"}} href={this.getPayUrl(product, provider)}>
<span style={{width: "200px", cursor: "pointer"}} onClick={() => this.buyProduct(product, provider)}>
{
this.getPayButton(provider)
}
</a>
</span>
</span>
)
}
@@ -163,31 +188,33 @@ class ProductBuyPage extends React.Component {
return (
<div>
<Descriptions title={i18next.t("product:Buy Product")} bordered>
<Descriptions.Item label={i18next.t("general:Name")} span={3}>
<Spin spinning={this.state.isPlacingOrder} size="large" tip={i18next.t("product:Placing order...")} style={{paddingTop: "10%"}} >
<Descriptions title={i18next.t("product:Buy Product")} bordered>
<Descriptions.Item label={i18next.t("general:Name")} span={3}>
<span style={{fontSize: 28}}>
{product?.displayName}
</span>
</Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Detail")}><span style={{fontSize: 16}}>{product?.detail}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Tag")}><span style={{fontSize: 16}}>{product?.tag}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:SKU")}><span style={{fontSize: 16}}>{product?.name}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Image")} span={3}>
<img src={product?.image} alt={product?.image} height={90} style={{marginBottom: '20px'}}/>
</Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Price")}>
</Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Detail")}><span style={{fontSize: 16}}>{product?.detail}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Tag")}><span style={{fontSize: 16}}>{product?.tag}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:SKU")}><span style={{fontSize: 16}}>{product?.name}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Image")} span={3}>
<img src={product?.image} alt={product?.image} height={90} style={{marginBottom: '20px'}}/>
</Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Price")}>
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
{`${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`}
</span>
</Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Quantity")}><span style={{fontSize: 16}}>{product?.quantity}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Sold")}><span style={{fontSize: 16}}>{product?.sold}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Pay")} span={3}>
{
this.renderPay(product)
}
</Descriptions.Item>
</Descriptions>
</Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Quantity")}><span style={{fontSize: 16}}>{product?.quantity}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Sold")}><span style={{fontSize: 16}}>{product?.sold}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Pay")} span={3}>
{
this.renderPay(product)
}
</Descriptions.Item>
</Descriptions>
</Spin>
</div>
)
}

View File

@@ -225,11 +225,12 @@ class ProductListPage extends BaseListPage {
title: i18next.t("general:Action"),
dataIndex: '',
key: 'op',
width: '170px',
width: '230px',
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} onClick={() => this.props.history.push(`/products/${record.name}/buy`)}>{i18next.t("product:Buy")}</Button>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/products/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
title={`Sure to delete product: ${record.name} ?`}

View File

@@ -484,6 +484,7 @@ class LoginPage extends React.Component {
<span style={{float: "right"}}>
{i18next.t("login:No account?")}&nbsp;
<a onClick={() => {
sessionStorage.setItem("loginURL", window.location.href)
Setting.goToSignup(this, application);
}}>
{i18next.t("login:sign up now")}

View File

@@ -65,7 +65,12 @@ class ResultPage extends React.Component {
subTitle={i18next.t("signup:Please click the below button to sign in")}
extra={[
<Button type="primary" key="login" onClick={() => {
Setting.goToLogin(this, application);
let linkInStorage = sessionStorage.getItem("loginURL")
if (linkInStorage != "") {
Setting.goToLink(linkInStorage)
} else {
Setting.goToLogin(this, application)
}
}}>
{i18next.t("login:Sign In")}
</Button>

View File

@@ -557,7 +557,12 @@ class SignupPage extends React.Component {
</Button>
&nbsp;&nbsp;{i18next.t("signup:Have account?")}&nbsp;
<a onClick={() => {
Setting.goToLogin(this, application);
let linkInStorage = sessionStorage.getItem("loginURL")
if(linkInStorage != ""){
Setting.goToLink(linkInStorage)
}else{
Setting.goToLogin(this, application)
}
}}>
{i18next.t("signup:sign in now")}
</a>

View File

@@ -54,3 +54,10 @@ export function deleteProduct(product) {
body: JSON.stringify(newProduct),
}).then(res => res.json());
}
export function buyProduct(owner, name, providerId) {
return fetch(`${Setting.ServerUrl}/api/buy-product?id=${owner}/${encodeURIComponent(name)}&providerId=${providerId}`, {
method: 'POST',
credentials: 'include',
}).then(res => res.json());
}

View File

@@ -12,18 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import 'core-js/es';
import 'react-app-polyfill/ie9';
import 'react-app-polyfill/stable';
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter } from 'react-router-dom';
import {BrowserRouter} from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change

View File

@@ -126,6 +126,7 @@
"LDAPs": "LDAPs",
"LDAPs - Tooltip": "LDAPs - Tooltip",
"Last name": "Last name",
"Logo": "Logo",
"Logo - Tooltip": "App's image tag",
"Master password": "Master-Passwort",
"Master password - Tooltip": "Masterpasswort - Tooltip",
@@ -251,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier"
},
"payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"Good": "Good",
"Good - Tooltip": "Good - Tooltip",
"New Payment": "New Payment"
"New Payment": "New Payment",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
},
"permission": {
"Actions": "Aktionen",
@@ -273,6 +276,7 @@
},
"product": {
"Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product",
"CNY": "CNY",
"Currency": "Currency",
@@ -287,6 +291,7 @@
"Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",

View File

@@ -126,6 +126,7 @@
"LDAPs": "LDAPs",
"LDAPs - Tooltip": "LDAPs - Tooltip",
"Last name": "Last name",
"Logo": "Logo",
"Logo - Tooltip": "Logo - Tooltip",
"Master password": "Master password",
"Master password - Tooltip": "Master password - Tooltip",
@@ -251,14 +252,16 @@
"Website URL - Tooltip": "Website URL - Tooltip"
},
"payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"Good": "Good",
"Good - Tooltip": "Good - Tooltip",
"New Payment": "New Payment"
"New Payment": "New Payment",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
},
"permission": {
"Actions": "Actions",
@@ -273,6 +276,7 @@
},
"product": {
"Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product",
"CNY": "CNY",
"Currency": "Currency",
@@ -287,6 +291,7 @@
"Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",

View File

@@ -126,6 +126,7 @@
"LDAPs": "LDAPs",
"LDAPs - Tooltip": "LDAPs - Infobulle",
"Last name": "Last name",
"Logo": "Logo",
"Logo - Tooltip": "App's image tag",
"Master password": "Mot de passe maître",
"Master password - Tooltip": "Mot de passe maître - Infobulle",
@@ -251,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier"
},
"payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"Good": "Good",
"Good - Tooltip": "Good - Tooltip",
"New Payment": "New Payment"
"New Payment": "New Payment",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
},
"permission": {
"Actions": "Actions",
@@ -273,6 +276,7 @@
},
"product": {
"Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product",
"CNY": "CNY",
"Currency": "Currency",
@@ -287,6 +291,7 @@
"Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",

View File

@@ -126,6 +126,7 @@
"LDAPs": "LDAP",
"LDAPs - Tooltip": "LDAP - ツールチップ",
"Last name": "Last name",
"Logo": "Logo",
"Logo - Tooltip": "App's image tag",
"Master password": "マスターパスワード",
"Master password - Tooltip": "マスターパスワード - ツールチップ",
@@ -251,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier"
},
"payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"Good": "Good",
"Good - Tooltip": "Good - Tooltip",
"New Payment": "New Payment"
"New Payment": "New Payment",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
},
"permission": {
"Actions": "アクション",
@@ -273,6 +276,7 @@
},
"product": {
"Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product",
"CNY": "CNY",
"Currency": "Currency",
@@ -287,6 +291,7 @@
"Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",

View File

@@ -126,6 +126,7 @@
"LDAPs": "LDAPs",
"LDAPs - Tooltip": "LDAPs - Tooltip",
"Last name": "Last name",
"Logo": "Logo",
"Logo - Tooltip": "App's image tag",
"Master password": "Master password",
"Master password - Tooltip": "Master password - Tooltip",
@@ -251,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier"
},
"payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"Good": "Good",
"Good - Tooltip": "Good - Tooltip",
"New Payment": "New Payment"
"New Payment": "New Payment",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
},
"permission": {
"Actions": "Actions",
@@ -273,6 +276,7 @@
},
"product": {
"Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product",
"CNY": "CNY",
"Currency": "Currency",
@@ -287,6 +291,7 @@
"Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",

View File

@@ -126,6 +126,7 @@
"LDAPs": "LDAPы",
"LDAPs - Tooltip": "LDAPs - Подсказки",
"Last name": "Last name",
"Logo": "Logo",
"Logo - Tooltip": "App's image tag",
"Master password": "Мастер-пароль",
"Master password - Tooltip": "Мастер-пароль - Tooltip",
@@ -251,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier"
},
"payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"Good": "Good",
"Good - Tooltip": "Good - Tooltip",
"New Payment": "New Payment"
"New Payment": "New Payment",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
},
"permission": {
"Actions": "Действия",
@@ -273,6 +276,7 @@
},
"product": {
"Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product",
"CNY": "CNY",
"Currency": "Currency",
@@ -287,6 +291,7 @@
"Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",

View File

@@ -14,7 +14,7 @@
"Enable signup": "启用注册",
"Enable signup - Tooltip": "是否允许用户注册",
"File uploaded successfully": "文件上传成功",
"Grant types": "Grant types",
"Grant types": "OAuth授权类型",
"Grant types - Tooltip": "选择允许哪些OAuth协议中的Grant types",
"New Application": "添加应用",
"Password ON": "开启密码",
@@ -126,6 +126,7 @@
"LDAPs": "LDAP",
"LDAPs - Tooltip": "LDAPs",
"Last name": "姓氏",
"Logo": "Logo",
"Logo - Tooltip": "应用程序向外展示的图标",
"Master password": "万能密码",
"Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题",
@@ -251,14 +252,16 @@
"Website URL - Tooltip": "网页地址"
},
"payment": {
"Amount": "金额",
"Amount - Tooltip": "付款的金额",
"Currency": "币种",
"Currency - Tooltip": "如USD美元CNY人民币等",
"Edit Payment": "编辑付款",
"Good": "商品",
"Good - Tooltip": "购买的商品名称",
"New Payment": "添加付款"
"New Payment": "添加付款",
"Price": "价格",
"Price - Tooltip": "商品价格",
"Product": "商品",
"Product - Tooltip": "商品名称",
"Type": "支付方式",
"Type - Tooltip": "商品购买时的支付方式"
},
"permission": {
"Actions": "动作",
@@ -273,6 +276,7 @@
},
"product": {
"Alipay": "支付宝",
"Buy": "购买",
"Buy Product": "购买商品",
"CNY": "人民币",
"Currency": "币种",
@@ -287,6 +291,7 @@
"Payment providers": "支付提供商",
"Payment providers - Tooltip": "支付提供商 - 工具提示",
"Paypal": "Paypal",
"Placing order...": "正在下单...",
"Price": "价格",
"Price - Tooltip": "价格 - 工具提示",
"Quantity": "库存",

View File

@@ -3760,6 +3760,11 @@ core-js@^2.4.0:
resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
core-js@^3.19.2, core-js@^3.21.1:
version "3.21.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.1.tgz#f2e0ddc1fc43da6f904706e8e955bc19d06a0d94"
integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==
core-js@^3.6.5:
version "3.9.1"
resolved "https://registry.npmjs.org/core-js/-/core-js-3.9.1.tgz#cec8de593db8eb2a85ffb0dbdeb312cb6e5460ae"
@@ -9812,6 +9817,18 @@ react-app-polyfill@^2.0.0:
regenerator-runtime "^0.13.7"
whatwg-fetch "^3.4.1"
react-app-polyfill@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz#95221e0a9bd259e5ca6b177c7bb1cb6768f68fd7"
integrity sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==
dependencies:
core-js "^3.19.2"
object-assign "^4.1.1"
promise "^8.1.0"
raf "^3.4.1"
regenerator-runtime "^0.13.9"
whatwg-fetch "^3.6.2"
react-codemirror2@^7.2.1:
version "7.2.1"
resolved "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-7.2.1.tgz#38dab492fcbe5fb8ebf5630e5bb7922db8d3a10c"
@@ -10158,6 +10175,11 @@ regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
regenerator-runtime@^0.13.9:
version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
regenerator-transform@^0.14.2:
version "0.14.5"
resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4"
@@ -12155,7 +12177,7 @@ whatwg-encoding@^1.0.5:
dependencies:
iconv-lite "0.4.24"
whatwg-fetch@^3.4.1:
whatwg-fetch@^3.4.1, whatwg-fetch@^3.6.2:
version "3.6.2"
resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c"
integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==

View File

@@ -11,7 +11,8 @@
// 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.
// +build !skipCi
//go:build !skipCi
package xlsx