Payment sources
Read saved payment sources and use their Wallkit IDs for checkout. For Stripe, create a SetupIntent and save the payment method in separate steps. Neither step charges the member.
Member and resource context
Use an existing member token in the token header and the resource public key in resource. These actions require user access and both contexts. See credentials.
Provider credentials, local customer ownership, resource attachment and live/test selection differ by operation. Check the requirements beside the selected call. A resource header required for initialization does not necessarily filter the returned collection.
Modern source operations
| Task | Operation |
|---|---|
| List the member’s saved sources | List the member’s saved sources |
| Create a Stripe customer SetupIntent | Create a Stripe customer SetupIntent |
| Save the attached Stripe payment method | Save the attached Stripe payment method |
| Read or create the modern Stripe customer | Read or create the modern Stripe customer |
| Select the local default source | Select the local default source |
| Remove a saved source | Remove a saved source |
Legacy cards and customers
| Task | Operation |
|---|---|
| Add a legacy native card | Add a legacy native card |
| List legacy cards | List legacy cards |
| Select a legacy card default | Select a legacy card default |
| Remove a legacy local card | Remove a legacy local card |
| Record and gate a card-attachment attempt | Record and gate a card-attachment attempt |
| Create or import a legacy customer | Create or import a legacy customer |
| List owned legacy customers | List owned legacy customers |
| Attach a local customer to a resource | Attach a local customer to a resource |
Example runtimes, base-address conventions and synthetic values follow response conventions.
List the member’s saved sources
Use this read to choose a source for the selected resource and provider. It performs no provider setup or charge.
GET /api/v1/user/payment-sources
Before you call
Use the member and resource context; apply the source-specific requirements below.
Request
No body.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
pay-system | query | optional trimmed string | Provider selector. Omission/empty uses resource default_paysystem, falling back to stripe. The validator casts supplied values to integer before a loose comparison with provider names; acceptance is PHP-version-sensitive. Do not rely on it to validate a provider label. |
Result
HTTP 200, items array of source summaries. No pagination or general filters. Payment methods come first, then cards without a linked payment method; each group orders saved default first, then descending local ID. At most one summary is marked default: the first saved default encountered.
A default card represented by a method is not duplicated.
No live/test filter is applied to this list; inspect method.mode and use the resource/provider context appropriate for payment.
Listing uses the selected/default provider, while Stripe setup/save use the Stripe operator’s configured mode. With a non-stripe resource default, an unqualified list after saving can omit the newly saved Stripe method; the displayed default describes only the returned collection.
Example: Choose local method 3001
Read the current resource’s sources before checkout. The returned payment-method entry supplies local ID 3001 and test mode; its provider ID is a different value.
curl -X GET "${WALLKIT_API_BASE}/api/v1/user/payment-sources" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/payment-sources`, {
method: "GET",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/payment-sources",
method="GET", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"]})
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 response excerpt:
{
"items": [
{
"source_type": "payment-method",
"default": true,
"pay_system": "stripe",
"customer_id": "cus_SYNTHETIC",
"user_card": null,
"payment_method": {
"id": 3001,
"payment_method_id": "pm_SYNTHETIC",
"mode": "test",
"type": "card",
"data": {
"card_brand": "visa",
"card_country": "US",
"card_last4": "0000",
"expiration_date": "2030-12-31"
}
}
}
]
}
Consequential alternate
HTTP 200 {"items":[]} means this member has no visible saved sources in the selected resource/provider scope. Start the appropriate configured provider setup path. Visibility follows member-owned customers attached to the resource, not every source held by the provider.
Recovery
| Status | Code | Cause | Next action |
|---|---|---|---|
| 409 | invalid_pay_system | Selector validation fails. | Omit the selector to use configured provider; have the integration administrator check its intended label. |
| 409 | user_sources_error | Local source lookup/mapping fails. | Keep checkout disabled for that selection and investigate resource/customer associations. |
Shared credential failures are described in credentials.
Next task
For source_type payment-method, use payment_method.id (3001 here) as payment_method_id in member calculation, then payment.
For user-card, use user_card.id as user_card_id. Provider IDs are not substitutes.
Create a Stripe customer SetupIntent
Prepare provider setup for an already linked Stripe customer. This creates a provider SetupIntent using off_session usage and automatic payment methods; it does not save a Wallkit payment method, select a default or charge.
POST /api/v1/user/payment-provider/stripe/setup-intents
Before you call
Use the member and resource context; apply the source-specific requirements below.
Require the existing Stripe customer association for this member/resource and configured Stripe private key/account. Use the provider customer ID from the modern Stripe customer result. Provider mode uses resource payments_in_live_mode (default true); no caller mode field is accepted.
Request
JSON object required.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
stripe_customer_id | JSON | required nonempty string after trim | Stripe provider customer ID, such as cus_SYNTHETIC. Lookup restricts it to this member, resource attachment and stripe provider. It is not the numeric Wallkit customer ID. |
Result
HTTP 200 setup_intent is a dynamic Stripe SetupIntent object passed through from the configured provider. A nonempty id is checked. Existing consumer code reads client_secret for provider-client confirmation; neither its presence nor the remaining provider properties form a closed Wallkit schema. Preserve the returned object for the existing provider-client flow. This customer-linked SetupIntent path differs from checkout’s direct setup request: it checks the existing local member/resource/customer association before creating provider setup.
Example: Prepare setup for cus_SYNTHETIC
Use the already linked provider customer. The excerpt’s seti_SYNTHETIC ID names a provider SetupIntent; it is not a saved Wallkit source or charge.
curl -X POST "${WALLKIT_API_BASE}/api/v1/user/payment-provider/stripe/setup-intents" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}" \
-H "Content-Type: application/json" \
--data '{"stripe_customer_id":"cus_SYNTHETIC"}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/payment-provider/stripe/setup-intents`, {
method: "POST",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY, "Content-Type": "application/json"},
body: JSON.stringify({"stripe_customer_id":"cus_SYNTHETIC"})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'stripe_customer_id': 'cus_SYNTHETIC'}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/payment-provider/stripe/setup-intents",
method="POST", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"], "Content-Type": "application/json"}, data=json.dumps(body).encode("utf-8"))
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 response excerpt:
{
"setup_intent": {
"id": "seti_SYNTHETIC"
}
}
Consequential alternate
The excerpt omits all other provider properties, including client_secret. A customer outside the selected association produces HTTP 409 stripe_setup_intent_error, rather than preparing an intent for it.
Recovery
| Status | Code | Cause | Next action |
|---|---|---|---|
| 409 | incorrect_data | Missing JSON body. | Send a JSON object and media header. |
| 409 | "invalid_customer_id" | Missing/empty provider customer ID; quotes are part of code. | Read the existing customer result and use its provider customer_id. |
| 409 | stripe_setup_intent_error | Customer association, Stripe configuration or provider creation fails. | Check member/resource/customer context and configured account; do not assume an intent was created or blindly repeat. |
Next task
Complete setup through the existing provider client, then save its attached method. The provider client step is an external prerequisite; this documentation does not execute it.
Save the attached Stripe payment method
Persist a provider payment method for future checkout. For cards this also creates a local card. It clears visible local method/card default flags and marks new records default. It does not charge or update the provider customer’s default payment method.
POST /api/v1/user/payment-provider/stripe/save-intents
Before you call
Use the member and resource context; apply the source-specific requirements below.
Use an attached method obtained from the existing confirmed provider setup flow in the configured resource/account/mode. The action reads only setup_intent.payment_method: it does not retrieve the SetupIntent, verify its ID/status/client secret, or independently prove setup confirmation.
Provider retrieval supplies the method and its customer.
No explicit check proves that provider customer belongs to the authenticated member before it creates/reuses a member-owned local customer association and attaches it to the resource. Trusted integration code must supply the correct member’s method.
Customer/resource association, local card creation and default resets precede the final method save.
Save booleans are unchecked and this path has no enclosing transaction. An error or HTTP success does not establish all writes persisted; read sources before retrying or charging.
Duplicate detection and local method/card default resets use the resource’s default_paysystem (fallback stripe), while the new customer association and saved records use stripe. For this modern Stripe flow, configure the resource default as stripe.
If it selects another provider, these checks inspect that provider’s attached customers: existing Stripe methods can evade duplicate detection, existing Stripe defaults can remain set, and the other provider’s defaults can be cleared. No transaction makes these steps atomic.
Request
JSON object with a nonempty nested object.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
setup_intent | JSON | required nonempty object | Existing provider-client setup result. Only the field below is used. |
setup_intent.payment_method | JSON | required nonempty trimmed string | Provider method ID, not Wallkit payment_method_id input for checkout. Provider method must already have a customer. |
Result
HTTP 200 payment_method uses the saved method record projection, not the compact source-list DTO. The user_add_payment_method event follows local save attempts.
Example: Save attached method pm_SYNTHETIC
Supply the attached method from the existing provider-client setup result. The returned local ID 3001 is the checkout identifier; attempted default persistence still needs inspection.
curl -X POST "${WALLKIT_API_BASE}/api/v1/user/payment-provider/stripe/save-intents" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}" \
-H "Content-Type: application/json" \
--data '{"setup_intent":{"payment_method":"pm_SYNTHETIC"}}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/payment-provider/stripe/save-intents`, {
method: "POST",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY, "Content-Type": "application/json"},
body: JSON.stringify({"setup_intent":{"payment_method":"pm_SYNTHETIC"}})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'setup_intent': {'payment_method': 'pm_SYNTHETIC'}}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/payment-provider/stripe/save-intents",
method="POST", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"], "Content-Type": "application/json"}, data=json.dumps(body).encode("utf-8"))
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 response excerpt:
{
"payment_method": {
"id": 3001,
"payment_method_id": "pm_SYNTHETIC",
"customer_id": "cus_SYNTHETIC",
"default": true
}
}
Consequential alternate
This excerpt shows attempted local default selection, not proof of provider default or a charge. Repeating a scoped existing method returns HTTP 409 stripe_payment_method_error with description Payment method already exists. With the resource default set to stripe, read the source list and reuse the saved local ID rather than blindly replaying. A non-stripe default can prevent this duplicate check from finding an existing Stripe method.
Recovery
| Status | Code | Cause | Next action |
|---|---|---|---|
| 409 | incorrect_data | No JSON body. | Send the JSON object shown. |
| 409 | "invalid_setup_intent" / invalid_setup_intent | Missing / empty nested value. | Supply the confirmed provider-client result object. |
| 409 | "invalid_payment_method_id" | Nested method ID absent/empty. | Obtain the attached provider method through the existing provider client. |
| 409 | payment_method_not_attached_to_customer | Retrieved provider method has no customer; code begins with a space. | Finish attachment for the correct provider customer before saving. |
| 409 | stripe_payment_method_error | Duplicate, provider/configuration or local write failure. | Read saved sources and inspect member/account association before deciding whether a corrected save is needed. |
Next task
Read sources to inspect the saved method, mode and effective list default, then calculate with local payment_method_id 3001. This endpoint is separate from checkout PaymentIntent confirmation.
Saved payment method record
All rows below are selected by save-intents. Stored numeric/flag values have no explicit fetch cast here; callers should handle their serialized representation. Provider-derived optional values can be null. This projection differs from the typed source-list DTO.
| Field | Type / presence | Meaning |
|---|---|---|
id | stored integer | Local method ID for checkout. |
payment_method_id | string | Provider method ID. |
customer_id | string | Provider customer ID. |
created | stored integer | Provider creation value; local source does not convert its unit. |
livemode, default | stored flags | Provider mode flag and local default selection; no explicit output cast. |
metadata, billing_details, card | stored JSON text / null | Provider properties stored encoded when objects; this projection does not decode them into JSON objects. Their provider schema is open. |
object, type | provider string / null | Provider object/type labels; no complete enum established. |
user_id, payment_customer_id, card_id | stored integer / null | Local member/customer/card references; card_id is null for non-card methods. |
created_at, updated_at | string | Local Y-m-d H:i:s timestamps; timezone unspecified. |
user_card | object / null | For a resolved linked card: id, card_brand, card_country, card_last4, default, expiration_date, created_at; pay_system is conditional on a resolved customer. Numeric ID and default are stored values; remaining fields stored text/null, expiration_date uses Y-m-d. No provider card_id is included here. |
Read or create the modern Stripe customer
Obtain the provider customer association needed for modern SetupIntent setup. This GET can create a Stripe customer using the member’s name/email and save local customer/resource associations.
GET /api/v1/user/payment-provider/stripe/customer
Before you call
Use the member and resource context; apply the source-specific requirements below.
Requires configured Stripe account/private key and selected resource mode (payments_in_live_mode, default true). Lookup uses member-owned stripe customers attached to this resource, newest created first. A provider lookup exception is treated as a missing customer: the local customer is deleted and lookup repeats, so temporary provider failure can remove a local association and lead to replacement creation. This path does not establish atomic cleanup of cards/methods/resource relationships. Provider creation precedes unchecked local saves; no enclosing transaction or replay guarantee. Inspect current state before retrying.
Request
No body, query filters or pagination. No caller-selected mode.
Result
HTTP 200 payment_customer is a local customer projection, not a raw provider customer.
| Field | Type / presence | Meaning |
|---|---|---|
payment_customer.id | stored integer | Wallkit customer record ID; not SetupIntent input. |
payment_customer.pay_system | string | stripe for this path. |
payment_customer.customer_id | string / nullable annotation | Provider customer identifier used as stripe_customer_id for setup. |
payment_customer.created_at, updated_at | string | Local Y-m-d H:i:s timestamps; timezone unspecified. |
payment_customer.payment_methods | array | Every method related to this customer, no pagination/mode filtering or additional per-method resource filter; empty [] allowed. Each uses the saved method record. |
Example: Prepare customer 4001 for setup
Read or create the customer in this resource. The result separates local customer 4001 from provider cus_SYNTHETIC; an empty method list means no saved method is shown.
curl -X GET "${WALLKIT_API_BASE}/api/v1/user/payment-provider/stripe/customer" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/payment-provider/stripe/customer`, {
method: "GET",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/payment-provider/stripe/customer",
method="GET", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"]})
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 response excerpt:
{
"payment_customer": {
"id": 4001,
"pay_system": "stripe",
"customer_id": "cus_SYNTHETIC",
"payment_methods": []
}
}
Consequential alternate
An empty payment_methods array is successful customer preparation, not a saved payment source. Provider retrieval/creation or local failures can return HTTP 409 stripe_customer_error after earlier effects; do not interpret that error as no customer having been created.
Recovery
| Status | Code | Cause | Next action |
|---|---|---|---|
| 409 | stripe_customer_error | Configured account, provider or local association step fails. | Have the integration administrator inspect selected member/resource/provider state before repeating this GET that can write. |
Next task
Use payment_customer.customer_id (cus_SYNTHETIC here) to create a SetupIntent. Customer id 4001 is a different local identifier.
Select the local default source
Select which saved source Wallkit treats as default. This does not charge or change a membership.
PUT /api/v1/user/payment-sources/{source_type}/{id}/default
Before you call
Use the member and resource context; apply the source-specific requirements below.
Choose a source from the member/resource/provider list. Lookup restricts customer ownership and attachment to the resource/provider; no live/test predicate is applied. Provider calls use configured Stripe mode/account.
Local records can be shared through the same customer across resources, so changing/deleting them can affect another resource using that customer.
Only stripe provider dispatch is implemented for these operations; other selected providers fail with Unsupported pay system. Local save/delete booleans are unchecked. Database transaction calls do not guarantee rollback of provider effects.
For payment-method, local method flags reset to the selected ID and card flags reset to its linked card (or all clear when no card). The provider operation retrieves/attaches the method to the customer; it does not update Stripe invoice_settings.default_payment_method.
For user-card, card flags reset and a linked method is selected or all method flags clear; the provider call updates Stripe customer default_source. These branches are different.
Missing source detection follows attempted flag resets; exceptions trigger database rollback, but unchecked save results and provider effects prevent an all-or-nothing guarantee. Empty provider source ID can skip the provider update.
Request
No request body. Use the selected resource/default provider, or optional pay-system query selector with the list validator limitation.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
source_type | path | required string | Exactly payment-method or user-card. |
id | path | required local integer | payment_method.id or user_card.id from the source list. Route itself has no digit-only constraint; supply the integer expected by the action. Never substitute provider method/card/customer IDs. |
pay-system | query | optional string | Omission/empty selects resource default_paysystem, fallback stripe; selector validation is PHP-sensitive. |
Result
HTTP 200 success boolean true acknowledges that the path completed. No source projection or provider default confirmation is returned.
Example: Select method 3001 as local default
Select the local payment-method entry from the source list. success acknowledges completion of this branch; read sources afterward to inspect local flags.
curl -X PUT "${WALLKIT_API_BASE}/api/v1/user/payment-sources/payment-method/3001/default" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/payment-sources/payment-method/3001/default`, {
method: "PUT",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/payment-sources/payment-method/3001/default",
method="PUT", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"]})
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 response excerpt:
{
"success": true
}
Consequential alternate
HTTP 409 user_set_default_payment_method_error can describe Payment method not found or User card not found. The selected source must be visible in this scope. The path can already have attempted local flag changes before detecting that failure.
Recovery
| Status | Code | Cause | Next action |
|---|---|---|---|
| 409 | unsupported_type | Source type outside the two supported values. | Use the returned source_type and its matching local ID. |
| 409 | invalid_pay_system | Selector validation fails. | Use configured provider context; see selector limitation. |
| 409 | user_set_default_payment_method_error | Missing scoped source, unsupported provider, provider or local failure. | Read sources again and inspect provider/account state before deciding on a corrected write. |
Next task
Read sources to inspect the effective list default. An explicit local source ID can then be supplied to checkout calculation.
Remove a saved source
Remove a saved method/card and perform its provider detach/delete path. This does not delete the customer or resource attachment, cancel membership or refund transactions.
DELETE /api/v1/user/payment-sources/{source_type}/{id}
Before you call
Use the member and resource context; apply the source-specific requirements below.
Choose a source from the member/resource/provider list. Lookup restricts customer ownership and attachment to the resource/provider; no live/test predicate is applied. Provider calls use configured Stripe mode/account.
Local records can be shared through the same customer across resources, so changing/deleting them can affect another resource using that customer.
Only stripe provider dispatch is implemented for these operations; other selected providers fail with Unsupported pay system. Local save/delete booleans are unchecked. Database transaction calls do not guarantee rollback of provider effects.
Method deletion deletes its linked local card through the card path first, then detaches the provider method, then deletes the local method.
Card deletion attempts its linked method detach/delete before deleting the provider legacy source and local card. A card provider ID beginning pm_ skips legacy source deletion; method detach handles that provider object when its relationship resolves. Missing linked method is tolerated.
No replacement default is selected. The model definitions show no additional deletion hook for these records; customer and resource relationship cleanup is not performed here.
Provider detach/delete can succeed before later local failures or rollback, so an error does not prove the source remains usable.
Request
No request body. Use the selected resource/default provider, or optional pay-system query selector with the list validator limitation.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
source_type | path | required string | Exactly payment-method or user-card. |
id | path | required local integer | payment_method.id or user_card.id from the source list. Route itself has no digit-only constraint; supply the integer expected by the action. Never substitute provider method/card/customer IDs. |
pay-system | query | optional string | Omission/empty selects resource default_paysystem, fallback stripe; selector validation is PHP-sensitive. |
Result
HTTP 200 success boolean true; no deleted source/customer projection. Read sources again to inspect local visibility. It is not an erasure or refund acknowledgment.
Example: Remove method 3001
Remove the saved local method and its applicable linked-card/provider associations. The acknowledgment does not delete the customer or select a replacement default.
curl -X DELETE "${WALLKIT_API_BASE}/api/v1/user/payment-sources/payment-method/3001" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/payment-sources/payment-method/3001`, {
method: "DELETE",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/payment-sources/payment-method/3001",
method="DELETE", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"]})
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 response excerpt:
{
"success": true
}
Consequential alternate
A missing or already removed scoped source returns HTTP 409 user_delete_payment_method_error, not a promised replay-safe success. A partial failure can leave local and provider states different.
Recovery
| Status | Code | Cause | Next action |
|---|---|---|---|
| 409 | unsupported_type | Invalid path type. | Use the source list’s type and matching local ID. |
| 409 | invalid_pay_system | Selector validation fails. | Check configured provider context. |
| 409 | user_delete_payment_method_error | Missing scoped source, unsupported provider, detach/delete/local failure. | Read current sources and ask the integration administrator to inspect provider state before repeating deletion. |
Next task
Read sources, choose a remaining usable source and inspect default. If none remain, start the appropriate provider setup path before another checkout.
Legacy card record
| Field | Type / presence | Meaning |
|---|---|---|
id | stored integer | Local card ID for user_card_id in checkout. |
card_brand, card_country, card_last4 | stored text / null | Provider card display properties; not full card credentials. |
default | stored flag | Local default, no explicit response cast. |
expiration_date | stored string / null | Stripe imports format last day of expiry month as Y-m-d; other import paths can leave it unset. |
created_at | stored string | Local Y-m-d H:i:s timestamp; timezone unspecified. |
pay_system | string, conditional getCard; selected in card list | Related customer provider label. |
No provider card_id or local payment_customer_id is selected. The card list returns these same selected fields. getCard adds pay_system only when its customer resolves.
Legacy customer record
| Field | Type / presence | Meaning |
|---|---|---|
id | stored integer | Wallkit customer ID used for resource attachment. |
pay_system | stored string | Customer provider label. |
customer_id | stored string / null | Provider customer identifier, not local ID. |
created_at, updated_at | stored string | Local Y-m-d H:i:s timestamps; timezone unspecified. |
cards | array | All local cards related to this customer, each legacy card; [] allowed. No method projection or per-card resource filter. |
Add a legacy native card
Create a provider token/customer and import local cards from native card fields. This legacy action is marked deprecated for test/bot use in source; a new integration should choose its compatible provider-client flow.
POST /api/v1/user/card
Before you call
Use the member and resource context; apply the source-specific requirements below.
Provider selection uses resource default_paysystem fallback stripe and its existing configuration. Native-card token creation is implemented by Stripe; other operators do not establish a universal native-card contract.
Stripe creates token/customer, attempts local customer/resource attachment, imports cards, selects local default and emits add_card.
auto_attach_customer_to_resource defaults true; disabling it can leave created sources absent from resource-scoped reads.
Provider writes precede unchecked local saves; no encompassing transaction, charge or replay guarantee.
The request is a field-layout illustration with an intentionally invalid card-number placeholder. The success excerpt illustrates the configured successful path after appropriate synthetic card input; the literal placeholder instead fails validation. No real card data belongs in these examples.
Request
JSON or form accepted; nonempty form fields take precedence over JSON.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
number | body | required string; credit-card validator | Native card number. SYNTHETIC_CARD_NUMBER is an obvious documentation placeholder, not a valid card input. |
exp_month, exp_year | body | required; integer sanitization | Card expiry month/year; no handler range constraint. |
cvc | body | required; integer sanitization | Card security input; no handler range/length guarantee. |
Result
HTTP 201 top-level legacy customer record, not a customer wrapper or user profile.
Example: Read native-card field layout
This request uses an intentionally invalid card-number placeholder. The conditional successful-path excerpt shows local card 5001 only after suitable synthetic input; the literal request takes the validation-error branch.
curl -X POST "${WALLKIT_API_BASE}/api/v1/user/card" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}" \
-H "Content-Type: application/json" \
--data '{"number":"SYNTHETIC_CARD_NUMBER","exp_month":12,"exp_year":2030,"cvc":123}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/card`, {
method: "POST",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY, "Content-Type": "application/json"},
body: JSON.stringify({"number":"SYNTHETIC_CARD_NUMBER","exp_month":12,"exp_year":2030,"cvc":123})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'number': 'SYNTHETIC_CARD_NUMBER', 'exp_month': 12, 'exp_year': 2030, 'cvc': 123}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/card",
method="POST", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"], "Content-Type": "application/json"}, data=json.dumps(body).encode("utf-8"))
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 201 successful-path excerpt:
{
"id": 4001,
"pay_system": "stripe",
"customer_id": "cus_SYNTHETIC",
"cards": [
{
"id": 5001,
"card_last4": "0000"
}
]
}
Consequential alternate
HTTP 409 invalid_number: The credit card number is not valid. The literal placeholder demonstrates that invalid-data branch; it cannot create a customer. Provider failure can also follow earlier writes without guaranteeing cleanup.
Recovery
| Status | Code / shape | Cause | Next action |
|---|---|---|---|
| 409 | invalid_number, invalid_exp_month, invalid_exp_year, invalid_cvc | Required/native input invalid. | Correct synthetic field input in the existing configured integration. |
| 406 | card_failed / add_card_fail | Creation/import/configuration failure. | Inspect existing customer/card/default state before repeating. |
| 500 | incorrect_action | Historical body guard (object casting limits its effectiveness). | Send explicit supported fields and media. |
Next task
List cards to obtain local card ID for checkout; customer creation does not prove entitlement.
List legacy cards
Read local card display data across providers for this resource. Unlike the modern source list, this does not suppress cards linked to payment methods or select one normalized default.
GET /api/v1/user/card
Before you call
Use the member and resource context; apply the source-specific requirements below.
Request
No body. page and limit query values are integers; supply page=1 explicitly. limit defaults to 10. No provider/mode filter is applied. Order is default DESC then created_at DESC. Lookup restricts card user_id and customer attachment resource; it does not add customer-owner or relationship-user predicates.
Result
HTTP 200 items array of legacy card records, plus paginator object: current_page/page integer selected page, total_pages integer page count, total_items integer total results, limit integer selected limit. Duplicated attachment rows can duplicate cards; no DISTINCT is applied.
Example: Read the first page of resource cards
Read ten cards from page 1. The excerpt shows local card 5001 and pagination; its presence does not verify provider usability or entitlement.
curl -X GET "${WALLKIT_API_BASE}/api/v1/user/card?page=1&limit=10" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/card?page=1&limit=10`, {
method: "GET",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/card?page=1&limit=10",
method="GET", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"]})
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 response excerpt:
{
"items": [
{
"id": 5001,
"card_last4": "0000",
"pay_system": "stripe",
"default": true
}
],
"paginator": {
"current_page": 1,
"page": 1,
"total_pages": 1,
"total_items": 1,
"limit": 10
}
}
Consequential alternate
items can be [] with a paginator; there are no resource-visible local cards. This does not prove the provider has no cards.
Recovery
| Status | Code / shape | Cause | Next action |
|---|---|---|---|
| Shared auth status | Credential errors | Invalid member/resource context. | Restore the selected context. |
No dedicated list error code is added by this handler; query/paginator failures use shared handling.
Next task
Use the local card id as user_card_id for member calculation, or select its local default.
Also registered as
GET /api/v1/user/cards calls this same list action and shares the canonical card-list section.
Select a legacy card default
Set local card/default method flags. This path makes no provider default update or charge.
POST /api/v1/user/card/{card_id}/default
Before you call
Use the member and resource context; apply the source-specific requirements below.
Card lookup restricts card user_id, selected resource attachment and local card ID; no provider/mode filter or additional customer-owner/relationship-user predicate. Shared local records may be visible through another resource. PaymentHelper construction still initializes the resource default provider, so provider configuration can fail even when the following changes are local.
Local default cards across attached providers are cleared, then the selected card is marked default.
All local default methods in the resource are cleared; a method linked to this card is marked default if found. The method result is ignored. No transaction; save booleans unchecked, so a successful result does not prove every flag persisted.
Request
Bodyless. card_id path is required digits: local card ID from the card list, not provider card ID.
Result
HTTP 200 result boolean from the local card helper, not the subsequent method helper. false is possible if its second lookup does not resolve.
Example: Select local card 5001
Select a resource-visible legacy card. result reports the card helper, while the subsequent method-flag change has a separate ignored result.
curl -X POST "${WALLKIT_API_BASE}/api/v1/user/card/5001/default" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/card/5001/default`, {
method: "POST",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/card/5001/default",
method="POST", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"]})
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 response excerpt:
{
"result": true
}
Consequential alternate
HTTP 200 {"result":false} means the local card helper did not select the card. The method helper can still have changed flags; read current lists before another write.
Recovery
| Status | Code / shape | Cause | Next action |
|---|---|---|---|
| 409 | incorrect_user_card_id | Initial scoped card lookup fails. | Read resource card list and select its local ID. |
| Shared failure | No action-specific configuration catch | Default provider initialization fails. | Have administrator check resource provider configuration; do not assume flags changed. |
Next task
List cards and modern sources to inspect current flags before checkout.
Remove a legacy local card
Delete the local card record. This legacy operation does not detach/delete the provider card or linked payment method, remove customer attachments, cancel memberships or refund.
DELETE /api/v1/user/card/{id}
Before you call
Use the member and resource context; apply the source-specific requirements below.
Card lookup restricts card user_id, selected resource attachment and local card ID; no provider/mode filter or additional customer-owner/relationship-user predicate. Shared local records may be visible through another resource. PaymentHelper construction still initializes the resource default provider, so provider configuration can fail even when the following changes are local.
The helper attempts unchecked local delete; when the deleted card was default it selects a remaining attached card ordered default ASC then customer created_at DESC and marks it default.
There is no provider filter, transaction or guarantee of linked method/default cleanup. The remove_card event receives the pre-deletion card snapshot.
Request
Bodyless. id path is required digits: local card ID.
Result
HTTP 200 result boolean true after the helper path even though delete/save return values are not checked. No card/customer projection.
Example: Remove local card 5001
Delete the legacy local card. result does not acknowledge provider deletion or linked-method cleanup.
curl -X DELETE "${WALLKIT_API_BASE}/api/v1/user/card/5001" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/card/5001`, {
method: "DELETE",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/card/5001",
method="DELETE", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"]})
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 response excerpt:
{
"result": true
}
Consequential alternate
An unknown or already removed local card returns HTTP 409 incorrect_user_card_id; deletion is not a promised replay-safe success. A linked method can remain after card removal.
Recovery
| Status | Code / shape | Cause | Next action |
|---|---|---|---|
| 409 | incorrect_user_card_id | Card missing from scoped lookup. | Refresh card list; do not replace a local ID with a provider ID. |
| Shared failure | No local provider/configuration catch | Helper initialization fails. | Inspect configured provider and current local records before retrying. |
Next task
Read cards and sources to inspect the remaining default/methods. Use modern removal if that compatible path needs provider detach.
Record and gate a card-attachment attempt
Record an attempt and apply configured locks. It does not tokenize, validate, attach or charge a card. Calling this consumes an attempt; it is not a harmless preflight read.
POST /api/v1/user/card/attempt-attach
Before you call
Use the member and resource context; apply the source-specific requirements below.
A user created within roughly 5 seconds is suspended in this resource and receives user_suspended.
Existing resource membership is needed by the suspension helper; missing relationship failure has no action-specific catch.
Limits use card_attach_attempts_limits (fallback: 3 attempts in 1 minute) and configured card_attach_attempts_locks (fallback levels 1 → 5 minutes, 2 → 30 minutes, 3 → 60 minutes).
Previous attempt level/time/count can deny or escalate; an unknown positive lock level can suspend the relationship.
Events and unchecked attempt/relationship saves occur; success is not proof of provider readiness. The source-specific manual-review helper is not invoked.
Request
Bodyless; no card fields, token or nonce consumed.
Result
HTTP 200 success boolean true after the attempt-record path. No remaining-attempt count, lock-expiry timestamp or source record.
Example: Record one attachment attempt
Record a single attempt in the existing member/resource context. success permits continuation of the existing flow; it supplies no card, token or nonce.
curl -X POST "${WALLKIT_API_BASE}/api/v1/user/card/attempt-attach" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/card/attempt-attach`, {
method: "POST",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/card/attempt-attach",
method="POST", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"]})
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 response excerpt:
{
"success": true
}
Consequential alternate
HTTP 409 user_suspended can follow an attempted resource suspension. HTTP 409 limit_reached denies another attempt, with a dynamic description; no exact retry timestamp is returned.
Recovery
| Status | Code / shape | Cause | Next action |
|---|---|---|---|
| 409 | user_suspended | New-user gate or exhausted configured level. | Stop attachment; contact the resource administrator to resolve suspension. |
| 409 | limit_reached | Current attempt window/lock applies. | Honor the configured lock and displayed guidance; do not loop retries. |
| 409 | incorrect_method | Method check fails. | Use POST. |
Next task
After success, continue only the existing authorized provider attachment flow; this result alone supplies no saved source ID.
Create or import a legacy customer
Use a provider customer ID or token/nonce to create/import local customer/cards. This returns a member profile, not a single customer envelope.
POST /api/v1/user/customers
Before you call
Use the member and resource context; apply the source-specific requirements below.
Provider token/customer_id is trusted input: the action does not independently prove provider customer ownership before assigning it to the current local member. Existing configuration for the explicit provider is required.
Customer retrieval/creation precedes local customer reuse/create and card import.
auto_attach_customer_to_resource defaults true; attachment failures are logged/swallowed, and false disables attachment.
Local saves unchecked; no enclosing transaction/replay guarantee.
Stripe imports legacy card sources and sets local card defaults; Braintree imports bin-backed local cards without a universal default/method projection. No charge is made.
Request
JSON or form; nonempty form wins.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| pay_system | body | required exact label | Validation accepts stripe/braintree/paypal. PaymentHelper does not implement paypal, so that accepted label later fails. square is not accepted here. |
| customer_id | body | conditional nonempty string | Provider customer ID when isset; retrieves/imports existing provider data. Null behaves as absent. |
| token_id | body | required when customer_id not isset; string | Provider token for stripe or nonce for braintree. If both fields are set, customer import runs first then token creation also runs; use one branch to avoid two write sequences. |
Result
HTTP 201 resource-aware user, subscriptions and teams from that definition, plus payment_customers array of legacy customer records filtered to member-owned customers attached to this resource and relationship user. No history/tickets/last_action added. Created customer can be absent when resource attachment is disabled/fails.
Example: Import cus_SYNTHETIC for the member
Choose only the existing-customer branch. The profile excerpt shows local customer 4001 in payment_customers; imported cards can still be empty.
curl -X POST "${WALLKIT_API_BASE}/api/v1/user/customers" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}" \
-H "Content-Type: application/json" \
--data '{"pay_system":"stripe","customer_id":"cus_SYNTHETIC"}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/customers`, {
method: "POST",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY, "Content-Type": "application/json"},
body: JSON.stringify({"pay_system":"stripe","customer_id":"cus_SYNTHETIC"})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'pay_system': 'stripe', 'customer_id': 'cus_SYNTHETIC'}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/customers",
method="POST", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"], "Content-Type": "application/json"}, data=json.dumps(body).encode("utf-8"))
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 201 response excerpt:
{
"id": 1001,
"payment_customers": [
{
"id": 4001,
"pay_system": "stripe",
"customer_id": "cus_SYNTHETIC",
"cards": []
}
]
}
Consequential alternate
HTTP 201 payment_customers:[] can occur despite earlier provider/local creation if attachment does not make the customer resource-visible. HTTP 406 incorrect_payment can follow paypal passing validation but unsupported provider initialization.
Recovery
| Status | Code / shape | Cause | Next action |
|---|---|---|---|
| 409 | invalid_pay_system, invalid_customer_id, invalid_token_id | Field validation fails. | Supply one supported branch and exact provider label. |
| 406 | incorrect_customer | Provider customer retrieval/create failure. | Check correct account/member provider input and inspect partial state. |
| 406 | incorrect_payment | Unsupported/missing provider configuration. | Ask administrator to correct provider configuration; paypal has no helper dispatch. |
| 406 | incorrect_customer_card | Card import fails. | Inspect existing customer and imported cards before repeating. |
| 406 | add_customer_fail | Other failure. | Investigate local/provider partial records before retry. |
Next task
List owned customers to get local id, or list cards to choose a local checkout card ID.
List owned legacy customers
Read every local customer owned by the member, across resources/providers. A resource header is required by initialization but does not filter this collection.
GET /api/v1/user/customers
Before you call
Use the member and resource context; apply the source-specific requirements below.
Request
No body, pagination, provider/resource filter or specified order.
Result
HTTP 200 items contains legacy customer records. All related cards are included; no method projection. When no customer exists setItem is never called, so items can be omitted rather than []. Shared response metadata can still appear.
Example: Find local customer 4001
Read the member’s owned customers across resources. Use local id 4001 for authorized resource attachment; provider customer_id is a different identifier.
curl -X GET "${WALLKIT_API_BASE}/api/v1/user/customers" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/customers`, {
method: "GET",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/customers",
method="GET", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"]})
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 response excerpt:
{
"items": [
{
"id": 4001,
"pay_system": "stripe",
"customer_id": "cus_SYNTHETIC",
"cards": []
}
]
}
Consequential alternate
An empty owned-customer result can omit items entirely. Treat absence as no collected customer rows; do not expect a paginator.
Recovery
| Status | Code / shape | Cause | Next action |
|---|---|---|---|
| Shared auth status | Credential errors | Invalid member/resource context. | Restore selected context. |
No dedicated list error is caught here.
Next task
Use local customer id for resource attachment, subject to trusted ownership handling described there.
Attach a local customer to a resource
Attempt a local resource/customer relationship insert for the authenticated member. This does not create a provider customer or source.
PUT /api/v1/user/customers/{customer_id}/resources/{resource_key}
Before you call
Use the member and resource context; apply the source-specific requirements below.
The header resource is required, but the destination is the path resource_key. Lookup finds any local customer by ID and any destination resource by public key: no explicit customer-owner, destination membership, account scope or provider/mode check. Trusted integration code must verify the member owns the intended customer and is allowed to attach it to the destination before calling.
An inserted relationship uses current member ID even when customer owner differs; customer-owner filters elsewhere can still hide it.
Existing duplicates are not prechecked. save return false is unchecked, so result true does not prove insertion.
Request
Bodyless.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| customer_id | path | required digits | Local customer record ID from owned customer list, not provider customer_id. |
| resource_key | path | required letters/digits/underscore | Destination resource public key (registered regex [\d\w]+); separate from header context. |
Result
HTTP 200 result boolean true after attempted save, no relationship/source projection.
Example: Attach customer 4001 to the intended resource
After trusted ownership/destination checks, attempt the local relationship insert. result true does not independently prove ownership or a persisted save.
curl -X PUT "${WALLKIT_API_BASE}/api/v1/user/customers/4001/resources/RESOURCE_KEY" \
-H "token: ${USER_TOKEN}" \
-H "resource: ${RESOURCE_KEY}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/customers/4001/resources/RESOURCE_KEY`, {
method: "PUT",
headers: {token: process.env.USER_TOKEN, resource: process.env.RESOURCE_KEY}
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/customers/4001/resources/RESOURCE_KEY",
method="PUT", headers={"token": os.environ["USER_TOKEN"],
"resource": os.environ["RESOURCE_KEY"]})
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 response excerpt:
{
"result": true
}
Consequential alternate
HTTP 406 resource_customer_duplicate describes caught insertion exceptions as already attached, but does not distinguish every possible insert failure. HTTP 200 can still accompany an unchecked false save.
Recovery
| Status | Code / shape | Cause | Next action |
|---|---|---|---|
| 404 | attach_customer_fail | Empty/unknown local customer or destination key. | Check owned local customer ID and destination public key. |
| 406 | resource_customer_duplicate | Insert throws; reported as duplicate. | Inspect existing authorized relationship and local error before retry. |
Next task
List destination cards using that resource as header to inspect visibility. Attachment alone does not establish ownership, saved defaults or a charge.