Skip to content

Commit e6931bf

Browse files
committed
feat: add stripe support for one-off payment
1 parent 9759ca2 commit e6931bf

File tree

4 files changed

+223
-9
lines changed

4 files changed

+223
-9
lines changed

services/skus/model/model.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,10 @@ type CreateOrderRequestNew struct {
461461
Locale string `json:"locale" validate:"omitempty,bcp47_language_tag"`
462462
}
463463

464+
func (r *CreateOrderRequestNew) IsOneOffPayment() bool {
465+
return r.Metadata["product_pricing"] == "one-off"
466+
}
467+
464468
// OrderItemRequestNew represents an item in an order request.
465469
type OrderItemRequestNew struct {
466470
Quantity int `json:"quantity" validate:"required,gte=1"`

services/skus/model/model_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1958,3 +1958,53 @@ func TestOrderItem_IsSearchAnnual(t *testing.T) {
19581958
func ptrTo[T any](v T) *T {
19591959
return &v
19601960
}
1961+
1962+
func TestCreateOrderRequestNew_IsOneOffPayment(t *testing.T) {
1963+
type tcGiven struct {
1964+
req *model.CreateOrderRequestNew
1965+
}
1966+
1967+
type tcExpected struct {
1968+
res bool
1969+
}
1970+
1971+
type testCase struct {
1972+
name string
1973+
given tcGiven
1974+
exp tcExpected
1975+
}
1976+
1977+
tests := []testCase{
1978+
{
1979+
name: "product_pricing_none",
1980+
given: tcGiven{
1981+
req: &model.CreateOrderRequestNew{
1982+
Metadata: map[string]string{},
1983+
},
1984+
},
1985+
},
1986+
1987+
{
1988+
name: "product_pricing_one_off",
1989+
given: tcGiven{
1990+
req: &model.CreateOrderRequestNew{
1991+
Metadata: map[string]string{
1992+
"product_pricing": "one-off",
1993+
},
1994+
},
1995+
},
1996+
exp: tcExpected{
1997+
res: true,
1998+
},
1999+
},
2000+
}
2001+
2002+
for i := range tests {
2003+
tc := tests[i]
2004+
2005+
t.Run(tc.name, func(t *testing.T) {
2006+
actual := tc.given.req.IsOneOffPayment()
2007+
should.Equal(t, tc.exp.res, actual)
2008+
})
2009+
}
2010+
}

services/skus/service.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2163,6 +2163,11 @@ func (s *Service) createStripeSession(ctx context.Context, req *model.CreateOrde
21632163
return "", err
21642164
}
21652165

2166+
csm := string(stripe.CheckoutSessionModeSubscription)
2167+
if req.IsOneOffPayment() {
2168+
csm = string(stripe.CheckoutSessionModePayment)
2169+
}
2170+
21662171
sreq := createStripeSessionRequest{
21672172
orderID: oid,
21682173
email: req.Email,
@@ -2174,6 +2179,7 @@ func (s *Service) createStripeSession(ctx context.Context, req *model.CreateOrde
21742179
discounts: buildStripeDiscounts(req.Discounts),
21752180
metadata: req.Metadata,
21762181
Locale: req.Locale,
2182+
csMode: csm,
21772183
}
21782184

21792185
return createStripeSession(ctx, s.stripeCl, sreq, s.stripeLocaleValid)
@@ -2769,6 +2775,7 @@ func (s *Service) recreateStripeSession(ctx context.Context, dbi sqlx.ExecerCont
27692775
trialDays: ord.GetTrialDays(),
27702776
items: buildStripeLineItems(ord.Items),
27712777
Locale: oldSess.Locale,
2778+
csMode: string(oldSess.Mode),
27722779
}
27732780

27742781
if req.email == "" {
@@ -2999,12 +3006,13 @@ type createStripeSessionRequest struct {
29993006
discounts []*stripe.CheckoutSessionDiscountParams
30003007
metadata map[string]string
30013008
Locale string
3009+
csMode string
30023010
}
30033011

30043012
func createStripeSession(ctx context.Context, cl stripeClient, req createStripeSessionRequest, slv xstripe.LocaleValidator) (string, error) {
30053013
params := &stripe.CheckoutSessionParams{
30063014
PaymentMethodTypes: []*string{ptrTo("card")},
3007-
Mode: ptrTo(string(stripe.CheckoutSessionModeSubscription)),
3015+
Mode: ptrTo(req.csMode),
30083016
SuccessURL: &req.successURL,
30093017
CancelURL: &req.cancelURL,
30103018
ClientReferenceID: &req.orderID,
@@ -3035,8 +3043,28 @@ func createStripeSession(ctx context.Context, cl stripeClient, req createStripeS
30353043
}
30363044
}
30373045

3038-
if req.trialDays > 0 {
3039-
params.SubscriptionData.TrialPeriodDays = &req.trialDays
3046+
switch params.Mode {
3047+
case ptrTo(string(stripe.CheckoutSessionModeSubscription)):
3048+
params.SubscriptionData = &stripe.CheckoutSessionSubscriptionDataParams{}
3049+
3050+
if req.trialDays > 0 {
3051+
params.SubscriptionData.TrialPeriodDays = &req.trialDays
3052+
}
3053+
3054+
params.SubscriptionData.AddMetadata("orderID", req.orderID)
3055+
3056+
for k, v := range req.metadata {
3057+
params.SubscriptionData.AddMetadata(k, v)
3058+
}
3059+
3060+
case ptrTo(string(stripe.CheckoutSessionModePayment)):
3061+
params.PaymentIntentData = &stripe.CheckoutSessionPaymentIntentDataParams{}
3062+
3063+
params.PaymentIntentData.AddMetadata("orderID", req.orderID)
3064+
3065+
for k, v := range req.metadata {
3066+
params.PaymentIntentData.AddMetadata(k, v)
3067+
}
30403068
}
30413069

30423070
// Only allow user-facing promotion codes if params.Discounts is empty:
@@ -3045,12 +3073,6 @@ func createStripeSession(ctx context.Context, cl stripeClient, req createStripeS
30453073
params.AddExtra("allow_promotion_codes", "true")
30463074
}
30473075

3048-
params.SubscriptionData.AddMetadata("orderID", req.orderID)
3049-
3050-
for k, v := range req.metadata {
3051-
params.SubscriptionData.AddMetadata(k, v)
3052-
}
3053-
30543076
sess, err := cl.CreateSession(ctx, params)
30553077
if err != nil {
30563078
return "", err

services/skus/service_nonint_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4260,6 +4260,54 @@ func TestService_createStripeSession(t *testing.T) {
42604260
val: "cs_test_id",
42614261
},
42624262
},
4263+
4264+
{
4265+
name: "success_mode_subscription",
4266+
given: tcGiven{
4267+
cl: &xstripe.MockClient{
4268+
FnCreateSession: func(ctx context.Context, params *stripe.CheckoutSessionParams) (*stripe.CheckoutSession, error) {
4269+
if *params.Mode != string(stripe.CheckoutSessionModeSubscription) {
4270+
return nil, model.Error("unexpected_payment_mode")
4271+
}
4272+
4273+
result := &stripe.CheckoutSession{ID: "cs_test_id"}
4274+
4275+
return result, nil
4276+
},
4277+
},
4278+
req: &model.CreateOrderRequestNew{},
4279+
ord: &model.Order{},
4280+
},
4281+
exp: tcExpected{
4282+
val: "cs_test_id",
4283+
},
4284+
},
4285+
4286+
{
4287+
name: "success_mode_payment",
4288+
given: tcGiven{
4289+
cl: &xstripe.MockClient{
4290+
FnCreateSession: func(ctx context.Context, params *stripe.CheckoutSessionParams) (*stripe.CheckoutSession, error) {
4291+
if *params.Mode != string(stripe.CheckoutSessionModePayment) {
4292+
return nil, model.Error("unexpected_payment_mode")
4293+
}
4294+
4295+
result := &stripe.CheckoutSession{ID: "cs_test_id"}
4296+
4297+
return result, nil
4298+
},
4299+
},
4300+
req: &model.CreateOrderRequestNew{
4301+
Metadata: map[string]string{
4302+
"product_pricing": "one-off",
4303+
},
4304+
},
4305+
ord: &model.Order{},
4306+
},
4307+
exp: tcExpected{
4308+
val: "cs_test_id",
4309+
},
4310+
},
42634311
}
42644312

42654313
for i := range tests {
@@ -4529,6 +4577,66 @@ func TestService_recreateStripeSession(t *testing.T) {
45294577
},
45304578
},
45314579

4580+
{
4581+
name: "success_mode_subscription",
4582+
given: tcGiven{
4583+
ordRepo: &repository.MockOrder{},
4584+
cl: &xstripe.MockClient{
4585+
FnSession: func(ctx context.Context, id string, params *stripe.CheckoutSessionParams) (*stripe.CheckoutSession, error) {
4586+
result := &stripe.CheckoutSession{
4587+
Mode: stripe.CheckoutSessionModeSubscription,
4588+
}
4589+
4590+
return result, nil
4591+
},
4592+
4593+
FnCreateSession: func(ctx context.Context, params *stripe.CheckoutSessionParams) (*stripe.CheckoutSession, error) {
4594+
if *params.Mode != string(stripe.CheckoutSessionModeSubscription) {
4595+
return nil, model.Error("unexpected_payment_mode")
4596+
}
4597+
4598+
result := &stripe.CheckoutSession{ID: "cs_test_id"}
4599+
4600+
return result, nil
4601+
},
4602+
},
4603+
ord: &model.Order{},
4604+
},
4605+
exp: tcExpected{
4606+
val: "cs_test_id",
4607+
},
4608+
},
4609+
4610+
{
4611+
name: "success_mode_payment",
4612+
given: tcGiven{
4613+
ordRepo: &repository.MockOrder{},
4614+
cl: &xstripe.MockClient{
4615+
FnSession: func(ctx context.Context, id string, params *stripe.CheckoutSessionParams) (*stripe.CheckoutSession, error) {
4616+
result := &stripe.CheckoutSession{
4617+
Mode: stripe.CheckoutSessionModePayment,
4618+
}
4619+
4620+
return result, nil
4621+
},
4622+
4623+
FnCreateSession: func(ctx context.Context, params *stripe.CheckoutSessionParams) (*stripe.CheckoutSession, error) {
4624+
if *params.Mode != string(stripe.CheckoutSessionModePayment) {
4625+
return nil, model.Error("unexpected_payment_mode")
4626+
}
4627+
4628+
result := &stripe.CheckoutSession{ID: "cs_test_id"}
4629+
4630+
return result, nil
4631+
},
4632+
},
4633+
ord: &model.Order{},
4634+
},
4635+
exp: tcExpected{
4636+
val: "cs_test_id",
4637+
},
4638+
},
4639+
45324640
{
45334641
name: "success_email_from_request",
45344642
given: tcGiven{
@@ -5004,6 +5112,36 @@ func TestCreateStripeSession(t *testing.T) {
50045112
},
50055113
},
50065114

5115+
{
5116+
name: "success_mode_payment",
5117+
given: tcGiven{
5118+
cl: &xstripe.MockClient{
5119+
FnCreateSession: func(ctx context.Context, params *stripe.CheckoutSessionParams) (*stripe.CheckoutSession, error) {
5120+
if *params.Mode != string(stripe.CheckoutSessionModePayment) {
5121+
return nil, model.Error("unexpected_payment_mode")
5122+
}
5123+
5124+
result := &stripe.CheckoutSession{ID: "cs_test_id"}
5125+
5126+
return result, nil
5127+
},
5128+
5129+
FnFindCustomer: func(ctx context.Context, email string) (*stripe.Customer, bool) {
5130+
panic("unexpected_find_customer")
5131+
},
5132+
},
5133+
5134+
req: createStripeSessionRequest{
5135+
csMode: string(stripe.CheckoutSessionModePayment),
5136+
},
5137+
5138+
slv: xstripe.NewLocaleValidator(),
5139+
},
5140+
exp: tcExpected{
5141+
val: "cs_test_id",
5142+
},
5143+
},
5144+
50075145
{
50085146
name: "invalid_locale",
50095147
given: tcGiven{

0 commit comments

Comments
 (0)