fix: Fixed paddle subs
Chart Release / chart (push) Successful in 28s
Server Deploy / deploy (push) Successful in 1m17s

This commit is contained in:
2026-08-03 15:32:01 +01:00
parent 1f08e90009
commit b5f684c4fe
6 changed files with 157 additions and 14 deletions
+49
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"log"
"strings"
"time"
"gitea.hostxtra.co.uk/mrhid6/vantage/admin/internal/db"
@@ -210,6 +211,54 @@ func Backfill(ctx context.Context) error {
if err := backfillEntitlements(ctx); err != nil {
return err
}
// Pass 6: instances claimed before linked_from_placeholder_id existed have no
// alias, and Paddle's custom_data still names the placeholder they were
// claimed from — so their next webhook resolves to nothing. The claim wrote an
// audit entry naming both ids, which is the only surviving record of the
// placeholder, so reconstruct the alias from it.
if err := backfillPlaceholderAliases(ctx); err != nil {
return err
}
return nil
}
// backfillPlaceholderAliases rebuilds linked_from_placeholder_id from the
// instance.placeholder_linked audit entries. Idempotent by filtering on the
// absence of the field.
func backfillPlaceholderAliases(ctx context.Context) error {
cur, err := db.Admin("admin_audit").Find(ctx,
bson.M{"action": "instance.placeholder_linked"})
if err != nil {
return err
}
var entries []AuditEntry
if err := cur.All(ctx, &entries); err != nil {
return err
}
const prefix = "from placeholder "
linked := 0
for _, e := range entries {
if e.Target == "" || !strings.HasPrefix(e.Detail, prefix) {
continue
}
placeholderID := strings.TrimSpace(strings.TrimPrefix(e.Detail, prefix))
if placeholderID == "" || placeholderID == e.Target {
continue
}
res, err := db.Admin("admin_instances").UpdateOne(ctx,
bson.M{"instance_id": e.Target,
"linked_from_placeholder_id": bson.M{"$exists": false}},
bson.M{"$set": bson.M{"linked_from_placeholder_id": placeholderID}})
if err != nil {
return err
}
linked += int(res.ModifiedCount)
}
if linked > 0 {
log.Printf("backfill: recovered %d placeholder aliases from the audit log", linked)
}
return nil
}
+7
View File
@@ -147,6 +147,13 @@ type Instance struct {
// checkout has something to attach custom_data to, before the customer has
// pasted their install's real UUID. Cleared when the instance is linked.
Placeholder bool `bson:"placeholder,omitempty" json:"placeholder,omitempty"`
// LinkedFromPlaceholderID is the id this row carried while it was a
// self-hosted placeholder, kept forever after the claim rewrote InstanceID to
// the install's real UUID. Paddle's copy of custom_data still names the
// placeholder on any subscription created before the claim (and on any webhook
// delivered while the outbound patch was failing), so this is what lets a
// later webhook resolve to the right instance instead of erroring as unknown.
LinkedFromPlaceholderID string `bson:"linked_from_placeholder_id,omitempty" json:"-"`
// PendingOwnerUserID is the customer_user who bought a paid-cloud placeholder,
// remembered so the confirmed-payment webhook can provision the instance with
// them as owner. Cleared once provisioned. Only ever set on a cloud placeholder.