Hello,
So I have succesfully created in our hydrogen store a way to create subscriptions in our PDP that hits the Shopify Subscriptions endpoint. Everything appears in the subscriptions app and can be managed there. However, I want to allow the customer a way to manage them within their account page. I have created an account.subscriptions.jsx route and I’m really close, just having issues with the customer_access_token and querying their data. Though the order history page pulls everything just fine. Please advise, below is my account.subscriptions.jsx
import { json, redirect } from '@shopify/remix-oxygen';
import { useLoaderData, Form, useActionData } from '@remix-run/react';
import { useState } from 'react';
// Admin API query for subscription contracts
const SUBSCRIPTION_CONTRACTS_QUERY = `#graphql
query SubscriptionContracts($customerId: ID!) {
subscriptionContracts(first: 10, query: "customer_id:$customerId") {
edges {
node {
id
status
nextBillingDate
customer {
id
firstName
lastName
}
billingPolicy {
interval
intervalCount
}
deliveryPolicy {
interval
intervalCount
}
lines(first: 5) {
edges {
node {
productId
variantId
title
quantity
}
}
}
}
}
}
}
`;
// Admin API mutation to cancel a subscription
const CANCEL_SUBSCRIPTION_MUTATION = `#graphql
mutation SubscriptionContractUpdate($contractId: ID!) {
subscriptionContractUpdate(
contractId: $contractId
contract: { status: CANCELLED }
) {
subscriptionContract {
id
status
}
userErrors {
field
message
}
}
}
`;
export async function loader({ context, request }) {
const { session, admin } = context;
console.log('Session contents:', session.data);
const customerId = session.get('customerId');
const accessToken = session.get('customer_access_token');
console.log('Loader - Customer ID:', customerId);
console.log('Loader - Access Token:', accessToken);
if (!accessToken || !customerId) {
console.log('Redirecting to login - Missing session data');
return redirect('/account/login');
}
try {
const customerGid = `gid://shopify/Customer/${customerId}`;
const result = await admin.graphql(SUBSCRIPTION_CONTRACTS_QUERY, {
variables: { customerId: customerGid },
});
const data = await result.json();
console.log('Raw GraphQL response:', data); // Debug full response
const subscriptions = data?.data?.subscriptionContracts?.edges || [];
console.log('Subscriptions fetched:', subscriptions);
return json({ subscriptions });
} catch (error) {
console.error('Error fetching subscriptions:', error);
return json({ subscriptions: [], error: 'Unable to fetch subscriptions' }, { status: 500 });
}
}
export async function action({ request, context }) {
const { admin } = context;
const formData = await request.formData();
const contractId = formData.get('contractId');
if (!contractId) {
return json({ error: 'No subscription ID provided' }, { status: 400 });
}
try {
const result = await admin.graphql(CANCEL_SUBSCRIPTION_MUTATION, {
variables: { contractId },
});
const data = await result.json();
const { subscriptionContract, userErrors } = data.data.subscriptionContractUpdate;
if (userErrors?.length > 0) {
return json({ error: userErrors[0].message }, { status: 400 });
}
if (subscriptionContract?.status === 'CANCELLED') {
return json({ success: `Subscription ${contractId} cancelled successfully` });
}
return json({ error: 'Failed to cancel subscription' }, { status: 500 });
} catch (error) {
console.error('Error cancelling subscription:', error);
return json({ error: 'An error occurred while cancelling the subscription' }, { status: 500 });
}
}
export default function SubscriptionManagement() {
const { subscriptions } = useLoaderData();
const actionData = useActionData();
const [message, setMessage] = useState('');
if (actionData?.success) {
setTimeout(() => setMessage(actionData.success), 100);
} else if (actionData?.error) {
setTimeout(() => setMessage(actionData.error), 100);
}
return (
<div className="account-subscriptions">
<h1>Your Subscriptions</h1>
{message && <p className="text-center text-sm mt-2">{message}</p>}
{subscriptions.length === 0 ? (
<p>No active subscriptions found.</p>
) : (
<ul className="subscription-list">
{subscriptions.map(({ node: subscription }) => (
<li
key={subscription.id}
className="subscription-item border p-4 rounded my-4"
>
<p>
<strong>Subscription ID:</strong> {subscription.id.split('/').pop()}
</p>
<p>
<strong>Status:</strong> {subscription.status}
</p>
<p>
<strong>Next Billing Date:</strong>{' '}
{new Date(subscription.nextBillingDate).toLocaleDateString()}
</p>
<p>
<strong>Billing:</strong> Every {subscription.billingPolicy.intervalCount}{' '}
{subscription.billingPolicy.interval.toLowerCase()}
</p>
<p>
<strong>Delivery:</strong> Every {subscription.deliveryPolicy.intervalCount}{' '}
{subscription.deliveryPolicy.interval.toLowerCase()}
</p>
<div>
<strong>Items:</strong>
<ul>
{subscription.lines.edges.map(({ node: line }) => (
<li key={line.variantId}>
{line.title} (Qty: {line.quantity})
</li>
))}
</ul>
</div>
{subscription.status === 'ACTIVE' && (
<Form method="post">
<input type="hidden" name="contractId" value={subscription.id} />
<button
type="submit"
className="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 mt-2"
>
Cancel Subscription
</button>
</Form>
)}
</li>
))}
</ul>
)}
</div>
);
}
Thank you!