Add NotifyPayment API.

This commit is contained in:
Yang Luo 2022-03-07 00:33:45 +08:00
parent bf5d4eea48
commit a4fc04474e
17 changed files with 215 additions and 105 deletions

View File

@ -16,10 +16,12 @@ package controllers
import (
"encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
"github.com/go-pay/gopay/alipay"
)
// GetPayments
@ -122,12 +124,18 @@ func (c *ApiController) DeletePayment() {
// @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)
bm, err := alipay.ParseNotifyToBodyMap(c.Ctx.Request)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.NotifyPayment("111", "222"))
c.ServeJSON()
ok := object.NotifyPayment(bm)
if ok {
_, err = c.Ctx.ResponseWriter.Write([]byte("success"))
if err != nil {
panic(err)
}
} else {
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))
}
}

View File

@ -18,6 +18,8 @@ import (
"fmt"
"github.com/casdoor/casdoor/util"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/alipay"
"xorm.io/core"
)
@ -27,13 +29,14 @@ type Payment struct {
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Provider string `xorm:"varchar(100)" json:"provider"`
Type string `xorm:"varchar(100)" json:"type"`
Organization string `xorm:"varchar(100)" json:"organization"`
User string `xorm:"varchar(100)" json:"user"`
Good string `xorm:"varchar(100)" json:"good"`
Amount string `xorm:"varchar(100)" json:"amount"`
Currency string `xorm:"varchar(100)" json:"currency"`
Provider string `xorm:"varchar(100)" json:"provider"`
Type string `xorm:"varchar(100)" json:"type"`
Organization string `xorm:"varchar(100)" json:"organization"`
User string `xorm:"varchar(100)" json:"user"`
ProductId string `xorm:"varchar(100)" json:"productId"`
ProductName string `xorm:"varchar(100)" json:"productName"`
Price float64 `json:"price"`
Currency string `xorm:"varchar(100)" json:"currency"`
State string `xorm:"varchar(100)" json:"state"`
}
@ -124,16 +127,58 @@ 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
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))
}
payment.State = state
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))
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(payment)
if price != product.Price {
panic(fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", price, product.Price))
}
payment := getPayment(owner, paymentId)
if payment == nil {
panic(fmt.Errorf("the payment: %s does not exist", paymentId))
}
provider, err := product.getProvider(providerId)
if err != nil {
panic(err)
}
cert := getCert(owner, provider.Cert)
if cert == nil {
panic(fmt.Errorf("the cert: %s does not exist", provider.Cert))
}
ok, err := alipay.VerifySignWithCert(cert.AuthorityPublicKey, bm)
if err != nil {
panic(err)
}
if ok {
payment.State = "Paid"
} else {
if cert == nil {
panic(fmt.Errorf("VerifySignWithCert() failed: %v", ok))
}
//payment.State = "Failed"
}
affected, err := adapter.Engine.ID(core.PK{owner, paymentId}).AllCols().Update(payment)
if err != nil {
panic(err)
}

View File

@ -139,19 +139,28 @@ func (product *Product) isValidProvider(provider *Provider) bool {
return false
}
func (product *Product) getProvider(providerId string) (*Provider, error) {
provider := getProvider(product.Owner, providerId)
if provider == nil {
return nil, fmt.Errorf("the payment provider: %s does not exist", providerId)
}
if !product.isValidProvider(provider) {
return nil, fmt.Errorf("the payment provider: %s is not valid for the product: %s", providerId, product.Name)
}
return provider, nil
}
func BuyProduct(id string, providerId string, 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)
provider, err := product.getProvider(providerId)
if err != nil {
return "", err
}
cert := getCert(product.Owner, provider.Cert)
@ -170,6 +179,6 @@ func BuyProduct(id string, providerId string, host string) (string, error) {
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)
payUrl, err := pProvider.Pay(product.DisplayName, product.Name, provider.Name, paymentId, product.Price, returnUrl, notifyUrl)
return payUrl, err
}

View File

