# 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 Berrypod 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 Berrypod" 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 Berrypod"

**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

Berrypod 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 Berrypod 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)

**Note:** Webhooks DO work with personal tokens - merchant just needs to enable `webhooks.read` and `webhooks.write` scopes when generating the token.

### OAuth Platform Integration

**How it works:**
- Register Berrypod 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 Berrypod" 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)
