The Trifecta Approach: How Forward Email Built a Bulletproof Payment System with Stripe and PayPal
Learn how our development team integrated both Stripe and PayPal using a trifecta approach that ensures 1:1 real-time accuracy across our entire system.
- Stránka vyhledávání
- Obsah
Úvodní slovo
Ve Forward Email jsme vždy upřednostňovali vytváření systémů, které jsou spolehlivé, přesné a uživatelsky přívětivé. Když došlo na implementaci našeho systému zpracování plateb, věděli jsme, že potřebujeme řešení, které zvládne více zpracovatelů plateb a zároveň zachová perfektní konzistenci dat. Tento blogový příspěvek podrobně popisuje, jak náš vývojový tým integroval Stripe i PayPal pomocí přístupu trifecta, který zajišťuje přesnost 1:1 v reálném čase v celém našem systému.
Výzva: Více zpracovatelů plateb, jeden zdroj pravdy
Jako e-mailová služba zaměřená na soukromí jsme chtěli našim uživatelům poskytnout platební možnosti. Někteří preferují jednoduchost plateb kreditní kartou prostřednictvím Stripe, zatímco jiní oceňují další vrstvu oddělení, kterou PayPal poskytuje. Podpora více zpracovatelů plateb však přináší značnou složitost:
- Jak zajistíme konzistentní data napříč různými platebními systémy?
- Jak řešíme okrajové případy, jako jsou spory, refundace nebo neúspěšné platby?
- Jak udržujeme jediný zdroj pravdy v naší databázi?
Naším řešením bylo implementovat to, co nazýváme „trifecta approach“ – třívrstvý systém, který poskytuje redundanci a zajišťuje konzistenci dat bez ohledu na to, co se stane.
Přístup Trifecta: Tři vrstvy spolehlivosti
Náš platební systém se skládá ze tří kritických komponent, které spolupracují na zajištění dokonalé synchronizace dat:
- Přesměrování po pokladně - Zachycení platebních údajů ihned po zaplacení
- Obslužné nástroje pro webhooky - Zpracování událostí v reálném čase od zpracovatelů plateb
- Automatizované úlohy - Pravidelné ověřování a odsouhlasování platebních údajů
Pojďme se ponořit do jednotlivých komponent a podívat se, jak spolu fungují.
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;
Vrstva 1: Přesměrování po pokladně
První vrstva našeho přístupu trifecta nastává okamžitě poté, co uživatel dokončí platbu. Stripe i PayPal poskytují mechanismy pro přesměrování uživatelů zpět na naše stránky s informacemi o transakcích.
Implementace Stripe Checkout
Pro Stripe používáme jejich rozhraní Checkout Sessions API k vytvoření bezproblémového platebního prostředí. Když si uživatel vybere plán a rozhodne se zaplatit kreditní kartou, vytvoříme relaci pokladny s konkrétním úspěchem a zrušíme adresy URL:
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 };
}
Kritická část je zde success_url
parametr, který zahrnuje session_id
jako parametr dotazu. Když Stripe po úspěšné platbě přesměruje uživatele zpět na naše stránky, můžeme toto ID relace použít k ověření transakce a odpovídající aktualizaci naší databáze.
Tok plateb PayPal
Pro PayPal používáme podobný přístup s jejich Orders API:
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'
}
]
}
]
};
Podobně jako u Stripe specifikujeme return_url
a cancel_url
parametry pro zpracování přesměrování po platbě. Když PayPal přesměruje uživatele zpět na naše stránky, můžeme zachytit platební údaje a aktualizovat naši databázi.
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
Vrstva 2: Webhook Handlers s ověřením podpisu
I když přesměrování po pokladně funguje dobře pro většinu scénářů, nejsou spolehlivá. Uživatelé mohou před přesměrováním zavřít svůj prohlížeč nebo v dokončení přesměrování mohou zabránit problémy se sítí. Zde přichází na řadu webhooky.
Stripe i PayPal poskytují webhook systémy, které odesílají oznámení o platebních událostech v reálném čase. Implementovali jsme robustní obslužné nástroje webhooku, které ověřují pravost těchto oznámení a podle toho je zpracovávají.
Implementace Stripe Webhooku
Náš obslužný program webhooku Stripe ověřuje podpis příchozích událostí webhooku, aby se ujistil, že jsou legitimní:
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 }));
});
}
The stripe.webhooks.constructEvent
funkce ověří podpis pomocí našeho tajného klíče koncového bodu. Pokud je podpis platný, zpracujeme událost asynchronně, abychom zabránili zablokování odpovědi webhooku.
Implementace PayPal Webhooku
Podobně náš webhook pro PayPal ověřuje pravost příchozích oznámení:
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));
});
}
Oba obslužné nástroje webhooku se řídí stejným vzorem: ověřte podpis, potvrďte přijetí a zpracujte událost asynchronně. To zajišťuje, že nikdy nezmeškáme žádnou platební událost, i když se přesměrování po pokladně nezdaří.
Vrstva 3: Automatizované úlohy s Bree
Poslední vrstvou našeho přístupu trifecta je sada automatizovaných úloh, které pravidelně ověřují a slaďují platební údaje. Ke spouštění těchto úloh v pravidelných intervalech používáme Bree, plánovač úloh pro Node.js.
Kontrola přesnosti předplatného
Jednou z našich klíčových úloh je kontrola přesnosti předplatného, která zajišťuje, že naše databáze přesně odráží stav předplatného v Stripe:
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) )}
}
});
}
}
Tato úloha kontroluje nesrovnalosti mezi naší databází a Stripe, jako jsou například neshodné e-mailové adresy nebo více aktivních odběrů. Pokud zjistí nějaké problémy, zaznamená je a zašle upozornění našemu týmu administrátorů.
Synchronizace předplatného PayPal
Máme podobnou práci pro předplatné PayPal:
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...
);
}
Tyto automatizované úlohy slouží jako naše konečná záchranná síť, která zajišťuje, že naše databáze vždy odráží skutečný stav předplatného a plateb v Stripe i PayPal.
Manipulace s pouzdry Edge
Robustní platební systém musí zvládat hraniční případy elegantně. Podívejme se, jak řešíme některé běžné scénáře.
Odhalování a prevence podvodů
Implementovali jsme sofistikované mechanismy odhalování podvodů, které automaticky identifikují a zpracovávají podezřelé platební aktivity:
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
}
}
}
Tento kód automaticky zablokuje uživatele, kteří mají více neúspěšných poplatků a nemají ověřené domény, což je silný indikátor podvodné činnosti.
Řešení sporů
Když uživatel zpochybní platbu, automaticky přijmeme nárok a podnikneme příslušné kroky:
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
}
}
}
Tento přístup minimalizuje dopad sporů na naše podnikání a zároveň zajišťuje dobrou zákaznickou zkušenost.
Opětovné použití kódu: Principy KISS a DRY
V celém našem platebním systému jsme se drželi zásad KISS (Keep It Simple, Stupid) a DRY (Don't Repeat Yourself). Zde je několik příkladů:
-
Sdílené pomocné funkce: Vytvořili jsme opakovaně použitelné pomocné funkce pro běžné úkoly, jako je synchronizace plateb a odesílání e-mailů.
-
Konzistentní zpracování chyb: Obsluha webhooku Stripe i PayPal používají stejný vzor pro zpracování chyb a upozornění pro správce.
-
Jednotné schéma databáze: Naše schéma databáze je navrženo tak, aby obsahovalo data Stripe i PayPal, se společnými poli pro stav platby, částku a informace o plánu.
-
Centralizovaná konfigurace: Konfigurace související s platbami je centralizovaná v jediném souboru, což usnadňuje aktualizaci cen a informací o produktech.
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;
Implementace požadavků na předplatné VISA
Kromě našeho přístupu trifecta jsme implementovali specifické funkce, abychom vyhověli požadavkům na předplatné VISA a zároveň zlepšili uživatelskou zkušenost. Jedním z klíčových požadavků společnosti VISA je, že uživatelé musí být informováni dříve, než jim bude účtováno předplatné, zejména při přechodu ze zkušebního na placené předplatné.
Automatická e-mailová upozornění před obnovením
Vybudovali jsme automatický systém, který identifikuje uživatele s aktivním zkušebním odběrem a pošle jim e-mail s upozorněním, než dojde k prvnímu poplatku. To nám nejen udržuje shodu s požadavky VISA, ale také snižuje zpětné zúčtování a zvyšuje spokojenost zákazníků.
Tuto funkci jsme implementovali takto:
// 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()
}
});
}
Tato implementace zajišťuje, že uživatelé jsou vždy informováni o nadcházejících poplatcích s jasnými podrobnostmi o:
- Kdy dojde k prvnímu nabití
- Frekvence budoucích poplatků (měsíční, roční atd.)
- Přesná částka, kterou jim budou účtovány
- Které domény jsou pokryty jejich předplatným
Automatizací tohoto procesu udržujeme dokonalý soulad s požadavky společnosti VISA (které nařizují oznámení nejméně 7 dní před zpoplatněním), přičemž snižujeme požadavky na podporu a zlepšujeme celkovou uživatelskou zkušenost.
Manipulace s pouzdry Edge
Naše implementace také zahrnuje robustní zpracování chyb. Pokud se během procesu oznámení něco pokazí, náš systém automaticky upozorní náš tým:
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>
}
});
}
Tím je zajištěno, že i když dojde k problému s oznamovacím systémem, náš tým jej dokáže rychle vyřešit a zajistit soulad s požadavky společnosti VISA.
Oznamovací systém předplatného VISA je dalším příkladem toho, jak jsme vybudovali naši platební infrastrukturu s ohledem na dodržování předpisů i na uživatelskou zkušenost a doplňuje náš přístup trifecta k zajištění spolehlivého a transparentního zpracování plateb.
Zkušební období a podmínky předplatného
Pro uživatele, kteří povolí automatické obnovení stávajících plánů, vypočítáme vhodné zkušební období, abychom zajistili, že jim nebudou účtovány poplatky, dokud nevyprší jejich aktuální plán:
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
}
Poskytujeme také jasné informace o podmínkách předplatného, včetně frekvence fakturace a zásad zrušení, a ke každému předplatnému uvádíme podrobná metadata, abychom zajistili správné sledování a správu.
Závěr: Výhody našeho přístupu Trifecta
Náš přístup trifecta ke zpracování plateb poskytuje několik klíčových výhod:
-
Spolehlivost: Zavedením tří vrstev ověřování plateb zajišťujeme, že žádná platba nebude zmeškána nebo nesprávně zpracována.
-
Přesnost: Naše databáze vždy odráží skutečný stav předplatného a plateb v Stripe i PayPal.
-
Flexibilita: Uživatelé si mohou vybrat preferovaný způsob platby, aniž by tím byla ohrožena spolehlivost našeho systému.
-
Robustnost: Náš systém elegantně řeší okrajové případy, od selhání sítě po podvodné aktivity.
Pokud implementujete platební systém, který podporuje více procesorů, důrazně doporučujeme tento přístup trifecta. Vyžaduje to více počátečního vývoje, ale dlouhodobé výhody, pokud jde o spolehlivost a přesnost, za to stojí.
Pro více informací o Forward Email a našich e-mailových službách zaměřených na soukromí navštivte naše webové stránky.