mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-22 06:03:50 +08:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
9f7924a6e0 | |||
377e200837 | |||
93a76de044 | |||
35bef969fd | |||
4dca3bd3f7 | |||
5de417ecf7 | |||
bf24594fb4 | |||
4a87b4790e | |||
fde8c4b5f6 | |||
55a84644e1 | |||
ca87dd7dea | |||
32af4a766e | |||
4d035bf66d | |||
743dcc9725 |
@ -88,6 +88,10 @@ p, *, *, GET, /api/get-application, *, *
|
||||
p, *, *, GET, /api/get-user, *, *
|
||||
p, *, *, GET, /api/get-user-application, *, *
|
||||
p, *, *, GET, /api/get-resources, *, *
|
||||
p, *, *, GET, /api/get-product, *, *
|
||||
p, *, *, POST, /api/buy-product, *, *
|
||||
p, *, *, GET, /api/get-payment, *, *
|
||||
p, *, *, GET, /api/get-providers, *, *
|
||||
p, *, *, POST, /api/unlink, *, *
|
||||
p, *, *, POST, /api/set-password, *, *
|
||||
p, *, *, POST, /api/send-verification-code, *, *
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -172,6 +173,13 @@ func (c *ApiController) Signup() {
|
||||
Karma: 0,
|
||||
}
|
||||
|
||||
if len(organization.Tags) > 0 {
|
||||
tokens := strings.Split(organization.Tags[0], "|")
|
||||
if len(tokens) > 0 {
|
||||
user.Tag = tokens[0]
|
||||
}
|
||||
}
|
||||
|
||||
if application.GetSignupItemRule("Display name") == "First, last" {
|
||||
if form.FirstName != "" || form.LastName != "" {
|
||||
user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName)
|
||||
|
@ -167,9 +167,16 @@ func (c *ApiController) Login() {
|
||||
var verificationCodeType string
|
||||
var checkResult string
|
||||
|
||||
if form.Name != "" {
|
||||
user = object.GetUserByFields(form.Organization, form.Name)
|
||||
}
|
||||
|
||||
// check result through Email or Phone
|
||||
if strings.Contains(form.Username, "@") {
|
||||
verificationCodeType = "email"
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
|
||||
form.Username = user.Email
|
||||
}
|
||||
checkResult = object.CheckVerificationCode(form.Username, form.Code)
|
||||
} else {
|
||||
verificationCodeType = "phone"
|
||||
@ -178,6 +185,9 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(responseText)
|
||||
return
|
||||
}
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
|
||||
form.Username = user.Phone
|
||||
}
|
||||
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
|
||||
checkResult = object.CheckVerificationCode(checkPhone, form.Code)
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-pay/gopay/alipay"
|
||||
)
|
||||
|
||||
// GetPayments
|
||||
@ -50,6 +49,24 @@ func (c *ApiController) GetPayments() {
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserPayments
|
||||
// @Title GetUserPayments
|
||||
// @Tag Payment API
|
||||
// @Description get payments for a user
|
||||
// @Param owner query string true "The owner of payments"
|
||||
// @Param organization query string true "The organization of the user"
|
||||
// @Param user query string true "The username of the user"
|
||||
// @Success 200 {array} object.Payment The Response object
|
||||
// @router /get-user-payments [get]
|
||||
func (c *ApiController) GetUserPayments() {
|
||||
owner := c.Input().Get("owner")
|
||||
organization := c.Input().Get("organization")
|
||||
user := c.Input().Get("user")
|
||||
|
||||
payments := object.GetUserPayments(owner, organization, user)
|
||||
c.ResponseOk(payments)
|
||||
}
|
||||
|
||||
// @Title GetPayment
|
||||
// @Tag Payment API
|
||||
// @Description get payment
|
||||
@ -124,14 +141,16 @@ func (c *ApiController) DeletePayment() {
|
||||
// @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)
|
||||
}
|
||||
owner := c.Ctx.Input.Param(":owner")
|
||||
providerName := c.Ctx.Input.Param(":provider")
|
||||
productName := c.Ctx.Input.Param(":product")
|
||||
paymentName := c.Ctx.Input.Param(":payment")
|
||||
|
||||
ok := object.NotifyPayment(bm)
|
||||
body := c.Ctx.Input.RequestBody
|
||||
|
||||
ok := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName)
|
||||
if ok {
|
||||
_, err = c.Ctx.ResponseWriter.Write([]byte("success"))
|
||||
_, err := c.Ctx.ResponseWriter.Write([]byte("success"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -119,13 +119,13 @@ func (c *ApiController) DeleteProduct() {
|
||||
// @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"
|
||||
// @Param id query string true "The id of the product"
|
||||
// @Param providerName query string true "The name 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")
|
||||
providerName := c.Input().Get("providerName")
|
||||
host := c.Ctx.Request.Host
|
||||
|
||||
userId := c.GetSessionUsername()
|
||||
@ -140,7 +140,7 @@ func (c *ApiController) BuyProduct() {
|
||||
return
|
||||
}
|
||||
|
||||
payUrl, err := object.BuyProduct(id, providerId, user, host)
|
||||
payUrl, err := object.BuyProduct(id, providerName, user, host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@ -194,15 +194,19 @@ func (c *ApiController) GetEmailAndPhone() {
|
||||
return
|
||||
}
|
||||
|
||||
respUser := object.User{Email: user.Email, Phone: user.Phone, Name: user.Name}
|
||||
respUser := object.User{Name: user.Name}
|
||||
var contentType string
|
||||
switch form.Username {
|
||||
case user.Email:
|
||||
contentType = "email"
|
||||
respUser.Email = user.Email
|
||||
case user.Phone:
|
||||
contentType = "phone"
|
||||
respUser.Phone = user.Phone
|
||||
case user.Name:
|
||||
contentType = "username"
|
||||
respUser.Email = util.GetMaskedEmail(user.Email)
|
||||
respUser.Phone = util.GetMaskedPhone(user.Phone)
|
||||
}
|
||||
|
||||
c.ResponseOk(respUser, contentType)
|
||||
|
@ -74,8 +74,16 @@ func (c *ApiController) SendVerificationCode() {
|
||||
}
|
||||
|
||||
sendResp := errors.New("Invalid dest type")
|
||||
|
||||
if user == nil && checkUser != "" && checkUser != "true" {
|
||||
_, name := util.GetOwnerAndNameFromId(orgId)
|
||||
user = object.GetUser(fmt.Sprintf("%s/%s", name, checkUser))
|
||||
}
|
||||
switch destType {
|
||||
case "email":
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == dest {
|
||||
dest = user.Email
|
||||
}
|
||||
if !util.IsEmailValid(dest) {
|
||||
c.ResponseError("Invalid Email address")
|
||||
return
|
||||
@ -84,6 +92,9 @@ func (c *ApiController) SendVerificationCode() {
|
||||
provider := application.GetEmailProvider()
|
||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||
case "phone":
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
|
||||
dest = user.Phone
|
||||
}
|
||||
if !util.IsPhoneCnValid(dest) {
|
||||
c.ResponseError("Invalid phone number")
|
||||
return
|
||||
|
2
go.mod
2
go.mod
@ -9,7 +9,7 @@ require (
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||
github.com/casbin/casbin/v2 v2.30.1
|
||||
github.com/casbin/xorm-adapter/v2 v2.5.1
|
||||
github.com/casdoor/go-sms-sender v0.0.5
|
||||
github.com/casdoor/go-sms-sender v0.2.0
|
||||
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
|
||||
|
2
go.sum
2
go.sum
@ -81,6 +81,8 @@ github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0B
|
||||
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
|
||||
github.com/casdoor/go-sms-sender v0.0.5 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
|
||||
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
|
||||
github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
|
||||
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
|
@ -47,9 +47,10 @@ func initBuiltInOrganization() {
|
||||
DisplayName: "Built-in Organization",
|
||||
WebsiteUrl: "https://example.com",
|
||||
Favicon: "https://cdn.casbin.com/static/favicon.ico",
|
||||
PasswordType: "plain",
|
||||
PhonePrefix: "86",
|
||||
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||
PasswordType: "plain",
|
||||
Tags: []string{},
|
||||
}
|
||||
AddOrganization(organization)
|
||||
}
|
||||
|
@ -25,15 +25,16 @@ type Organization struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
|
||||
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
|
||||
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
}
|
||||
|
||||
func GetOrganizationCount(owner, field, value string) int {
|
||||
|
@ -16,10 +16,9 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/alipay"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
@ -29,17 +28,22 @@ 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"`
|
||||
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"`
|
||||
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"`
|
||||
ProductName string `xorm:"varchar(100)" json:"productName"`
|
||||
ProductDisplayName string `xorm:"varchar(100)" json:"productDisplayName"`
|
||||
|
||||
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
Detail string `xorm:"varchar(100)" json:"detail"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||
Price float64 `json:"price"`
|
||||
|
||||
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
|
||||
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
Message string `xorm:"varchar(1000)" json:"message"`
|
||||
}
|
||||
|
||||
func GetPaymentCount(owner, field, value string) int {
|
||||
@ -62,6 +66,16 @@ func GetPayments(owner string) []*Payment {
|
||||
return payments
|
||||
}
|
||||
|
||||
func GetUserPayments(owner string, organization string, user string) []*Payment {
|
||||
payments := []*Payment{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&payments, &Payment{Owner: owner, Organization: organization, User: user})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return payments
|
||||
}
|
||||
|
||||
func GetPaginationPayments(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Payment {
|
||||
payments := []*Payment{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
@ -128,63 +142,59 @@ 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))
|
||||
func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (*Payment, error) {
|
||||
payment := getPayment(owner, paymentName)
|
||||
if payment == nil {
|
||||
return nil, fmt.Errorf("the payment: %s does not exist", paymentName)
|
||||
}
|
||||
|
||||
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))
|
||||
product := getProduct(owner, productName)
|
||||
if product == nil {
|
||||
return nil, fmt.Errorf("the product: %s does not exist", productName)
|
||||
}
|
||||
|
||||
provider, err := product.getProvider(providerName)
|
||||
if err != nil {
|
||||
return payment, err
|
||||
}
|
||||
|
||||
pProvider, cert, err := provider.getPaymentProvider()
|
||||
if err != nil {
|
||||
return payment, err
|
||||
}
|
||||
|
||||
productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey)
|
||||
if err != nil {
|
||||
return payment, err
|
||||
}
|
||||
|
||||
if productDisplayName != "" && productDisplayName != product.DisplayName {
|
||||
return nil, fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", productDisplayName, 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))
|
||||
return nil, 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))
|
||||
}
|
||||
return payment, nil
|
||||
}
|
||||
|
||||
provider, err := product.getProvider(providerId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) bool {
|
||||
payment, err := notifyPayment(request, body, owner, providerName, productName, paymentName)
|
||||
|
||||
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))
|
||||
if payment != nil {
|
||||
if err != nil {
|
||||
payment.State = "Error"
|
||||
payment.Message = err.Error()
|
||||
} else {
|
||||
payment.State = "Paid"
|
||||
}
|
||||
//payment.State = "Failed"
|
||||
|
||||
UpdatePayment(payment.GetId(), payment)
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, paymentId}).AllCols().Update(payment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
ok := err == nil
|
||||
return ok
|
||||
}
|
||||
|
||||
func (payment *Payment) GetId() string {
|
||||
|
@ -17,7 +17,6 @@ package object
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/pp"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
@ -36,6 +35,7 @@ type Product struct {
|
||||
Quantity int `json:"quantity"`
|
||||
Sold int `json:"sold"`
|
||||
Providers []string `xorm:"varchar(100)" json:"providers"`
|
||||
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
}
|
||||
@ -152,55 +152,54 @@ func (product *Product) getProvider(providerId string) (*Provider, error) {
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func BuyProduct(id string, providerId string, user *User, host string) (string, error) {
|
||||
func BuyProduct(id string, providerName 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)
|
||||
provider, err := product.getProvider(providerName)
|
||||
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, _, err := provider.getPaymentProvider()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
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
|
||||
owner := product.Owner
|
||||
productName := product.Name
|
||||
paymentName := util.GenerateTimeId()
|
||||
productDisplayName := product.DisplayName
|
||||
|
||||
originFrontend, originBackend := getOriginFromHost(host)
|
||||
returnUrl := fmt.Sprintf("%s/payments/%s", originFrontend, paymentId)
|
||||
notifyUrl := fmt.Sprintf("%s/api/notify-payment", originBackend)
|
||||
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName)
|
||||
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
|
||||
|
||||
payUrl, err := pProvider.Pay(productName, productId, providerId, paymentId, product.Price, returnUrl, notifyUrl)
|
||||
payUrl, err := pProvider.Pay(providerName, productName, paymentName, productDisplayName, 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",
|
||||
Owner: product.Owner,
|
||||
Name: paymentName,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: paymentName,
|
||||
Provider: provider.Name,
|
||||
Type: provider.Type,
|
||||
Organization: user.Owner,
|
||||
User: user.Name,
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
Detail: product.Detail,
|
||||
Tag: product.Tag,
|
||||
Currency: product.Currency,
|
||||
Price: product.Price,
|
||||
PayUrl: payUrl,
|
||||
ReturnUrl: product.ReturnUrl,
|
||||
State: "Created",
|
||||
}
|
||||
affected := AddPayment(&payment)
|
||||
if !affected {
|
||||
|
@ -29,7 +29,7 @@ func TestProduct(t *testing.T) {
|
||||
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)
|
||||
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
||||
|
||||
paymentId := util.GenerateTimeId()
|
||||
returnUrl := ""
|
||||
|
@ -17,6 +17,7 @@ package object
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/pp"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
@ -182,6 +183,23 @@ func DeleteProvider(provider *Provider) bool {
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
|
||||
cert := &Cert{}
|
||||
if p.Cert != "" {
|
||||
cert = getCert(p.Owner, p.Cert)
|
||||
if cert == nil {
|
||||
return nil, nil, fmt.Errorf("the cert: %s does not exist", p.Cert)
|
||||
}
|
||||
}
|
||||
|
||||
pProvider := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
||||
if pProvider == nil {
|
||||
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
||||
}
|
||||
|
||||
return pProvider, cert, nil
|
||||
}
|
||||
|
||||
func (p *Provider) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import (
|
||||
|
||||
type Resource struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
Name string `xorm:"varchar(200) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
|
@ -18,6 +18,9 @@ import "github.com/casdoor/go-sms-sender"
|
||||
|
||||
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
|
||||
client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
|
||||
if provider.Type == go_sms_sender.HuaweiCloud {
|
||||
client, err = go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -380,7 +380,9 @@ func AddUsers(users []*User) bool {
|
||||
|
||||
affected, err := adapter.Engine.Insert(users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if !strings.Contains(err.Error(), "Duplicate entry") {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
|
43
pp/alipay.go
43
pp/alipay.go
@ -17,8 +17,9 @@ package pp
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"net/http"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/alipay"
|
||||
)
|
||||
@ -44,20 +45,20 @@ func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey s
|
||||
return pp
|
||||
}
|
||||
|
||||
func (pp *AlipayPaymentProvider) Pay(productName string, productId string, providerId string, paymentId string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, paymentName string, productDisplayName 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("providerName", providerName)
|
||||
bm.Set("productName", productName)
|
||||
|
||||
bm.Set("return_url", returnUrl)
|
||||
bm.Set("notify_url", notifyUrl)
|
||||
|
||||
bm.Set("productId", productId)
|
||||
bm.Set("providerId", productId)
|
||||
bm.Set("subject", productDisplayName)
|
||||
bm.Set("out_trade_no", paymentName)
|
||||
bm.Set("total_amount", getPriceString(price))
|
||||
|
||||
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
|
||||
if err != nil {
|
||||
@ -65,3 +66,27 @@ func (pp *AlipayPaymentProvider) Pay(productName string, productId string, provi
|
||||
}
|
||||
return payUrl, nil
|
||||
}
|
||||
|
||||
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||
bm, err := alipay.ParseNotifyToBodyMap(request)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
|
||||
providerName := bm.Get("providerName")
|
||||
productName := bm.Get("productName")
|
||||
|
||||
productDisplayName := bm.Get("subject")
|
||||
paymentName := bm.Get("out_trade_no")
|
||||
price := util.ParseFloat(bm.Get("total_amount"))
|
||||
|
||||
ok, err := alipay.VerifySignWithCert(authorityPublicKey, bm)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
if !ok {
|
||||
return "", "", 0, "", "", fmt.Errorf("VerifySignWithCert() failed: %v", ok)
|
||||
}
|
||||
|
||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||
}
|
||||
|
232
pp/gc.go
Normal file
232
pp/gc.go
Normal file
@ -0,0 +1,232 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type GcPaymentProvider struct {
|
||||
Xmpch string
|
||||
SecretKey string
|
||||
Host string
|
||||
}
|
||||
|
||||
type GcPayReqInfo struct {
|
||||
OrderDate string `json:"orderdate"`
|
||||
OrderNo string `json:"orderno"`
|
||||
Amount string `json:"amount"`
|
||||
PayerId string `json:"payerid"`
|
||||
PayerName string `json:"payername"`
|
||||
Xmpch string `json:"xmpch"`
|
||||
ReturnUrl string `json:"return_url"`
|
||||
NotifyUrl string `json:"notify_url"`
|
||||
}
|
||||
|
||||
type GcPayRespInfo struct {
|
||||
Jylsh string `json:"jylsh"`
|
||||
Amount string `json:"amount"`
|
||||
PayerId string `json:"payerid"`
|
||||
PayerName string `json:"payername"`
|
||||
PayUrl string `json:"payurl"`
|
||||
}
|
||||
|
||||
type GcNotifyRespInfo struct {
|
||||
Xmpch string `json:"xmpch"`
|
||||
OrderDate string `json:"orderdate"`
|
||||
OrderNo string `json:"orderno"`
|
||||
Amount float64 `json:"amount"`
|
||||
Jylsh string `json:"jylsh"`
|
||||
TradeNo string `json:"tradeno"`
|
||||
PayMethod string `json:"paymethod"`
|
||||
OrderState string `json:"orderstate"`
|
||||
ReturnType string `json:"return_type"`
|
||||
PayerId string `json:"payerid"`
|
||||
PayerName string `json:"payername"`
|
||||
}
|
||||
|
||||
type GcRequestBody struct {
|
||||
Op string `json:"op"`
|
||||
Xmpch string `json:"xmpch"`
|
||||
Version string `json:"version"`
|
||||
Data string `json:"data"`
|
||||
RequestTime string `json:"requesttime"`
|
||||
Sign string `json:"sign"`
|
||||
}
|
||||
|
||||
type GcResponseBody struct {
|
||||
Op string `json:"op"`
|
||||
Xmpch string `json:"xmpch"`
|
||||
Version string `json:"version"`
|
||||
ReturnCode string `json:"return_code"`
|
||||
ReturnMsg string `json:"return_msg"`
|
||||
Data string `json:"data"`
|
||||
NotifyTime string `json:"notifytime"`
|
||||
Sign string `json:"sign"`
|
||||
}
|
||||
|
||||
func NewGcPaymentProvider(clientId string, clientSecret string, host string) *GcPaymentProvider {
|
||||
pp := &GcPaymentProvider{}
|
||||
|
||||
pp.Xmpch = clientId
|
||||
pp.SecretKey = clientSecret
|
||||
pp.Host = host
|
||||
return pp
|
||||
}
|
||||
|
||||
func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
|
||||
client := &http.Client{}
|
||||
|
||||
var resp *http.Response
|
||||
var err error
|
||||
|
||||
contentType := "text/plain;charset=UTF-8"
|
||||
body := bytes.NewReader(postBytes)
|
||||
|
||||
req, err := http.NewRequest("POST", pp.Host, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
func (pp *GcPaymentProvider) Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||
payReqInfo := GcPayReqInfo{
|
||||
OrderDate: util.GenerateSimpleTimeId(),
|
||||
OrderNo: util.GenerateTimeId(),
|
||||
Amount: getPriceString(price),
|
||||
PayerId: "",
|
||||
PayerName: "",
|
||||
Xmpch: pp.Xmpch,
|
||||
ReturnUrl: returnUrl,
|
||||
NotifyUrl: notifyUrl,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(payReqInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
body := GcRequestBody{
|
||||
Op: "OrderCreate",
|
||||
Xmpch: pp.Xmpch,
|
||||
Version: "1.4",
|
||||
Data: base64.StdEncoding.EncodeToString(b),
|
||||
RequestTime: util.GenerateSimpleTimeId(),
|
||||
}
|
||||
|
||||
params := fmt.Sprintf("data=%s&op=%s&requesttime=%s&version=%s&xmpch=%s%s", body.Data, body.Op, body.RequestTime, body.Version, body.Xmpch, pp.SecretKey)
|
||||
body.Sign = strings.ToUpper(util.GetMd5Hash(params))
|
||||
|
||||
bodyBytes, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
respBytes, err := pp.doPost(bodyBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var respBody GcResponseBody
|
||||
err = json.Unmarshal(respBytes, &respBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if respBody.ReturnCode != "SUCCESS" {
|
||||
return "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
|
||||
}
|
||||
|
||||
payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var payRespInfo GcPayRespInfo
|
||||
err = json.Unmarshal(payRespInfoBytes, &payRespInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return payRespInfo.PayUrl, nil
|
||||
}
|
||||
|
||||
func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||
reqBody := GcRequestBody{}
|
||||
m, err := url.ParseQuery(string(body))
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
|
||||
reqBody.Op = m["op"][0]
|
||||
reqBody.Xmpch = m["xmpch"][0]
|
||||
reqBody.Version = m["version"][0]
|
||||
reqBody.Data = m["data"][0]
|
||||
reqBody.RequestTime = m["requesttime"][0]
|
||||
reqBody.Sign = m["sign"][0]
|
||||
|
||||
notifyReqInfoBytes, err := base64.StdEncoding.DecodeString(reqBody.Data)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
|
||||
var notifyRespInfo GcNotifyRespInfo
|
||||
err = json.Unmarshal(notifyReqInfoBytes, ¬ifyRespInfo)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
|
||||
providerName := ""
|
||||
productName := ""
|
||||
|
||||
productDisplayName := ""
|
||||
paymentName := notifyRespInfo.OrderNo
|
||||
price := notifyRespInfo.Amount
|
||||
|
||||
if notifyRespInfo.OrderState != "1" {
|
||||
return "", "", 0, "", "", fmt.Errorf("error order state: %s", notifyRespInfo.OrderDate)
|
||||
}
|
||||
|
||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||
}
|
@ -14,13 +14,18 @@
|
||||
|
||||
package pp
|
||||
|
||||
import "net/http"
|
||||
|
||||
type PaymentProvider interface {
|
||||
Pay(productName string, productId string, providerId string, paymentId string, price float64, returnUrl string, notifyUrl string) (string, error)
|
||||
Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error)
|
||||
Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error)
|
||||
}
|
||||
|
||||
func GetPaymentProvider(typ string, appId string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
|
||||
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
|
||||
if typ == "Alipay" {
|
||||
return NewAlipayPaymentProvider(appId, appPublicKey, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
|
||||
} else if typ == "GC" {
|
||||
return NewGcPaymentProvider(appId, clientSecret, host)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
25
pp/util.go
Normal file
25
pp/util.go
Normal file
@ -0,0 +1,25 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getPriceString(price float64) string {
|
||||
priceString := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", price), "0"), ".")
|
||||
return priceString
|
||||
}
|
@ -29,10 +29,8 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
|
||||
// GET parameter like "/page?access_token=123" or
|
||||
// HTTP Bearer token like "Authorization: Bearer 123"
|
||||
accessToken := ctx.Input.Query("accessToken")
|
||||
if accessToken == "" {
|
||||
accessToken = parseBearerToken(ctx)
|
||||
}
|
||||
accessToken := util.GetMaxLenStr(ctx.Input.Query("accessToken"), ctx.Input.Query("access_token"), parseBearerToken(ctx))
|
||||
|
||||
if accessToken != "" {
|
||||
token := object.GetTokenByAccessToken(accessToken)
|
||||
if token == nil {
|
||||
|
@ -159,11 +159,12 @@ func initAPI() {
|
||||
beego.Router("/api/buy-product", &controllers.ApiController{}, "POST:BuyProduct")
|
||||
|
||||
beego.Router("/api/get-payments", &controllers.ApiController{}, "GET:GetPayments")
|
||||
beego.Router("/api/get-user-payments", &controllers.ApiController{}, "GET:GetUserPayments")
|
||||
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/notify-payment/?:owner/?:provider/?:product/?:payment", &controllers.ApiController{}, "POST:NotifyPayment")
|
||||
|
||||
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||
|
@ -20,10 +20,12 @@ import (
|
||||
)
|
||||
|
||||
var rePhoneCn *regexp.Regexp
|
||||
var rePhone *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
// https://learnku.com/articles/31543
|
||||
rePhoneCn, _ = regexp.Compile(`^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$`)
|
||||
rePhone, _ = regexp.Compile("(\\d{3})\\d*(\\d{4})")
|
||||
}
|
||||
|
||||
func IsEmailValid(email string) bool {
|
||||
@ -34,3 +36,7 @@ func IsEmailValid(email string) bool {
|
||||
func IsPhoneCnValid(phone string) bool {
|
||||
return rePhoneCn.MatchString(phone)
|
||||
}
|
||||
|
||||
func getMaskedPhone(phone string) string {
|
||||
return rePhone.ReplaceAllString(phone, "$1****$2")
|
||||
}
|
@ -109,6 +109,14 @@ func GenerateTimeId() string {
|
||||
return res
|
||||
}
|
||||
|
||||
func GenerateSimpleTimeId() string {
|
||||
timestamp := time.Now().Unix()
|
||||
tm := time.Unix(timestamp, 0)
|
||||
t := tm.Format("20060102150405")
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func GetId(name string) string {
|
||||
return fmt.Sprintf("admin/%s", name)
|
||||
}
|
||||
@ -198,3 +206,28 @@ func IsChinese(str string) bool {
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
||||
func GetMaskedPhone(phone string) string {
|
||||
return getMaskedPhone(phone)
|
||||
}
|
||||
|
||||
func GetMaskedEmail(email string) string {
|
||||
if email == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
tokens := strings.Split(email, "@")
|
||||
username := maskString(tokens[0])
|
||||
domain := tokens[1]
|
||||
domainTokens := strings.Split(domain, ".")
|
||||
domainTokens[len(domainTokens) - 2] = maskString(domainTokens[len(domainTokens) - 2])
|
||||
return fmt.Sprintf("%s@%s", username, strings.Join(domainTokens, "."))
|
||||
}
|
||||
|
||||
func maskString(str string) string {
|
||||
if len(str) <= 2 {
|
||||
return str
|
||||
} else {
|
||||
return fmt.Sprintf("%c%s%c", str[0], strings.Repeat("*", len(str) - 2), str[len(str) - 1])
|
||||
}
|
||||
}
|
@ -48,6 +48,7 @@ import ProductEditPage from "./ProductEditPage";
|
||||
import ProductBuyPage from "./ProductBuyPage";
|
||||
import PaymentListPage from "./PaymentListPage";
|
||||
import PaymentEditPage from "./PaymentEditPage";
|
||||
import PaymentResultPage from "./PaymentResultPage";
|
||||
import AccountPage from "./account/AccountPage";
|
||||
import HomePage from "./basic/HomePage";
|
||||
import CustomGithubCorner from "./CustomGithubCorner";
|
||||
@ -529,6 +530,7 @@ class App extends Component {
|
||||
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />}/>
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
|
@ -113,7 +113,7 @@ class OrganizationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel("general:Favicon", i18next.t("general:Favicon - Tooltip"))} :
|
||||
{Setting.getLabel( i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
@ -208,6 +208,18 @@ class OrganizationEditPage extends React.Component {
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("organization:Tags"), i18next.t("organization:Tags - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.organization.tags} onChange={(value => {this.updateOrganizationField('tags', value);})}>
|
||||
{
|
||||
this.state.organization.tags?.map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Master password"), i18next.t("general:Master password - Tooltip"))} :
|
||||
|
@ -36,6 +36,7 @@ class OrganizationListPage extends BaseListPage {
|
||||
PasswordSalt: "",
|
||||
phonePrefix: "86",
|
||||
defaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||
tags: [],
|
||||
masterPassword: "",
|
||||
enableSoftDeletion: false,
|
||||
}
|
||||
|
@ -160,6 +160,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:Message"), i18next.t("payment:Message - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.payment.message} onChange={e => {
|
||||
// this.updatePaymentField('message', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
@ -34,11 +34,16 @@ class PaymentListPage extends BaseListPage {
|
||||
type: "PayPal",
|
||||
organization: "built-in",
|
||||
user: "admin",
|
||||
productId: "computer-1",
|
||||
productName: "A notebook computer",
|
||||
price: 300.00,
|
||||
productName: "computer-1",
|
||||
productDisplayName: "A notebook computer",
|
||||
detail: "This is a computer with excellent CPU, memory and disk",
|
||||
tag: "Promotion-1",
|
||||
currency: "USD",
|
||||
price: 300.00,
|
||||
payUrl: "https://pay.com/pay.php",
|
||||
returnUrl: "https://door.casdoor.com/payments",
|
||||
state: "Paid",
|
||||
message: "",
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,11 +172,11 @@ class PaymentListPage extends BaseListPage {
|
||||
},
|
||||
{
|
||||
title: i18next.t("payment:Product"),
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
dataIndex: 'productDisplayName',
|
||||
key: 'productDisplayName',
|
||||
// width: '160px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('productName'),
|
||||
...this.getColumnSearchProps('productDisplayName'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("payment:Price"),
|
||||
@ -201,11 +206,12 @@ class PaymentListPage extends BaseListPage {
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: '',
|
||||
key: 'op',
|
||||
width: '170px',
|
||||
width: '240px',
|
||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} onClick={() => this.props.history.push(`/payments/${record.name}/result`)}>{i18next.t("payment:Result")}</Button>
|
||||
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/payments/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Popconfirm
|
||||
title={`Sure to delete payment: ${record.name} ?`}
|
||||
|
115
web/src/PaymentResultPage.js
Normal file
115
web/src/PaymentResultPage.js
Normal file
@ -0,0 +1,115 @@
|
||||
// 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.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Result, Spin} from 'antd';
|
||||
import * as PaymentBackend from "./backend/PaymentBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
class PaymentResultPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
paymentName: props.match.params.paymentName,
|
||||
payment: null,
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getPayment();
|
||||
}
|
||||
|
||||
getPayment() {
|
||||
PaymentBackend.getPayment("admin", this.state.paymentName)
|
||||
.then((payment) => {
|
||||
this.setState({
|
||||
payment: payment,
|
||||
});
|
||||
|
||||
if (payment.state === "Created") {
|
||||
setTimeout(() => this.getPayment(), 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const payment = this.state.payment;
|
||||
|
||||
if (payment === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (payment.state === "Paid") {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
Setting.renderHelmet(payment)
|
||||
}
|
||||
<Result
|
||||
status="success"
|
||||
title={`${i18next.t("payment:You have successfully completed the payment")}: ${payment.productDisplayName}`}
|
||||
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
|
||||
extra={[
|
||||
<Button type="primary" key="returnUrl" onClick={() => {
|
||||
Setting.goToLink(payment.returnUrl);
|
||||
}}>
|
||||
{i18next.t("payment:Return to Website")}
|
||||
</Button>
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} else if (payment.state === "Created") {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
Setting.renderHelmet(payment)
|
||||
}
|
||||
<Result
|
||||
status="info"
|
||||
title={`${i18next.t("payment:The payment is still under processing")}: ${payment.productDisplayName}, ${i18next.t("payment:the current state is")}: ${payment.state}, ${i18next.t("payment:please wait for a few seconds...")}`}
|
||||
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
|
||||
extra={[
|
||||
<Spin size="large" tip={i18next.t("payment:Processing...")} />,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
Setting.renderHelmet(payment)
|
||||
}
|
||||
<Result
|
||||
status="error"
|
||||
title={`${i18next.t("payment:The payment has failed")}: ${payment.productDisplayName}, ${i18next.t("payment:the current state is")}: ${payment.state}`}
|
||||
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
|
||||
extra={[
|
||||
<Button type="primary" key="returnUrl" onClick={() => {
|
||||
Setting.goToLink(payment.returnUrl);
|
||||
}}>
|
||||
{i18next.t("payment:Return to Website")}
|
||||
</Button>
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PaymentResultPage;
|
@ -186,6 +186,10 @@ class ProductBuyPage extends React.Component {
|
||||
render() {
|
||||
const product = this.getProductObj();
|
||||
|
||||
if (product === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Spin spinning={this.state.isPlacingOrder} size="large" tip={i18next.t("product:Placing order...")} style={{paddingTop: "10%"}} >
|
||||
|
@ -212,6 +212,16 @@ class ProductEditPage extends React.Component {
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("product:Return URL"), i18next.t("product:Return URL - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined/>} value={this.state.product.returnUrl} onChange={e => {
|
||||
this.updateProductField('returnUrl', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:State"), i18next.t("general:State - Tooltip"))} :
|
||||
|
@ -72,6 +72,8 @@ class ProviderEditPage extends React.Component {
|
||||
case "SMS":
|
||||
if (this.state.provider.type === "Volc Engine SMS")
|
||||
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
|
||||
if (this.state.provider.type === "Huawei Cloud SMS")
|
||||
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
|
||||
default:
|
||||
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
||||
}
|
||||
@ -84,6 +86,8 @@ class ProviderEditPage extends React.Component {
|
||||
case "SMS":
|
||||
if (this.state.provider.type === "Volc Engine SMS")
|
||||
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
|
||||
if (this.state.provider.type === "Huawei Cloud SMS")
|
||||
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
|
||||
default:
|
||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||
}
|
||||
@ -103,6 +107,9 @@ class ProviderEditPage extends React.Component {
|
||||
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Volc Engine SMS") {
|
||||
text = i18next.t("provider:SMS account");
|
||||
tooltip = i18next.t("provider:SMS account - Tooltip");
|
||||
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Huawei Cloud SMS") {
|
||||
text = i18next.t("provider:Channel No.");
|
||||
tooltip = i18next.t("provider:Channel No. - Tooltip");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -423,6 +423,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: 'Aliyun SMS', name: 'Aliyun SMS'},
|
||||
{id: 'Tencent Cloud SMS', name: 'Tencent Cloud SMS'},
|
||||
{id: 'Volc Engine SMS', name: 'Volc Engine SMS'},
|
||||
{id: 'Huawei Cloud SMS', name: 'Huawei Cloud SMS'},
|
||||
]
|
||||
);
|
||||
} else if (category === "Storage") {
|
||||
@ -444,6 +445,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: 'Alipay', name: 'Alipay'},
|
||||
{id: 'WeChat Pay', name: 'WeChat Pay'},
|
||||
{id: 'PayPal', name: 'PayPal'},
|
||||
{id: 'GC', name: 'GC'},
|
||||
]);
|
||||
} else {
|
||||
return [];
|
||||
|
@ -301,9 +301,24 @@ class UserEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.tag} onChange={e => {
|
||||
this.updateUserField('tag', e.target.value);
|
||||
}} />
|
||||
{
|
||||
this.state.application?.organizationObj.tags?.length > 0 ? (
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.user.tag} onChange={(value => {this.updateUserField('tag', value);})}>
|
||||
{
|
||||
this.state.application.organizationObj.tags?.map((tag, index) => {
|
||||
const tokens = tag.split("|");
|
||||
const value = tokens[0];
|
||||
const displayValue = Setting.getLanguage() !== "zh" ? tokens[0] : tokens[1];
|
||||
return <Option key={index} value={value}>{displayValue}</Option>
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
) : (
|
||||
<Input value={this.state.user.tag} onChange={e => {
|
||||
this.updateUserField('tag', e.target.value);
|
||||
}} />
|
||||
)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Col, Form, Select, Input, Row, Steps} from "antd";
|
||||
import {Button, Col, Form, Input, Row, Select, Steps} from "antd";
|
||||
import * as AuthBackend from "./AuthBackend";
|
||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||
import * as Util from "./Util";
|
||||
@ -43,6 +43,7 @@ class ForgetPage extends React.Component {
|
||||
msg: null,
|
||||
userId: "",
|
||||
username: "",
|
||||
name: "",
|
||||
email: "",
|
||||
isFixed: false,
|
||||
fixedContent: "",
|
||||
@ -100,7 +101,7 @@ class ForgetPage extends React.Component {
|
||||
if (res.status === "ok") {
|
||||
const phone = res.data.phone;
|
||||
const email = res.data.email;
|
||||
this.setState({phone: phone, email: email, username: res.data.name});
|
||||
this.setState({phone: phone, email: email, username: res.data.name, name: res.data.name});
|
||||
|
||||
if (phone !== "" && email === "") {
|
||||
this.setState({
|
||||
@ -134,15 +135,16 @@ class ForgetPage extends React.Component {
|
||||
break;
|
||||
case "step2":
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
if(this.state.verifyType=="email"){
|
||||
if (this.state.verifyType === "email") {
|
||||
this.setState({username: this.state.email})
|
||||
}else if(this.state.verifyType=="phone"){
|
||||
} else if (this.state.verifyType === "phone") {
|
||||
this.setState({username: this.state.phone})
|
||||
}
|
||||
AuthBackend.login({
|
||||
application: forms.step2.getFieldValue("application"),
|
||||
organization: forms.step2.getFieldValue("organization"),
|
||||
username: this.state.username,
|
||||
name: this.state.name,
|
||||
code: forms.step2.getFieldValue("emailCode"),
|
||||
phonePrefix: this.state.application?.organizationObj.phonePrefix,
|
||||
type: "login"
|
||||
@ -179,7 +181,7 @@ class ForgetPage extends React.Component {
|
||||
if (this.state.phone !== "") {
|
||||
options.push(
|
||||
<Option key={"phone"} value={"phone"}>
|
||||
{Setting.getMaskedPhone(this.state.phone)}
|
||||
{this.state.phone}
|
||||
</Option>
|
||||
);
|
||||
}
|
||||
@ -187,7 +189,7 @@ class ForgetPage extends React.Component {
|
||||
if (this.state.email !== "") {
|
||||
options.push(
|
||||
<Option key={"email"} value={"email"}>
|
||||
{Setting.getMaskedEmail(this.state.email)}
|
||||
{this.state.email}
|
||||
</Option>
|
||||
);
|
||||
}
|
||||
@ -349,12 +351,12 @@ class ForgetPage extends React.Component {
|
||||
{this.state.verifyType === "email" ? (
|
||||
<CountDownInput
|
||||
disabled={this.state.username === "" || this.state.verifyType === ""}
|
||||
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationOrgName(this.state.application)]}
|
||||
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationOrgName(this.state.application), this.state.name]}
|
||||
/>
|
||||
) : (
|
||||
<CountDownInput
|
||||
disabled={this.state.username === "" || this.state.verifyType === ""}
|
||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationOrgName(this.state.application)]}
|
||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationOrgName(this.state.application), this.state.name]}
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
@ -112,6 +112,10 @@ const otherProviderInfo = {
|
||||
logo: `${StaticBaseUrl}/img/social_volc_engine.jpg`,
|
||||
url: "https://www.volcengine.com/products/cloud-sms",
|
||||
},
|
||||
"Huawei Cloud SMS": {
|
||||
logo: `${StaticBaseUrl}/img/social_huawei.png`,
|
||||
url: "https://www.huaweicloud.com/product/msgsms.html",
|
||||
},
|
||||
},
|
||||
Email: {
|
||||
"Default": {
|
||||
@ -160,6 +164,10 @@ const otherProviderInfo = {
|
||||
logo: `${StaticBaseUrl}/img/payment_paypal.png`,
|
||||
url: "https://www.paypal.com/"
|
||||
},
|
||||
"GC": {
|
||||
logo: `${StaticBaseUrl}/img/payment_gc.png`,
|
||||
url: "https://gc.org"
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -56,7 +56,7 @@ export function deleteProduct(product) {
|
||||
}
|
||||
|
||||
export function buyProduct(owner, name, providerId) {
|
||||
return fetch(`${Setting.ServerUrl}/api/buy-product?id=${owner}/${encodeURIComponent(name)}&providerId=${providerId}`, {
|
||||
return fetch(`${Setting.ServerUrl}/api/buy-product?id=${owner}/${encodeURIComponent(name)}&providerName=${providerId}`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
}).then(res => res.json());
|
||||
|
@ -49,14 +49,6 @@ export const CountDownInput = (props) => {
|
||||
|
||||
const handleOk = () => {
|
||||
setVisible(false);
|
||||
if (isValidEmail(onButtonClickArgs[0])) {
|
||||
onButtonClickArgs[1] = "email";
|
||||
} else if (isValidPhone(onButtonClickArgs[0])) {
|
||||
onButtonClickArgs[1] = "phone";
|
||||
} else {
|
||||
Util.showMessage("error", i18next.t("login:Invalid Email or phone"))
|
||||
return;
|
||||
}
|
||||
setButtonLoading(true)
|
||||
UserBackend.sendCode(checkType, checkId, key, ...onButtonClickArgs).then(res => {
|
||||
setKey("");
|
||||
|
@ -113,6 +113,7 @@
|
||||
"Edit": "Bearbeiten",
|
||||
"Email": "E-Mail",
|
||||
"Email - Tooltip": "email",
|
||||
"Favicon": "Favicon",
|
||||
"Favicon - Tooltip": "Application icon",
|
||||
"First name": "First name",
|
||||
"Forget URL": "URL vergessen",
|
||||
@ -248,6 +249,8 @@
|
||||
"New Organization": "New Organization",
|
||||
"Soft deletion": "Weiche Löschung",
|
||||
"Soft deletion - Tooltip": "Weiche Löschung - Tooltip",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"Website URL": "Website-URL",
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
},
|
||||
@ -256,12 +259,23 @@
|
||||
"Currency - Tooltip": "Currency - Tooltip",
|
||||
"Edit Payment": "Edit Payment",
|
||||
"New Payment": "New Payment",
|
||||
"Please click the below button to return to the original website": "Please click the below button to return to the original website",
|
||||
"Price": "Price",
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Processing...": "Processing...",
|
||||
"Product": "Product",
|
||||
"Product - Tooltip": "Product - Tooltip",
|
||||
"Result": "Result",
|
||||
"Return to Website": "Return to Website",
|
||||
"State": "State",
|
||||
"State - Tooltip": "State - Tooltip",
|
||||
"The payment has failed": "The payment has failed",
|
||||
"The payment is still under processing": "The payment is still under processing",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip"
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"You have successfully completed the payment": "You have successfully completed the payment",
|
||||
"please wait for a few seconds...": "please wait for a few seconds...",
|
||||
"the current state is": "the current state is"
|
||||
},
|
||||
"permission": {
|
||||
"Actions": "Aktionen",
|
||||
@ -296,6 +310,8 @@
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Quantity": "Quantity",
|
||||
"Quantity - Tooltip": "Quantity - Tooltip",
|
||||
"Return URL": "Return URL",
|
||||
"Return URL - Tooltip": "Return URL - Tooltip",
|
||||
"SKU": "SKU",
|
||||
"Sold": "Sold",
|
||||
"Sold - Tooltip": "Sold - Tooltip",
|
||||
|
@ -113,6 +113,7 @@
|
||||
"Edit": "Edit",
|
||||
"Email": "Email",
|
||||
"Email - Tooltip": "Email - Tooltip",
|
||||
"Favicon": "Favicon",
|
||||
"Favicon - Tooltip": "Favicon - Tooltip",
|
||||
"First name": "First name",
|
||||
"Forget URL": "Forget URL",
|
||||
@ -248,6 +249,8 @@
|
||||
"New Organization": "New Organization",
|
||||
"Soft deletion": "Soft deletion",
|
||||
"Soft deletion - Tooltip": "Soft deletion - Tooltip",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"Website URL": "Website URL",
|
||||
"Website URL - Tooltip": "Website URL - Tooltip"
|
||||
},
|
||||
@ -256,12 +259,23 @@
|
||||
"Currency - Tooltip": "Currency - Tooltip",
|
||||
"Edit Payment": "Edit Payment",
|
||||
"New Payment": "New Payment",
|
||||
"Please click the below button to return to the original website": "Please click the below button to return to the original website",
|
||||
"Price": "Price",
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Processing...": "Processing...",
|
||||
"Product": "Product",
|
||||
"Product - Tooltip": "Product - Tooltip",
|
||||
"Result": "Result",
|
||||
"Return to Website": "Return to Website",
|
||||
"State": "State",
|
||||
"State - Tooltip": "State - Tooltip",
|
||||
"The payment has failed": "The payment has failed",
|
||||
"The payment is still under processing": "The payment is still under processing",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip"
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"You have successfully completed the payment": "You have successfully completed the payment",
|
||||
"please wait for a few seconds...": "please wait for a few seconds...",
|
||||
"the current state is": "the current state is"
|
||||
},
|
||||
"permission": {
|
||||
"Actions": "Actions",
|
||||
@ -296,6 +310,8 @@
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Quantity": "Quantity",
|
||||
"Quantity - Tooltip": "Quantity - Tooltip",
|
||||
"Return URL": "Return URL",
|
||||
"Return URL - Tooltip": "Return URL - Tooltip",
|
||||
"SKU": "SKU",
|
||||
"Sold": "Sold",
|
||||
"Sold - Tooltip": "Sold - Tooltip",
|
||||
|
@ -113,6 +113,7 @@
|
||||
"Edit": "Editer",
|
||||
"Email": "Courriel",
|
||||
"Email - Tooltip": "email",
|
||||
"Favicon": "Favicon",
|
||||
"Favicon - Tooltip": "Application icon",
|
||||
"First name": "First name",
|
||||
"Forget URL": "Oublier l'URL",
|
||||
@ -248,6 +249,8 @@
|
||||
"New Organization": "New Organization",
|
||||
"Soft deletion": "Suppression du logiciel",
|
||||
"Soft deletion - Tooltip": "Suppression de soft - infobulle",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"Website URL": "URL du site web",
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
},
|
||||
@ -256,12 +259,23 @@
|
||||
"Currency - Tooltip": "Currency - Tooltip",
|
||||
"Edit Payment": "Edit Payment",
|
||||
"New Payment": "New Payment",
|
||||
"Please click the below button to return to the original website": "Please click the below button to return to the original website",
|
||||
"Price": "Price",
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Processing...": "Processing...",
|
||||
"Product": "Product",
|
||||
"Product - Tooltip": "Product - Tooltip",
|
||||
"Result": "Result",
|
||||
"Return to Website": "Return to Website",
|
||||
"State": "State",
|
||||
"State - Tooltip": "State - Tooltip",
|
||||
"The payment has failed": "The payment has failed",
|
||||
"The payment is still under processing": "The payment is still under processing",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip"
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"You have successfully completed the payment": "You have successfully completed the payment",
|
||||
"please wait for a few seconds...": "please wait for a few seconds...",
|
||||
"the current state is": "the current state is"
|
||||
},
|
||||
"permission": {
|
||||
"Actions": "Actions",
|
||||
@ -296,6 +310,8 @@
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Quantity": "Quantity",
|
||||
"Quantity - Tooltip": "Quantity - Tooltip",
|
||||
"Return URL": "Return URL",
|
||||
"Return URL - Tooltip": "Return URL - Tooltip",
|
||||
"SKU": "SKU",
|
||||
"Sold": "Sold",
|
||||
"Sold - Tooltip": "Sold - Tooltip",
|
||||
|
@ -113,6 +113,7 @@
|
||||
"Edit": "編集",
|
||||
"Email": "Eメールアドレス",
|
||||
"Email - Tooltip": "email",
|
||||
"Favicon": "Favicon",
|
||||
"Favicon - Tooltip": "Application icon",
|
||||
"First name": "First name",
|
||||
"Forget URL": "URLを忘れた",
|
||||
@ -248,6 +249,8 @@
|
||||
"New Organization": "New Organization",
|
||||
"Soft deletion": "ソフト削除",
|
||||
"Soft deletion - Tooltip": "ソフト削除 - ツールチップ",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"Website URL": "Website URL",
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
},
|
||||
@ -256,12 +259,23 @@
|
||||
"Currency - Tooltip": "Currency - Tooltip",
|
||||
"Edit Payment": "Edit Payment",
|
||||
"New Payment": "New Payment",
|
||||
"Please click the below button to return to the original website": "Please click the below button to return to the original website",
|
||||
"Price": "Price",
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Processing...": "Processing...",
|
||||
"Product": "Product",
|
||||
"Product - Tooltip": "Product - Tooltip",
|
||||
"Result": "Result",
|
||||
"Return to Website": "Return to Website",
|
||||
"State": "State",
|
||||
"State - Tooltip": "State - Tooltip",
|
||||
"The payment has failed": "The payment has failed",
|
||||
"The payment is still under processing": "The payment is still under processing",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip"
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"You have successfully completed the payment": "You have successfully completed the payment",
|
||||
"please wait for a few seconds...": "please wait for a few seconds...",
|
||||
"the current state is": "the current state is"
|
||||
},
|
||||
"permission": {
|
||||
"Actions": "アクション",
|
||||
@ -296,6 +310,8 @@
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Quantity": "Quantity",
|
||||
"Quantity - Tooltip": "Quantity - Tooltip",
|
||||
"Return URL": "Return URL",
|
||||
"Return URL - Tooltip": "Return URL - Tooltip",
|
||||
"SKU": "SKU",
|
||||
"Sold": "Sold",
|
||||
"Sold - Tooltip": "Sold - Tooltip",
|
||||
|
@ -113,6 +113,7 @@
|
||||
"Edit": "Edit",
|
||||
"Email": "Email",
|
||||
"Email - Tooltip": "email",
|
||||
"Favicon": "Favicon",
|
||||
"Favicon - Tooltip": "Application icon",
|
||||
"First name": "First name",
|
||||
"Forget URL": "Forget URL",
|
||||
@ -248,6 +249,8 @@
|
||||
"New Organization": "New Organization",
|
||||
"Soft deletion": "Soft deletion",
|
||||
"Soft deletion - Tooltip": "Soft deletion - Tooltip",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"Website URL": "Website URL",
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
},
|
||||
@ -256,12 +259,23 @@
|
||||
"Currency - Tooltip": "Currency - Tooltip",
|
||||
"Edit Payment": "Edit Payment",
|
||||
"New Payment": "New Payment",
|
||||
"Please click the below button to return to the original website": "Please click the below button to return to the original website",
|
||||
"Price": "Price",
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Processing...": "Processing...",
|
||||
"Product": "Product",
|
||||
"Product - Tooltip": "Product - Tooltip",
|
||||
"Result": "Result",
|
||||
"Return to Website": "Return to Website",
|
||||
"State": "State",
|
||||
"State - Tooltip": "State - Tooltip",
|
||||
"The payment has failed": "The payment has failed",
|
||||
"The payment is still under processing": "The payment is still under processing",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip"
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"You have successfully completed the payment": "You have successfully completed the payment",
|
||||
"please wait for a few seconds...": "please wait for a few seconds...",
|
||||
"the current state is": "the current state is"
|
||||
},
|
||||
"permission": {
|
||||
"Actions": "Actions",
|
||||
@ -296,6 +310,8 @@
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Quantity": "Quantity",
|
||||
"Quantity - Tooltip": "Quantity - Tooltip",
|
||||
"Return URL": "Return URL",
|
||||
"Return URL - Tooltip": "Return URL - Tooltip",
|
||||
"SKU": "SKU",
|
||||
"Sold": "Sold",
|
||||
"Sold - Tooltip": "Sold - Tooltip",
|
||||
|
@ -113,6 +113,7 @@
|
||||
"Edit": "Редактирование",
|
||||
"Email": "Почта",
|
||||
"Email - Tooltip": "email",
|
||||
"Favicon": "Favicon",
|
||||
"Favicon - Tooltip": "Application icon",
|
||||
"First name": "First name",
|
||||
"Forget URL": "Забыть URL",
|
||||
@ -248,6 +249,8 @@
|
||||
"New Organization": "New Organization",
|
||||
"Soft deletion": "Мягкое удаление",
|
||||
"Soft deletion - Tooltip": "Мягкое удаление - Подсказка",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"Website URL": "URL сайта",
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
},
|
||||
@ -256,12 +259,23 @@
|
||||
"Currency - Tooltip": "Currency - Tooltip",
|
||||
"Edit Payment": "Edit Payment",
|
||||
"New Payment": "New Payment",
|
||||
"Please click the below button to return to the original website": "Please click the below button to return to the original website",
|
||||
"Price": "Price",
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Processing...": "Processing...",
|
||||
"Product": "Product",
|
||||
"Product - Tooltip": "Product - Tooltip",
|
||||
"Result": "Result",
|
||||
"Return to Website": "Return to Website",
|
||||
"State": "State",
|
||||
"State - Tooltip": "State - Tooltip",
|
||||
"The payment has failed": "The payment has failed",
|
||||
"The payment is still under processing": "The payment is still under processing",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip"
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"You have successfully completed the payment": "You have successfully completed the payment",
|
||||
"please wait for a few seconds...": "please wait for a few seconds...",
|
||||
"the current state is": "the current state is"
|
||||
},
|
||||
"permission": {
|
||||
"Actions": "Действия",
|
||||
@ -296,6 +310,8 @@
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Quantity": "Quantity",
|
||||
"Quantity - Tooltip": "Quantity - Tooltip",
|
||||
"Return URL": "Return URL",
|
||||
"Return URL - Tooltip": "Return URL - Tooltip",
|
||||
"SKU": "SKU",
|
||||
"Sold": "Sold",
|
||||
"Sold - Tooltip": "Sold - Tooltip",
|
||||
|
@ -113,7 +113,8 @@
|
||||
"Edit": "编辑",
|
||||
"Email": "电子邮箱",
|
||||
"Email - Tooltip": "电子邮件:",
|
||||
"Favicon - Tooltip": "网站的图标",
|
||||
"Favicon": "网站图标",
|
||||
"Favicon - Tooltip": "网站的Favicon图标",
|
||||
"First name": "名字",
|
||||
"Forget URL": "忘记密码URL",
|
||||
"Forget URL - Tooltip": "忘记密码URL",
|
||||
@ -248,6 +249,8 @@
|
||||
"New Organization": "添加组织",
|
||||
"Soft deletion": "软删除",
|
||||
"Soft deletion - Tooltip": "启用后,删除用户信息时不会在数据库彻底清除,只会标记为已删除状态",
|
||||
"Tags": "标签集合",
|
||||
"Tags - Tooltip": "可供用户选择的标签的集合",
|
||||
"Website URL": "网页地址",
|
||||
"Website URL - Tooltip": "网页地址"
|
||||
},
|
||||
@ -256,12 +259,23 @@
|
||||
"Currency - Tooltip": "如USD(美元),CNY(人民币)等",
|
||||
"Edit Payment": "编辑付款",
|
||||
"New Payment": "添加付款",
|
||||
"Please click the below button to return to the original website": "请点击下方按钮返回原网站",
|
||||
"Price": "价格",
|
||||
"Price - Tooltip": "商品价格",
|
||||
"Processing...": "正在处理...",
|
||||
"Product": "商品",
|
||||
"Product - Tooltip": "商品名称",
|
||||
"Result": "结果",
|
||||
"Return to Website": "返回原网站",
|
||||
"State": "状态",
|
||||
"State - Tooltip": "交易状态",
|
||||
"The payment has failed": "支付失败",
|
||||
"The payment is still under processing": "支付正在处理",
|
||||
"Type": "支付方式",
|
||||
"Type - Tooltip": "商品购买时的支付方式"
|
||||
"Type - Tooltip": "商品购买时的支付方式",
|
||||
"You have successfully completed the payment": "支付成功",
|
||||
"please wait for a few seconds...": "请稍后...",
|
||||
"the current state is": "当前状态为"
|
||||
},
|
||||
"permission": {
|
||||
"Actions": "动作",
|
||||
@ -296,6 +310,8 @@
|
||||
"Price - Tooltip": "价格 - 工具提示",
|
||||
"Quantity": "库存",
|
||||
"Quantity - Tooltip": "库存 - 工具提示",
|
||||
"Return URL": "返回URL",
|
||||
"Return URL - Tooltip": "返回URL - 工具提示",
|
||||
"SKU": "货号",
|
||||
"Sold": "售出",
|
||||
"Sold - Tooltip": "售出 - 工具提示",
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !skipCi
|
||||
// +build !skipCi
|
||||
|
||||
package xlsx
|
||||
|
||||
|
Reference in New Issue
Block a user