Files
Jiahui ea8e5cf0a9 feat(account): Add workspace subscription plan (#6103)
* initialize the data structure

* Initialize the lifecycle management of workspace subscriptions

* fix index

* add workspace subscription debt

* optimize parse period & init workspace subscription & fix subscription create bug

* remove quota controller

* add user notify framework:
content_generator:content generation
service:event notification service
providers:vms/sms/email ... provider implementation
types: core structure type definition

* optimize struct & adjust the database to obtain data

* Implement the initialization of subscriptions and the implementation of subscription traffic packages

* service implement the database layer

* auto add column payment.stripe

* add stripe pay interface

* add workspace subscription service

* add the usercr tag for subscription based spaces, Add delete workspace subscription operator & Add api interfaces related to obtaining workspace costs

* optimize interface

* overall adjustment and repair

* optimize traffic early warning notifications

* optimize traffic early warning notifications

* optimize

* optimize & add deletion account api

* optimize log

* add get payment status api && support workspace subscription AI quota package

* optimize

* Fixed the issue where traffic was not distributed after renewing the subscription package

* fix

* fix workspace subscription transaction process add ai quota package

* fix ci lint

* fix controller/account ci lint

* fix controller/pkg ci lint

* fix controller/account ci lint

* fix controller/pkg ci lint

* add admin add workspace subscription

* fix upgrade workspace subscription

* add get workspace subscription plans api

* add get workspace subscription plans api

* fix ci lint

* fix ci lint

* fix ci lint
2025-10-26 16:15:52 +08:00

158 lines
4.5 KiB
Go

// Copyright © 2022 sealos.
//
// 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 pay
import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"log"
"os"
"time"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/native"
"github.com/wechatpay-apiv3/wechatpay-go/utils"
)
type WechatPayment struct{}
// ENV keys
const (
WechatPrivateKey = "WechatPrivateKey"
MchID = "MchID"
MchCertificateSerialNumber = "MchCertificateSerialNumber"
MchAPIv3Key = "MchAPIv3Key"
AppID = "AppID"
NotifyCallbackURL = "NotifyCallbackURL"
StatusSuccess = "SUCCESS"
StatusProcessing = "PROCESSING"
StatusNotPay = "NOTPAY"
StatusFail = "FAILED"
DefaultCallbackURL = "https://sealos.io/payment/wechat/callback"
)
func NewClient(ctx context.Context, opts ...core.ClientOption) (*core.Client, error) {
mchID := os.Getenv(MchID) // 商户号
mchCertificateSerialNumber := os.Getenv(MchCertificateSerialNumber) // 商户证书序列号
mchAPIv3Key := os.Getenv(MchAPIv3Key) // 商户APIv3密钥
mchPrivateKey, err := utils.LoadPrivateKey(os.Getenv(WechatPrivateKey))
if err != nil {
log.Print("private key is: ", os.Getenv(WechatPrivateKey))
return nil, fmt.Errorf("load merchant private key error: %w", err)
}
opts = append(
opts,
option.WithWechatPayAutoAuthCipher(
mchID,
mchCertificateSerialNumber,
mchPrivateKey,
mchAPIv3Key,
),
)
return core.NewClient(ctx, opts...)
}
func QueryOrder(orderID string) (*payments.Transaction, error) {
ctx := context.Background()
client, err := NewClient(context.Background())
if err != nil {
return nil, fmt.Errorf("new wechat pay client err:%w", err)
}
svc := native.NativeApiService{Client: client}
resp, _, err := svc.QueryOrderByOutTradeNo(ctx,
native.QueryOrderByOutTradeNoRequest{
Mchid: core.String(os.Getenv(MchID)),
OutTradeNo: core.String(orderID),
},
)
if err != nil {
return nil, fmt.Errorf("call QueryOrder err:%w", err)
}
return resp, nil
}
// 1 ¥ = amount 100
func WechatPay(amount int64, user, tradeNO, describe, callback string) (string, error) {
ctx := context.Background()
client, err := NewClient(context.Background())
if err != nil {
return "", fmt.Errorf("new wechat pay client err:%w", err)
}
if tradeNO == "" {
tradeNO = GetRandomString(32)
}
if tradeNO == "" {
return "", errors.New("generate tradeNO failed")
}
if describe == "" {
describe = "sealos cloud recharge"
}
if callback == "" {
callback = DefaultCallbackURL
}
svc := native.NativeApiService{Client: client}
resp, _, err := svc.Prepay(ctx,
native.PrepayRequest{
Appid: core.String(os.Getenv(AppID)),
Mchid: core.String(os.Getenv(MchID)),
Description: core.String(describe),
OutTradeNo: core.String(tradeNO),
TimeExpire: core.Time(time.Now()),
Attach: core.String(user),
NotifyUrl: core.String(callback),
GoodsTag: core.String("sealos recharge"),
SupportFapiao: core.Bool(false),
Amount: &native.Amount{
Currency: core.String("CNY"),
Total: core.Int64(amount),
},
Detail: &native.Detail{
CostPrice: core.Int64(608800),
GoodsDetail: []native.GoodsDetail{
{
GoodsName: core.String("sealos cloud recharge"),
MerchantGoodsId: core.String("ABC"),
Quantity: core.Int64(1),
UnitPrice: core.Int64(828800),
WechatpayGoodsId: core.String("1001"),
},
},
},
SettleInfo: &native.SettleInfo{
ProfitSharing: core.Bool(false),
},
},
)
if err != nil {
return "", fmt.Errorf("call Prepay err:%w", err)
}
return *resp.CodeUrl, nil
}
func GetRandomString(n int) string {
randBytes := make([]byte, n/2)
if _, err := rand.Read(randBytes); err != nil {
return ""
}
return hex.EncodeToString(randBytes)
}