feat: migrate serevices
Some checks failed
build quyun / Build (push) Failing after 2m50s

This commit is contained in:
2025-12-19 19:05:12 +08:00
parent 005585c53b
commit 557a641f41
71 changed files with 5626 additions and 280 deletions

View File

@@ -0,0 +1,330 @@
package wepay
import (
"context"
"crypto/rsa"
"encoding/json"
"fmt"
"net/http"
"time"
w "quyun/v2/providers/wechat"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/wechat/v3"
"github.com/go-pay/util/js"
"github.com/gofiber/fiber/v3"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"go.ipao.vip/atom/container"
"go.ipao.vip/atom/opt"
)
type Config struct{}
func Provide(opts ...opt.Option) error {
o := opt.New(opts...)
var config Config
if err := o.UnmarshalConfig(&config); err != nil {
return err
}
return container.Container.Provide(func(wechatConfig *w.Config) (*Client, error) {
client, err := wechat.NewClientV3(
wechatConfig.Pay.MchID,
wechatConfig.Pay.SerialNo,
wechatConfig.Pay.ApiV3Key,
wechatConfig.Pay.PrivateKey,
)
if err != nil {
return nil, err
}
client.DebugSwitch = gopay.DebugOff
if wechatConfig.DevMode {
client.DebugSwitch = gopay.DebugOn
}
err = client.AutoVerifySignByPublicKey([]byte(wechatConfig.Pay.PublicKey), wechatConfig.Pay.PublicKeyID)
if err != nil {
return nil, errors.Wrap(err, "AutoVerifySignByPublicKey")
}
return &Client{
payClient: client,
config: wechatConfig,
}, nil
}, o.DiOptions()...)
}
type Client struct {
payClient *wechat.ClientV3
config *w.Config
}
func (c *Client) GetClient() *wechat.ClientV3 {
return c.payClient
}
// WxPublicKeyMap
func (c *Client) WxPublicKeyMap() map[string]*rsa.PublicKey {
return c.payClient.WxPublicKeyMap()
}
type PrepayData struct {
client *Client
AppID string `json:"app_id"`
PrepayID string `json:"prepay_id"`
}
// PaySignOfJSAPI
func (pay *PrepayData) PaySignOfJSAPI() (*wechat.JSAPIPayParams, error) {
return pay.client.payClient.PaySignOfJSAPI(pay.AppID, pay.PrepayID)
}
func (c *Client) Refund(ctx context.Context, f func(*BodyMap)) (*wechat.RefundOrderResponse, error) {
bm := NewRefundBodyMap(c.config)
f(bm)
resp, err := c.payClient.V3Refund(ctx, bm.bm)
if err != nil {
return nil, err
}
if resp.Code != wechat.Success {
log.Errorf("WePay Refund error: %s", resp.Error)
return nil, errors.New(resp.Error)
}
return resp.Response, nil
}
func (c *Client) V3TransactionJsapi(ctx context.Context, f func(*BodyMap)) (*PrepayData, error) {
bm := NewBodyMap(c.config)
f(bm)
resp, err := c.payClient.V3TransactionJsapi(ctx, bm.bm)
if err != nil {
return nil, err
}
if resp.Code != wechat.Success {
b, _ := json.Marshal(resp)
log.Errorf("WePay V3TransactionJsapi error: %s", b)
return nil, errors.New(resp.Error)
}
return &PrepayData{
client: c,
AppID: c.config.AppID,
PrepayID: resp.Response.PrepayId,
}, nil
}
func (c *Client) ParseNotify(
ctx fiber.Ctx,
payCallback func(fiber.Ctx, *wechat.V3DecryptPayResult) error,
refundCallback func(fiber.Ctx, *wechat.V3DecryptRefundResult) error,
) error {
body := ctx.Body()
si := &wechat.SignInfo{
HeaderTimestamp: ctx.Get(wechat.HeaderTimestamp),
HeaderNonce: ctx.Get(wechat.HeaderNonce),
HeaderSignature: ctx.Get(wechat.HeaderSignature),
HeaderSerial: ctx.Get(wechat.HeaderSerial),
SignBody: string(body),
}
notifyReq := &wechat.V3NotifyReq{SignInfo: si}
if err := js.UnmarshalBytes(body, notifyReq); err != nil {
log.Errorf("json unmarshal error:%v", err)
return ctx.Status(http.StatusBadRequest).JSON(fiber.Map{"error": fmt.Sprintf("json unmarshal error:%v", err)})
}
// 获取微信平台证书
certMap := c.WxPublicKeyMap()
// 验证异步通知的签名
if err := notifyReq.VerifySignByPKMap(certMap); err != nil {
log.Errorf("verify sign error:%v", err)
return ctx.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "Invalid signature"})
}
// TRANSACTION.SUCCESS :支付成功通知
// REFUND.SUCCESS退款成功通知
// REFUND.ABNORMAL退款异常通知
// REFUND.CLOSED退款关闭通知
switch notifyReq.EventType {
case "TRANSACTION.SUCCESS":
var notifyData wechat.V3DecryptPayResult
if err := notifyReq.DecryptCipherTextToStruct(c.config.Pay.ApiV3Key, &notifyData); err != nil {
return ctx.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "Invalid cipher text"})
}
log.Infof("Successfully decrypted cipher text for pay notify data: %+v", notifyData)
if err := payCallback(ctx, &notifyData); err != nil {
log.Errorf("payCallback error:%v", err)
return err
}
case "REFUND.SUCCESS", "REFUND.ABNORMAL", "REFUND.CLOSED":
var notifyData wechat.V3DecryptRefundResult
if err := notifyReq.DecryptCipherTextToStruct(c.config.Pay.ApiV3Key, &notifyData); err != nil {
return ctx.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "Invalid cipher text"})
}
log.Infof("Successfully decrypted cipher text for refund notify data: %+v", notifyData)
if err := refundCallback(ctx, &notifyData); err != nil {
log.Errorf("refundCallback error:%v", err)
return err
}
}
return ctx.Status(http.StatusOK).JSON(&wechat.V3NotifyRsp{
Code: gopay.SUCCESS,
Message: "成功",
})
}
type BodyMap struct {
bm gopay.BodyMap
}
func NewRefundBodyMap(c *w.Config) *BodyMap {
bm := make(gopay.BodyMap)
bm.Set("notify_url", c.Pay.NotifyURL)
return &BodyMap{
bm: bm,
}
}
func NewBodyMap(c *w.Config) *BodyMap {
bm := make(gopay.BodyMap)
bm.Set("appid", c.AppID).
Set("mchid", c.Pay.MchID).
Set("notify_url", c.Pay.NotifyURL)
// .
// SetBodyMap("amount", func(bm gopay.BodyMap) {
// bm.Set("total", 1).
// Set("currency", "CNY")
// })
return &BodyMap{
bm: bm,
}
}
func (b *BodyMap) Set(key string, value interface{}) *BodyMap {
b.bm.Set(key, value)
return b
}
func (b *BodyMap) SetBodyMap(key string, f func(bm gopay.BodyMap)) *BodyMap {
b.bm.SetBodyMap(key, f)
return b
}
// Expire time
func (b *BodyMap) Expire(t time.Duration) *BodyMap {
return b.Set("time_expire", time.Now().Add(t).Format(time.RFC3339))
}
// Description
func (b *BodyMap) Description(desc string) *BodyMap {
return b.Set("description", desc)
}
// OutTradeNo
func (b *BodyMap) OutTradeNo(outTradeNo string) *BodyMap {
return b.Set("out_trade_no", outTradeNo)
}
// TransactionID
func (b *BodyMap) TransactionID(transactionID string) *BodyMap {
return b.Set("transaction_id", transactionID)
}
// OutRefundNo
func (b *BodyMap) OutRefundNo(outRefundNo string) *BodyMap {
return b.Set("out_refund_no", outRefundNo)
}
// RefundReason
func (b *BodyMap) RefundReason(refundReason string) *BodyMap {
return b.Set("reason", refundReason)
}
// RefundAmount
func (b *BodyMap) RefundAmount(total, refund int64, currency CURRENCY) *BodyMap {
return b.SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.
Set("total", total).
Set("refund", refund).
Set("currency", currency.String())
})
}
func (b *BodyMap) CNYRefundAmount(total, refund int64) *BodyMap {
return b.RefundAmount(total, refund, CNY)
}
type RefundGoodsInfo struct {
MerchantGoodsID string `json:"merchant_goods_id"`
GoodsName string `json:"goods_name"`
RefundQuantity int64 `json:"refund_quantity"`
RefundAmount int64 `json:"refund_amount"`
UnitPrice int64 `json:"unit_price"`
}
// RefundGoodsInfo
func (b *BodyMap) RefundGoods(goods []RefundGoodsInfo) *BodyMap {
return b.Set("goods_detail", goods)
}
// Amount
func (b *BodyMap) Amount(total int64, currency CURRENCY) *BodyMap {
return b.SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.
Set("total", total).
Set("currency", currency.String())
})
}
func (b *BodyMap) CNYAmount(total int64) *BodyMap {
return b.Amount(total, CNY)
}
type GoodsInfo struct {
MerchantGoodsID string `json:"merchant_goods_id"`
GoodsName string `json:"goods_name"`
Quantity int64 `json:"quantity"`
UnitPrice int64 `json:"unit_price"`
}
func (b *BodyMap) Detail(goods []GoodsInfo) *BodyMap {
return b.SetBodyMap("detail", func(bm gopay.BodyMap) {
bm.Set("goods_detail", goods)
})
}
// Payer
func (b *BodyMap) Payer(spOpenId string) *BodyMap {
return b.SetBodyMap("payer", func(bm gopay.BodyMap) {
bm.Set("openid", spOpenId)
})
}
// SubMchId
func (b *BodyMap) SubMchId(subMchId string) *BodyMap {
return b.Set("sub_mchid", subMchId)
}
type CURRENCY string
func (c CURRENCY) String() string {
return string(c)
}
const (
CNY CURRENCY = "CNY"
USD CURRENCY = "USD"
EUR CURRENCY = "EUR"
)