Skip to content

Commit

Permalink
Add alipay provider.
Browse files Browse the repository at this point in the history
  • Loading branch information
hsluoyz committed Mar 6, 2022
1 parent 0e40a1d commit bf5d4ee
Show file tree
Hide file tree
Showing 17 changed files with 296 additions and 9 deletions.
8 changes: 8 additions & 0 deletions controllers/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()}
}
}
17 changes: 17 additions & 0 deletions controllers/payment.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,20 @@ 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() {
var payment object.Payment
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payment)
if err != nil {
panic(err)
}

c.Data["json"] = wrapActionResponse(object.NotifyPayment("111", "222"))
c.ServeJSON()
}
21 changes: 21 additions & 0 deletions controllers/product.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,24 @@ 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

payUrl, err := object.BuyProduct(id, providerId, host)
if err != nil {
c.ResponseError(err.Error())
return
}

c.ResponseOk(payUrl)
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
6 changes: 4 additions & 2 deletions object/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
17 changes: 17 additions & 0 deletions object/payment.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,23 @@ func DeletePayment(payment *Payment) bool {
return affected != 0
}

func NotifyPayment(id string, state string) bool {
owner, name := util.GetOwnerAndNameFromId(id)
payment := getPayment(owner, name)
if payment == nil {
return false
}

payment.State = state

affected, err := adapter.Engine.ID(core.PK{owner, name}).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)
}
47 changes: 46 additions & 1 deletion object/product.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package object
import (
"fmt"

"github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
Expand All @@ -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"`
Expand Down Expand Up @@ -128,3 +129,47 @@ 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 BuyProduct(id string, providerId string, host string) (string, error) {
product := GetProduct(id)
if product == nil {
return "", fmt.Errorf("the product: %s does not exist", id)
}

provider := getProvider(product.Owner, providerId)
if provider == nil {
return "", fmt.Errorf("the payment provider: %s does not exist", providerId)
}

if !product.isValidProvider(provider) {
return "", fmt.Errorf("the payment provider: %s is not valid for the product: %s", providerId, id)
}

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()

originFrontend, originBackend := getOriginFromHost(host)
returnUrl := fmt.Sprintf("%s/payments/%s", originFrontend, paymentId)
notifyUrl := fmt.Sprintf("%s/api/notify-payment", originBackend)

payUrl, err := pProvider.Pay(product.DisplayName, paymentId, product.Price, returnUrl, notifyUrl)
return payUrl, err
}
41 changes: 41 additions & 0 deletions object/product_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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 object

import (
"testing"

"github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util"
)

func TestProvider(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, paymentId, product.Price, returnUrl, notifyUrl)
if err != nil {
panic(err)
}

println(payUrl)
}
1 change: 1 addition & 0 deletions object/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
65 changes: 65 additions & 0 deletions pp/alipay.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// 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, 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)

payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
if err != nil {
return "", err
}
return payUrl, nil
}
26 changes: 26 additions & 0 deletions pp/provider.go
Original file line number Diff line number Diff line change
@@ -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, 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
}
2 changes: 2 additions & 0 deletions routers/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
12 changes: 12 additions & 0 deletions util/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"os"
"strconv"
"strings"
"time"
"unicode"

"github.com/google/uuid"
Expand Down Expand Up @@ -88,6 +89,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)
}
Expand Down
Loading

0 comments on commit bf5d4ee

Please sign in to comment.