Files
pay/internal/provider/fake/fake.go
T

92 lines
2.9 KiB
Go

// Package fake is an in-process Provider used to exercise the pay v2 pipeline
// end-to-end without any real channel. Real adapters (crypto/alipay/stripe) land
// in P3. provider_ref = "FAKE-"+OutTradeNo+"-"+<nanosecond>; render_type = crypto_address.
// The nanosecond suffix is fake-only: Attempt has uniqueIndex(channel,provider_ref),
// and a retry on the same order/method would otherwise reuse the same OutTradeNo and
// collide. Real channels naturally mint a distinct ref per create call.
package fake
import (
"context"
"encoding/json"
"errors"
"strconv"
"sync"
"time"
"github.com/wangjia/pay/internal/provider"
)
type Provider struct {
mu sync.Mutex
queryResults map[string]provider.PaidEvent
}
func New() *Provider { return &Provider{queryResults: map[string]provider.PaidEvent{}} }
func (p *Provider) Method() string { return "fake" }
func (p *Provider) Capabilities() provider.Capabilities {
return provider.Capabilities{
RenderTypes: []provider.RenderType{provider.RenderCryptoAddress},
SupportsRefund: false,
SettleCurrencies: []string{"USDT"},
Regions: []string{"global"},
}
}
func (p *Provider) Create(_ context.Context, req provider.CreateRequest) (*provider.Session, error) {
exp := time.Now().Add(15 * time.Minute)
ref := "FAKE-" + req.OutTradeNo + "-" + strconv.FormatInt(time.Now().UnixNano(), 36)
return &provider.Session{
ProviderRef: ref,
RenderType: provider.RenderCryptoAddress,
Payload: map[string]any{
"address": "TFake" + req.Account.AccountID + req.OutTradeNo,
"amount_minor": req.AmountMinor,
"currency": req.Currency,
},
ExpiresAt: &exp,
}, nil
}
// VerifyCallback 解析测试注入的 JSON 回调体成归一化 PaidEvent。
func (p *Provider) VerifyCallback(_ context.Context, in provider.CallbackInput) (*provider.PaidEvent, error) {
var b struct {
ProviderRef string `json:"provider_ref"`
Status string `json:"status"`
AmountMinor int64 `json:"amount_minor"`
Currency string `json:"currency"`
}
if err := json.Unmarshal(in.Raw, &b); err != nil {
return nil, err
}
if b.ProviderRef == "" {
return nil, errors.New("fake: missing provider_ref")
}
return &provider.PaidEvent{
ProviderRef: b.ProviderRef,
Status: provider.PaidStatus(b.Status),
PaidAmountMinor: b.AmountMinor,
PaidCurrency: b.Currency,
Raw: string(in.Raw),
}, nil
}
func (p *Provider) Query(_ context.Context, req provider.QueryRequest) (*provider.PaidEvent, error) {
p.mu.Lock()
defer p.mu.Unlock()
if ev, ok := p.queryResults[req.ProviderRef]; ok {
e := ev
return &e, nil
}
return &provider.PaidEvent{ProviderRef: req.ProviderRef, Status: provider.PaidPending}, nil
}
// SetQueryResult primes Query to report a specific event (test seam).
func (p *Provider) SetQueryResult(providerRef string, ev provider.PaidEvent) {
p.mu.Lock()
defer p.mu.Unlock()
p.queryResults[providerRef] = ev
}