A Trifecta megközelítés: Hogyan épített fel a Forward Email egy golyóálló fizetési rendszert a Stripe és a PayPal segítségével
Ismerje meg, hogyan integrálta fejlesztőcsapatunk a Stripe-ot és a PayPal-t egy trifecta megközelítéssel, amely 1:1 valós idejű pontosságot biztosít a teljes rendszerünkben.
- Keresés oldal
- Tartalomjegyzék
Előszó
A Forward Emailnél mindig is fontosnak tartottuk a megbízható, pontos és felhasználóbarát rendszerek létrehozását. Amikor fizetésfeldolgozó rendszerünk bevezetésére került sor, tudtuk, hogy olyan megoldásra van szükségünk, amely több fizetésfeldolgozót is képes kezelni, miközben megőrzi az adatok tökéletes konzisztenciáját. Ez a blogbejegyzés részletezi, hogyan integrálta fejlesztőcsapatunk a Stripe-ot és a PayPal-t egy trifecta megközelítéssel, amely 1:1 valós idejű pontosságot biztosít a teljes rendszerünkben.
A kihívás: Több fizetési processzor, az igazság egyetlen forrása
Adatvédelemre összpontosító e-mail szolgáltatásként fizetési lehetőségeket kívántunk biztosítani felhasználóinknak. Vannak, akik a Stripe-on keresztül történő hitelkártyás fizetés egyszerűségét részesítik előnyben, míg mások értékelik a PayPal által biztosított további elválasztási réteget. A több fizetésfeldolgozó támogatása azonban jelentős bonyolultságot jelent:
- Hogyan biztosítjuk a konzisztens adatokat a különböző fizetési rendszerek között?
- Hogyan kezeljük a szélsőséges eseteket, például vitákat, visszatérítéseket vagy sikertelen fizetéseket?
- Hogyan tarthatunk fenn egyetlen igazságforrást adatbázisunkban?
Megoldásunk az volt, hogy megvalósítottuk az általunk "trifecta megközelítésnek" nevezett háromrétegű rendszert, amely redundanciát biztosít, és biztosítja az adatok konzisztenciáját, bármi is történjen.
A Trifecta megközelítés: a megbízhatóság három rétege
Fizetési rendszerünk három kritikus komponensből áll, amelyek együttesen biztosítják a tökéletes adatszinkronizálást:
- Kijelentkezés utáni átirányítások - Fizetési információk rögzítése közvetlenül a fizetés után
- Webhook kezelők - Valós idejű események feldolgozása a fizetésfeldolgozóktól
- Automatizált munkák - A fizetési adatok rendszeres ellenőrzése és egyeztetése
Nézzük meg az egyes összetevőket, és nézzük meg, hogyan működnek együtt.
flowchart TD
User([User]) --> |Selects plan| Checkout[Checkout Page]
%% Layer 1: Post-checkout redirects
subgraph "Layer 1: Post-checkout Redirects"
Checkout --> |Credit Card| Stripe[Stripe Checkout]
Checkout --> |PayPal| PayPal[PayPal Payment]
Stripe --> |Success URL with session_id| SuccessPage[Success Page]
PayPal --> |Return URL| SuccessPage
SuccessPage --> |Verify payment| Database[(Database Update)]
end
%% Layer 2: Webhooks
subgraph "Layer 2: Webhook Handlers"
StripeEvents[Stripe Events] --> |Real-time notifications| StripeWebhook[Stripe Webhook Handler]
PayPalEvents[PayPal Events] --> |Real-time notifications| PayPalWebhook[PayPal Webhook Handler]
StripeWebhook --> |Verify signature| ProcessStripeEvent[Process Stripe Event]
PayPalWebhook --> |Verify signature| ProcessPayPalEvent[Process PayPal Event]
ProcessStripeEvent --> Database
ProcessPayPalEvent --> Database
end
%% Layer 3: Automated jobs
subgraph "Layer 3: Bree Automated Jobs"
BreeScheduler[Bree Scheduler] --> StripeSync[Stripe Sync Job]
BreeScheduler --> PayPalSync[PayPal Sync Job]
BreeScheduler --> AccuracyCheck[Subscription Accuracy Check]
StripeSync --> |Verify & reconcile| Database
PayPalSync --> |Verify & reconcile| Database
AccuracyCheck --> |Ensure consistency| Database
end
%% Edge cases
subgraph "Edge Case Handling"
ProcessStripeEvent --> |Fraud detection| FraudCheck[Fraud Check]
ProcessPayPalEvent --> |Dispute created| DisputeHandler[Dispute Handler]
FraudCheck --> |Ban user if fraudulent| Database
DisputeHandler --> |Accept claim & refund| Database
FraudCheck --> |Send alert| AdminNotification[Admin Notification]
DisputeHandler --> |Send alert| AdminNotification
end
%% Style definitions
classDef primary fill:blue,stroke:#333,stroke-width:2px;
classDef secondary fill:red,stroke:#333,stroke-width:1px;
classDef tertiary fill:green,stroke:#333,stroke-width:1px;
class Checkout,SuccessPage primary;
class Stripe,PayPal,StripeWebhook,PayPalWebhook,BreeScheduler secondary;
class FraudCheck,DisputeHandler tertiary;
1. réteg: Kijelentkezés utáni átirányítások
A trifecta megközelítés első rétege azonnal megtörténik, miután a felhasználó befejezte a fizetést. Mind a Stripe, mind a PayPal biztosít olyan mechanizmusokat, amelyek a tranzakciós információkkal visszairányítják a felhasználókat webhelyünkre.
Stripe Checkout megvalósítás
A Stripe esetében a Checkout Sessions API-t használjuk a zökkenőmentes fizetési élmény megteremtésére. Amikor egy felhasználó kiválaszt egy csomagot, és úgy dönt, hogy hitelkártyával fizet, akkor konkrét sikerrel létrehozunk egy Checkout munkamenetet, és töröljük az URL-eket:
const options = {
mode: paymentType === 'one-time' ? 'payment' : 'subscription',
customer: ctx.state.user[config.userFields.stripeCustomerID],
client_reference_id: reference,
metadata: {
plan
},
line_items: [
{
price,
quantity: 1,
description
}
],
locale: config.STRIPE_LOCALES.has(ctx.locale) ? ctx.locale : 'auto',
cancel_url: `${config.urls.web}${ctx.path}${
isMakePayment || isEnableAutoRenew ? '' : `/?plan=${plan}`
}`,
success_url: `${config.urls.web}${ctx.path}/?${
isMakePayment || isEnableAutoRenew ? '' : `plan=${plan}&`
}session_id={CHECKOUT_SESSION_ID}`,
allow_promotion_codes: true
};
// Create the checkout session and redirect
const session = await stripe.checkout.sessions.create(options);
const redirectTo = session.url;
if (ctx.accepts('html')) {
ctx.status = 303;
ctx.redirect(redirectTo);
} else {
ctx.body = { redirectTo };
}
A kritikus rész itt az success_url
paraméter, amely magában foglalja a session_id
lekérdezési paraméterként. Amikor a Stripe sikeres fizetés után visszairányítja a felhasználót az oldalunkra, ezzel a munkamenet-azonosítóval ellenőrizhetjük a tranzakciót, és ennek megfelelően frissíthetjük adatbázisunkat.
PayPal fizetési folyamat
A PayPal esetében hasonló megközelítést alkalmazunk a megrendelések API-jukkal:
const requestBody = {
intent: 'CAPTURE',
application_context: {
cancel_url: `${config.urls.web}${ctx.path}${
isMakePayment || isEnableAutoRenew ? '' : `/?plan=${plan}`
}`,
return_url: `${config.urls.web}${ctx.path}/?plan=${plan}`,
brand_name: 'Forward Email',
shipping_preference: 'NO_SHIPPING',
user_action: 'PAY_NOW'
},
payer: {
email_address: ctx.state.user.email
},
purchase_units: [
{
reference_id: ctx.state.user.id,
description,
custom_id: sku,
invoice_id: reference,
soft_descriptor: sku,
amount: {
currency_code: 'USD',
value: price,
breakdown: {
item_total: {
currency_code: 'USD',
value: price
}
}
},
items: [
{
name,
description,
sku,
unit_amount: {
currency_code: 'USD',
value: price
},
quantity: '1',
category: 'DIGITAL_GOODS'
}
]
}
]
};
A Stripe-hoz hasonlóan megadjuk return_url
és cancel_url
paraméterek a fizetés utáni átirányítások kezelésére. Amikor a PayPal visszairányítja a felhasználót az oldalunkra, rögzíthetjük a fizetési adatokat, és frissíthetjük adatbázisunkat.
sequenceDiagram
participant User
participant FE as Forward Email
participant Stripe
participant PayPal
participant DB as Database
participant Bree as Bree Job Scheduler
%% Initial checkout flow
User->>FE: Select plan & payment method
alt Credit Card Payment
FE->>Stripe: Create Checkout Session
Stripe-->>FE: Return session URL
FE->>User: Redirect to Stripe Checkout
User->>Stripe: Complete payment
Stripe->>User: Redirect to success URL with session_id
User->>FE: Return to success page
FE->>Stripe: Verify session using session_id
Stripe-->>FE: Return session details
FE->>DB: Update user plan & payment status
else PayPal Payment
FE->>PayPal: Create Order
PayPal-->>FE: Return approval URL
FE->>User: Redirect to PayPal
User->>PayPal: Approve payment
PayPal->>User: Redirect to return URL
User->>FE: Return to success page
FE->>PayPal: Capture payment
PayPal-->>FE: Return payment details
FE->>DB: Update user plan & payment status
end
%% Webhook flow (asynchronous)
Note over Stripe,PayPal: Payment events occur (async)
alt Stripe Webhook
Stripe->>FE: Send event notification
FE->>FE: Verify webhook signature
FE->>DB: Process event & update data
FE-->>Stripe: Acknowledge receipt (200 OK)
else PayPal Webhook
PayPal->>FE: Send event notification
FE->>FE: Verify webhook signature
FE->>DB: Process event & update data
FE-->>PayPal: Acknowledge receipt (200 OK)
end
%% Bree automated jobs
Note over Bree: Scheduled jobs run periodically
Bree->>Stripe: Get all customers & subscriptions
Stripe-->>Bree: Return customer data
Bree->>DB: Compare & reconcile data
Bree->>PayPal: Get all subscriptions & transactions
PayPal-->>Bree: Return subscription data
Bree->>DB: Compare & reconcile data
%% Edge case: Dispute handling
Note over User,PayPal: User disputes a charge
PayPal->>FE: DISPUTE.CREATED webhook
FE->>PayPal: Accept claim automatically
FE->>DB: Update user status
FE->>User: Send notification email
2. réteg: Webhook-kezelők aláírás-ellenőrzéssel
Míg a fizetés utáni átirányítások jól működnek a legtöbb forgatókönyv esetén, nem biztonságosak. A felhasználók bezárhatják böngészőjüket az átirányítás előtt, vagy hálózati problémák akadályozhatják az átirányítást. Itt jönnek be a webhookok.
A Stripe és a PayPal is biztosít webhook rendszereket, amelyek valós idejű értesítéseket küldenek a fizetési eseményekről. Robusztus webhook-kezelőket vezettünk be, amelyek ellenőrzik ezen értesítések hitelességét, és ennek megfelelően dolgozzák fel őket.
Stripe Webhook megvalósítás
A Stripe webhook-kezelőnk ellenőrzi a bejövő webhook-események aláírását, hogy megbizonyosodjon azok jogszerűségéről:
async function webhook(ctx) {
const sig = ctx.request.get('stripe-signature');
// throw an error if something was wrong
if (!isSANB(sig))
throw Boom.badRequest(ctx.translateError('INVALID_STRIPE_SIGNATURE'));
const event = stripe.webhooks.constructEvent(
ctx.request.rawBody,
sig,
env.STRIPE_ENDPOINT_SECRET
);
// throw an error if something was wrong
if (!event)
throw Boom.badRequest(ctx.translateError('INVALID_STRIPE_SIGNATURE'));
ctx.logger.info('stripe webhook', { event });
// return a response to acknowledge receipt of the event
ctx.body = { received: true };
// run in background
processEvent(ctx, event)
.then()
.catch((err) => {
ctx.logger.fatal(err, { event });
// email admin errors
emailHelper({
template: 'alert',
message: {
to: config.email.message.from,
subject: `Error with Stripe Webhook (Event ID ${event.id})`
},
locals: {
message: `<pre><code>${safeStringify(
parseErr(err),
null,
2
)}</code></pre>`
}
})
.then()
.catch((err) => ctx.logger.fatal(err, { event }));
});
}
A stripe.webhooks.constructEvent
függvény ellenőrzi az aláírást a végponti titkunk segítségével. Ha az aláírás érvényes, akkor aszinkron módon dolgozzuk fel az eseményt, hogy elkerüljük a webhook válasz blokkolását.
PayPal Webhook megvalósítás
Hasonlóképpen, a PayPal webhook kezelője ellenőrzi a bejövő értesítések hitelességét:
async function webhook(ctx) {
const response = await promisify(
paypal.notification.webhookEvent.verify,
paypal.notification.webhookEvent
)(ctx.request.headers, ctx.request.body, env.PAYPAL_WEBHOOK_ID);
// throw an error if something was wrong
if (!_.isObject(response) || response.verification_status !== 'SUCCESS')
throw Boom.badRequest(ctx.translateError('INVALID_PAYPAL_SIGNATURE'));
// return a response to acknowledge receipt of the event
ctx.body = { received: true };
// run in background
processEvent(ctx)
.then()
.catch((err) => {
ctx.logger.fatal(err);
// email admin errors
emailHelper({
template: 'alert',
message: {
to: config.email.message.from,
subject: `Error with PayPal Webhook (Event ID ${ctx.request.body.id})`
},
locals: {
message: `<pre><code>${safeStringify(
parseErr(err),
null,
2
)}</code></pre>`
}
})
.then()
.catch((err) => ctx.logger.fatal(err));
});
}
Mindkét webhook-kezelő ugyanazt a mintát követi: ellenőrizze az aláírást, nyugtázza a kézhezvételt, és aszinkron módon dolgozza fel az eseményt. Ez biztosítja, hogy soha ne maradjunk le egy fizetési eseményről, még akkor sem, ha a fizetés utáni átirányítás sikertelen.
3. réteg: Automatizált munkák Bree-vel
Trifecta-megközelítésünk utolsó rétegét olyan automatizált feladatok alkotják, amelyek időszakonként ellenőrzik és egyeztetik a fizetési adatokat. A Bree-t, a Node.js jobütemezőjét használjuk a jobok rendszeres időközönkénti futtatására.
Előfizetés pontosság-ellenőrzője
Az egyik legfontosabb feladatunk az előfizetés pontosságának ellenőrzője, amely biztosítja, hogy adatbázisunk pontosan tükrözze az előfizetés állapotát a Stripe-ban:
async function mapper(customer) {
// wait a second to prevent rate limitation error
await setTimeout(ms('1s'));
// check for user on our side
let user = await Users.findOne({
[config.userFields.stripeCustomerID]: customer.id
})
.lean()
.exec();
if (!user) return;
if (user.is_banned) return;
// if emails did not match
if (user.email !== customer.email) {
logger.info(
User email ${user.email} did not match customer email ${customer.email} (${customer.id})
);
customer = await stripe.customers.update(customer.id, {
email: user.email
});
logger.info(Updated user email to match ${user.email}
);
}
// check for active subscriptions
const [activeSubscriptions, trialingSubscriptions] = await Promise.all([
stripe.subscriptions.list({
customer: customer.id,
status: 'active'
}),
stripe.subscriptions.list({
customer: customer.id,
status: 'trialing'
})
]);
// Combine active and trialing subscriptions
let subscriptions = [
...activeSubscriptions.data,
...trialingSubscriptions.data
];
// Handle edge case: multiple subscriptions for one user
if (subscriptions.length > 1) {
await logger.error(
new Error(
We may need to refund: User had multiple subscriptions ${user.email} (${customer.id})
)
);
await emailHelper({
template: 'alert',
message: {
to: config.email.message.from,
subject: User had multiple subscriptions ${user.email}
},
locals: {
message: User ${user.email} (${customer.id}) had multiple subscriptions: ${JSON.stringify( subscriptions.map((s) => s.id) )}
}
});
}
}
Ez a munka ellenőrzi az adatbázisunk és a Stripe közötti eltéréseket, például az e-mail-címek nem egyezését vagy több aktív előfizetést. Ha bármilyen problémát talál, naplózza azokat, és riasztásokat küld az adminisztrációs csapatunknak.
PayPal előfizetés szinkronizálása
Hasonló feladatunk van a PayPal előfizetéseknél:
async function syncPayPalSubscriptionPayments() {
const paypalCustomers = await Users.find({
$or: [
{
[config.userFields.paypalSubscriptionID]: { $exists: true, $ne: null }
},
{
[config.userFields.paypalPayerID]: { $exists: true, $ne: null }
}
]
})
// sort by newest customers first
.sort('-created_at')
.lean()
.exec();
await logger.info(
Syncing payments for ${paypalCustomers.length} paypal customers
);
// Process each customer and sync their payments
const errorEmails = await pReduce(
paypalCustomers,
// Implementation details...
);
}
Ezek az automatizált munkák jelentik a végső biztonsági hálónkat, biztosítva, hogy adatbázisunk mindig tükrözze az előfizetések és a fizetések valós állapotát mind a Stripe-on, mind a PayPal-on.
Edge tokok kezelése
Egy robusztus fizetési rendszernek kecsesen kell kezelnie az éles eseteket. Nézzük meg, hogyan kezelünk néhány gyakori forgatókönyvet.
Csalás felderítése és megelőzése
Kifinomult csalásészlelési mechanizmusokat vezettünk be, amelyek automatikusan azonosítják és kezelik a gyanús fizetési tevékenységeket:
case 'charge.failed': {
// Get all failed charges in the last 30 days
const charges = await stripe.charges.list({
customer: event.data.object.customer,
created: {
gte: dayjs().subtract(1, 'month').unix()
}
});
// Filter for declined charges
const filtered = charges.data.filter(
(d) => d.status === 'failed' && d.failure_code === 'card_declined'
);
// if not more than 5 then return early
if (filtered.length < 5) break;
// Check if user has verified domains
const count = await Domains.countDocuments({
members: {
$elemMatch: {
user: user._id,
group: 'admin'
}
},
plan: { $in: ['enhanced_protection', 'team'] },
has_txt_record: true
});
if (!user.is_banned) {
// If no verified domains, ban the user and refund all charges
if (count === 0) {
// Ban the user
user.is_banned = true;
await user.save();
// Refund all successful charges
}
}
}
Ez a kód automatikusan kitiltja azokat a felhasználókat, akiknek több sikertelen terhelése van, és nincs ellenőrzött domain, ami a csalárd tevékenység erős jele.
Vitakezelés
Amikor egy felhasználó vitat egy terhelést, automatikusan elfogadjuk a követelést, és megtesszük a megfelelő lépéseket:
case 'CUSTOMER.DISPUTE.CREATED': {
// accept claim
const agent = await paypalAgent();
await agent
.post(`/v1/customer/disputes/${body.resource.dispute_id}/accept-claim`)
.send({
note: 'Full refund to the customer.'
});
// Find the payment in our database
const payment = await Payments.findOne({ $or });
if (!payment) throw new Error('Payment does not exist');
const user = await Users.findById(payment.user);
if (!user) throw new Error('User did not exist for customer');
// Cancel the user's subscription if they have one
if (isSANB(user[config.userFields.paypalSubscriptionID])) {
try {
const agent = await paypalAgent();
await agent.post(
/v1/billing/subscriptions/${ user[config.userFields.paypalSubscriptionID] }/cancel
);
} catch (err) {
// Handle subscription cancellation errors
}
}
}
Ez a megközelítés minimálisra csökkenti a viták üzleti tevékenységünkre gyakorolt hatását, miközben biztosítja a jó ügyfélélményt.
A kód újrafelhasználása: KISS és DRY elvek
Fizetési rendszerünkben végig betartottuk a KISS (Keep It Simple, Stupid) és DRY (Don't Repeat Yourself) elveket. Íme néhány példa:
-
Megosztott segítő funkciók: Újrahasználható segédfunkciókat hoztunk létre az olyan gyakori feladatokhoz, mint a fizetések szinkronizálása és az e-mailek küldése.
-
Következetes hibakezelés: Mind a Stripe, mind a PayPal webhook kezelője ugyanazt a mintát használja a hibakezeléshez és a rendszergazdai értesítésekhez.
-
Egységes adatbázis-séma: Adatbázissémánkat úgy alakítottuk ki, hogy a Stripe és a PayPal adatait egyaránt befogadja, közös mezőkkel a fizetési állapot, az összeg és a tervadatok számára.
-
Központosított konfiguráció: A fizetéssel kapcsolatos konfiguráció egyetlen fájlban van központosítva, így könnyen frissíthető az árak és a termékinformációk.
graph TD
subgraph "Code Reuse Patterns"
A[Helper Functions] --> B[syncStripePaymentIntent]
A --> C[syncPayPalOrderPaymentByPaymentId]
A --> D[syncPayPalSubscriptionPaymentsByUser]
end
classDef primary fill:blue,stroke:#333,stroke-width:2px;
classDef secondary fill:red,stroke:#333,stroke-width:1px;
class A,P,V primary;
class B,C,D,E,I,L,Q,R,S,W,X,Y,Z secondary;
graph TD
subgraph "Code Reuse Patterns"
E[Error Handling] --> F[Common Error Logging]
E --> G[Admin Email Notifications]
E --> H[User Notifications]
end
classDef primary fill:blue,stroke:#333,stroke-width:2px;
classDef secondary fill:red,stroke:#333,stroke-width:1px;
class A,P,V primary;
class B,C,D,E,I,L,Q,R,S,W,X,Y,Z secondary;
graph TD
subgraph "Code Reuse Patterns"
I[Configuration] --> J[Centralized Payment Config]
I --> K[Shared Environment Variables]
end
classDef primary fill:blue,stroke:#333,stroke-width:2px;
classDef secondary fill:red,stroke:#333,stroke-width:1px;
class A,P,V primary;
class B,C,D,E,I,L,Q,R,S,W,X,Y,Z secondary;
graph TD
subgraph "Code Reuse Patterns"
L[Webhook Processing] --> M[Signature Verification]
L --> N[Async Event Processing]
L --> O[Background Processing]
end
classDef primary fill:blue,stroke:#333,stroke-width:2px;
classDef secondary fill:red,stroke:#333,stroke-width:1px;
class A,P,V primary;
class B,C,D,E,I,L,Q,R,S,W,X,Y,Z secondary;
graph TD
subgraph "KISS Principle"
P[Simple Data Flow] --> Q[Unidirectional Updates]
P --> R[Clear Responsibility Separation]
S[Explicit Error Handling] --> T[No Silent Failures]
S --> U[Comprehensive Logging]
end
classDef primary fill:blue,stroke:#333,stroke-width:2px;
classDef secondary fill:red,stroke:#333,stroke-width:1px;
class A,P,V primary;
class B,C,D,E,I,L,Q,R,S,W,X,Y,Z secondary;
graph TD
subgraph "DRY Principle"
V[Shared Logic] --> W[Payment Processing Functions]
V --> X[Email Templates]
V --> Y[Validation Logic]
Z[Common Database Operations] --> AA[User Updates]
Z --> AB[Payment Recording]
end
classDef primary fill:blue,stroke:#333,stroke-width:2px;
classDef secondary fill:red,stroke:#333,stroke-width:1px;
class A,P,V primary;
class B,C,D,E,I,L,Q,R,S,W,X,Y,Z secondary;
VISA előfizetési követelmények végrehajtása
A trifecta megközelítésen kívül speciális funkciókat is bevezettünk, hogy megfeleljenek a VISA előfizetési követelményeinek, miközben javítjuk a felhasználói élményt. A VISA egyik kulcsfontosságú követelménye, hogy a felhasználókat értesíteni kell az előfizetés díjának felszámítása előtt, különösen, ha próbaidőszakról fizetős előfizetésre váltanak.
Automatizált megújítás előtti e-mail értesítések
Kiépítettünk egy automatizált rendszert, amely azonosítja az aktív próba-előfizetéssel rendelkező felhasználókat, és értesítő e-mailt küld nekik az első terhelés előtt. Ez nemcsak a VISA-követelményeknek való megfelelést biztosítja, hanem csökkenti a visszaterheléseket és javítja az ügyfelek elégedettségét.
A funkciót a következőképpen valósítottuk meg:
// Find users with trial subscriptions who haven't received a notification yet
const users = await Users.find({
$or: [
{
$and: [
{ [config.userFields.stripeSubscriptionID]: { $exists: true } },
{ [config.userFields.stripeTrialSentAt]: { $exists: false } },
// Exclude subscriptions that have already had payments
...(paidStripeSubscriptionIds.length > 0
? [
{
[config.userFields.stripeSubscriptionID]: {
$nin: paidStripeSubscriptionIds
}
}
]
: [])
]
},
{
$and: [
{ [config.userFields.paypalSubscriptionID]: { $exists: true } },
{ [config.userFields.paypalTrialSentAt]: { $exists: false } },
// Exclude subscriptions that have already had payments
...(paidPayPalSubscriptionIds.length > 0
? [
{
[config.userFields.paypalSubscriptionID]: {
$nin: paidPayPalSubscriptionIds
}
}
]
: [])
]
}
]
});
// Process each user and send notification
for (const user of users) {
// Get subscription details from payment processor
const subscription = await getSubscriptionDetails(user);
// Calculate subscription duration and frequency
const duration = getDurationFromPlanId(subscription.plan_id);
const frequency = getHumanReadableFrequency(duration, user.locale);
const amount = getPlanAmount(user.plan, duration);
// Get user's domains for personalized email
const domains = await Domains.find({
'members.user': user._id
}).sort('name').lean().exec();
// Send VISA-compliant notification email
await emailHelper({
template: 'visa-trial-subscription-requirement',
message: {
to: user.receipt_email || user.email,
...(user.receipt_email ? { cc: user.email } : {})
},
locals: {
user,
firstChargeDate: new Date(subscription.start_time),
frequency,
formattedAmount: numeral(amount).format('$0,0,0.00'),
domains
}
});
// Record that notification was sent
await Users.findByIdAndUpdate(user._id, {
$set: {
[config.userFields.paypalTrialSentAt]: new Date()
}
});
}
Ez a megvalósítás biztosítja, hogy a felhasználók mindig tájékozódjanak a közelgő díjakról, egyértelmű részletekkel a következőkről:
- Amikor az első töltés megtörténik
- A jövőbeni díjak gyakorisága (havi, éves stb.)
- A pontos összeg, amelyet felszámítanak
- Mely domainekre vonatkozik az előfizetésük
Ennek a folyamatnak az automatizálásával fenntartjuk a VISA követelményeinek való tökéletes megfelelést (amelyek legalább 7 nappal a terhelés előtt értesítik), miközben csökkentjük a támogatási kéréseket és javítjuk az általános felhasználói élményt.
Edge tokok kezelése
Megvalósításunk robusztus hibakezelést is tartalmaz. Ha az értesítési folyamat során bármi elromlik, rendszerünk automatikusan figyelmezteti csapatunkat:
try {
await mapper(user);
} catch (err) {
logger.error(err);
// Send alert to administrators
await emailHelper({
template: 'alert',
message: {
to: config.email.message.from,
subject: 'VISA Trial Subscription Requirement Error'
},
locals: {
message: <pre><code>${safeStringify( parseErr(err), null, 2 )}</code></pre>
}
});
}
Ez biztosítja, hogy még akkor is, ha probléma adódik az értesítési rendszerrel, csapatunk gyorsan meg tudja oldani azt, és fenntartani tudja a VISA követelményeinek való megfelelést.
A VISA-előfizetés értesítési rendszere egy másik példa arra, hogyan építettük ki fizetési infrastruktúránkat a megfelelőség és a felhasználói élmény szem előtt tartásával, kiegészítve trifecta megközelítésünket a megbízható, átlátható fizetési feldolgozás érdekében.
Próbaidőszakok és előfizetési feltételek
Azon felhasználók esetében, akik engedélyezik az automatikus megújítást a meglévő csomagoknál, kiszámítjuk a megfelelő próbaidőszakot annak biztosítására, hogy ne számítsanak fel díjat az aktuális csomag lejártáig:
if (
isEnableAutoRenew &&
dayjs(ctx.state.user[config.userFields.planExpiresAt]).isAfter(
dayjs()
)
) {
const hours = dayjs(
ctx.state.user[config.userFields.planExpiresAt]
).diff(dayjs(), 'hours');
// Handle trial period calculation
}
Világos tájékoztatást adunk az előfizetési feltételekről is, beleértve a számlázási gyakoriságot és a lemondási szabályokat, és minden egyes előfizetéshez mellékeljük a részletes metaadatokat a megfelelő nyomon követés és kezelés biztosítása érdekében.
Következtetés: Trifecta-megközelítésünk előnyei
A fizetésfeldolgozás trifecta megközelítése számos kulcsfontosságú előnnyel jár:
-
Megbízhatóság: A fizetés-ellenőrzés háromszintű megvalósításával biztosítjuk, hogy egyetlen fizetés se maradjon el vagy hibásan dolgozzon fel.
-
Pontosság: Adatbázisunk mindig tükrözi az előfizetések és fizetések valós állapotát mind a Stripe-on, mind a PayPal-on.
-
Rugalmasság: A felhasználók kiválaszthatják a kívánt fizetési módot anélkül, hogy veszélyeztetnék rendszerünk megbízhatóságát.
-
Robusztusság: Rendszerünk kecsesen kezeli az éles eseteket, a hálózati hibáktól a csaló tevékenységekig.
Ha több processzort támogató fizetési rendszert valósít meg, akkor erősen ajánljuk ezt a trifecta megközelítést. Több előzetes fejlesztési erőfeszítést igényel, de a megbízhatóság és a pontosság hosszú távú előnyei megérik.
Az e-mail továbbításról és az adatvédelemre összpontosító e-mail szolgáltatásainkról további információért látogassa meg oldalunkat weboldal.