packwise-skills 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursorrules +23 -0
- package/CLAUDE.md +25 -0
- package/README.md +295 -0
- package/audit.md +224 -0
- package/bin/packwise.js +155 -0
- package/package.json +31 -0
- package/skill.md +719 -0
- package/sub-skills/ai/local-llm.md +183 -0
- package/sub-skills/ai/python-ml.md +164 -0
- package/sub-skills/backend/go-server.md +184 -0
- package/sub-skills/backend/java-spring.md +241 -0
- package/sub-skills/backend/node-server.md +164 -0
- package/sub-skills/backend/php-laravel.md +175 -0
- package/sub-skills/backend/python-server.md +164 -0
- package/sub-skills/backend/rust-backend.md +118 -0
- package/sub-skills/cli/python-cli.md +236 -0
- package/sub-skills/cli/sdk-library.md +497 -0
- package/sub-skills/cloud/ci-cd-pipelines.md +350 -0
- package/sub-skills/cloud/docker.md +191 -0
- package/sub-skills/cloud/kubernetes.md +277 -0
- package/sub-skills/cloud/payment-integration.md +307 -0
- package/sub-skills/cross-platform/multiplatform.md +252 -0
- package/sub-skills/desktop/electron.md +783 -0
- package/sub-skills/desktop/game-dev.md +443 -0
- package/sub-skills/desktop/native-app.md +123 -0
- package/sub-skills/desktop/scenarios.md +443 -0
- package/sub-skills/desktop/smart-platforms.md +324 -0
- package/sub-skills/desktop/tauri.md +428 -0
- package/sub-skills/desktop/vr-ar.md +252 -0
- package/sub-skills/desktop/web-to-desktop.md +153 -0
- package/sub-skills/embedded/car-infotainment.md +129 -0
- package/sub-skills/embedded/esp32.md +184 -0
- package/sub-skills/embedded/ros.md +150 -0
- package/sub-skills/embedded/stm32.md +160 -0
- package/sub-skills/mobile/android.md +322 -0
- package/sub-skills/mobile/capacitor.md +232 -0
- package/sub-skills/mobile/flutter-mobile.md +138 -0
- package/sub-skills/mobile/harmonyos.md +150 -0
- package/sub-skills/mobile/ios.md +245 -0
- package/sub-skills/mobile/react-native.md +443 -0
- package/sub-skills/mobile/wearables.md +230 -0
- package/sub-skills/plugins/browser-extension.md +308 -0
- package/sub-skills/plugins/jetbrains-plugin.md +226 -0
- package/sub-skills/plugins/vscode-extension.md +204 -0
- package/sub-skills/security/security-tools.md +174 -0
- package/sub-skills/web/monorepo.md +274 -0
- package/sub-skills/web/pwa.md +220 -0
- package/sub-skills/web/serverless-edge.md +295 -0
- package/sub-skills/web/spa.md +266 -0
- package/sub-skills/web/ssr.md +228 -0
- package/sub-skills/web/wasm.md +243 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Kubernetes Build Sub-Skill
|
|
2
|
+
|
|
3
|
+
Package and deploy applications to Kubernetes clusters (K8s, K3s, EKS, GKE, AKS).
|
|
4
|
+
|
|
5
|
+
**Current version**: Kubernetes 1.31 / Helm 3.x / Kustomize 5.x (2025-2026)
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
- Multi-container applications (Web + DB + Cache + Worker)
|
|
10
|
+
- Microservices architecture
|
|
11
|
+
- Auto-scaling requirements
|
|
12
|
+
- Multi-cloud or hybrid-cloud deployment
|
|
13
|
+
- Need rolling updates and zero-downtime deployments
|
|
14
|
+
|
|
15
|
+
## Core Concepts
|
|
16
|
+
|
|
17
|
+
| Concept | What It Does |
|
|
18
|
+
|---------|-------------|
|
|
19
|
+
| **Pod** | Smallest unit — runs one or more containers |
|
|
20
|
+
| **Deployment** | Manages pod replicas, rolling updates |
|
|
21
|
+
| **Service** | Network endpoint — stable IP/DNS for pods |
|
|
22
|
+
| **Ingress** | HTTP routing — domain → service mapping |
|
|
23
|
+
| **ConfigMap** | Non-secret configuration data |
|
|
24
|
+
| **Secret** | Sensitive data (passwords, tokens) |
|
|
25
|
+
| **PersistentVolume** | Durable storage (databases, uploads) |
|
|
26
|
+
| **Namespace** | Virtual cluster isolation |
|
|
27
|
+
|
|
28
|
+
## K8s Manifests
|
|
29
|
+
|
|
30
|
+
### Deployment + Service
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
# k8s/deployment.yaml
|
|
34
|
+
apiVersion: apps/v1
|
|
35
|
+
kind: Deployment
|
|
36
|
+
metadata:
|
|
37
|
+
name: myapp
|
|
38
|
+
labels:
|
|
39
|
+
app: myapp
|
|
40
|
+
spec:
|
|
41
|
+
replicas: 3
|
|
42
|
+
selector:
|
|
43
|
+
matchLabels:
|
|
44
|
+
app: myapp
|
|
45
|
+
strategy:
|
|
46
|
+
type: RollingUpdate
|
|
47
|
+
rollingUpdate:
|
|
48
|
+
maxSurge: 1
|
|
49
|
+
maxUnavailable: 0 # Zero downtime
|
|
50
|
+
template:
|
|
51
|
+
metadata:
|
|
52
|
+
labels:
|
|
53
|
+
app: myapp
|
|
54
|
+
spec:
|
|
55
|
+
containers:
|
|
56
|
+
- name: myapp
|
|
57
|
+
image: registry.example.com/myapp:1.0.0
|
|
58
|
+
ports:
|
|
59
|
+
- containerPort: 3000
|
|
60
|
+
env:
|
|
61
|
+
- name: DATABASE_URL
|
|
62
|
+
valueFrom:
|
|
63
|
+
secretKeyRef:
|
|
64
|
+
name: myapp-secrets
|
|
65
|
+
key: database-url
|
|
66
|
+
resources:
|
|
67
|
+
requests:
|
|
68
|
+
cpu: "100m"
|
|
69
|
+
memory: "128Mi"
|
|
70
|
+
limits:
|
|
71
|
+
cpu: "500m"
|
|
72
|
+
memory: "512Mi"
|
|
73
|
+
livenessProbe:
|
|
74
|
+
httpGet:
|
|
75
|
+
path: /health
|
|
76
|
+
port: 3000
|
|
77
|
+
initialDelaySeconds: 10
|
|
78
|
+
periodSeconds: 30
|
|
79
|
+
readinessProbe:
|
|
80
|
+
httpGet:
|
|
81
|
+
path: /health
|
|
82
|
+
port: 3000
|
|
83
|
+
initialDelaySeconds: 5
|
|
84
|
+
periodSeconds: 10
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
# k8s/service.yaml
|
|
89
|
+
apiVersion: v1
|
|
90
|
+
kind: Service
|
|
91
|
+
metadata:
|
|
92
|
+
name: myapp
|
|
93
|
+
spec:
|
|
94
|
+
selector:
|
|
95
|
+
app: myapp
|
|
96
|
+
ports:
|
|
97
|
+
- port: 80
|
|
98
|
+
targetPort: 3000
|
|
99
|
+
type: ClusterIP
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
# k8s/ingress.yaml
|
|
104
|
+
apiVersion: networking.k8s.io/v1
|
|
105
|
+
kind: Ingress
|
|
106
|
+
metadata:
|
|
107
|
+
name: myapp
|
|
108
|
+
annotations:
|
|
109
|
+
cert-manager.io/cluster-issuer: letsencrypt-prod
|
|
110
|
+
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
|
111
|
+
spec:
|
|
112
|
+
tls:
|
|
113
|
+
- hosts: [myapp.example.com]
|
|
114
|
+
secretName: myapp-tls
|
|
115
|
+
rules:
|
|
116
|
+
- host: myapp.example.com
|
|
117
|
+
http:
|
|
118
|
+
paths:
|
|
119
|
+
- path: /
|
|
120
|
+
pathType: Prefix
|
|
121
|
+
backend:
|
|
122
|
+
service:
|
|
123
|
+
name: myapp
|
|
124
|
+
port:
|
|
125
|
+
number: 80
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
# k8s/configmap.yaml
|
|
130
|
+
apiVersion: v1
|
|
131
|
+
kind: ConfigMap
|
|
132
|
+
metadata:
|
|
133
|
+
name: myapp-config
|
|
134
|
+
data:
|
|
135
|
+
NODE_ENV: "production"
|
|
136
|
+
LOG_LEVEL: "info"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
# k8s/secret.yaml
|
|
141
|
+
apiVersion: v1
|
|
142
|
+
kind: Secret
|
|
143
|
+
metadata:
|
|
144
|
+
name: myapp-secrets
|
|
145
|
+
type: Opaque
|
|
146
|
+
stringData:
|
|
147
|
+
database-url: "postgres://user:pass@db:5432/myapp"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## K3s (Lightweight Kubernetes)
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Install K3s (single-node, production-ready)
|
|
154
|
+
curl -sfL https://get.k3s.io | sh -
|
|
155
|
+
|
|
156
|
+
# Verify
|
|
157
|
+
kubectl get nodes
|
|
158
|
+
|
|
159
|
+
# Deploy
|
|
160
|
+
kubectl apply -f k8s/
|
|
161
|
+
|
|
162
|
+
# K3s is ideal for:
|
|
163
|
+
# - Edge computing
|
|
164
|
+
# - IoT
|
|
165
|
+
# - Development/staging
|
|
166
|
+
# - Small production clusters
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Helm (Package Manager for K8s)
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Install
|
|
173
|
+
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
|
174
|
+
|
|
175
|
+
# Create chart
|
|
176
|
+
helm create myapp-chart
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
myapp-chart/
|
|
181
|
+
├── Chart.yaml ← Chart metadata
|
|
182
|
+
├── values.yaml ← Default values
|
|
183
|
+
├── templates/
|
|
184
|
+
│ ├── deployment.yaml ← Template with {{ .Values.xxx }}
|
|
185
|
+
│ ├── service.yaml
|
|
186
|
+
│ ├── ingress.yaml
|
|
187
|
+
│ └── _helpers.tpl ← Template helpers
|
|
188
|
+
└── charts/ ← Sub-charts (dependencies)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```yaml
|
|
192
|
+
# values.yaml
|
|
193
|
+
replicaCount: 3
|
|
194
|
+
image:
|
|
195
|
+
repository: registry.example.com/myapp
|
|
196
|
+
tag: "1.0.0"
|
|
197
|
+
pullPolicy: IfNotPresent
|
|
198
|
+
service:
|
|
199
|
+
type: ClusterIP
|
|
200
|
+
port: 80
|
|
201
|
+
ingress:
|
|
202
|
+
enabled: true
|
|
203
|
+
host: myapp.example.com
|
|
204
|
+
tls: true
|
|
205
|
+
resources:
|
|
206
|
+
requests:
|
|
207
|
+
cpu: 100m
|
|
208
|
+
memory: 128Mi
|
|
209
|
+
limits:
|
|
210
|
+
cpu: 500m
|
|
211
|
+
memory: 512Mi
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# Install
|
|
216
|
+
helm install myapp ./myapp-chart -f values.yaml
|
|
217
|
+
|
|
218
|
+
# Upgrade
|
|
219
|
+
helm upgrade myapp ./myapp-chart --set image.tag=1.1.0
|
|
220
|
+
|
|
221
|
+
# Rollback
|
|
222
|
+
helm rollback myapp 1
|
|
223
|
+
|
|
224
|
+
# Uninstall
|
|
225
|
+
helm uninstall myapp
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Kustomize (Template-Free Config)
|
|
229
|
+
|
|
230
|
+
```yaml
|
|
231
|
+
# kustomization.yaml
|
|
232
|
+
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
233
|
+
kind: Kustomization
|
|
234
|
+
resources:
|
|
235
|
+
- deployment.yaml
|
|
236
|
+
- service.yaml
|
|
237
|
+
- ingress.yaml
|
|
238
|
+
commonLabels:
|
|
239
|
+
app: myapp
|
|
240
|
+
images:
|
|
241
|
+
- name: registry.example.com/myapp
|
|
242
|
+
newTag: "1.0.0"
|
|
243
|
+
patches:
|
|
244
|
+
- path: patches/replicas.yaml
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# Apply
|
|
249
|
+
kubectl apply -k .
|
|
250
|
+
|
|
251
|
+
# Different overlays for environments
|
|
252
|
+
# overlays/production/kustomization.yaml
|
|
253
|
+
# overlays/staging/kustomization.yaml
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Helm vs Kustomize
|
|
257
|
+
|
|
258
|
+
| Feature | Helm | Kustomize |
|
|
259
|
+
|---------|------|-----------|
|
|
260
|
+
| Approach | Template-based (Go templates) | Overlay/patch based |
|
|
261
|
+
| Complexity | Higher (chart structure) | Lower (plain YAML) |
|
|
262
|
+
| Package registry | ArtifactHub (thousands of charts) | No registry |
|
|
263
|
+
| Best for | Distributable packages | Environment overlays |
|
|
264
|
+
| Secret management | External (Vault, Sealed Secrets) | External |
|
|
265
|
+
|
|
266
|
+
## Common Pitfalls
|
|
267
|
+
|
|
268
|
+
| Issue | Fix |
|
|
269
|
+
|-------|-----|
|
|
270
|
+
| Pod CrashLoopBackOff | Check logs: `kubectl logs pod-name`; verify health endpoint |
|
|
271
|
+
| ImagePullBackOff | Check image name/tag; verify registry credentials (`imagePullSecrets`) |
|
|
272
|
+
| Service not reachable | Check selector labels match; verify port mapping |
|
|
273
|
+
| ConfigMap/Secret not updating | K8s caches them; restart pods or use `--force` |
|
|
274
|
+
| Out of memory | Increase `resources.limits.memory`; check for memory leaks |
|
|
275
|
+
| PersistentVolume not mounting | Check PVC status; verify storage class exists |
|
|
276
|
+
| DNS not resolving | Check CoreDNS pods running; use fully qualified names |
|
|
277
|
+
| Ingress 404 | Verify Ingress controller installed; check host and path rules |
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# Payment Integration Build Sub-Skill
|
|
2
|
+
|
|
3
|
+
Integrate payment systems into applications during build and packaging.
|
|
4
|
+
|
|
5
|
+
**Current versions**: Stripe API 2024-12 / Alipay / WeChat Pay / Apple IAP / Google Play Billing (2025-2026)
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
- SaaS applications with subscription billing
|
|
10
|
+
- E-commerce applications
|
|
11
|
+
- Mobile apps with in-app purchases
|
|
12
|
+
- Marketplace / platform payments
|
|
13
|
+
- Donations / tipping
|
|
14
|
+
|
|
15
|
+
## Payment Platform Comparison
|
|
16
|
+
|
|
17
|
+
| Platform | Coverage | Fee | Best For |
|
|
18
|
+
|----------|----------|-----|---------|
|
|
19
|
+
| Stripe | Global (46+ countries) | 2.9% + $0.30/txn | Global SaaS, web apps |
|
|
20
|
+
| Alipay | China | 0.6% | China market |
|
|
21
|
+
| WeChat Pay | China | 0.6% | China market, mini-programs |
|
|
22
|
+
| PayPal | Global | 2.9% + $0.30/txn | Broad consumer reach |
|
|
23
|
+
| Apple IAP | iOS/macOS | 15–30% | iOS in-app purchases (required) |
|
|
24
|
+
| Google Play Billing | Android | 15–30% | Android in-app purchases (required) |
|
|
25
|
+
| Paddle | Global | 5% + fee | SaaS (handles tax/compliance) |
|
|
26
|
+
| LemonSqueezy | Global | 5% + fee | Digital products, indie SaaS |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Stripe Integration
|
|
31
|
+
|
|
32
|
+
### Web (Node.js Backend)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install stripe
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
// server/stripe.js
|
|
40
|
+
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
|
|
41
|
+
|
|
42
|
+
// Create checkout session
|
|
43
|
+
app.post('/create-checkout', async (req, res) => {
|
|
44
|
+
const session = await stripe.checkout.sessions.create({
|
|
45
|
+
payment_method_types: ['card'],
|
|
46
|
+
line_items: [{
|
|
47
|
+
price_data: {
|
|
48
|
+
currency: 'usd',
|
|
49
|
+
product_data: { name: 'My Product' },
|
|
50
|
+
unit_amount: 2000, // $20.00
|
|
51
|
+
},
|
|
52
|
+
quantity: 1,
|
|
53
|
+
}],
|
|
54
|
+
mode: 'payment',
|
|
55
|
+
success_url: `${process.env.APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
56
|
+
cancel_url: `${process.env.APP_URL}/cancel`,
|
|
57
|
+
});
|
|
58
|
+
res.json({ url: session.url });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Webhook handler (required for production)
|
|
62
|
+
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
|
|
63
|
+
const sig = req.headers['stripe-signature'];
|
|
64
|
+
const event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
|
|
65
|
+
|
|
66
|
+
switch (event.type) {
|
|
67
|
+
case 'checkout.session.completed':
|
|
68
|
+
// Fulfill order
|
|
69
|
+
break;
|
|
70
|
+
case 'invoice.paid':
|
|
71
|
+
// Update subscription status
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
res.json({ received: true });
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### React Frontend
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm install @stripe/stripe-js @stripe/react-stripe-js
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
import { loadStripe } from '@stripe/stripe-js';
|
|
86
|
+
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
|
|
87
|
+
|
|
88
|
+
const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Subscription Billing
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
// Create subscription
|
|
95
|
+
const subscription = await stripe.subscriptions.create({
|
|
96
|
+
customer: customerId,
|
|
97
|
+
items: [{ price: 'price_monthly_xxx' }],
|
|
98
|
+
payment_behavior: 'default_incomplete',
|
|
99
|
+
expand: ['latest_invoice.payment_intent'],
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Alipay & WeChat Pay
|
|
106
|
+
|
|
107
|
+
### Stripe + Alipay/WeChat (Recommended for international)
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// Stripe supports Alipay and WeChat Pay as payment methods
|
|
111
|
+
const session = await stripe.checkout.sessions.create({
|
|
112
|
+
payment_method_types: ['card', 'alipay', 'wechat_pay'],
|
|
113
|
+
// ...
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Native Alipay SDK
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pip install alipay-sdk-python
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
|
|
125
|
+
from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
|
|
126
|
+
|
|
127
|
+
config = AlipayClientConfig()
|
|
128
|
+
config.app_id = "your_app_id"
|
|
129
|
+
config.app_private_key = "your_private_key"
|
|
130
|
+
config.alipay_public_key = "alipay_public_key"
|
|
131
|
+
|
|
132
|
+
client = DefaultAlipayClient(config)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Native WeChat Pay SDK
|
|
136
|
+
|
|
137
|
+
```xml
|
|
138
|
+
<!-- Java/Maven -->
|
|
139
|
+
<dependency>
|
|
140
|
+
<groupId>com.github.wxpay</groupId>
|
|
141
|
+
<artifactId>wxpay-sdk</artifactId>
|
|
142
|
+
<version>3.4.0</version>
|
|
143
|
+
</dependency>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Apple In-App Purchase (IAP)
|
|
149
|
+
|
|
150
|
+
### When Required
|
|
151
|
+
|
|
152
|
+
- Digital content consumed in the app (subscriptions, premium features, virtual goods)
|
|
153
|
+
- **Physical goods**: Use Stripe/PayPal instead
|
|
154
|
+
- **External links**: Apple allows external payment links in some regions (EU, US)
|
|
155
|
+
|
|
156
|
+
### Swift Implementation
|
|
157
|
+
|
|
158
|
+
```swift
|
|
159
|
+
import StoreKit
|
|
160
|
+
|
|
161
|
+
class StoreManager: ObservableObject {
|
|
162
|
+
@Published var products: [Product] = []
|
|
163
|
+
|
|
164
|
+
func loadProducts() async {
|
|
165
|
+
do {
|
|
166
|
+
products = try await Product.products(for: ["com.app.premium_monthly", "com.app.premium_yearly"])
|
|
167
|
+
} catch {
|
|
168
|
+
print("Failed to load products: \(error)")
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
func purchase(_ product: Product) async throws -> Transaction? {
|
|
173
|
+
let result = try await product.purchase()
|
|
174
|
+
switch result {
|
|
175
|
+
case .success(let verification):
|
|
176
|
+
let transaction = try checkVerified(verification)
|
|
177
|
+
await transaction.finish()
|
|
178
|
+
return transaction
|
|
179
|
+
case .userCancelled:
|
|
180
|
+
return nil
|
|
181
|
+
case .pending:
|
|
182
|
+
return nil
|
|
183
|
+
@unknown default:
|
|
184
|
+
return nil
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### App Store Configuration
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
1. App Store Connect → In-App Purchases → Create
|
|
194
|
+
2. Set product ID, price, localization
|
|
195
|
+
3. Submit for review (can take 24-48 hours)
|
|
196
|
+
4. Implement receipt validation on server (recommended)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Google Play Billing
|
|
202
|
+
|
|
203
|
+
### Kotlin Implementation
|
|
204
|
+
|
|
205
|
+
```kotlin
|
|
206
|
+
// build.gradle.kts
|
|
207
|
+
dependencies {
|
|
208
|
+
implementation("com.android.billingclient:billing-ktx:7.0.0")
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// BillingManager.kt
|
|
212
|
+
class BillingManager(context: Context) {
|
|
213
|
+
private val billingClient = BillingClient.newBuilder(context)
|
|
214
|
+
.setListener { billingResult, purchases ->
|
|
215
|
+
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
|
|
216
|
+
for (purchase in purchases) handlePurchase(purchase)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
.enablePendingPurchases()
|
|
220
|
+
.build()
|
|
221
|
+
|
|
222
|
+
fun startConnection() {
|
|
223
|
+
billingClient.startConnection(object : BillingClientStateListener {
|
|
224
|
+
override fun onBillingSetupFinished(result: BillingResult) {
|
|
225
|
+
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
|
226
|
+
queryProducts()
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
override fun onBillingServiceDisconnected() { /* retry */ }
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private fun queryProducts() {
|
|
234
|
+
val productList = listOf(
|
|
235
|
+
QueryProductDetailsParams.Product.newBuilder()
|
|
236
|
+
.setProductId("premium_monthly")
|
|
237
|
+
.setProductType(BillingClient.ProductType.SUBS)
|
|
238
|
+
.build()
|
|
239
|
+
)
|
|
240
|
+
val params = QueryProductDetailsParams.newBuilder().setProductList(productList).build()
|
|
241
|
+
billingClient.queryProductDetailsAsync(params) { result, productDetailsList ->
|
|
242
|
+
// Display products to user
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Google Play Console Configuration
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
1. Google Play Console → Monetize → Products → Subscriptions
|
|
252
|
+
2. Create subscription product with base plan
|
|
253
|
+
3. Set pricing, grace period, offers
|
|
254
|
+
4. Upload app with billing permission
|
|
255
|
+
5. Test with license testers
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Server-Side Receipt Validation (Critical for Security)
|
|
261
|
+
|
|
262
|
+
```javascript
|
|
263
|
+
// Apple receipt validation
|
|
264
|
+
const appleReceiptVerify = require('node-apple-receipt-verify');
|
|
265
|
+
appleReceiptVerify.config({ secret: process.env.APPLE_SHARED_SECRET });
|
|
266
|
+
|
|
267
|
+
const products = await appleReceiptVerify.validate({
|
|
268
|
+
receipt: receiptData,
|
|
269
|
+
verbose: true,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Google Play receipt validation
|
|
273
|
+
const { google } = require('googleapis');
|
|
274
|
+
const androidpublisher = google.androidpublisher('v3');
|
|
275
|
+
const purchase = await androidpublisher.purchases.subscriptions.get({
|
|
276
|
+
packageName: 'com.example.app',
|
|
277
|
+
subscriptionId: 'premium_monthly',
|
|
278
|
+
token: purchaseToken,
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Payment Security Checklist
|
|
285
|
+
|
|
286
|
+
| Requirement | Implementation |
|
|
287
|
+
|-------------|---------------|
|
|
288
|
+
| **Never trust client-side** | Always validate payments server-side |
|
|
289
|
+
| **Webhook signature verification** | Verify Stripe/Alipay webhook signatures |
|
|
290
|
+
| **HTTPS only** | All payment pages must use HTTPS |
|
|
291
|
+
| **PCI compliance** | Use Stripe Elements (no raw card data on your server) |
|
|
292
|
+
| **Idempotency** | Use idempotency keys to prevent double charges |
|
|
293
|
+
| **Receipt validation** | Validate IAP receipts server-side (Apple/Google) |
|
|
294
|
+
| **Fraud detection** | Implement rate limiting; flag suspicious patterns |
|
|
295
|
+
| **Secure key storage** | Use environment variables / secret managers; never commit keys |
|
|
296
|
+
|
|
297
|
+
## Common Pitfalls
|
|
298
|
+
|
|
299
|
+
| Issue | Fix |
|
|
300
|
+
|-------|-----|
|
|
301
|
+
| Apple IAP rejected | Ensure IAP is for digital content; physical goods use other processors |
|
|
302
|
+
| Stripe webhook not receiving | Check webhook URL; verify signature; use Stripe CLI for testing |
|
|
303
|
+
| Double charges | Implement idempotency keys; check payment status before fulfilling |
|
|
304
|
+
| Currency mismatch | Use `currency` consistently; Stripe amounts are in cents |
|
|
305
|
+
| Alipay/WeChat cross-border | Use Stripe's built-in support; or apply for merchant accounts directly |
|
|
306
|
+
| Google Play billing crash | Handle `BillingClientStateListener` disconnection; retry logic |
|
|
307
|
+
| Tax compliance | Use Stripe Tax or Paddle for automatic tax calculation |
|