How to get Product and Quantity information from payment_intent.succeeded event via Webhooks in Stripe? - node.js

Here is how I'm initializing the Checkout Process in Node.js.
let email = req.body.email
let product_id = req.body.product_id
let YOUR_DOMAIN = 'http://localhost:8000'
const session = await stripe.checkout.sessions.create({
line_items: [{
price: product_id,
adjustable_quantity: {
enabled: true,
minimum: 1,
maximum: 5,
},
quantity: 1,
}],
customer_email: email,
mode: 'payment',
success_url: `${YOUR_DOMAIN}/payment-success`,
cancel_url: `${YOUR_DOMAIN}/payment-failure`,
});
Here are the events I have subscribed to in Webhooks
payment_intent.succeeded
checkout.session.completed
Object Received in Payment Intent Succeeded
{
"id": "evt_1KEqIxI5cib7rtVMoKMkak96",
"object": "event",
"api_version": "2020-03-02",
"created": 1641453754,
"data": {
"object": {
"id": "pi_1KEqIeI5cib7rtVMcz8zsXys",
"object": "payment_intent",
"amount": 59700,
"amount_capturable": 0,
"amount_received": 59700,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"object": "list",
"data": [{
"id": "ch_1KEqIvI5cib7rtVMYUVn5gfQ",
"object": "charge",
"amount": 59700,
"amount_captured": 59700,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_1KEqIwI5cib7rtVMGfJlEahg",
"billing_details": {
"address": {
"city": "NY",
"country": "US",
"line1": "sada",
"line2": "asd",
"postal_code": "10001",
"state": "NY"
},
"email": "test#gmail.com",
"name": "John Doe",
"phone": null
},
"calculated_statement_descriptor": "VIDEOFORM",
"captured": true,
"created": 1641453753,
"currency": "usd",
"customer": "cus_KufeYc1hifvZGA",
"description": null,
"destination": null,
"dispute": null,
"disputed": false,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
"invoice": null,
"livemode": false,
"metadata": {},
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 25,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_1KEqIeI5cib7rtVMcz8zsXys",
"payment_method": "pm_1KEqIuI5cib7rtVMYPWlLV8S",
"payment_method_details": {
"card": {
"brand": "visa",
"checks": {
"address_line1_check": "pass",
"address_postal_code_check": "pass",
"cvc_check": "pass"
},
"country": "US",
"exp_month": 1,
"exp_year": 2023,
"fingerprint": "QaiTi5PATFO8ZoC3",
"funding": "credit",
"installments": null,
"last4": "4242",
"mandate": null,
"network": "visa",
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"receipt_email": null,
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/acct_1GhUSuI5cib7rtVM/ch_1KEqIvI5cib7rtVMYUVn5gfQ/rcpt_KufeysPW3LD8Yu47q14iYOIIlcGth9h",
"refunded": false,
"refunds": {
"object": "list",
"data": [],
"has_more": false,
"total_count": 0,
"url": "/v1/charges/ch_1KEqIvI5cib7rtVMYUVn5gfQ/refunds"
},
"review": null,
"shipping": null,
"source": null,
"source_transfer": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}],
"has_more": false,
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_1KEqIeI5cib7rtVMcz8zsXys"
},
"client_secret": "pi_1KEqIeI5cib7rtVMcz8zsXys_secret_uZwm3uziwfaWePgTHENYXeraD",
"confirmation_method": "automatic",
"created": 1641453736,
"currency": "usd",
"customer": "cus_KufeYc1hifvZGA",
"description": null,
"invoice": null,
"last_payment_error": null,
"livemode": false,
"metadata": {},
"next_action": null,
"on_behalf_of": null,
"payment_method": "pm_1KEqIuI5cib7rtVMYPWlLV8S",
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": ["card"],
"processing": null,
"receipt_email": null,
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
},
"livemode": false,
"pending_webhooks": 3,
"request": {
"id": "req_Y87umQuitz9WCc",
"idempotency_key": "a4b6af76-73d8-49b4-8393-9017e673af7e"
},
"type": "payment_intent.succeeded"
}
Object Received in Checkout Session Completed
{
"id": "evt_1KEqQOI5cib7rtVMRwAHWDvV",
"object": "event",
"api_version": "2020-03-02",
"created": 1641454214,
"data": {
"object": {
"id": "cs_test_a1AQ9EKIaxi8dXFAJzZ86xYm5meRkWb9Vu0eLowfkiKgUO4cyToooBzSTd",
"object": "checkout.session",
"after_expiration": null,
"allow_promotion_codes": null,
"amount_subtotal": 59700,
"amount_total": 59700,
"automatic_tax": {
"enabled": false,
"status": null
},
"billing_address_collection": null,
"cancel_url": "http://localhost:8000/payment-failure",
"client_reference_id": null,
"consent": null,
"consent_collection": null,
"currency": "usd",
"customer": "cus_Kuflo6N0n4HTzL",
"customer_details": {
"email": "test#gmail.com",
"phone": null,
"tax_exempt": "none",
"tax_ids": []
},
"customer_email": "test#gmail.com",
"expires_at": 1641540596,
"livemode": false,
"locale": null,
"mode": "payment",
"payment_intent": "pi_1KEqQ4I5cib7rtVMyND6NhQG",
"payment_method_options": {},
"payment_method_types": ["card"],
"payment_status": "paid",
"phone_number_collection": {
"enabled": false
},
"recovered_from": null,
"setup_intent": null,
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
"shipping_rate": null,
"status": "complete",
"submit_type": null,
"subscription": null,
"success_url": "http://localhost:8000/payment-success",
"total_details": {
"amount_discount": 0,
"amount_shipping": 0,
"amount_tax": 0
},
"url": null
}
},
"livemode": false,
"pending_webhooks": 4,
"request": {
"id": null,
"idempotency_key": null
},
"type": "checkout.session.completed"
}
I'm unable to figure out how to get the Product ID and the Quantity. Could anyone suggest the right approach here?

You can find the Product and the quantity from the Checkout Session by using expand on the line_items property. The code should look like this.
// In "checkout.session.completed" event handler, get the ID of the Checkout Session
const id = event.data.object.id; // "cs_xxx"
// Retrieve the Checkout Session with expand
const session = await stripe.checkout.sessions.retrieve(id, {
expand: [ "line_items" ]
});
// Get the quantity
console.log(session.line_items.data[0].quantity);
// Get the product ID
console.log(session.line_items.data[0].price.product);

Related

Creating Custom Account for Stripe

I tried creating Stripe Custom account on test mode
Request
const createAccountTeacher = catchAsync(async (req, res) =>
{
const date = new Date();
let time = Math.floor(date.getTime() / 1000);
const email = req.body.email;
const country = req.body.country;
const createAccount = await stripe.accounts.create({
type: 'custom',
country: country,
business_type: 'individual',
tos_acceptance: {
date: time,
ip: '8.8.8.8',
},
business_profile: {
mcc: '5734',
url: 'http://google.com',
product_description: 'Good Product',
support_phone: '12345567',
product_description: 'Teaching Courses available',
support_phone: '+10000000000',
},
individual: {
first_name: 'ABC',
last_name: 'XYZ',
dob: {
day: 24,
month: 6,
year: 1992,
},
address: {
line1: '1996 W Highland Ave',
postal_code: 90002,
city: ' San Bernardino',
state: 'California',
},
email: email,
phone: '+1202-555-0454',
ssn_last_4: 9999,
id_number: 123459999,
},
external_account: {
object: 'bank_account',
country: 'US',
currency: 'usd',
account_number: '000123456789',
routing_number: 121000358,
},
capabilities: {
card_payments: { requested: true },
transfers: { requested: true },
},
});
if (createAccount) {
res.status(200).json({ data: createAccount });
}
});
Here as response
{
"data":
{
"details_submitted": true,
"type": "custom",
"metadata": {},
"id": "acct_1LNcOTD4Ev4rC234",
"object": "account",
"default_currency": "usd",
"capabilities": {
"transfers": "pending",
"card_payments": "pending"
},
"business_type": "individual",
"individual": {
"metadata": {},
"id": "person_1LNcOVD4Ev4rC234OQjvuHiP",
"object": "person",
"account": "acct_1LNcOTD4Ev4rC234",
"dob": {
"year": 1992,
"day": 24,
"month": 6
},
"requirements": {
"currently_due": [],
"past_due": [],
"eventually_due": [],
"pending_verification": [
"id_number",
"verification.document"
],
"errors": [],
"alternatives": []
},
"ssn_last_4_provided": true,
"phone": "+12025550454",
"relationship": {
"percent_ownership": null,
"title": null,
"owner": false,
"director": false,
"representative": true,
"executive": false
},
"future_requirements": {
"currently_due": [],
"past_due": [],
"eventually_due": [],
"pending_verification": [],
"errors": [],
"alternatives": []
},
"verification": {
"status": "pending",
"details": null,
"document": {
"details_code": null,
"front": null,
"details": null,
"back": null
},
"additional_document": {
"details_code": null,
"front": null,
"details": null,
"back": null
},
"details_code": null
},
"address": {
"line2": null,
"line1": "1996 W Highland Ave",
"state": "California",
"postal_code": "90002",
"city": " San Bernardino",
"country": "US"
},
"email": "lilypota#ema-sofia.eu",
"created": 1658321573,
"first_name": "ABC",
"id_number_provided": true,
"last_name": "XYZ"
},
"charges_enabled": false,
"settings": {
"dashboard": {
"display_name": "Google",
"timezone": "Etc/UTC"
},
"payouts": {
"debit_negative_balances": false,
"statement_descriptor": null,
"schedule": {
"interval": "daily",
"delay_days": 2
}
},
"card_issuing": {
"tos_acceptance": {
"ip": null,
"date": null
}
},
"bacs_debit_payments": {},
"payments": {
"statement_descriptor_kanji": null,
"statement_descriptor_kana": null,
"statement_descriptor": "GOOGLE.COM"
},
"sepa_debit_payments": {},
"card_payments": {
"statement_descriptor_prefix_kanji": null,
"statement_descriptor_prefix": null,
"statement_descriptor_prefix_kana": null,
"decline_on": {
"avs_failure": false,
"cvc_failure": false
}
},
"branding": {
"icon": null,
"secondary_color": null,
"logo": null,
"primary_color": null
}
},
"tos_acceptance": {
"ip": "8.8.8.8",
"user_agent": null,
"date": 1658321567
},
"requirements": {
"current_deadline": null,
"past_due": [],
"errors": [],
"disabled_reason": "requirements.pending_verification",
"pending_verification": [
"individual.id_number",
"individual.verification.document"
],
"currently_due": [],
"eventually_due": [],
"alternatives": []
},
"payouts_enabled": false,
"company": {
"tax_id_provided": false,
"phone": "+12025550454",
"owners_provided": true,
"verification": {
"document": {
"details_code": null,
"front": null,
"details": null,
"back": null
}
},
"address": {
"line2": null,
"line1": "1996 W Highland Ave",
"state": "California",
"postal_code": "90002",
"city": " San Bernardino",
"country": "US"
},
"executives_provided": true,
"directors_provided": true,
"name": null
},
"external_accounts": {
"has_more": false,
"total_count": 1,
"object": "list",
"url": "/v1/accounts/acct_1LNcOTD4Ev4rC234/external_accounts",
"data": [
{
"last4": "6789",
"account_holder_name": null,
"metadata": {},
"id": "ba_1LNcOUD4Ev4rC234XwzzfiqR",
"object": "bank_account",
"account_type": null,
"default_for_currency": true,
"account_holder_type": null,
"account": "acct_1LNcOTD4Ev4rC234",
"status": "new",
"available_payout_methods": [
"standard"
],
"bank_name": "BANK OF AMERICA, N.A.",
"currency": "usd",
"country": "US",
"routing_number": "121000358",
"fingerprint": "gqPBt6FUMZJkqc9q"
}
]
},
"future_requirements": {
"current_deadline": null,
"past_due": [],
"errors": [],
"disabled_reason": null,
"pending_verification": [],
"currently_due": [],
"eventually_due": [],
"alternatives": []
},
"country": "US",
"email": null,
"created": 1658321576,
"business_profile": {
"support_email": null,
"product_description": "Teaching Courses available",
"mcc": "5734",
"support_url": null,
"support_address": null,
"url": "http://google.com",
"support_phone": "+10000000000",
"name": null
}
}
}
Custom Account is created but the problem it is restricted because of identity document
I am trying to upload the document like this
const updateAccount = catchAsync(async (req, res) => {
// let imagepath = ${req.protocol}://${req.get('host')}/uploads/${req.file.filename};
if(req.file.path){
const file = await stripe.files.create({
purpose: 'identity_document',
file: {
data: fs.readFileSync(req.file.path),
name: req.file.filename,
type: 'application/octet-stream',
},
}, {
stripeAccount: 'acct_1LNcOTD4Ev4rC234',
});
if(file){
res.status(200).json({data:file})
}
}
})
Still the custom account is restricted.
I would appreciate little help.

Stripe events: How to capture the product that was successfully paid with Stripe events and a webhook?

My website's users will buy credits using Stripe's single-payment workflow with a checkout session. They can buy single credits or credit packages, like 10 credits package at discount price.
After a successfully payment I want to capture the event with a webhook and update the user balance according to the product and quantity paid by the user.
I'm currently tracking payment_intent.succeeded events and I can see payment and charge information however I can't find any information related to the product ordered. What am I missing? Thank you.
This is an example of the payment intent event I'm capturing:
{
"amount": 5250,
"amount_capturable": 0,
"amount_received": 5250,
"application": null,
"application_fee_amount": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"data": [
{
"amount": 5250,
"amount_captured": 5250,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_1HUSonCfZ37XLQD8mLERR2YR",
"billing_details": {
"address": {
"city": null,
"country": "JP",
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": "xxxx.xxxx.xxxx#gmail.com",
"name": "asd",
"phone": null
},
"calculated_statement_descriptor": "XXX XXXX",
"captured": true,
"created": 1600847712,
"currency": "jpy",
"customer": "cus_I4c27DIUQuvAHQ",
"description": null,
"destination": null,
"dispute": null,
"disputed": false,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
"id": "ch_1HUSomCfZ37XLQD8d133buOK",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "charge",
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 39,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_1HUSoWCfZ37XLQD82vnE1yQT",
"payment_method": "pm_1HUSolCfZ37XLQD8gSn0oy4x",
"payment_method_details": {
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2050,
"fingerprint": "uaJa23vzDgA7fnSC",
"funding": "credit",
"installments": null,
"last4": "4242",
"network": "visa",
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"receipt_email": null,
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/acct_1HU77uCfZ37XLQD8/ch_1HUSomCfZ37XLQD8d133buOK/rcpt_I4c233eGeSYOaN9cPvncC4AcU2Sm4s7",
"refunded": false,
"refunds": {},
"review": null,
"shipping": null,
"source": null,
"source_transfer": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_1HUSoWCfZ37XLQD82vnE1yQT"
},
"client_secret": "pi_1HUSoWCfZ37XLQD82vnE1yQT_secret_XXX",
"confirmation_method": "automatic",
"created": 1600847696,
"currency": "jpy",
"customer": "cus_I4c27DIUQuvAHQ",
"description": null,
"id": "pi_1HUSoWCfZ37XLQD82vnE1yQT",
"invoice": null,
"last_payment_error": null,
"livemode": false,
"metadata": {},
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": "pm_1HUSolCfZ37XLQD8gSn0oy4x",
"payment_method_options": {
"card": {
"installments": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"receipt_email": null,
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
Assuming you've put the product details in the line_items of the Checkout session you created (see guide step), then what you're looking for is how to "fulfill the order" (see guide step), which you can do by listening to checkout.session.completed events.
When you receive an event, the event data will be a Checkout session by id, and you can then retrieve that session and specify expand\[\]=line_items to be able to inspect the line_items for the session.
Update: I modified the above to reflect that line_items is not included by default. You must retrieve the session and include it in an expand.

Nodejs 2nd call on same external API return previous result

I am writing a service In which I have to register an order tracking number to an external source to track my order automatically.
let trackingObj = {
tracking: {
tracking_number: params.trackingNumber,
order_id: params.orderId,
emails: params.email,
smses: params.phoneNumber,
note: params.optIn
}
}
const aftershipTracking = await AftershipService.postTrackingInfo(trackingObj);
postTrackingInfo: async (body) => {
let _URL = helper.env('AFTER_SHIP_API_URL') + 'trackings';
return await ((await fetch(_URL, {
method: 'POST',
body: JSON.stringify(body),
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'aftership-api-key': helper.env('AFTER_SHIP_API_KEY')
},
})).json());
}
When I make the first call to register the tracking number, it returns an immediate default object with tag pending.
This returns me which is expected behavior
{
"meta": {
"code": 201
},
"data": {
"tracking": {
"id": "xyzxyzxyzxyzxyzxyxxyzxyxzxyz",
"created_at": "2020-06-25T14:22:09+00:00",
"updated_at": "2020-06-25T14:22:09+00:00",
"last_updated_at": "2020-06-25T14:22:09+00:00",
"tracking_number": "xxxxxxxxxxxxxxxxxx",
"slug": "fedex",
"active": true,
"android": [],
"custom_fields": null,
"customer_name": null,
"delivery_time": null,
"destination_country_iso3": null,
"courier_destination_country_iso3": null,
"emails": [],
"expected_delivery": null,
"ios": [],
"note": null,
"order_id": null,
"order_id_path": null,
"origin_country_iso3": null,
"shipment_package_count": null,
"shipment_pickup_date": null,
"shipment_delivery_date": null,
"shipment_type": null,
"shipment_weight": null,
"shipment_weight_unit": null,
"signed_by": null,
"smses": [],
"source": "api",
"tag": "Pending",
"subtag": "Pending_001",
"subtag_message": "Pending",
"title": "642028981335",
"tracked_count": 0,
"last_mile_tracking_supported": null,
"language": null,
"unique_token": "deprecated",
"checkpoints": [],
"subscribed_smses": [],
"subscribed_emails": [],
"return_to_sender": false,
"order_promised_delivery_date": null,
"delivery_type": null,
"pickup_location": null,
"pickup_note": null,
"courier_tracking_link": "https://www.fedex.com/apps/fedextrack/?tracknumbers=642028981335&cntry_code=US",
"first_attempted_at": null,
"courier_redirect_link": "https://www.fedex.com/apps/fedextrack/?action=track&tracknumbers=642028981335&cntry_code=US",
"tracking_account_number": null,
"tracking_origin_country": null,
"tracking_destination_country": null,
"tracking_key": null,
"tracking_postal_code": null,
"tracking_ship_date": null,
"tracking_state": null
}
}
}
To get the latest or current tag of a tracking number, I have to make back to back the second request to the same external source with the different endpoint. This has to return the latest information this is what happening in Postman but not happening in Nodejs.
const aftershipTrackingStatus = await AftershipService.getTrackingInfo(aftershipTracking.data.tracking.slug, params.trackingNumber);
getTrackingInfo: async (slug, trackingNumber) => {
let _URL = helper.env('AFTER_SHIP_API_URL') + 'trackings/' + slug + '/' + trackingNumber;
return await ((await fetch(_URL, {
method: 'GET',
body: null,
headers: {
'aftership-api-key': helper.env('AFTER_SHIP_API_KEY')
},
})).json());
},
This again returns me the same result as above in 2nd block which is not expected.
As per Postman, it should return this.
{
"meta": {
"code": 200
},
"data": {
"tracking": {
"id": "xyzxyzxyzxyzxyzxyzxyzxyzxyzxyz",
"created_at": "2020-06-25T06:09:07+00:00",
"updated_at": "2020-06-25T06:09:41+00:00",
"last_updated_at": "2020-06-25T06:09:41+00:00",
"tracking_number": "xxxxxxxxxxxxxxxxx",
"slug": "fedex",
"active": false,
"android": [],
"custom_fields": null,
"customer_name": null,
"delivery_time": 7,
"destination_country_iso3": "USA",
"courier_destination_country_iso3": "USA",
"emails": [],
"expected_delivery": null,
"ios": [],
"note": null,
"order_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"order_id_path": null,
"origin_country_iso3": "USA",
"shipment_package_count": 1,
"shipment_pickup_date": "2020-06-03T06:00:00",
"shipment_delivery_date": "2020-06-09T21:17:56",
"shipment_type": "FedEx Ground",
"shipment_weight": 11.3,
"shipment_weight_unit": "lb",
"signed_by": "Signature not required",
"smses": [],
"source": "api",
"tag": "Delivered",
"subtag": "Delivered_001",
"subtag_message": "Delivered",
"title": "642028981335",
"tracked_count": 1,
"last_mile_tracking_supported": null,
"language": null,
"unique_token": "deprecated",
"checkpoints": [
{
"slug": "fedex",
"city": null,
"created_at": "2020-06-25T06:09:41+00:00",
"location": null,
"country_name": null,
"message": "Shipment information sent to FedEx",
"country_iso3": null,
"tag": "InfoReceived",
"subtag": "InfoReceived_001",
"subtag_message": "Info Received",
"checkpoint_time": "2020-06-03T12:48:00-04:00",
"coordinates": [],
"state": null,
"zip": null,
"raw_tag": "OC"
},
{
"slug": "fedex",
"city": "AUSTELL",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "AUSTELL, GA",
"country_name": null,
"message": "Picked up",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_002",
"subtag_message": "Acceptance scan",
"checkpoint_time": "2020-06-03T15:37:00-04:00",
"coordinates": [],
"state": "GA",
"zip": null,
"raw_tag": "PU"
},
{
"slug": "fedex",
"city": "AUSTELL",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "AUSTELL, GA",
"country_name": null,
"message": "Arrived at FedEx location",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_003",
"subtag_message": "Arrival scan",
"checkpoint_time": "2020-06-03T17:52:00-04:00",
"coordinates": [],
"state": "GA",
"zip": null,
"raw_tag": "AR"
},
{
"slug": "fedex",
"city": "AUSTELL",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "AUSTELL, GA",
"country_name": null,
"message": "Left FedEx origin facility",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_007",
"subtag_message": "Departure Scan",
"checkpoint_time": "2020-06-04T01:13:35-04:00",
"coordinates": [],
"state": "GA",
"zip": null,
"raw_tag": "DP"
},
{
"slug": "fedex",
"city": "POWDER SPRINGS",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "POWDER SPRINGS, GA",
"country_name": null,
"message": "In transit",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"checkpoint_time": "2020-06-04T13:19:27-04:00",
"coordinates": [],
"state": "GA",
"zip": null,
"raw_tag": "IT"
},
{
"slug": "fedex",
"city": "FORREST CITY",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "FORREST CITY, AR",
"country_name": null,
"message": "In transit",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"checkpoint_time": "2020-06-05T00:19:37-05:00",
"coordinates": [],
"state": "AR",
"zip": null,
"raw_tag": "IT"
},
{
"slug": "fedex",
"city": "PANHANDLE",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "PANHANDLE, TX",
"country_name": null,
"message": "In transit",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"checkpoint_time": "2020-06-05T12:21:24-05:00",
"coordinates": [],
"state": "TX",
"zip": null,
"raw_tag": "IT"
},
{
"slug": "fedex",
"city": "WILLIAMS",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "WILLIAMS, AZ",
"country_name": null,
"message": "In transit",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"checkpoint_time": "2020-06-05T22:24:11-07:00",
"coordinates": [],
"state": "AZ",
"zip": null,
"raw_tag": "IT"
},
{
"slug": "fedex",
"city": "TRACY",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "TRACY, CA",
"country_name": null,
"message": "In transit",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"checkpoint_time": "2020-06-06T17:25:49-07:00",
"coordinates": [],
"state": "CA",
"zip": null,
"raw_tag": "IT"
},
{
"slug": "fedex",
"city": "TRACY",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "TRACY, CA",
"country_name": null,
"message": "In transit",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"checkpoint_time": "2020-06-07T06:03:27-07:00",
"coordinates": [],
"state": "CA",
"zip": null,
"raw_tag": "IT"
},
{
"slug": "fedex",
"city": "TRACY",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "TRACY, CA",
"country_name": null,
"message": "In transit",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"checkpoint_time": "2020-06-07T18:03:54-07:00",
"coordinates": [],
"state": "CA",
"zip": null,
"raw_tag": "IT"
},
{
"slug": "fedex",
"city": "TRACY",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "TRACY, CA",
"country_name": null,
"message": "In transit",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_001",
"subtag_message": "In Transit",
"checkpoint_time": "2020-06-08T06:04:14-07:00",
"coordinates": [],
"state": "CA",
"zip": null,
"raw_tag": "IT"
},
{
"slug": "fedex",
"city": "TRACY",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "TRACY, CA",
"country_name": null,
"message": "Arrived at FedEx location",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_003",
"subtag_message": "Arrival scan",
"checkpoint_time": "2020-06-08T21:04:00-07:00",
"coordinates": [],
"state": "CA",
"zip": null,
"raw_tag": "AR"
},
{
"slug": "fedex",
"city": "TRACY",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "TRACY, CA",
"country_name": null,
"message": "Departed FedEx location",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_007",
"subtag_message": "Departure Scan",
"checkpoint_time": "2020-06-08T23:34:27-07:00",
"coordinates": [],
"state": "CA",
"zip": null,
"raw_tag": "DP"
},
{
"slug": "fedex",
"city": "SAN LEANDRO",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "SAN LEANDRO, CA",
"country_name": null,
"message": "At local FedEx facility",
"country_iso3": null,
"tag": "InTransit",
"subtag": "InTransit_007",
"subtag_message": "Departure Scan",
"checkpoint_time": "2020-06-09T04:36:00-07:00",
"coordinates": [],
"state": "CA",
"zip": null,
"raw_tag": "AR"
},
{
"slug": "fedex",
"city": "SAN LEANDRO",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "SAN LEANDRO, CA",
"country_name": null,
"message": "On FedEx vehicle for delivery",
"country_iso3": null,
"tag": "OutForDelivery",
"subtag": "OutForDelivery_001",
"subtag_message": "Out for Delivery",
"checkpoint_time": "2020-06-09T04:48:00-07:00",
"coordinates": [],
"state": "CA",
"zip": null,
"raw_tag": "OD"
},
{
"slug": "fedex",
"city": "Oakland",
"created_at": "2020-06-25T06:09:41+00:00",
"location": "Oakland, CA",
"country_name": null,
"message": "Delivered - Left at front door. Signature Service not requested.",
"country_iso3": null,
"tag": "Delivered",
"subtag": "Delivered_001",
"subtag_message": "Delivered",
"checkpoint_time": "2020-06-09T14:17:56-07:00",
"coordinates": [],
"state": "CA",
"zip": null,
"raw_tag": "DL"
}
],
"subscribed_smses": [],
"subscribed_emails": [],
"return_to_sender": false,
"order_promised_delivery_date": null,
"delivery_type": null,
"pickup_location": null,
"pickup_note": null,
"courier_tracking_link": "https://www.fedex.com/apps/fedextrack/?tracknumbers=642028981335&cntry_code=US",
"first_attempted_at": "2020-06-09T14:17:56-07:00",
"courier_redirect_link": "https://www.fedex.com/apps/fedextrack/?action=track&tracknumbers=642028981335&cntry_code=US",
"tracking_account_number": null,
"tracking_origin_country": null,
"tracking_destination_country": null,
"tracking_key": null,
"tracking_postal_code": null,
"tracking_ship_date": null,
"tracking_state": null
}
}
}

How to update a subscription (adding a pricing plan) by Stripe in Node.js?

I'm building a web app which sells several products, each product corresponds to a pricing plan. All the payment system is managed by Stripe.
It happens very often that a user has a subscription that contains Product A (i.e., one pricing plan), and then he wants to add Product B (i.e., another pricing plan) to the same subscription. I want to know how to achieve this by APIs of Stripe.
There is a webpage of Stripe to update a subscription (e.g., https://dashboard.stripe.com/test/subscriptions/sub_H4pQGW8nnc80vF/edit where sub_H4pQGW8nnc80vF is the subscription ID). Let's assume this customer already has 1 Verificator in the subscription, and then he wants to add 1 Pretty Formula to the same subscription. I do this update via the website. Here is the log:
Here is the full Request POST body:
{
"items": {
"0": {
"billing_thresholds": "",
"deleted": "false",
"id": "si_H4pQal4ZxGzLbW",
"quantity": "1",
"tax_rates": ""
},
"1": {
"billing_thresholds": "",
"plan": "plan_Gz6i9yPVIjrDPX",
"deleted": "false",
"quantity": "1"
}
},
"off_session": "true",
"prorate": "true",
"cancel_at": "",
"days_until_due": "30",
"default_tax_rates": "",
"collection_method": "send_invoice",
"billing_thresholds": "",
"enable_incomplete_payments": "false",
"invoice_settings": {
"description": "",
"send_hosted_payment_email": "true",
"supported_payment_methods": {
"ach_credit_transfer": "false",
"au_becs_debit": "false",
"bancontact": "false",
"card": "true",
"fpx": "false",
"giropay": "false",
"ideal": "false",
"jp_credit_transfer": "false",
"paper_check": "false",
"sepa_credit_transfer": "false",
"sofort": "false"
},
"custom_fields": "",
"footer": ""
},
"default_payment_method": "",
"default_source": ""
}
Here is the full Response body:
{
"id": "sub_H4pQGW8nnc80vF",
"object": "subscription",
"application_fee_percent": null,
"billing_cycle_anchor": 1586597833,
"billing_thresholds": null,
"cancel_at": null,
"cancel_at_period_end": false,
"canceled_at": null,
"collection_method": "send_invoice",
"created": 1586597833,
"current_period_end": 1589189833,
"current_period_start": 1586597833,
"customer": "5e575130651c5721d808d25b",
"customer_email": "sdtikply#gmail.com",
"customer_name": "Thomas Joseph",
"days_until_due": 30,
"default_payment_method": null,
"default_source": null,
"default_tax_rates": [
],
"discount": null,
"ended_at": null,
"invoice_settings": {
"send_hosted_payment_email": true,
"supported_payment_methods": {
"ach_credit_transfer": false,
"au_becs_debit": false,
"bancontact": false,
"card": true,
"fpx": false,
"giropay": false,
"ideal": false,
"jp_credit_transfer": false,
"paper_check": false,
"sepa_credit_transfer": false,
"sofort": false
}
},
"items": {
"object": "list",
"data": [
{
"id": "si_H4pQal4ZxGzLbW",
"object": "subscription_item",
"billing_thresholds": null,
"created": 1586597833,
"metadata": {
},
"plan": {
"id": "plan_Ga6n9yMYCDnHCu",
"object": "plan",
"active": true,
"aggregate_usage": null,
"amount": 500,
"amount_decimal": "500",
"billing_scheme": "per_unit",
"created": 1579512574,
"currency": "usd",
"interval": "month",
"interval_count": 1,
"livemode": false,
"metadata": {
},
"name": "Verificator",
"nickname": "Verificator",
"owning_merchant": "acct_1CiOQBEV4K2GahYL",
"owning_merchant_info": "acct_1CiOQBEV4K2GahYL",
"product": "prod_Ga6mVdA8KXyZ8I",
"tiers": null,
"tiers_mode": null,
"transform_usage": null,
"trial_period_days": null,
"usage_type": "licensed"
},
"quantity": 1,
"subscription": "sub_H4pQGW8nnc80vF",
"tax_rates": [
]
},
{
"id": "si_H82ES9BdIKZCNG",
"object": "subscription_item",
"billing_thresholds": null,
"created": 1587337381,
"metadata": {
},
"plan": {
"id": "plan_Gz6i9yPVIjrDPX",
"object": "plan",
"active": true,
"aggregate_usage": null,
"amount": 500,
"amount_decimal": "500",
"billing_scheme": "per_unit",
"created": 1585278262,
"currency": "usd",
"interval": "month",
"interval_count": 1,
"livemode": false,
"metadata": {
},
"name": "Pretty Formula",
"nickname": "Pretty Formula",
"owning_merchant": "acct_1CiOQBEV4K2GahYL",
"owning_merchant_info": "acct_1CiOQBEV4K2GahYL",
"product": "prod_GxqkRFdI08DvyR",
"tiers": null,
"tiers_mode": null,
"transform_usage": null,
"trial_period_days": null,
"usage_type": "licensed"
},
"quantity": 1,
"subscription": "sub_H4pQGW8nnc80vF",
"tax_rates": [
]
}
],
"has_more": false,
"total_count": 2,
"url": "/v1/subscription_items?subscription=sub_H4pQGW8nnc80vF"
},
"latest_invoice": "in_1GWfm1EV4K2GahYLlmtUEISo",
"livemode": false,
"metadata": {
},
"next_pending_invoice_item_invoice": null,
"owning_merchant": "acct_1CiOQBEV4K2GahYL",
"owning_merchant_info": "acct_1CiOQBEV4K2GahYL",
"pause_collection": null,
"pending_invoice_item_interval": null,
"pending_setup_intent": null,
"pending_update": null,
"plan": null,
"quantity": null,
"schedule": null,
"start_date": 1586597833,
"status": "active",
"tax_percent": null,
"trial_end": null,
"trial_start": null
}
So my question is, how can I code in my backend (Node.js) to achieve exactly the same thing?
You just need to use the Update Subscription API and provide the items portion just as you're seeing above:
"items": {
"0": {
"billing_thresholds": "",
"deleted": "false",
"id": "si_H4pQal4ZxGzLbW",
"quantity": "1",
"tax_rates": ""
},
"1": {
"billing_thresholds": "",
"plan": "plan_Gz6i9yPVIjrDPX",
"deleted": "false",
"quantity": "1"
}
},
The first item is the existing Subscription Item (si_), and the second one is the new one you want to add.

How to remove null objects of whois results

I use the python-whois module.
I have this code:
for whoisInfo in domainWhoisInfo :
stringWhoisInfo = str(stringWhoisInfo) + str(whoisInfo) + "\n\n"
domainWhoisInfo => array which contains whois data of each domain
stringWhoisInfo => string which concatenate whois data of all domains
I concatenate whois results of many domain name in a string.
And When I store these results in a log file I get the
following output :
{
"updated_date": null,
"status": null,
"name": null,
"dnssec": null,
"city": null,
"expiration_date": null,
"zipcode": null,
"domain_name": null,
"country": null,
"whois_server": null,
"state": null,
"registrar": null,
"referral_url": null,
"address": null,
"name_servers": null,
"org": null,
"creation_date": null,
"emails": null
}
{
"updated_date": null,
"status": [
"clientDeleteProhibited -- http://www.icann.org/epp#clientDeleteProhibited",
"clientTransferProhibited -- http://www.icann.org/epp#clientTransferProhibited",
"serverTransferProhibited -- http://www.icann.org/epp#serverTransferProhibited"
],
"name": "Panayiotis Yianni",
"dnssec": null,
"city": "Enfield",
"expiration_date": "2017-03-06 23:25:46",
"zipcode": "EN3 7FY",
"domain_name": "AMON.PRO",
"country": "GB",
"whois_server": null,
"state": "London, City of",
"registrar": "OVH (R2367-PRO)",
"referral_url": null,
"address": null,
"name_servers": [
"DNS200.ANYCAST.ME",
"NS200.ANYCAST.ME"
],
"org": "Amon Interactive Ltd",
"creation_date": null,
"emails": [
"panayiotisyianni#live.com",
"panayiotis#amon.pro"
]
}
{
"updated_date": null,
"status": null,
"name": null,
"dnssec": null,
"city": null,
"expiration_date": null,
"zipcode": null,
"domain_name": null,
"country": null,
"whois_server": null,
"state": null,
"registrar": null,
"referral_url": null,
"address": null,
"name_servers": null,
"org": null,
"creation_date": null,
"emails": null
}
I am looking for deleting the whois results which are null
from my string.
And get finally only this output :
{
"updated_date": null,
"status": [
"clientDeleteProhibited -- http://www.icann.org/epp#clientDeleteProhibited",
"clientTransferProhibited -- http://www.icann.org/epp#clientTransferProhibited",
"serverTransferProhibited -- http://www.icann.org/epp#serverTransferProhibited"
],
"name": "Panayiotis Yianni",
"dnssec": null,
"city": "Enfield",
"expiration_date": "2017-03-06 23:25:46",
"zipcode": "EN3 7FY",
"domain_name": "AMON.PRO",
"country": "GB",
"whois_server": null,
"state": "London, City of",
"registrar": "OVH (R2367-PRO)",
"referral_url": null,
"address": null,
"name_servers": [
"DNS200.ANYCAST.ME",
"NS200.ANYCAST.ME"
],
"org": "Amon Interactive Ltd",
"creation_date": null,
"emails": [
"panayiotisyianni#live.com",
"panayiotis#amon.pro"
]
}

Resources