Skip to content

Commit

Permalink
refactor: refactor calculations for iOS expiration dates
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelbrm committed Feb 7, 2024
1 parent 6b13818 commit de55378
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 2 deletions.
29 changes: 27 additions & 2 deletions services/skus/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1588,6 +1588,8 @@ func (s *Service) verifyIOSNotification(ctx context.Context, txInfo *appstore.JW
return errors.New("notification has no tx or renewal")
}

// TODO: The documentation says nothing about these conditions.
// Shall this be gone?
if !govalidator.IsAlphanumeric(txInfo.OriginalTransactionId) || len(txInfo.OriginalTransactionId) > 32 {
return errors.New("original transaction id should be alphanumeric and less than 32 chars")
}
Expand All @@ -1604,9 +1606,8 @@ func (s *Service) verifyIOSNotification(ctx context.Context, txInfo *appstore.JW

// Check if we are past the expiration date on transaction or the order was revoked.
now := time.Now()
if now.After(time.Unix(0, txInfo.ExpiresDate*int64(time.Millisecond))) ||
(txInfo.RevocationDate > 0 && now.After(time.Unix(0, txInfo.RevocationDate*int64(time.Millisecond)))) {

if shouldCancelOrderIOS(txInfo, now) {
if err := s.Datastore.UpdateOrder(o.ID, model.OrderStatusCanceled); err != nil {
return fmt.Errorf("failed to cancel subscription in skus: %w", err)
}
Expand Down Expand Up @@ -2131,3 +2132,27 @@ type tlv1CredPresentation struct {
func ptrTo[T any](v T) *T {
return &v
}

func shouldCancelOrderIOS(info *appstore.JWSTransactionDecodedPayload, now time.Time) bool {
tx := (*appStoreTransaction)(info)

return tx.hasExpired(now) || tx.isRevoked(now)
}

type appStoreTransaction appstore.JWSTransactionDecodedPayload

func (x *appStoreTransaction) hasExpired(now time.Time) bool {
if x == nil {
return false
}

return x.ExpiresDate > 0 && now.After(time.UnixMilli(x.ExpiresDate))
}

func (x *appStoreTransaction) isRevoked(now time.Time) bool {
if x == nil {
return false
}

return x.RevocationDate > 0 && now.After(time.UnixMilli(x.RevocationDate))
}
116 changes: 116 additions & 0 deletions services/skus/service_nonint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"
"time"

"github.com/awa/go-iap/appstore"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
uuid "github.com/satori/go.uuid"
Expand Down Expand Up @@ -942,6 +943,121 @@ func TestService_checkOrderReceipt(t *testing.T) {
}
}

func TestShouldCancelOrderIOS(t *testing.T) {
type tcGiven struct {
now time.Time
info *appstore.JWSTransactionDecodedPayload
}

type testCase struct {
name string
given tcGiven
exp bool
}

tests := []testCase{
{
name: "nil",
given: tcGiven{
now: time.Date(2024, time.January, 1, 0, 0, 1, 0, time.UTC),
},
},

{
name: "empty_dates_not_expired",
given: tcGiven{
now: time.Date(2024, time.January, 1, 0, 0, 1, 0, time.UTC),
info: &appstore.JWSTransactionDecodedPayload{},
},
},

{
name: "expires_date_before_no_revocation_date",
given: tcGiven{
now: time.Date(2024, time.January, 1, 0, 0, 1, 0, time.UTC),
info: &appstore.JWSTransactionDecodedPayload{
// 2023-12-31 23:59:59.
ExpiresDate: 1704067199000,
},
},
exp: true,
},

{
name: "expires_date_after_no_revocation_date",
given: tcGiven{
now: time.Date(2024, time.January, 1, 0, 0, 1, 0, time.UTC),
info: &appstore.JWSTransactionDecodedPayload{
// 2024-01-01 01:00:01.
ExpiresDate: 1704070801000,
},
},
},

{
name: "expires_date_after_revocation_date_after",
given: tcGiven{
now: time.Date(2024, time.January, 1, 0, 0, 1, 0, time.UTC),
info: &appstore.JWSTransactionDecodedPayload{
// 2024-01-01 01:00:01.
ExpiresDate: 1704070801000,

// 2024-01-01 00:30:01.
RevocationDate: 1704069001000,
},
},
},

{
name: "expires_date_after_revocation_date_before",
given: tcGiven{
now: time.Date(2024, time.January, 1, 0, 0, 1, 0, time.UTC),
info: &appstore.JWSTransactionDecodedPayload{
// 2024-01-01 01:00:01.
ExpiresDate: 1704070801000,

// 2023-12-31 23:30:01.
RevocationDate: 1704065401000,
},
},
exp: true,
},

{
name: "no_expires_date_revocation_date_before",
given: tcGiven{
now: time.Date(2024, time.January, 1, 0, 0, 1, 0, time.UTC),
info: &appstore.JWSTransactionDecodedPayload{
// 2023-12-31 23:59:59.
RevocationDate: 1704067199000,
},
},
exp: true,
},

{
name: "no_expires_date_revocation_date_after",
given: tcGiven{
now: time.Date(2024, time.January, 1, 0, 0, 1, 0, time.UTC),
info: &appstore.JWSTransactionDecodedPayload{
// 2024-01-01 01:00:01.
RevocationDate: 1704070801000,
},
},
},
}

for i := range tests {
tc := tests[i]

t.Run(tc.name, func(t *testing.T) {
actual := shouldCancelOrderIOS(tc.given.info, tc.given.now)

should.Equal(t, tc.exp, actual)
})
}
}

type mockPaidOrderCreator struct {
fnCreateOrder func(ctx context.Context, req *model.CreateOrderRequestNew, ordNew *model.OrderNew, items []model.OrderItem) (*model.Order, error)
fnUpdateOrderStatusPaidWithMetadata func(ctx context.Context, oid *uuid.UUID, mdata datastore.Metadata) error
Expand Down

0 comments on commit de55378

Please sign in to comment.