added logic recurrent payment

This commit is contained in:
pasha1coil 2025-06-15 10:48:37 +03:00
parent a1f734a87d
commit 8f7f0db142
5 changed files with 96 additions and 0 deletions

@ -97,6 +97,7 @@ func (r *PaymentController) createPayment(ctx context.Context, paymentType model
Items: utils.ProtoItems2ReceiptItems(in.MainSettings.Items), Items: utils.ProtoItems2ReceiptItems(in.MainSettings.Items),
}), }),
"save_payment_method": strconv.FormatBool(in.MainSettings.Auto), "save_payment_method": strconv.FormatBool(in.MainSettings.Auto),
"recurrent": strconv.FormatBool(in.MainSettings.Recurrent),
} }
link, err := provider.CreateInvoice(ctx, request) link, err := provider.CreateInvoice(ctx, request)
if err != nil { if err != nil {

@ -59,6 +59,7 @@ type CreatePayment[T any] struct {
ClientIP string ClientIP string
Requisites T Requisites T
Auto bool Auto bool
Recurrent bool
} }
type BankCard struct { type BankCard struct {

@ -41,3 +41,10 @@ type Customer struct {
Email string `json:"email,omitempty"` Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"` Phone string `json:"phone,omitempty"`
} }
type CreateRecurrentPayment struct {
Amount Amount `json:"amount"`
Capture bool `json:"capture"`
PaymentMethodID string `json:"payment_method_id"`
Description string `json:"description,omitempty"`
}

@ -79,6 +79,11 @@ func (p *Provider) CreateInvoice(ctx context.Context, req map[string]string) (st
p.logger.Error("failed to create payment yandex receipt by parse map", zap.Error(err)) p.logger.Error("failed to create payment yandex receipt by parse map", zap.Error(err))
return "", errors.NewWithMessage("failed to parse input request by parse map", errors.ErrInvalidArgs) return "", errors.NewWithMessage("failed to parse input request by parse map", errors.ErrInvalidArgs)
} }
if request.Recurrent {
return p.CreateRecurrentPayment(ctx, request)
}
idempotenceKey := uuid.New().String() idempotenceKey := uuid.New().String()
yandexPayment, err := p.httpClient.R(). yandexPayment, err := p.httpClient.R().
@ -250,3 +255,77 @@ func (p *Provider) handleWebhook(ctx *fiber.Ctx) error {
return ctx.SendStatus(http.StatusOK) return ctx.SendStatus(http.StatusOK)
} }
func (p *Provider) CreateRecurrentPayment(ctx context.Context, request *models.CreatePayment[yandex.Receipt]) (string, errors.Error) {
methods, err := p.paymentMethodRepository.GetByUserID(ctx, request.UserID)
if err != nil {
p.logger.Error("failed to get payment methods", zap.Error(err), zap.String("userId", request.UserID))
return "", errors.NewWithError(err, errors.ErrInternalError)
}
if len(methods) == 0 {
p.logger.Warn("no saved payment methods found", zap.String("userId", request.UserID))
return "", errors.NewWithMessage("no saved payment methods found", errors.ErrInvalidArgs)
}
for _, method := range methods {
idempotenceKey := uuid.New().String()
yandexPayment, err := p.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetHeader("Idempotence-Key", idempotenceKey).
SetHeader("Authorization", utils.ConvertYoomoneySercetsToAuth("Basic", p.config.StoreID, p.config.SecretKey)).
SetBody(&yandex.CreateRecurrentPayment{
Amount: yandex.Amount{
Value: utils.ConvertAmountToStringFloat(request.Amount),
Currency: request.Currency,
},
PaymentMethodID: method.MethodID,
Capture: true,
}).
Post(p.config.PaymentsURL)
if err != nil {
p.logger.Error("failed to create recurrent payment", zap.Error(err), zap.String("userId", request.UserID))
return "", errors.NewWithError(fmt.Errorf("failed to create recurrent payment"), errors.ErrInternalError)
}
if yandexPayment.StatusCode() != http.StatusOK {
p.logger.Error("unexpected status code from yandex", zap.Int("statusCode", yandexPayment.StatusCode()), zap.String("userId", request.UserID))
return "", errors.NewWithError(fmt.Errorf("unexpected status code: %d", yandexPayment.StatusCode()), errors.ErrInternalError)
}
var payment yandex.Payment
if err := json.Unmarshal(yandexPayment.Body(), &payment); err != nil {
p.logger.Error("failed to unmarshal payment response", zap.Error(err), zap.String("userId", request.UserID), zap.String("methodId", method.MethodID))
return "", errors.NewWithError(err, errors.ErrInternalError)
}
if payment.Status != yandex.PaymentStatusSuccessfully {
p.logger.Error("payment not succeeded", zap.String("userId", request.UserID), zap.String("status", string(payment.Status)))
continue
}
_, err = p.repository.Insert(ctx, &models.Payment{
UserID: request.UserID,
PaymentID: payment.ID,
IdempotencePaymentID: idempotenceKey,
ClientIP: request.ClientIP,
Currency: request.Currency,
Amount: request.Amount,
Type: request.Type,
Status: models.PaymentStatusMap[string(payment.Status)],
Completed: false,
RawPaymentBody: payment,
CallbackHostGRPC: request.CallbackHostGRPC,
})
if err != nil {
p.logger.Error("failed to save payment to database", zap.Error(err))
return "", errors.NewWithError(fmt.Errorf("failed to save payment to database: %w", err), errors.ErrInternalError)
}
return payment.ID, nil
}
return "", errors.NewWithMessage("failed to create recurrent payment with any saved method", errors.ErrInternalError)
}

@ -36,6 +36,13 @@ func MapToCreatePaymentYandexReceipt(data map[string]string) (*models.CreatePaym
return nil, fmt.Errorf("invalid save_payment_method field: %w", err) return nil, fmt.Errorf("invalid save_payment_method field: %w", err)
} }
} }
recurrent := false
if val, ok := data["recurrent"]; ok && val != "" {
recurrent, err = strconv.ParseBool(val)
if err != nil {
return nil, fmt.Errorf("invalid recurrent field: %w", err)
}
}
payment := &models.CreatePayment[yandex.Receipt]{ payment := &models.CreatePayment[yandex.Receipt]{
Type: models.PaymentType(data["type"]), Type: models.PaymentType(data["type"]),
@ -47,6 +54,7 @@ func MapToCreatePaymentYandexReceipt(data map[string]string) (*models.CreatePaym
ClientIP: data["client_ip"], ClientIP: data["client_ip"],
Requisites: requisites, Requisites: requisites,
Auto: auto, Auto: auto,
Recurrent: recurrent,
} }
return payment, nil return payment, nil