00955ca6 Added Stripe checkout collection into member custom fields
Stripe Checkout is the one moment where a publisher reliably gets an
answer out of a member, and Ghost threw all of it away. A print
publisher's subscriber types a delivery address into Stripe as part of
paying, and there was nowhere for it to go: custom field keys are minted
from publisher-chosen names, so one site's field is shipping_address and
another's is adresse_de_livraison, and there is no key to hardcode.
A binding is what fixes that. It says a source sends what it calls one
thing to a field the publisher calls another, and everything writes
through one: the questions a tier's checkout asks, the address and phone
number it collects, and whatever a landing form grows into later. Its
row carries both where a value lands and whether it is being collected,
because those are one decision and holding them in two tables let them
disagree, with no composite foreign key or check constraint able to
stop it.
Turning collection on always succeeds. A tier that has collected this
before gets the destination it already had, whatever the publisher has
since renamed it to; a tier that has not takes whatever another tier
settled on, so two tiers mean one field rather than two; and a site that
has never collected it at all gets a field made for it, worked around
if something already holds the key. The binding records the answer, so
the next time is a read rather than a guess.
What Stripe returns is read by the webhook and handed over as ports and
values, so nothing outside the bindings knows a field key. Each value is
written in its own transaction, atomic across the parts of an address
but nothing wider, because a postcode Stripe formatted differently must
not cost the phone number beside it. None of it may fail the webhook,
which is creating the member and linking the subscription.
Every value records the binding that wrote it, which resolves back to
the tier, the port and the field. Nothing reads that yet, but it cannot
be reconstructed later.
Every limit Stripe enforces was measured against the live API rather
than read from the reference, which disagreed with it in three of five
probes. One of those probes found that Stripe will not collect a tax
number for a customer it may not rename, so a tier collecting one could
not be bought by a member who already had a Stripe customer. The fake
Stripe server the tests run against enforces what the probes found, so
a request Stripe would reject fails a test here first.
ref https://linear.app/ghost/issue/BER-3872 0ee76324 Added Stripe checkout collection into member custom fields
Stripe Checkout is the one moment where a publisher reliably gets an
answer out of a member, and Ghost threw all of it away. A print
publisher's subscriber types a delivery address into Stripe as part of
paying, and there was nowhere for it to go: custom field keys are minted
from publisher-chosen names, so one site's field is shipping_address and
another's is adresse_de_livraison, and there is no key to hardcode.
A binding is what fixes that. It says a source sends what it calls one
thing to a field the publisher calls another, and everything writes
through one: the questions a tier's checkout asks, the address and phone
number it collects, and whatever a landing form grows into later. Its
row carries both where a value lands and whether it is being collected,
because those are one decision and holding them in two tables let them
disagree, with no composite foreign key or check constraint able to
stop it.
Turning collection on always succeeds. A tier that has collected this
before gets the destination it already had, whatever the publisher has
since renamed it to; a tier that has not takes whatever another tier
settled on, so two tiers mean one field rather than two; and a site that
has never collected it at all gets a field made for it, worked around
if something already holds the key. The binding records the answer, so
the next time is a read rather than a guess.
What Stripe returns is read by the webhook and handed over as ports and
values, so nothing outside the bindings knows a field key. Each value is
written in its own transaction, atomic across the parts of an address
but nothing wider, because a postcode Stripe formatted differently must
not cost the phone number beside it. None of it may fail the webhook,
which is creating the member and linking the subscription.
Every value records the binding that wrote it, which resolves back to
the tier, the port and the field. Nothing reads that yet, but it cannot
be reconstructed later.
Every limit Stripe enforces was measured against the live API rather
than read from the reference, which disagreed with it in three of five
probes. One of those probes found that Stripe will not collect a tax
number for a customer it may not rename, so a tier collecting one could
not be bought by a member who already had a Stripe customer. The fake
Stripe server the tests run against enforces what the probes found, so
a request Stripe would reject fails a test here first.
ref https://linear.app/ghost/issue/BER-3872 9eee94c4 Added Stripe checkout collection into member custom fields
Stripe Checkout is the one moment where a publisher reliably gets an
answer out of a member, and Ghost threw all of it away. A print
publisher's subscriber types a delivery address into Stripe as part of
paying, and there was nowhere for it to go: custom field keys are minted
from publisher-chosen names, so one site's field is shipping_address and
another's is adresse_de_livraison, and there is no key to hardcode.
A binding is what fixes that. It says a source sends what it calls one
thing to a field the publisher calls another, and everything writes
through one: the questions a tier's checkout asks, the address and phone
number it collects, and whatever a landing form grows into later. Its
row carries both where a value lands and whether it is being collected,
because those are one decision and holding them in two tables let them
disagree, with no composite foreign key or check constraint able to
stop it.
Turning collection on always succeeds. A tier that has collected this
before gets the destination it already had, whatever the publisher has
since renamed it to; a tier that has not takes whatever another tier
settled on, so two tiers mean one field rather than two; and a site that
has never collected it at all gets a field made for it, worked around
if something already holds the key. The binding records the answer, so
the next time is a read rather than a guess.
What Stripe returns is read by the webhook and handed over as ports and
values, so nothing outside the bindings knows a field key. Each value is
written in its own transaction, atomic across the parts of an address
but nothing wider, because a postcode Stripe formatted differently must
not cost the phone number beside it. None of it may fail the webhook,
which is creating the member and linking the subscription.
Every value records the binding that wrote it, which resolves back to
the tier, the port and the field. Nothing reads that yet, but it cannot
be reconstructed later.
Every limit Stripe enforces was measured against the live API rather
than read from the reference, which disagreed with it in three of five
probes. One of those probes found that Stripe will not collect a tax
number for a customer it may not rename, so a tier collecting one could
not be bought by a member who already had a Stripe customer. The fake
Stripe server the tests run against enforces what the probes found, so
a request Stripe would reject fails a test here first.
ref https://linear.app/ghost/issue/BER-3872 7d36a683 Added Stripe checkout collection into member custom fields
Stripe Checkout is the one moment where a publisher reliably gets an
answer out of a member, and Ghost threw all of it away. A print
publisher's subscriber types a delivery address into Stripe as part of
paying, and there was nowhere for it to go: custom field keys are minted
from publisher-chosen names, so one site's field is shipping_address and
another's is adresse_de_livraison, and there is no key to hardcode.
A binding is what fixes that. It says a source sends what it calls one
thing to a field the publisher calls another, and everything writes
through one: the questions a tier's checkout asks, the address and phone
number it collects, and whatever a landing form grows into later. Its
row carries both where a value lands and whether it is being collected,
because those are one decision and holding them in two tables let them
disagree, with no composite foreign key or check constraint able to
stop it.
Turning collection on always succeeds. A tier that has collected this
before gets the destination it already had, whatever the publisher has
since renamed it to; a tier that has not takes whatever another tier
settled on, so two tiers mean one field rather than two; and a site that
has never collected it at all gets a field made for it, worked around
if something already holds the key. The binding records the answer, so
the next time is a read rather than a guess.
What Stripe returns is read by the webhook and handed over as ports and
values, so nothing outside the bindings knows a field key. Each value is
written in its own transaction, atomic across the parts of an address
but nothing wider, because a postcode Stripe formatted differently must
not cost the phone number beside it. None of it may fail the webhook,
which is creating the member and linking the subscription.
Every value records the binding that wrote it, which resolves back to
the tier, the port and the field. Nothing reads that yet, but it cannot
be reconstructed later.
Every limit Stripe enforces was measured against the live API rather
than read from the reference, which disagreed with it in three of five
probes. One of those probes found that Stripe will not collect a tax
number for a customer it may not rename, so a tier collecting one could
not be bought by a member who already had a Stripe customer. The fake
Stripe server the tests run against enforces what the probes found, so
a request Stripe would reject fails a test here first.
ref https://linear.app/ghost/issue/BER-3872 40b7b506 Added Stripe checkout collection into member custom fields
Stripe Checkout is the one moment where a publisher reliably gets an
answer out of a member, and Ghost threw all of it away. A print
publisher's subscriber types a delivery address into Stripe as part of
paying, and there was nowhere for it to go: custom field keys are minted
from publisher-chosen names, so one site's field is shipping_address and
another's is adresse_de_livraison, and there is no key to hardcode.
A binding is what fixes that. It says a source sends what it calls one
thing to a field the publisher calls another, and everything writes
through one: the questions a tier's checkout asks, the address and phone
number it collects, and whatever a landing form grows into later. Its
row carries both where a value lands and whether it is being collected,
because those are one decision and holding them in two tables let them
disagree, with no composite foreign key or check constraint able to
stop it.
Turning collection on always succeeds. A tier that has collected this
before gets the destination it already had, whatever the publisher has
since renamed it to; a tier that has not takes whatever another tier
settled on, so two tiers mean one field rather than two; and a site that
has never collected it at all gets a field made for it, worked around
if something already holds the key. The binding records the answer, so
the next time is a read rather than a guess.
What Stripe returns is read by the webhook and handed over as ports and
values, so nothing outside the bindings knows a field key. Each value is
written in its own transaction, atomic across the parts of an address
but nothing wider, because a postcode Stripe formatted differently must
not cost the phone number beside it. None of it may fail the webhook,
which is creating the member and linking the subscription.
Every value records the binding that wrote it, which resolves back to
the tier, the port and the field. Nothing reads that yet, but it cannot
be reconstructed later.
Every limit Stripe enforces was measured against the live API rather
than read from the reference, which disagreed with it in three of five
probes. One of those probes found that Stripe will not collect a tax
number for a customer it may not rename, so a tier collecting one could
not be bought by a member who already had a Stripe customer. The fake
Stripe server the tests run against enforces what the probes found, so
a request Stripe would reject fails a test here first.
ref https://linear.app/ghost/issue/BER-3872 006abec7 Added Stripe checkout collection into member custom fields
Stripe Checkout is the one moment where a publisher reliably gets an
answer out of a member, and Ghost threw all of it away. A print
publisher's subscriber types a delivery address into Stripe as part of
paying, and there was nowhere for it to go: custom field keys are minted
from publisher-chosen names, so one site's field is shipping_address and
another's is adresse_de_livraison, and there is no key to hardcode.
A binding is what fixes that. It says a source sends what it calls one
thing to a field the publisher calls another, and everything writes
through one: the questions a tier's checkout asks, the address and phone
number it collects, and whatever a landing form grows into later. Its
row carries both where a value lands and whether it is being collected,
because those are one decision and holding them in two tables let them
disagree, with no composite foreign key or check constraint able to
stop it.
Turning collection on always succeeds. A tier that has collected this
before gets the destination it already had, whatever the publisher has
since renamed it to; a tier that has not takes whatever another tier
settled on, so two tiers mean one field rather than two; and a site that
has never collected it at all gets a field made for it, worked around
if something already holds the key. The binding records the answer, so
the next time is a read rather than a guess.
What Stripe returns is read by the webhook and handed over as ports and
values, so nothing outside the bindings knows a field key. Each value is
written in its own transaction, atomic across the parts of an address
but nothing wider, because a postcode Stripe formatted differently must
not cost the phone number beside it. None of it may fail the webhook,
which is creating the member and linking the subscription.
Every value records the binding that wrote it, which resolves back to
the tier, the port and the field. Nothing reads that yet, but it cannot
be reconstructed later.
Every limit Stripe enforces was measured against the live API rather
than read from the reference, which disagreed with it in three of five
probes. One of those probes found that Stripe will not collect a tax
number for a customer it may not rename, so a tier collecting one could
not be bought by a member who already had a Stripe customer. The fake
Stripe server the tests run against enforces what the probes found, so
a request Stripe would reject fails a test here first.
ref https://linear.app/ghost/issue/BER-3872 4a217b6a Added Stripe checkout collection into member custom fields
Stripe Checkout is the one moment where a publisher reliably gets an
answer out of a member, and Ghost threw all of it away. A print
publisher's subscriber types a delivery address into Stripe as part of
paying, and there was nowhere for it to go: custom field keys are minted
from publisher-chosen names, so one site's field is shipping_address and
another's is adresse_de_livraison, and there is no key to hardcode.
A binding is what fixes that. It says a source sends what it calls one
thing to a field the publisher calls another, and everything writes
through one: the questions a tier's checkout asks, the address and phone
number it collects, and whatever a landing form grows into later. Its
row carries both where a value lands and whether it is being collected,
because those are one decision and holding them in two tables let them
disagree, with no composite foreign key or check constraint able to
stop it.
Turning collection on always succeeds. A tier that has collected this
before gets the destination it already had, whatever the publisher has
since renamed it to; a tier that has not takes whatever another tier
settled on, so two tiers mean one field rather than two; and a site that
has never collected it at all gets a field made for it, worked around
if something already holds the key. The binding records the answer, so
the next time is a read rather than a guess.
What Stripe returns is read by the webhook and handed over as ports and
values, so nothing outside the bindings knows a field key. Each value is
written in its own transaction, atomic across the parts of an address
but nothing wider, because a postcode Stripe formatted differently must
not cost the phone number beside it. None of it may fail the webhook,
which is creating the member and linking the subscription.
Every value records the binding that wrote it, which resolves back to
the tier, the port and the field. Nothing reads that yet, but it cannot
be reconstructed later.
Every limit Stripe enforces was measured against the live API rather
than read from the reference, which disagreed with it in three of five
probes. One of those probes found that Stripe will not collect a tax
number for a customer it may not rename, so a tier collecting one could
not be bought by a member who already had a Stripe customer. The fake
Stripe server the tests run against enforces what the probes found, so
a request Stripe would reject fails a test here first.
ref https://linear.app/ghost/issue/BER-3872 148e4eed Added Stripe checkout collection into member custom fields
Stripe Checkout is the one moment where a publisher reliably gets an
answer out of a member, and Ghost threw all of it away. A print
publisher's subscriber types a delivery address into Stripe as part of
paying, and there was nowhere for it to go: custom field keys are minted
from publisher-chosen names, so one site's field is shipping_address and
another's is adresse_de_livraison, and there is no key to hardcode.
A binding is what fixes that. It says a source sends what it calls one
thing to a field the publisher calls another, and everything writes
through one: the questions a tier's checkout asks, the address and phone
number it collects, and whatever a landing form grows into later. Its
row carries both where a value lands and whether it is being collected,
because those are one decision and holding them in two tables let them
disagree, with no composite foreign key or check constraint able to
stop it.
Turning collection on always succeeds. A tier that has collected this
before gets the destination it already had, whatever the publisher has
since renamed it to; a tier that has not takes whatever another tier
settled on, so two tiers mean one field rather than two; and a site that
has never collected it at all gets a field made for it, worked around
if something already holds the key. The binding records the answer, so
the next time is a read rather than a guess.
What Stripe returns is read by the webhook and handed over as ports and
values, so nothing outside the bindings knows a field key. Each value is
written in its own transaction, atomic across the parts of an address
but nothing wider, because a postcode Stripe formatted differently must
not cost the phone number beside it. None of it may fail the webhook,
which is creating the member and linking the subscription.
Every value records the binding that wrote it, which resolves back to
the tier, the port and the field. Nothing reads that yet, but it cannot
be reconstructed later.
Every limit Stripe enforces was measured against the live API rather
than read from the reference, which disagreed with it in three of five
probes. One of those probes found that Stripe will not collect a tax
number for a customer it may not rename, so a tier collecting one could
not be bought by a member who already had a Stripe customer. The fake
Stripe server the tests run against enforces what the probes found, so
a request Stripe would reject fails a test here first.
ref https://linear.app/ghost/issue/BER-3872 1e541132 Fixed a renamed destination being provisioned a second time
Provisioning found the field it had made before by name, and a name is
the one thing about a field a publisher can change. Renaming what Ghost
made and then turning the collection off and on again therefore left two
fields: the renamed one holding everything collected so far, and a fresh
one taking every write from then on, with nothing saying they were the
same thing.
Each port now declares the key it intends rather than deriving one from
the name it wants. A key cannot change once minted, so it is the field's
identity, and stating it is also what makes provisioning idempotent
without recording anything: Ghost can work out which field it would have
made instead of remembering that it made one.
A key already taken means one of two things and the type says which.
Holding what the port collects makes it that field, whoever created it,
because the key is what the field is for. Holding something else makes
it unusable, so the next free variant is minted. The name falls back
separately, since an intended key can be free while its name is taken.
An archived destination is refused rather than reused or minted past,
because binding to one collects nothing until it is restored and nobody
can currently see what is bound. 1e541132 Fixed a renamed destination being provisioned a second time
Provisioning found the field it had made before by name, and a name is
the one thing about a field a publisher can change. Renaming what Ghost
made and then turning the collection off and on again therefore left two
fields: the renamed one holding everything collected so far, and a fresh
one taking every write from then on, with nothing saying they were the
same thing.
Each port now declares the key it intends rather than deriving one from
the name it wants. A key cannot change once minted, so it is the field's
identity, and stating it is also what makes provisioning idempotent
without recording anything: Ghost can work out which field it would have
made instead of remembering that it made one.
A key already taken means one of two things and the type says which.
Holding what the port collects makes it that field, whoever created it,
because the key is what the field is for. Holding something else makes
it unusable, so the next free variant is minted. The name falls back
separately, since an intended key can be free while its name is taken.
An archived destination is refused rather than reused or minted past,
because binding to one collects nothing until it is restored and nobody
can currently see what is bound.