simpleshop_theme/docs/plans/printify-integration-research.md
jamey 5b736b99fd feat: add admin provider setup UI with improved product sync
- Add /admin/providers LiveView for connecting and managing POD providers
- Implement pagination for Printify API (handles all products, not just first page)
- Add parallel processing (5 concurrent) for faster product sync
- Add slug-based fallback matching when provider_product_id changes
- Add error recovery with try/rescue to prevent stuck sync status
- Add checksum-based change detection to skip unchanged products
- Add upsert tests covering race conditions and slug matching
- Add Printify provider tests
- Document Printify integration research (product identity, order risks,
  open source vs managed hosting implications)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 22:08:34 +00:00

324 lines
10 KiB
Markdown

# Printify Integration Research
> Research notes from investigating Printify API integration, product syncing, and order submission risks. This document captures findings for future implementation work.
## Product Identity & Matching
### What identifiers exist?
| Field | Stable? | Notes |
|-------|---------|-------|
| `id` (provider_product_id) | ❌ | Changes if product deleted & recreated |
| `sku` (variant level) | ❌ | Auto-generated per product creation |
| `blueprint_id` | ✅ | The base product type (e.g., Gildan 5000) |
| `print_provider_id` | ✅ | The fulfillment provider |
| `title` | ✅ | User-defined, stable unless edited |
### Our matching strategy
1. **Primary**: Match by `provider_product_id`
2. **Fallback**: Match by `slug` (derived from title)
3. **Potential improvement**: Match by `blueprint_id + print_provider_id + title`
The slug fallback handles cases where Printify product IDs change (e.g., product deleted and recreated with same title).
### How Shopify/Printify official integration works
- SKU is generated once when first published to Shopify
- SKU becomes the stable link for future updates
- Republishing matches by SKU
- The "publish lock" prevents editing after publishing
## Duplicate Products
### When duplicates occur
- Mock/test product generators run multiple times
- User accidentally creates products twice in Printify
- Migration scenarios with orphaned products
- Using multiple POD providers on same store
### How we detect duplicates
Same `title` (and therefore same `slug`) but different `provider_product_id`.
In our test case, Printify returned 30 products but only 16 unique titles - every product existed twice with different IDs but identical:
- `title`
- `blueprint_id`
- `print_provider_id`
### How we handle duplicates
Our `upsert_product/2` function matches by slug when provider_product_id lookup fails, treating them as the same product and updating the provider_product_id to the latest value.
### Is this a common real-world issue?
Research suggests **no** - duplicate products aren't commonly reported. Main sync issues are:
- Orders not syncing
- Store connection expiring
- Publishing failures
- Product info being overwritten on republish
The official Shopify integration handles product identity well through SKUs.
## Sales Channel Integration Options
### Option 1: Personal API Token (current)
- Pull products via API
- Works immediately, no approval needed
- We don't appear in Printify's "Publish to..." UI
- Products not "locked" after sync
### Option 2: OAuth Platform Integration
- Apply at printify.com/printify-api
- ~1 week approval process
- Merchants authorize SimpleShop via OAuth
- We appear in Printify's publishing UI alongside Shopify/Etsy
- Products get "published" TO us with lock
- Webhooks for real-time updates
**How publishing works in Option 2:**
1. Merchant clicks "Publish to SimpleShop" in Printify
2. Printify sends `product:publish:started` webhook
3. We create the product on our side
4. We call `publishing_succeeded.json` to confirm
5. Product locked in Printify, shows as "Published to SimpleShop"
**Benefits of official integration:**
- Publish lock prevents editing after publishing (data consistency)
- Real-time webhooks
- "Official" feel for merchants
- Better UX in Printify dashboard
**Downsides:**
- Approval process
- More complex OAuth auth
- Ongoing partnership relationship
## Order Submission Risks
### How order submission works
We send to Printify:
```elixir
%{
product_id: "printify_product_id",
variant_id: 12345,
quantity: 2
}
```
We **do not send price**. Printify charges their current wholesale cost.
### Risk scenarios
| Scenario | Result | Who bears cost? |
|----------|--------|-----------------|
| Wholesale cost increased | Order succeeds | Shop owner (you) |
| Wholesale cost decreased | Order succeeds | Shop owner profits more |
| Variant discontinued | Order **fails** | Customer experience |
| Product deleted | Order **fails** | Customer experience |
| Design changed | Order succeeds with new design | Customer gets wrong item |
### Why the publish lock matters
In official integrations, the publish lock prevents merchants from editing products after publishing. This ensures:
- Price consistency between storefront and fulfillment cost
- Variant availability guaranteed
- Design matches what customer saw
Without the lock (our current approach), products can change between sync and order.
### Recommended mitigations
1. **Webhooks / frequent polling**
- Subscribe to `product:updated`, `product:deleted` events
- Or poll every N minutes for changes
- Update local product data immediately
2. **Pre-checkout validation**
- Before completing checkout, call Printify API
- Verify variant still exists
- Get current cost, compare to stored cost
- Alert or block if significant difference
3. **Cost monitoring**
- Store `cost` from Printify on each variant
- During sync, compare old vs new cost
- Alert shop owner if cost increased significantly
- Consider auto-adjusting retail price
4. **Graceful order failure handling**
- Catch "variant not found" errors
- Notify customer immediately
- Offer refund or alternative
- Don't leave order in limbo
5. **Stale data warnings**
- Track `last_synced_at` per product
- Warn in admin if product not synced recently
- Consider blocking orders for very stale products
## API Rate Limits
From Printify documentation:
- 600 requests/minute global
- 100 requests/minute for catalog endpoints
- 50 products per page max
- Product publishing: 200 requests per 30 minutes
## Implementation Status
### Completed
- [x] Product sync with pagination
- [x] Parallel processing (5 concurrent)
- [x] Slug-based fallback matching
- [x] Error recovery (try/rescue)
- [x] Checksum-based change detection
### Not yet implemented
- [ ] Webhook endpoint for real-time updates
- [ ] Pre-checkout variant validation
- [ ] Cost change monitoring/alerts
- [ ] OAuth platform integration (requires Printify approval)
- [ ] `blueprint_id + print_provider_id` matching
## Open Source vs Managed Hosting Considerations
### The core tension
SimpleShop exists in two forms:
1. **Open source** - self-hosted by anyone
2. **Managed hosting** - SaaS service run by us
Printify's integration options have different implications for each.
### Personal API Token (current approach)
**How it works:**
- Each merchant creates their own Printify API token
- Token entered in SimpleShop admin
- Direct API access, no intermediary
**Open source:** ✅ Works perfectly
- Each self-hosted instance uses merchant's own token
- No central service required
- Full functionality
**Managed hosting:** ✅ Works perfectly
- Same as self-hosted
- Each tenant uses their own token
- No special relationship with Printify needed
**Downsides:**
- Merchants must manually create/manage API tokens
- No "Connect with Printify" button UX
- Products not locked after sync (data consistency risk)
- No real-time webhooks (must poll or manual sync)
### OAuth Platform Integration
**How it works:**
- Register SimpleShop as an app with Printify
- Get OAuth client ID/secret
- Merchants click "Connect" and authorize
- We receive access tokens, appear in Printify UI
**Open source:** ❌ Problematic
- OAuth requires registered callback URLs
- Can't register infinite self-hosted domains
- Would need to proxy through a central service
- Defeats the "fully self-hosted" value proposition
**Managed hosting:** ✅ Works well
- Single registered callback URL
- "Connect with Printify" button
- Appears as official integration
- Real-time webhooks
- Publish lock for data consistency
### Hybrid approach possibilities
**Option A: Token for open source, OAuth for managed**
- Open source uses personal API tokens (current)
- Managed hosting uses OAuth integration
- Two code paths, more maintenance
- Clear value differentiation
**Option B: Webhook proxy service**
- Register OAuth app
- Self-hosted instances connect through managed webhook proxy
- Proxy forwards webhooks to self-hosted URLs
- Adds dependency on central service
- Could be free tier for open source users
**Option C: OAuth with self-registration**
- Document how to register own OAuth app with Printify
- Self-hosters go through Printify approval themselves
- Complex, unlikely many would do this
- Each instance is independent
### Business model implications
| Approach | Open Source | Managed Hosting |
|----------|-------------|-----------------|
| Personal API Token | Full feature parity | No differentiation |
| OAuth (managed only) | Basic sync only | Premium "official" integration |
| Webhook proxy | Depends on proxy | Full features |
**Potential differentiation for managed hosting:**
- Official "Publish to SimpleShop" in Printify UI
- Real-time sync via webhooks
- Publish lock (data consistency guarantee)
- Pre-checkout validation (verify before order)
- No token management for merchants
**What open source keeps:**
- Full product sync (polling-based)
- Order submission
- All admin features
- Self-hosted independence
### Printify partnership considerations
**What registering as an app might require:**
- Application/approval process (~1 week)
- Technical integration review
- Ongoing partnership relationship
- Support obligations?
- Usage/volume commitments?
**Questions to research:**
- [ ] What are Printify's requirements for app partners?
- [ ] Are there fees or revenue sharing?
- [ ] Can we register once for managed hosting only?
- [ ] What support/SLA obligations exist?
- [ ] Can app be limited to specific redirect URIs (managed only)?
### Recommendation
**Phase 1 (now):** Personal API tokens for both versions
- Works everywhere
- No partnership dependencies
- Validates product-market fit first
**Phase 2 (if managed hosting gains traction):** OAuth for managed only
- Apply for Printify app registration
- Implement OAuth flow for managed platform
- Keep token-based flow for open source
- Use as value differentiator
**Phase 3 (optional):** Webhook proxy for open source
- If demand exists for real-time sync in self-hosted
- Could be free or paid add-on
- Maintains open source independence while adding capability
## References
- [Printify API Reference](https://developers.printify.com/)
- [Printify Help - Sales Channels](https://help.printify.com/hc/en-us/articles/4483630572945-Which-sales-channels-does-Printify-integrate-with)
- [Printify Partner Application](https://printify.com/become-a-partner/)
- [Shopify Product Status](https://community.shopify.com/t/what-is-the-difference-of-unpublished-product-draft-product/30964)