Get customer payment methods from Shopify
Retrieves all payment methods associated with a customer directly from Shopify’s payment API. This endpoint returns detailed information about stored payment instruments including credit/debit cards, digital wallets, and other payment methods that the customer can use for subscription billing.
What This Endpoint Does: Queries Shopify’s GraphQL API to fetch the customer’s payment methods, including active instruments and optionally revoked (expired, removed, or failed) payment methods. This provides real-time payment method data directly from Shopify’s payment vault.
Payment Method Types Supported:
Credit/Debit Cards:
- Visa, Mastercard, American Express, Discover
- Card last 4 digits
- Expiry month and year
- Card brand and type
- Billing address associated with card
Digital Wallets:
- Shop Pay
- Apple Pay
- Google Pay
- PayPal (when stored)
Alternative Payment Methods:
- Bank accounts (ACH, SEPA)
- Buy Now Pay Later instruments
- Store credit
Payment Method Information Returned:
For Each Payment Method:
- Payment Instrument ID: Unique identifier (used for updates)
- Display Name: Human-readable name (e.g., “Visa ending in 4242”)
- Payment Type: Card, wallet, bank account, etc.
- Status: ACTIVE, REVOKED, EXPIRED, FAILED
- Is Default: Whether this is the customer’s default payment method
- Last 4 Digits: For cards and bank accounts
- Expiry Date: For cards (month/year)
- Brand: Visa, Mastercard, Amex, etc.
- Billing Address: Address associated with payment method
- Created Date: When payment method was added
Query Parameters:
allowRevokedMethod (optional, default: false):
false: Returns only active, usable payment methodstrue: Returns both active AND revoked payment methods
Revoked Payment Methods: Include expired cards, deleted payment methods, failed instruments, and customer-removed methods. Useful for historical records and troubleshooting, but cannot be used for new billing attempts.
Use Cases:
1. Customer Portal:
- Display saved payment methods
- Allow customer to select default payment method
- Show payment method update/delete options
- Validate payment methods before subscription modification
2. Subscription Management:
- Verify customer has valid payment method before creating subscription
- Check payment method expiry before next billing
- Identify subscriptions at risk due to expiring cards
- Prompt customer to update payment if needed
3. Payment Method Updates:
- List available payment methods for customer selection
- Identify payment instrument ID for update operations
- Validate payment method before switching subscription
4. Troubleshooting & Support:
- Debug payment failures
- Verify which payment method is being used
- Check if payment method is expired or revoked
- Assist customer with payment issues
5. Analytics & Alerts:
- Track payment methods approaching expiry
- Send proactive notifications to update cards
- Analyze payment method distribution
- Identify customers with no valid payment methods
Response Structure:
Returns CustomerPaymentMethodsQuery.PaymentMethods object from Shopify GraphQL:
{
"nodes": [
{
"id": "gid://shopify/CustomerPaymentMethod/abc123",
"instrument": {
"__typename": "CustomerCreditCard",
"brand": "VISA",
"lastDigits": "4242",
"expiryMonth": 12,
"expiryYear": 2025,
"name": "John Doe"
},
"revokedAt": null,
"revokedReason": null,
"subscriptionContracts": [...]
}
]
}
Common Scenarios:
Scenario 1: Customer with multiple cards Returns array with multiple payment method objects, each representing a stored card.
Scenario 2: Customer with no payment methods Returns empty nodes array - customer needs to add payment method.
Scenario 3: Expired card included (allowRevokedMethod=true) Returns both active cards and expired/revoked cards with revokedAt timestamp.
Important Considerations:
Data Source:
- Queries Shopify API in real-time (not Appstle database)
- Always returns current Shopify payment method state
- Subject to Shopify API rate limits
Performance:
- Response time: 300-800ms (depends on Shopify API)
- Slower than database queries
- Consider caching for non-critical displays
Security:
- Never returns full card numbers (PCI compliance)
- Returns only last 4 digits
- CVV is never stored or returned
- Payment instrument IDs are tokenized references
Privacy:
- Customer ID validated against shop
- Cannot query payment methods from other shops
- Requires appropriate API permissions
Best Practices:
- Default to Active Only: Use
allowRevokedMethod=falsefor payment selection UIs - Check Expiry Dates: Validate card expiry before using for subscriptions
- Cache Responsibly: Cache for short periods (5-10 min) to reduce API calls
- Handle Empty Response: Always handle case where customer has no payment methods
- Show User-Friendly Names: Display brand and last 4 (e.g., “Visa •••• 4242”)
- Indicate Default: Highlight the default payment method clearly
Integration Examples:
Example 1: Display payment methods in customer portal
const paymentMethods = await fetch(
`/api/external/v2/subscription-contract-details/shopify/customer/${customerId}/payment-methods`,
{ headers: { 'X-API-Key': 'your-key' } }
).then(r => r.json());
paymentMethods.nodes.forEach(pm => {
if (pm.instrument.__typename === 'CustomerCreditCard') {
console.log(`${pm.instrument.brand} ending in ${pm.instrument.lastDigits}`);
if (pm.instrument.expiryYear < currentYear) {
console.warn('Card expired!');
}
}
});
Example 2: Check for valid payment before creating subscription
const paymentMethods = await fetch(...);
const hasValidPayment = paymentMethods.nodes.some(pm =>
!pm.revokedAt && pm.instrument.expiryYear >= currentYear
);
if (!hasValidPayment) {
alert('Please add a valid payment method before subscribing');
}
Related Endpoints:
PUT /api/external/v2/subscription-contracts-update-payment-method- Update subscription payment methodPOST /api/external/v2/associate-shopify-customer-to-external-payment-gateways- Add external payment method
Authentication: Requires valid X-API-Key header
curl --request GET \
--url https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methodsimport requests
url = "https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methods"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methods', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const url = 'https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methods';
const options = {method: 'GET'};
fetch(url, options)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methods",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methods"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}require 'uri'
require 'net/http'
url = URI("https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methods")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"nodes": [
{
"id": "gid://shopify/CustomerPaymentMethod/abc123",
"instrument": {
"__typename": "CustomerCreditCard",
"brand": "VISA",
"expiryMonth": 12,
"expiryYear": 2025,
"lastDigits": "4242",
"name": "John Doe",
"source": "SHOPIFY"
},
"revokedAt": null,
"revokedReason": null
},
{
"id": "gid://shopify/CustomerPaymentMethod/def456",
"instrument": {
"__typename": "CustomerShopPayAgreement",
"lastDigits": "1234",
"name": "Shop Pay"
},
"revokedAt": null,
"revokedReason": null
}
]
}{
"detail": "Customer ID must be a valid positive integer",
"status": 400,
"title": "Invalid request",
"type": "https://example.com/errors/bad-request"
}{
"detail": "Valid X-API-Key header is required",
"status": 401,
"title": "Authentication required",
"type": "https://example.com/errors/unauthorized"
}{
"detail": "Customer does not belong to your shop or API key lacks payment method read permissions",
"status": 403,
"title": "Access denied",
"type": "https://example.com/errors/forbidden"
}{
"detail": "No customer found with ID 12345 in Shopify",
"status": 404,
"title": "Customer not found",
"type": "https://example.com/errors/not-found"
}{
"detail": "Shopify API rate limit exceeded. Please retry after 60 seconds.",
"retryAfter": 60,
"status": 429,
"title": "Rate limit exceeded",
"type": "https://example.com/errors/rate-limit"
}{
"detail": "Failed to retrieve payment methods from Shopify. Please try again later.",
"status": 502,
"title": "Shopify API error",
"type": "https://example.com/errors/bad-gateway"
}Headers
Path Parameters
Customer Id
Query Parameters
Get revoked payment methods?
curl --request GET \
--url https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methodsimport requests
url = "https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methods"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methods', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const url = 'https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methods';
const options = {method: 'GET'};
fetch(url, options)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methods",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methods"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}require 'uri'
require 'net/http'
url = URI("https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/shopify/customer/{customerId}/payment-methods")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"nodes": [
{
"id": "gid://shopify/CustomerPaymentMethod/abc123",
"instrument": {
"__typename": "CustomerCreditCard",
"brand": "VISA",
"expiryMonth": 12,
"expiryYear": 2025,
"lastDigits": "4242",
"name": "John Doe",
"source": "SHOPIFY"
},
"revokedAt": null,
"revokedReason": null
},
{
"id": "gid://shopify/CustomerPaymentMethod/def456",
"instrument": {
"__typename": "CustomerShopPayAgreement",
"lastDigits": "1234",
"name": "Shop Pay"
},
"revokedAt": null,
"revokedReason": null
}
]
}{
"detail": "Customer ID must be a valid positive integer",
"status": 400,
"title": "Invalid request",
"type": "https://example.com/errors/bad-request"
}{
"detail": "Valid X-API-Key header is required",
"status": 401,
"title": "Authentication required",
"type": "https://example.com/errors/unauthorized"
}{
"detail": "Customer does not belong to your shop or API key lacks payment method read permissions",
"status": 403,
"title": "Access denied",
"type": "https://example.com/errors/forbidden"
}{
"detail": "No customer found with ID 12345 in Shopify",
"status": 404,
"title": "Customer not found",
"type": "https://example.com/errors/not-found"
}{
"detail": "Shopify API rate limit exceeded. Please retry after 60 seconds.",
"retryAfter": 60,
"status": 429,
"title": "Rate limit exceeded",
"type": "https://example.com/errors/rate-limit"
}{
"detail": "Failed to retrieve payment methods from Shopify. Please try again later.",
"status": 502,
"title": "Shopify API error",
"type": "https://example.com/errors/bad-gateway"
}