diff --git a/object/product.go b/object/product.go index 312587f6..6e3d6cd7 100644 --- a/object/product.go +++ b/object/product.go @@ -164,12 +164,15 @@ func BuyProduct(id string, providerId string, user *User, host string) (string, return "", err } - cert := getCert(product.Owner, provider.Cert) - if cert == nil { - return "", fmt.Errorf("the cert: %s does not exist", provider.Cert) + cert := &Cert{} + if provider.Cert != "" { + 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) + pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey) if pProvider == nil { return "", fmt.Errorf("the payment provider type: %s is not supported", provider.Type) } diff --git a/object/product_test.go b/object/product_test.go index 627526d8..d89bf642 100644 --- a/object/product_test.go +++ b/object/product_test.go @@ -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 := "" diff --git a/pp/alipay.go b/pp/alipay.go index 77143b49..2c85af6f 100644 --- a/pp/alipay.go +++ b/pp/alipay.go @@ -16,8 +16,6 @@ package pp import ( "context" - "fmt" - "strings" "github.com/go-pay/gopay" "github.com/go-pay/gopay/alipay" @@ -47,8 +45,6 @@ func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey s 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("return_url", returnUrl) @@ -56,7 +52,7 @@ func (pp *AlipayPaymentProvider) Pay(productName string, productId string, provi bm.Set("subject", productName) bm.Set("out_trade_no", paymentId) - bm.Set("total_amount", priceString) + bm.Set("total_amount", getPriceString(price)) bm.Set("productId", productId) bm.Set("providerId", productId) diff --git a/pp/gc.go b/pp/gc.go new file mode 100644 index 00000000..3f7eb936 --- /dev/null +++ b/pp/gc.go @@ -0,0 +1,174 @@ +// 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" + "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 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(productName string, productId string, providerId string, paymentId 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 + } + + 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 +} diff --git a/pp/provider.go b/pp/provider.go index 12cec8fa..42584b17 100644 --- a/pp/provider.go +++ b/pp/provider.go @@ -18,9 +18,11 @@ 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 { +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 } diff --git a/pp/util.go b/pp/util.go new file mode 100644 index 00000000..d7123f26 --- /dev/null +++ b/pp/util.go @@ -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 +} diff --git a/util/string.go b/util/string.go index 6c80e023..281470c2 100644 --- a/util/string.go +++ b/util/string.go @@ -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) } diff --git a/web/src/Setting.js b/web/src/Setting.js index 8f2c2b7d..219749c9 100644 --- a/web/src/Setting.js +++ b/web/src/Setting.js @@ -444,6 +444,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 []; diff --git a/web/src/auth/Provider.js b/web/src/auth/Provider.js index c7943d85..159770b2 100644 --- a/web/src/auth/Provider.js +++ b/web/src/auth/Provider.js @@ -160,6 +160,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" + }, }, };