@ -32,7 +32,7 @@ func TestProvider(t *testing.T) {
paymentId := util.GenerateTimeId()
returnUrl := ""
notifyUrl := ""
payUrl, err := pProvider.Pay(product.DisplayName, paymentId, product.Price, returnUrl, notifyUrl)
payUrl, err := pProvider.Pay(product.DisplayName, product.Name, provider.Name, paymentId, product.Price, returnUrl, notifyUrl)
if err != nil {
panic(err)
}

View File

@ -44,7 +44,7 @@ func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey s
return pp
}
func (pp *AlipayPaymentProvider) Pay(productName string, paymentId string, price float64, returnUrl string, notifyUrl string) (string, error) {
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"), ".")
@ -53,10 +53,12 @@ func (pp *AlipayPaymentProvider) Pay(productName string, paymentId string, price
bm.Set("subject", productName)
bm.Set("out_trade_no", paymentId)
bm.Set("total_amount", priceString)
bm.Set("return_url", returnUrl)
bm.Set("notify_url", notifyUrl)
bm.Set("productId", productId)
bm.Set("providerId", productId)
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
if err != nil {
return "", err

View File

@ -15,7 +15,7 @@
package pp
type PaymentProvider interface {
Pay(productName string, paymentId string, price float64, returnUrl string, notifyUrl string) (string, error)
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 {

View File

@ -42,6 +42,15 @@ func ParseInt(s string) int {
return i
}
func ParseFloat(s string) float64 {
f, err := strconv.ParseFloat(s, 64)
if err != nil {
panic(err)
}
return f
}
func ParseBool(s string) bool {
i := ParseInt(s)
return i != 0

View File

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

View File

@ -34,8 +34,9 @@ class PaymentListPage extends BaseListPage {
type: "PayPal",
organization: "built-in",
user: "admin",
good: "A notebook computer",
amount: "300",
productId: "computer-1",
productName: "A notebook computer",
price: 300.00,
currency: "USD",
state: "Paid",
}
@ -151,7 +152,7 @@ class PaymentListPage extends BaseListPage {
}
},
{
title: i18next.t("provider:Type"),
title: i18next.t("payment:Type"),
dataIndex: 'type',
key: 'type',
width: '110px',
@ -165,20 +166,20 @@ class PaymentListPage extends BaseListPage {
}
},
{
title: i18next.t("payment:Good"),
dataIndex: 'good',
key: 'good',
title: i18next.t("payment:Product"),
dataIndex: 'productName',
key: 'productName',
width: '160px',
sorter: true,
...this.getColumnSearchProps('good'),
...this.getColumnSearchProps('productName'),
},
{
title: i18next.t("payment:Amount"),
dataIndex: 'amount',
key: 'amount',
title: i18next.t("payment:Price"),
dataIndex: 'price',
key: 'price',
width: '120px',
sorter: true,
...this.getColumnSearchProps('amount'),
...this.getColumnSearchProps('price'),
},
{
title: i18next.t("payment:Currency"),

View File

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

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier"
},
"payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"Good": "Good",
"Good - Tooltip": "Good - Tooltip",
"New Payment": "New Payment"
"New Payment": "New Payment",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
},
"permission": {
"Actions": "Aktionen",
@ -274,6 +276,7 @@
},
"product": {
"Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product",
"CNY": "CNY",
"Currency": "Currency",
@ -288,6 +291,7 @@
"Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "Website URL - Tooltip"
},
"payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"Good": "Good",
"Good - Tooltip": "Good - Tooltip",
"New Payment": "New Payment"
"New Payment": "New Payment",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
},
"permission": {
"Actions": "Actions",
@ -274,6 +276,7 @@
},
"product": {
"Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product",
"CNY": "CNY",
"Currency": "Currency",
@ -288,6 +291,7 @@
"Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier"
},
"payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"Good": "Good",
"Good - Tooltip": "Good - Tooltip",
"New Payment": "New Payment"
"New Payment": "New Payment",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
},
"permission": {
"Actions": "Actions",
@ -274,6 +276,7 @@
},
"product": {
"Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product",
"CNY": "CNY",
"Currency": "Currency",
@ -288,6 +291,7 @@
"Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier"
},
"payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"Good": "Good",
"Good - Tooltip": "Good - Tooltip",
"New Payment": "New Payment"
"New Payment": "New Payment",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
},
"permission": {
"Actions": "アクション",
@ -274,6 +276,7 @@
},
"product": {
"Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product",
"CNY": "CNY",
"Currency": "Currency",
@ -288,6 +291,7 @@
"Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier"
},
"payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"Good": "Good",
"Good - Tooltip": "Good - Tooltip",
"New Payment": "New Payment"
"New Payment": "New Payment",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
},
"permission": {
"Actions": "Actions",
@ -274,6 +276,7 @@
},
"product": {
"Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product",
"CNY": "CNY",
"Currency": "Currency",
@ -288,6 +291,7 @@
"Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier"
},
"payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"Good": "Good",
"Good - Tooltip": "Good - Tooltip",
"New Payment": "New Payment"
"New Payment": "New Payment",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
},
"permission": {
"Actions": "Действия",
@ -274,6 +276,7 @@
},
"product": {
"Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product",
"CNY": "CNY",
"Currency": "Currency",
@ -288,6 +291,7 @@
"Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "网页地址"
},
"payment": {
"Amount": "金额",
"Amount - Tooltip": "付款的金额",
"Currency": "币种",
"Currency - Tooltip": "如USD美元CNY人民币等",
"Edit Payment": "编辑付款",
"Good": "商品",
"Good - Tooltip": "购买的商品名称",
"New Payment": "添加付款"
"New Payment": "添加付款",
"Price": "价格",
"Price - Tooltip": "商品价格",
"Product": "商品",
"Product - Tooltip": "商品名称",
"Type": "支付方式",
"Type - Tooltip": "商品购买时的支付方式"
},
"permission": {
"Actions": "动作",
@ -274,6 +276,7 @@
},
"product": {
"Alipay": "支付宝",
"Buy": "购买",
"Buy Product": "购买商品",
"CNY": "人民币",
"Currency": "币种",
@ -288,6 +291,7 @@
"Payment providers": "支付提供商",
"Payment providers - Tooltip": "支付提供商 - 工具提示",
"Paypal": "Paypal",
"Placing order...": "正在下单...",
"Price": "价格",
"Price - Tooltip": "价格 - 工具提示",
"Quantity": "库存",