支付相关
This commit is contained in:
+32
-2
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
const (
|
||||
PaymentAlipay = "alipay"
|
||||
PaymentAlipayV3 = "alipay-v3"
|
||||
PaymentWechatV2 = "wechat-v2"
|
||||
PaymentWechatV3 = "wechat-v3"
|
||||
PaymentApple = "apple-iap"
|
||||
@@ -40,7 +41,7 @@ const (
|
||||
)
|
||||
|
||||
var SupportedPaymentProviders = []string{
|
||||
PaymentAlipay, PaymentWechatV2, PaymentWechatV3, PaymentApple,
|
||||
PaymentAlipay, PaymentAlipayV3, PaymentWechatV2, PaymentWechatV3, PaymentApple,
|
||||
PaymentChinaums, PaymentSFT, PaymentSuperPay, PaymentWechatGame, PaymentDouyinGame,
|
||||
PaymentDouyin, PaymentQQ, PaymentAllinPay, PaymentLakala, PaymentPayPal, PaymentSaobei,
|
||||
}
|
||||
@@ -228,7 +229,7 @@ func DefaultPaymentCallbackAck(provider string, success bool) PaymentCallbackAck
|
||||
status = 500
|
||||
}
|
||||
switch provider {
|
||||
case PaymentAlipay:
|
||||
case PaymentAlipay, PaymentAlipayV3:
|
||||
body := "success"
|
||||
if !success {
|
||||
body = "failure"
|
||||
@@ -1071,6 +1072,35 @@ func validatePaymentConfirmation(intent *PaymentIntent, provider, tradeNo string
|
||||
if strings.TrimSpace(intent.BusinessType) == "" || strings.TrimSpace(intent.BusinessID) == "" {
|
||||
return errors.New("支付业务标识不完整")
|
||||
}
|
||||
if provider == PaymentApple {
|
||||
if err := validateAppleProduct(intent.Extra, result.Payload); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAppleProduct(intentExtra, providerPayload json.RawMessage) error {
|
||||
var expected struct {
|
||||
ProductID string `json:"product_id"`
|
||||
}
|
||||
if err := json.Unmarshal(intentExtra, &expected); err != nil {
|
||||
return errors.New("Apple 支付订单商品配置无效")
|
||||
}
|
||||
expected.ProductID = strings.TrimSpace(expected.ProductID)
|
||||
if expected.ProductID == "" {
|
||||
return errors.New("Apple 支付订单缺少 product_id")
|
||||
}
|
||||
var actual struct {
|
||||
ProductID string `json:"productId"`
|
||||
}
|
||||
if err := json.Unmarshal(providerPayload, &actual); err != nil {
|
||||
return errors.New("Apple 交易载荷无效")
|
||||
}
|
||||
actual.ProductID = strings.TrimSpace(actual.ProductID)
|
||||
if actual.ProductID == "" || actual.ProductID != expected.ProductID {
|
||||
return errors.New("Apple 交易 productId 与业务订单不匹配")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package biz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log/slog"
|
||||
@@ -346,3 +347,23 @@ func TestPaymentConfirmationRejectsBrokenDiscountConservation(t *testing.T) {
|
||||
t.Fatal("broken discount conservation was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaymentConfirmationBindsAppleProductID(t *testing.T) {
|
||||
intent := &PaymentIntent{
|
||||
Provider: PaymentApple, TradeNo: "123e4567-e89b-12d3-a456-426614174000",
|
||||
Amount: 100, Currency: "USD", BusinessType: "item", BusinessID: "I1",
|
||||
Extra: json.RawMessage(`{"product_id":"com.example.coin100"}`),
|
||||
}
|
||||
result := &PaymentResult{
|
||||
Provider: PaymentApple, TradeNo: intent.TradeNo, ProviderTradeNo: "APPLE-TX-1",
|
||||
Status: "success", Amount: 100, Currency: "USD",
|
||||
Payload: json.RawMessage(`{"productId":"com.example.coin100"}`),
|
||||
}
|
||||
if err := validatePaymentConfirmation(intent, PaymentApple, intent.TradeNo, result); err != nil {
|
||||
t.Fatalf("matching productId rejected: %v", err)
|
||||
}
|
||||
result.Payload = json.RawMessage(`{"productId":"com.example.coin200"}`)
|
||||
if err := validatePaymentConfirmation(intent, PaymentApple, intent.TradeNo, result); err == nil {
|
||||
t.Fatal("mismatched Apple productId was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user