| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- package service
- import (
- "context"
- "fmt"
- "strconv"
- "strings"
- dbent "github.com/Wei-Shaw/sub2api/ent"
- "github.com/Wei-Shaw/sub2api/internal/payment"
- )
- type paymentOrderProviderSnapshot struct {
- SchemaVersion int
- ProviderInstanceID string
- ProviderKey string
- PaymentMode string
- MerchantAppID string
- MerchantID string
- Currency string
- }
- func psOrderProviderSnapshot(order *dbent.PaymentOrder) *paymentOrderProviderSnapshot {
- if order == nil || len(order.ProviderSnapshot) == 0 {
- return nil
- }
- snapshot := &paymentOrderProviderSnapshot{
- SchemaVersion: psSnapshotIntValue(order.ProviderSnapshot["schema_version"]),
- ProviderInstanceID: psSnapshotStringValue(order.ProviderSnapshot["provider_instance_id"]),
- ProviderKey: psSnapshotStringValue(order.ProviderSnapshot["provider_key"]),
- PaymentMode: psSnapshotStringValue(order.ProviderSnapshot["payment_mode"]),
- MerchantAppID: psSnapshotStringValue(order.ProviderSnapshot["merchant_app_id"]),
- MerchantID: psSnapshotStringValue(order.ProviderSnapshot["merchant_id"]),
- Currency: psSnapshotStringValue(order.ProviderSnapshot["currency"]),
- }
- if snapshot.SchemaVersion == 0 &&
- snapshot.ProviderInstanceID == "" &&
- snapshot.ProviderKey == "" &&
- snapshot.PaymentMode == "" &&
- snapshot.MerchantAppID == "" &&
- snapshot.MerchantID == "" &&
- snapshot.Currency == "" {
- return nil
- }
- return snapshot
- }
- func psSnapshotStringValue(value any) string {
- switch typed := value.(type) {
- case string:
- return strings.TrimSpace(typed)
- default:
- return ""
- }
- }
- func psSnapshotIntValue(value any) int {
- switch typed := value.(type) {
- case int:
- return typed
- case int32:
- return int(typed)
- case int64:
- return int(typed)
- case float32:
- return int(typed)
- case float64:
- return int(typed)
- case string:
- n, err := strconv.Atoi(strings.TrimSpace(typed))
- if err == nil {
- return n
- }
- }
- return 0
- }
- func (s *PaymentService) resolveSnapshotOrderProviderInstance(ctx context.Context, order *dbent.PaymentOrder, snapshot *paymentOrderProviderSnapshot) (*dbent.PaymentProviderInstance, error) {
- if s == nil || s.entClient == nil || order == nil || snapshot == nil {
- return nil, nil
- }
- snapshotInstanceID := strings.TrimSpace(snapshot.ProviderInstanceID)
- columnInstanceID := strings.TrimSpace(psStringValue(order.ProviderInstanceID))
- if snapshotInstanceID == "" {
- snapshotInstanceID = columnInstanceID
- }
- if snapshotInstanceID == "" {
- return nil, fmt.Errorf("order %d provider snapshot is missing provider_instance_id", order.ID)
- }
- if columnInstanceID != "" && snapshot.ProviderInstanceID != "" && !strings.EqualFold(columnInstanceID, snapshot.ProviderInstanceID) {
- return nil, fmt.Errorf("order %d provider snapshot instance mismatch: snapshot=%s order=%s", order.ID, snapshot.ProviderInstanceID, columnInstanceID)
- }
- instID, err := strconv.ParseInt(snapshotInstanceID, 10, 64)
- if err != nil {
- return nil, fmt.Errorf("order %d provider snapshot instance id is invalid: %s", order.ID, snapshotInstanceID)
- }
- inst, err := s.entClient.PaymentProviderInstance.Get(ctx, instID)
- if err != nil {
- if dbent.IsNotFound(err) {
- return nil, fmt.Errorf("order %d provider snapshot instance %s is missing", order.ID, snapshotInstanceID)
- }
- return nil, err
- }
- if snapshot.ProviderKey != "" && !strings.EqualFold(strings.TrimSpace(inst.ProviderKey), snapshot.ProviderKey) {
- return nil, fmt.Errorf("order %d provider snapshot key mismatch: snapshot=%s instance=%s", order.ID, snapshot.ProviderKey, inst.ProviderKey)
- }
- return inst, nil
- }
- func expectedNotificationProviderKeyForOrder(registry *payment.Registry, order *dbent.PaymentOrder, instanceProviderKey string) string {
- if order == nil {
- return strings.TrimSpace(instanceProviderKey)
- }
- orderProviderKey := psStringValue(order.ProviderKey)
- if snapshot := psOrderProviderSnapshot(order); snapshot != nil && snapshot.ProviderKey != "" {
- orderProviderKey = snapshot.ProviderKey
- }
- return expectedNotificationProviderKey(registry, order.PaymentType, orderProviderKey, instanceProviderKey)
- }
- func validateProviderSnapshotMetadata(order *dbent.PaymentOrder, providerKey string, metadata map[string]string) error {
- if order == nil || len(metadata) == 0 {
- return nil
- }
- snapshot := psOrderProviderSnapshot(order)
- if snapshot == nil {
- return nil
- }
- switch strings.TrimSpace(providerKey) {
- case payment.TypeWxpay:
- if expected := strings.TrimSpace(snapshot.MerchantAppID); expected != "" {
- actual := strings.TrimSpace(metadata["appid"])
- if actual == "" {
- return fmt.Errorf("wxpay notification missing appid")
- }
- if !strings.EqualFold(expected, actual) {
- return fmt.Errorf("wxpay appid mismatch: expected %s, got %s", expected, actual)
- }
- }
- if expected := strings.TrimSpace(snapshot.MerchantID); expected != "" {
- actual := strings.TrimSpace(metadata["mchid"])
- if actual == "" {
- return fmt.Errorf("wxpay notification missing mchid")
- }
- if !strings.EqualFold(expected, actual) {
- return fmt.Errorf("wxpay mchid mismatch: expected %s, got %s", expected, actual)
- }
- }
- if expected := strings.TrimSpace(snapshot.Currency); expected != "" {
- actual := strings.ToUpper(strings.TrimSpace(metadata["currency"]))
- if actual == "" {
- return fmt.Errorf("wxpay notification missing currency")
- }
- if !strings.EqualFold(expected, actual) {
- return fmt.Errorf("wxpay currency mismatch: expected %s, got %s", expected, actual)
- }
- }
- if actual := strings.TrimSpace(metadata["trade_state"]); actual != "" && !strings.EqualFold(actual, "SUCCESS") {
- return fmt.Errorf("wxpay trade_state mismatch: expected SUCCESS, got %s", actual)
- }
- case payment.TypeAlipay:
- if expected := strings.TrimSpace(snapshot.MerchantAppID); expected != "" {
- actual := strings.TrimSpace(metadata["app_id"])
- if actual == "" {
- return fmt.Errorf("alipay app_id missing")
- }
- if !strings.EqualFold(expected, actual) {
- return fmt.Errorf("alipay app_id mismatch: expected %s, got %s", expected, actual)
- }
- }
- case payment.TypeEasyPay:
- if expected := strings.TrimSpace(snapshot.MerchantID); expected != "" {
- actual := strings.TrimSpace(metadata["pid"])
- if actual == "" {
- return fmt.Errorf("easypay pid missing")
- }
- if !strings.EqualFold(expected, actual) {
- return fmt.Errorf("easypay pid mismatch: expected %s, got %s", expected, actual)
- }
- }
- case payment.TypeStripe:
- if expected := strings.TrimSpace(snapshot.Currency); expected != "" {
- actual := strings.ToUpper(strings.TrimSpace(metadata["currency"]))
- if actual == "" {
- return fmt.Errorf("stripe notification missing currency")
- }
- if !strings.EqualFold(expected, actual) {
- return fmt.Errorf("stripe currency mismatch: expected %s, got %s", expected, actual)
- }
- }
- case payment.TypeAirwallex:
- if expected := strings.TrimSpace(snapshot.MerchantID); expected != "" {
- actual := strings.TrimSpace(metadata["account_id"])
- if actual == "" {
- return fmt.Errorf("airwallex account_id missing")
- }
- if !strings.EqualFold(expected, actual) {
- return fmt.Errorf("airwallex account_id mismatch: expected %s, got %s", expected, actual)
- }
- }
- if expected := strings.TrimSpace(snapshot.Currency); expected != "" {
- actual := strings.ToUpper(strings.TrimSpace(metadata["currency"]))
- if actual == "" {
- return fmt.Errorf("airwallex notification missing currency")
- }
- if !strings.EqualFold(expected, actual) {
- return fmt.Errorf("airwallex currency mismatch: expected %s, got %s", expected, actual)
- }
- }
- if actual := strings.TrimSpace(metadata["status"]); actual != "" && !strings.EqualFold(actual, "SUCCEEDED") {
- return fmt.Errorf("airwallex status mismatch: expected SUCCEEDED, got %s", actual)
- }
- }
- return nil
- }
- func providerMerchantIdentityMetadata(prov payment.Provider) map[string]string {
- if prov == nil {
- return nil
- }
- reporter, ok := prov.(payment.MerchantIdentityProvider)
- if !ok {
- return nil
- }
- return reporter.MerchantIdentityMetadata()
- }
|