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>
This commit is contained in:
323
docs/plans/printify-integration-research.md
Normal file
323
docs/plans/printify-integration-research.md
Normal file
@@ -0,0 +1,323 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user